diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..3ba13e0cec --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/.vscode/launch.json b/.vscode/launch.json index 971df77367..e67212baab 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -67,17 +67,16 @@ "request": "launch", "name": "Launch azuredatastudio", "windows": { - "runtimeExecutable": "${workspaceFolder}/scripts/sql.bat", - "timeout": 45000 + "runtimeExecutable": "${workspaceFolder}/scripts/sql.bat" }, "osx": { - "runtimeExecutable": "${workspaceFolder}/scripts/sql.sh", - "timeout": 45000 + "runtimeExecutable": "${workspaceFolder}/scripts/sql.sh" }, "linux": { - "runtimeExecutable": "${workspaceFolder}/scripts/sql.sh", - "timeout": 45000 + "runtimeExecutable": "${workspaceFolder}/scripts/sql.sh" }, + "port": 9222, + "timeout": 20000, "env": { "VSCODE_EXTHOST_WILL_SEND_SOCKET": null }, @@ -260,6 +259,14 @@ "Attach to Main Process" ] }, + { + "name": "Debug azuredatastudio Main, Renderer & Extension Host", + "configurations": [ + "Launch azuredatastudio", + "Attach to Main Process", + "Attach to Extension Host" + ] + }, { "name": "Debug Renderer and search processes", "configurations": [ diff --git a/.vscode/settings.json b/.vscode/settings.json index 30b4aab454..ef1f7370d2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -39,6 +39,7 @@ ], "typescript.tsdk": "node_modules/typescript/lib", "npm.exclude": "**/extensions/**", + "npm.packageManager": "yarn", "emmet.excludeLanguages": [], "typescript.preferences.importModuleSpecifier": "non-relative", "typescript.preferences.quoteStyle": "single", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 8e0dcabd44..042f5058f8 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -33,15 +33,15 @@ }, { "type": "npm", - "script": "strict-initialization-watch", - "label": "TS - Strict Initialization", + "script": "strict-function-types-watch", + "label": "TS - Strict Function Types", "isBackground": true, "presentation": { "reveal": "never" }, "problemMatcher": { "base": "$tsc-watch", - "owner": "typescript-strict-initialization", + "owner": "typescript-function-types", "applyTo": "allDocuments" } }, diff --git a/.yarnrc b/.yarnrc index c54f7d6d6e..288e7393ab 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,3 +1,3 @@ disturl "https://atom.io/download/electron" -target "6.0.12" +target "6.1.5" runtime "electron" diff --git a/build/azure-pipelines/common/createAsset.ts b/build/azure-pipelines/common/createAsset.ts new file mode 100644 index 0000000000..7c41748f57 --- /dev/null +++ b/build/azure-pipelines/common/createAsset.ts @@ -0,0 +1,132 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as fs from 'fs'; +import { Readable } from 'stream'; +import * as crypto from 'crypto'; +import * as azure from 'azure-storage'; +import * as mime from 'mime'; +import { CosmosClient } from '@azure/cosmos'; + +interface Asset { + platform: string; + type: string; + url: string; + mooncakeUrl?: string; + hash: string; + sha256hash: string; + size: number; + supportsFastUpdate?: boolean; +} + +if (process.argv.length !== 6) { + console.error('Usage: node createAsset.js PLATFORM TYPE NAME FILE'); + process.exit(-1); +} + +function hashStream(hashName: string, stream: Readable): Promise { + return new Promise((c, e) => { + const shasum = crypto.createHash(hashName); + + stream + .on('data', shasum.update.bind(shasum)) + .on('error', e) + .on('close', () => c(shasum.digest('hex'))); + }); +} + +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, file: string): Promise { + const blobOptions: azure.BlobService.CreateBlockBlobRequestOptions = { + contentSettings: { + contentType: mime.lookup(file), + cacheControl: 'max-age=31536000, public' + } + }; + + await new Promise((c, e) => blobService.createBlockBlobFromLocalFile(quality, blobName, file, blobOptions, err => err ? e(err) : c())); +} + +function getEnv(name: string): string { + const result = process.env[name]; + + if (typeof result === 'undefined') { + throw new Error('Missing env: ' + name); + } + + return result; +} + +async function main(): Promise { + const [, , platform, type, name, file] = process.argv; + const quality = getEnv('VSCODE_QUALITY'); + const commit = getEnv('BUILD_SOURCEVERSION'); + + console.log('Creating asset...'); + + const stat = await new Promise((c, e) => fs.stat(file, (err, stat) => err ? e(err) : c(stat))); + const size = stat.size; + + console.log('Size:', size); + + const stream = fs.createReadStream(file); + const [sha1hash, sha256hash] = await Promise.all([hashStream('sha1', stream), hashStream('sha256', stream)]); + + console.log('SHA1:', sha1hash); + console.log('SHA256:', sha256hash); + + const blobName = commit + '/' + name; + 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); + + if (blobExists) { + console.log(`Blob ${quality}, ${blobName} already exists, not publishing again.`); + return; + } + + console.log('Uploading blobs to Azure storage...'); + + await uploadBlob(blobService, quality, blobName, file); + + console.log('Blobs successfully uploaded.'); + + const asset: Asset = { + platform, + type, + url: `${process.env['AZURE_CDN_URL']}/${quality}/${blobName}`, + hash: sha1hash, + sha256hash, + size + }; + + // Remove this if we ever need to rollback fast updates for windows + if (/win32/.test(platform)) { + asset.supportsFastUpdate = true; + } + + console.log('Asset:', JSON.stringify(asset, null, ' ')); + + const client = new CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT']!, key: process.env['AZURE_DOCUMENTDB_MASTERKEY'] }); + const scripts = client.database('builds').container(quality).scripts; + await scripts.storedProcedure('createAsset').execute('', [commit, asset, true]); +} + +main().then(() => { + console.log('Asset successfully created'); + process.exit(0); +}, err => { + console.error(err); + process.exit(1); +}); diff --git a/build/azure-pipelines/common/createBuild.ts b/build/azure-pipelines/common/createBuild.ts new file mode 100644 index 0000000000..eecf0e945e --- /dev/null +++ b/build/azure-pipelines/common/createBuild.ts @@ -0,0 +1,60 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { CosmosClient } from '@azure/cosmos'; + +if (process.argv.length !== 3) { + console.error('Usage: node createBuild.js VERSION'); + process.exit(-1); +} + +function getEnv(name: string): string { + const result = process.env[name]; + + if (typeof result === 'undefined') { + throw new Error('Missing env: ' + name); + } + + return result; +} + +async function main(): Promise { + const [, , _version] = process.argv; + const quality = getEnv('VSCODE_QUALITY'); + const commit = getEnv('BUILD_SOURCEVERSION'); + const queuedBy = getEnv('BUILD_QUEUEDBY'); + const sourceBranch = getEnv('BUILD_SOURCEBRANCH'); + const version = _version + (quality === 'stable' ? '' : `-${quality}`); + + console.log('Creating build...'); + console.log('Quality:', quality); + console.log('Version:', version); + console.log('Commit:', commit); + + const build = { + id: commit, + timestamp: (new Date()).getTime(), + version, + isReleased: false, + sourceBranch, + queuedBy, + assets: [], + updates: {} + }; + + const client = new CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT']!, key: process.env['AZURE_DOCUMENTDB_MASTERKEY'] }); + const scripts = client.database('builds').container(quality).scripts; + await scripts.storedProcedure('createBuild').execute('', [{ ...build, _partitionKey: '' }]); +} + +main().then(() => { + console.log('Build successfully created'); + process.exit(0); +}, err => { + console.error(err); + process.exit(1); +}); diff --git a/build/azure-pipelines/common/releaseBuild.ts b/build/azure-pipelines/common/releaseBuild.ts new file mode 100644 index 0000000000..e8a9835fb6 --- /dev/null +++ b/build/azure-pipelines/common/releaseBuild.ts @@ -0,0 +1,70 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { CosmosClient } from '@azure/cosmos'; + +function getEnv(name: string): string { + const result = process.env[name]; + + if (typeof result === 'undefined') { + throw new Error('Missing env: ' + name); + } + + return result; +} + +interface Config { + id: string; + frozen: boolean; +} + +function createDefaultConfig(quality: string): Config { + return { + id: quality, + frozen: false + }; +} + +async function getConfig(client: CosmosClient, quality: string): Promise { + const query = `SELECT TOP 1 * FROM c WHERE c.id = "${quality}"`; + + const res = await client.database('builds').container('config').items.query(query).fetchAll(); + + if (res.resources.length === 0) { + return createDefaultConfig(quality); + } + + return res.resources[0] as Config; +} + +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 config = await getConfig(client, quality); + + console.log('Quality config:', config); + + if (config.frozen) { + console.log(`Skipping release because quality ${quality} is frozen.`); + return; + } + + console.log(`Releasing build ${commit}...`); + + const scripts = client.database('builds').container(quality).scripts; + await scripts.storedProcedure('releaseBuild').execute('', [commit]); +} + +main().then(() => { + console.log('Build successfully released'); + process.exit(0); +}, err => { + console.error(err); + process.exit(1); +}); diff --git a/build/azure-pipelines/common/sync-mooncake.ts b/build/azure-pipelines/common/sync-mooncake.ts index a7dea2bcfe..97fc67c10f 100644 --- a/build/azure-pipelines/common/sync-mooncake.ts +++ b/build/azure-pipelines/common/sync-mooncake.ts @@ -8,7 +8,7 @@ import * as url from 'url'; import * as azure from 'azure-storage'; import * as mime from 'mime'; -import { DocumentClient, RetrievedDocument } from 'documentdb'; +import { CosmosClient } from '@azure/cosmos'; function log(...args: any[]) { console.log(...[`[${new Date().toISOString()}]`, ...args]); @@ -23,7 +23,7 @@ if (process.argv.length < 3) { process.exit(-1); } -interface Build extends RetrievedDocument { +interface Build { assets: Asset[]; } @@ -38,62 +38,20 @@ interface Asset { supportsFastUpdate?: boolean; } -function updateBuild(commit: string, quality: string, platform: string, type: string, asset: Asset): Promise { - const client = new DocumentClient(process.env['AZURE_DOCUMENTDB_ENDPOINT']!, { masterKey: process.env['AZURE_DOCUMENTDB_MASTERKEY'] }); - const collection = 'dbs/builds/colls/' + quality; - const updateQuery = { - query: 'SELECT TOP 1 * FROM c WHERE c.id = @id', - parameters: [{ name: '@id', value: commit }] - }; - - let updateTries = 0; - - function _update(): Promise { - updateTries++; - - return new Promise((c, e) => { - client.queryDocuments(collection, updateQuery).toArray((err, results) => { - if (err) { return e(err); } - if (results.length !== 1) { return e(new Error('No documents')); } - - const release = results[0]; - - release.assets = [ - ...release.assets.filter((a: any) => !(a.platform === platform && a.type === type)), - asset - ]; - - client.replaceDocument(release._self, release, err => { - if (err && err.code === 409 && updateTries < 5) { return c(_update()); } - if (err) { return e(err); } - - log('Build successfully updated.'); - c(); - }); - }); - }); - } - - return _update(); -} - async function sync(commit: string, quality: string): Promise { log(`Synchronizing Mooncake assets for ${quality}, ${commit}...`); - const cosmosdb = new DocumentClient(process.env['AZURE_DOCUMENTDB_ENDPOINT']!, { masterKey: process.env['AZURE_DOCUMENTDB_MASTERKEY'] }); - const collection = `dbs/builds/colls/${quality}`; - const query = { - query: 'SELECT TOP 1 * FROM c WHERE c.id = @id', - parameters: [{ name: '@id', value: commit }] - }; + const client = new CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT']!, key: process.env['AZURE_DOCUMENTDB_MASTERKEY'] }); + const container = client.database('builds').container(quality); - const build = await new Promise((c, e) => { - cosmosdb.queryDocuments(collection, query).toArray((err, results) => { - if (err) { return e(err); } - if (results.length !== 1) { return e(new Error('No documents')); } - c(results[0] as Build); - }); - }); + const query = `SELECT TOP 1 * FROM c WHERE c.id = "${commit}"`; + const res = await container.items.query(query, {}).fetchAll(); + + if (res.resources.length !== 1) { + throw new Error(`No builds found for ${commit}`); + } + + const build = res.resources[0]; log(`Found build for ${commit}, with ${build.assets.length} assets`); @@ -140,8 +98,9 @@ async function sync(commit: string, quality: string): Promise { await new Promise((c, e) => readStream.pipe(writeStream).on('finish', c).on('error', e)); log(` Updating build in DB...`); - asset.mooncakeUrl = `${process.env['MOONCAKE_CDN_URL']}${blobPath}`; - await updateBuild(commit, quality, asset.platform, asset.type, asset); + const mooncakeUrl = `${process.env['MOONCAKE_CDN_URL']}${blobPath}`; + await container.scripts.storedProcedure('setAssetMooncakeUrl') + .execute('', [commit, asset.platform, asset.type, mooncakeUrl]); log(` Done ✔️`); } catch (err) { diff --git a/build/azure-pipelines/darwin/continuous-build-darwin.yml b/build/azure-pipelines/darwin/continuous-build-darwin.yml index 644df32491..6edf9f3392 100644 --- a/build/azure-pipelines/darwin/continuous-build-darwin.yml +++ b/build/azure-pipelines/darwin/continuous-build-darwin.yml @@ -1,21 +1,21 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "10.15.1" + versionSpec: "12.13.0" +- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@3 # {{SQL CARBON EDIT}} update version + inputs: + versionSpec: "1.x" - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 inputs: keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' - vstsFeed: '$(build-cache)' # {{SQL CARBON EDIT}} update build cache + vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache - task: AzureKeyVault@1 displayName: 'Azure Key Vault: Get Secrets' inputs: azureSubscription: 'azuredatastudio-adointegration' KeyVaultName: ado-secrets SecretsFilter: 'github-distro-mixin-password' -- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@3 # {{SQL CARBON EDIT}} update version - inputs: - versionSpec: "1.x" - script: | CHILD_CONCURRENCY=1 yarn --frozen-lockfile displayName: Install Dependencies @@ -26,7 +26,7 @@ steps: inputs: keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' - vstsFeed: '$(build-cache)' # {{SQL CARBON EDIT}} update build cache + vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) - script: | yarn electron x64 diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index 0616a2d4dd..77f4af38c8 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -21,7 +21,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "10.15.1" + versionSpec: "12.13.0" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/darwin/publish.sh b/build/azure-pipelines/darwin/publish.sh index fa453bcaff..a8067a5eef 100755 --- a/build/azure-pipelines/darwin/publish.sh +++ b/build/azure-pipelines/darwin/publish.sh @@ -5,28 +5,20 @@ set -e zip -d ../VSCode-darwin.zip "*.pkg" # publish the build -PACKAGEJSON=`ls ../VSCode-darwin/*.app/Contents/Resources/app/package.json` -VERSION=`node -p "require(\"$PACKAGEJSON\").version"` -node build/azure-pipelines/common/publish.js \ - "$VSCODE_QUALITY" \ +node build/azure-pipelines/common/createAsset.js \ darwin \ archive \ "VSCode-darwin-$VSCODE_QUALITY.zip" \ - $VERSION \ - true \ ../VSCode-darwin.zip # package Remote Extension Host pushd .. && mv vscode-reh-darwin vscode-server-darwin && zip -Xry vscode-server-darwin.zip vscode-server-darwin && popd # publish Remote Extension Host -node build/azure-pipelines/common/publish.js \ - "$VSCODE_QUALITY" \ +node build/azure-pipelines/common/createAsset.js \ server-darwin \ archive-unsigned \ "vscode-server-darwin.zip" \ - $VERSION \ - true \ ../vscode-server-darwin.zip # publish hockeyapp symbols diff --git a/build/azure-pipelines/distro-build.yml b/build/azure-pipelines/distro-build.yml index 1d95842ced..edf92ccfcb 100644 --- a/build/azure-pipelines/distro-build.yml +++ b/build/azure-pipelines/distro-build.yml @@ -11,7 +11,7 @@ pr: steps: - task: NodeTool@0 inputs: - versionSpec: "10.15.1" + versionSpec: "12.13.0" - task: AzureKeyVault@1 displayName: 'Azure Key Vault: Get Secrets' diff --git a/build/azure-pipelines/exploration-build.yml b/build/azure-pipelines/exploration-build.yml index 305852528b..b91b01138d 100644 --- a/build/azure-pipelines/exploration-build.yml +++ b/build/azure-pipelines/exploration-build.yml @@ -7,7 +7,7 @@ pr: none steps: - task: NodeTool@0 inputs: - versionSpec: "10.15.1" + versionSpec: "12.13.0" - task: AzureKeyVault@1 displayName: 'Azure Key Vault: Get Secrets' diff --git a/build/azure-pipelines/linux/continuous-build-linux.yml b/build/azure-pipelines/linux/continuous-build-linux.yml index 94c1b0fb12..2a13f543bf 100644 --- a/build/azure-pipelines/linux/continuous-build-linux.yml +++ b/build/azure-pipelines/linux/continuous-build-linux.yml @@ -9,21 +9,21 @@ steps: sudo service xvfb start - task: NodeTool@0 inputs: - versionSpec: "10.15.1" -- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 + versionSpec: "12.13.0" +- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@3 inputs: - keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' - targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' - vstsFeed: '$(build-cache)' # {{SQL CARBON EDIT}} update build cache + versionSpec: "1.x" - task: AzureKeyVault@1 displayName: 'Azure Key Vault: Get Secrets' inputs: azureSubscription: 'azuredatastudio-adointegration' KeyVaultName: ado-secrets SecretsFilter: 'github-distro-mixin-password' -- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@3 # {{SQL CARBON EDIT}} update version +- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 inputs: - versionSpec: "1.x" + keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' + targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' + vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache - script: | CHILD_CONCURRENCY=1 yarn --frozen-lockfile displayName: Install Dependencies @@ -34,7 +34,7 @@ steps: inputs: keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' - vstsFeed: '$(build-cache)' # {{SQL CARBON EDIT}} update build cache + vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) - script: | yarn electron x64 diff --git a/build/azure-pipelines/linux/frozen-check.js b/build/azure-pipelines/linux/frozen-check.js deleted file mode 100644 index 76ba554f14..0000000000 --- a/build/azure-pipelines/linux/frozen-check.js +++ /dev/null @@ -1,40 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; -Object.defineProperty(exports, "__esModule", { value: true }); -const documentdb_1 = require("documentdb"); -function createDefaultConfig(quality) { - return { - id: quality, - frozen: false - }; -} -function getConfig(quality) { - const client = new documentdb_1.DocumentClient(process.env['AZURE_DOCUMENTDB_ENDPOINT'], { masterKey: process.env['AZURE_DOCUMENTDB_MASTERKEY'] }); - const collection = 'dbs/builds/colls/config'; - const query = { - query: `SELECT TOP 1 * FROM c WHERE c.id = @quality`, - parameters: [ - { name: '@quality', value: quality } - ] - }; - return new Promise((c, e) => { - client.queryDocuments(collection, query).toArray((err, results) => { - if (err && err.code !== 409) { - return e(err); - } - c(!results || results.length === 0 ? createDefaultConfig(quality) : results[0]); - }); - }); -} -getConfig(process.argv[2]) - .then(config => { - console.log(config.frozen); - process.exit(0); -}) - .catch(err => { - console.error(err); - process.exit(1); -}); diff --git a/build/azure-pipelines/linux/product-build-linux-multiarch.yml b/build/azure-pipelines/linux/product-build-linux-multiarch.yml index 4f5e39dcdf..68ae4ee8b6 100644 --- a/build/azure-pipelines/linux/product-build-linux-multiarch.yml +++ b/build/azure-pipelines/linux/product-build-linux-multiarch.yml @@ -21,7 +21,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "10.15.1" + versionSpec: "12.13.0" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 0d01ba8a60..573d7c7d4c 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -21,7 +21,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "10.15.1" + versionSpec: "12.13.0" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: @@ -118,6 +118,32 @@ steps: displayName: Run integration tests condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) +- script: | + set -e + yarn gulp "vscode-linux-x64-build-deb" + yarn gulp "vscode-linux-x64-build-rpm" + yarn gulp "vscode-linux-x64-prepare-snap" + displayName: Build packages + +- task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 + inputs: + ConnectedServiceName: 'ESRP CodeSign' + FolderPath: '.build/linux/rpm/x86_64' + Pattern: '*.rpm' + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "keyCode": "CP-450779-Pgp", + "operationSetCode": "LinuxSign", + "parameters": [ ], + "toolName": "sign", + "toolVersion": "1.0" + } + ] + SessionTimeout: 120 + displayName: Codesign rpm + - script: | set -e AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ diff --git a/build/azure-pipelines/linux/publish.sh b/build/azure-pipelines/linux/publish.sh index 5fc86d02d6..3da6ea3eed 100755 --- a/build/azure-pipelines/linux/publish.sh +++ b/build/azure-pipelines/linux/publish.sh @@ -10,13 +10,11 @@ BUILD="$ROOT/$BUILDNAME" BUILD_VERSION="$(date +%s)" [ -z "$VSCODE_QUALITY" ] && TARBALL_FILENAME="code-$BUILD_VERSION.tar.gz" || TARBALL_FILENAME="code-$VSCODE_QUALITY-$BUILD_VERSION.tar.gz" TARBALL_PATH="$ROOT/$TARBALL_FILENAME" -PACKAGEJSON="$BUILD/resources/app/package.json" -VERSION=$(node -p "require(\"$PACKAGEJSON\").version") rm -rf $ROOT/code-*.tar.* (cd $ROOT && tar -czf $TARBALL_PATH $BUILDNAME) -node build/azure-pipelines/common/publish.js "$VSCODE_QUALITY" "$PLATFORM_LINUX" archive-unsigned "$TARBALL_FILENAME" "$VERSION" true "$TARBALL_PATH" +node build/azure-pipelines/common/createAsset.js "$PLATFORM_LINUX" archive-unsigned "$TARBALL_FILENAME" "$TARBALL_PATH" # Publish Remote Extension Host LEGACY_SERVER_BUILD_NAME="vscode-reh-$PLATFORM_LINUX" @@ -27,32 +25,28 @@ SERVER_TARBALL_PATH="$ROOT/$SERVER_TARBALL_FILENAME" rm -rf $ROOT/vscode-server-*.tar.* (cd $ROOT && mv $LEGACY_SERVER_BUILD_NAME $SERVER_BUILD_NAME && tar --owner=0 --group=0 -czf $SERVER_TARBALL_PATH $SERVER_BUILD_NAME) -node build/azure-pipelines/common/publish.js "$VSCODE_QUALITY" "server-$PLATFORM_LINUX" archive-unsigned "$SERVER_TARBALL_FILENAME" "$VERSION" true "$SERVER_TARBALL_PATH" +node build/azure-pipelines/common/createAsset.js "server-$PLATFORM_LINUX" archive-unsigned "$SERVER_TARBALL_FILENAME" "$SERVER_TARBALL_PATH" # Publish hockeyapp symbols node build/azure-pipelines/common/symbols.js "$VSCODE_MIXIN_PASSWORD" "$VSCODE_HOCKEYAPP_TOKEN" "x64" "$VSCODE_HOCKEYAPP_ID_LINUX64" # Publish DEB -yarn gulp "vscode-linux-x64-build-deb" PLATFORM_DEB="linux-deb-x64" DEB_ARCH="amd64" DEB_FILENAME="$(ls $REPO/.build/linux/deb/$DEB_ARCH/deb/)" DEB_PATH="$REPO/.build/linux/deb/$DEB_ARCH/deb/$DEB_FILENAME" -node build/azure-pipelines/common/publish.js "$VSCODE_QUALITY" "$PLATFORM_DEB" package "$DEB_FILENAME" "$VERSION" true "$DEB_PATH" +node build/azure-pipelines/common/createAsset.js "$PLATFORM_DEB" package "$DEB_FILENAME" "$DEB_PATH" # Publish RPM -yarn gulp "vscode-linux-x64-build-rpm" PLATFORM_RPM="linux-rpm-x64" RPM_ARCH="x86_64" RPM_FILENAME="$(ls $REPO/.build/linux/rpm/$RPM_ARCH/ | grep .rpm)" RPM_PATH="$REPO/.build/linux/rpm/$RPM_ARCH/$RPM_FILENAME" -node build/azure-pipelines/common/publish.js "$VSCODE_QUALITY" "$PLATFORM_RPM" package "$RPM_FILENAME" "$VERSION" true "$RPM_PATH" +node build/azure-pipelines/common/createAsset.js "$PLATFORM_RPM" package "$RPM_FILENAME" "$RPM_PATH" # Publish Snap -yarn gulp "vscode-linux-x64-prepare-snap" - # Pack snap tarball artifact, in order to preserve file perms mkdir -p $REPO/.build/linux/snap-tarball SNAP_TARBALL_PATH="$REPO/.build/linux/snap-tarball/snap-x64.tar.gz" diff --git a/build/azure-pipelines/linux/snap-build-linux.yml b/build/azure-pipelines/linux/snap-build-linux.yml index 047081ced0..a530499b31 100644 --- a/build/azure-pipelines/linux/snap-build-linux.yml +++ b/build/azure-pipelines/linux/snap-build-linux.yml @@ -1,7 +1,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "10.15.1" + versionSpec: "12.13.0" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: @@ -43,12 +43,10 @@ steps: # Create snap package BUILD_VERSION="$(date +%s)" SNAP_FILENAME="code-$VSCODE_QUALITY-$BUILD_VERSION.snap" - PACKAGEJSON="$(ls $SNAP_ROOT/code*/usr/share/code*/resources/app/package.json)" - VERSION=$(node -p "require(\"$PACKAGEJSON\").version") SNAP_PATH="$SNAP_ROOT/$SNAP_FILENAME" (cd $SNAP_ROOT/code-* && sudo --preserve-env snapcraft snap --output "$SNAP_PATH") # Publish snap package AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ AZURE_STORAGE_ACCESS_KEY_2="$(vscode-storage-key)" \ - node build/azure-pipelines/common/publish.js "$VSCODE_QUALITY" "linux-snap-x64" package "$SNAP_FILENAME" "$VERSION" true "$SNAP_PATH" + node build/azure-pipelines/common/createAsset.js "linux-snap-x64" package "$SNAP_FILENAME" "$SNAP_PATH" diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index ecf47fa1cd..2eedaf8dce 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -67,7 +67,7 @@ jobs: - template: linux/product-build-linux-multiarch.yml - job: LinuxArm64 - condition: and(succeeded(), eq(variables['VSCODE_COMPILE_ONLY'], 'false'), eq(variables['VSCODE_BUILD_LINUX_ARM64'], 'true'), ne(variables['VSCODE_QUALITY'], 'stable')) + condition: and(succeeded(), eq(variables['VSCODE_COMPILE_ONLY'], 'false'), eq(variables['VSCODE_BUILD_LINUX_ARM64'], 'true')) pool: vmImage: 'Ubuntu-16.04' variables: @@ -118,6 +118,7 @@ jobs: - Linux - LinuxSnap - LinuxArmhf + - LinuxArm64 - LinuxAlpine - macOS steps: @@ -133,6 +134,7 @@ jobs: - Linux - LinuxSnap - LinuxArmhf + - LinuxArm64 - LinuxAlpine - LinuxWeb - macOS diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-compile.yml index 76e1f6a927..8029f8a566 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-compile.yml @@ -12,23 +12,24 @@ steps: vstsFeed: 'npm-vscode' platformIndependent: true alias: 'Compilation' + dryRun: true - task: NodeTool@0 inputs: - versionSpec: "10.15.1" - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + versionSpec: "12.13.0" + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: versionSpec: "1.x" - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) - task: AzureKeyVault@1 displayName: 'Azure Key Vault: Get Secrets' inputs: azureSubscription: 'vscode-builds-subscription' KeyVaultName: vscode - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) - script: | set -e @@ -41,7 +42,7 @@ steps: git config user.email "vscode@microsoft.com" git config user.name "VSCode" displayName: Prepare tooling - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) - script: | set -e @@ -49,33 +50,33 @@ steps: git fetch distro git merge $(node -p "require('./package.json').distro") displayName: Merge distro - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 inputs: keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' targetfolder: '**/node_modules, !**/node_modules/**/node_modules' vstsFeed: 'npm-vscode' - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) - script: | set -e CHILD_CONCURRENCY=1 yarn --frozen-lockfile displayName: Install dependencies - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true'), ne(variables['CacheRestored'], 'true')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true'), ne(variables['CacheRestored'], 'true')) - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 inputs: keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' targetfolder: '**/node_modules, !**/node_modules/**/node_modules' vstsFeed: 'npm-vscode' - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true'), ne(variables['CacheRestored'], 'true')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true'), ne(variables['CacheRestored'], 'true')) - script: | set -e yarn postinstall displayName: Run postinstall scripts - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true'), eq(variables['CacheRestored'], 'true')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true'), eq(variables['CacheRestored'], 'true')) # Mixin must run before optimize, because the CSS loader will # inline small SVGs @@ -83,7 +84,7 @@ steps: set -e node build/azure-pipelines/mixin displayName: Mix in quality - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) - script: | set -e @@ -91,20 +92,20 @@ steps: yarn gulp tslint yarn monaco-compile-check displayName: Run hygiene, tslint and monaco compile checks - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - script: | set - ./build/azure-pipelines/common/extract-telemetry.sh displayName: Extract Telemetry - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) - script: | set -e AZURE_WEBVIEW_STORAGE_ACCESS_KEY="$(vscode-webview-storage-key)" \ ./build/azure-pipelines/common/publish-webview.sh displayName: Publish Webview - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) - script: | set -e @@ -114,14 +115,22 @@ steps: yarn gulp minify-vscode-reh yarn gulp minify-vscode-reh-web displayName: Compile - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) - script: | set -e AZURE_STORAGE_ACCESS_KEY="$(ticino-storage-key)" \ node build/azure-pipelines/upload-sourcemaps displayName: Upload sourcemaps - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) + +- script: | + set -e + VERSION=`node -p "require(\"./package.json\").version"` + AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ + node build/azure-pipelines/common/createBuild.js $VERSION + displayName: Create build + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 inputs: @@ -130,4 +139,4 @@ steps: vstsFeed: 'npm-vscode' platformIndependent: true alias: 'Compilation' - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) diff --git a/build/azure-pipelines/publish-types/check-version.ts b/build/azure-pipelines/publish-types/check-version.ts index 3562261a80..3e3614a867 100644 --- a/build/azure-pipelines/publish-types/check-version.ts +++ b/build/azure-pipelines/publish-types/check-version.ts @@ -35,9 +35,9 @@ function isValidTag(t: string) { return false; } - if (parseInt(major, 10) === NaN || parseInt(minor, 10) === NaN) { + if (isNaN(parseInt(major, 10)) || isNaN(parseInt(minor, 10))) { return false; } return true; -} \ No newline at end of file +} diff --git a/build/azure-pipelines/publish-types/publish-types.yml b/build/azure-pipelines/publish-types/publish-types.yml index 6808054b3f..1d4ab83e1a 100644 --- a/build/azure-pipelines/publish-types/publish-types.yml +++ b/build/azure-pipelines/publish-types/publish-types.yml @@ -9,7 +9,7 @@ pr: none steps: - task: NodeTool@0 inputs: - versionSpec: "10.15.1" + versionSpec: "12.13.0" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/release.yml b/build/azure-pipelines/release.yml index eee54a1d8b..f5365b80c7 100644 --- a/build/azure-pipelines/release.yml +++ b/build/azure-pipelines/release.yml @@ -19,4 +19,4 @@ steps: (cd build ; yarn) AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ - node build/azure-pipelines/common/release.js + node build/azure-pipelines/common/releaseBuild.js diff --git a/build/azure-pipelines/sync-mooncake.yml b/build/azure-pipelines/sync-mooncake.yml index 1fb8ffcd88..2641830a41 100644 --- a/build/azure-pipelines/sync-mooncake.yml +++ b/build/azure-pipelines/sync-mooncake.yml @@ -1,7 +1,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "10.15.1" + versionSpec: "12.13.0" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/web/product-build-web.yml b/build/azure-pipelines/web/product-build-web.yml index 46ecb39eac..0c338203b4 100644 --- a/build/azure-pipelines/web/product-build-web.yml +++ b/build/azure-pipelines/web/product-build-web.yml @@ -21,7 +21,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "10.15.1" + versionSpec: "12.13.0" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/web/publish.sh b/build/azure-pipelines/web/publish.sh index 2c112362a2..827edc2661 100755 --- a/build/azure-pipelines/web/publish.sh +++ b/build/azure-pipelines/web/publish.sh @@ -7,12 +7,9 @@ ROOT="$REPO/.." WEB_BUILD_NAME="vscode-web" WEB_TARBALL_FILENAME="vscode-web.tar.gz" WEB_TARBALL_PATH="$ROOT/$WEB_TARBALL_FILENAME" -BUILD="$ROOT/$WEB_BUILD_NAME" -PACKAGEJSON="$BUILD/package.json" -VERSION=$(node -p "require(\"$PACKAGEJSON\").version") rm -rf $ROOT/vscode-web.tar.* (cd $ROOT && tar --owner=0 --group=0 -czf $WEB_TARBALL_PATH $WEB_BUILD_NAME) -node build/azure-pipelines/common/publish.js "$VSCODE_QUALITY" "web-standalone" archive-unsigned "$WEB_TARBALL_FILENAME" "$VERSION" true "$WEB_TARBALL_PATH" +node build/azure-pipelines/common/createAsset.js web-standalone archive-unsigned "$WEB_TARBALL_FILENAME" "$WEB_TARBALL_PATH" diff --git a/build/azure-pipelines/win32/continuous-build-win32.yml b/build/azure-pipelines/win32/continuous-build-win32.yml index c2c9b3b816..499ed99099 100644 --- a/build/azure-pipelines/win32/continuous-build-win32.yml +++ b/build/azure-pipelines/win32/continuous-build-win32.yml @@ -1,7 +1,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "10.15.1" + versionSpec: "12.13.0" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@3 # {{SQL CARBON EDIT}} update version inputs: versionSpec: "1.x" @@ -19,7 +19,7 @@ steps: inputs: keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' - vstsFeed: '$(build-cache)' # {{SQL CARBON EDIT}} update build cache + vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache - powershell: | yarn --frozen-lockfile env: @@ -31,7 +31,7 @@ steps: inputs: keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' - vstsFeed: '$(build-cache)' # {{SQL CARBON EDIT}} update build cache + vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) - powershell: | yarn electron diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index 3c95d01287..4c9336c6c1 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -21,7 +21,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "10.15.1" + versionSpec: "12.13.0" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/win32/publish.ps1 b/build/azure-pipelines/win32/publish.ps1 index 86da6df4e0..5a22d4749c 100644 --- a/build/azure-pipelines/win32/publish.ps1 +++ b/build/azure-pipelines/win32/publish.ps1 @@ -23,14 +23,13 @@ exec { .\node_modules\7zip\7zip-lite\7z.exe a -tzip $ServerZip $Server -r } # get version $PackageJson = Get-Content -Raw -Path "$Build\resources\app\package.json" | ConvertFrom-Json $Version = $PackageJson.version -$Quality = "$env:VSCODE_QUALITY" $AssetPlatform = if ("$Arch" -eq "ia32") { "win32" } else { "win32-x64" } -exec { node build/azure-pipelines/common/publish.js $Quality "$AssetPlatform-archive" archive "VSCode-win32-$Arch-$Version.zip" $Version true $Zip } -exec { node build/azure-pipelines/common/publish.js $Quality "$AssetPlatform" setup "VSCodeSetup-$Arch-$Version.exe" $Version true $SystemExe } -exec { node build/azure-pipelines/common/publish.js $Quality "$AssetPlatform-user" setup "VSCodeUserSetup-$Arch-$Version.exe" $Version true $UserExe } -exec { node build/azure-pipelines/common/publish.js $Quality "server-$AssetPlatform" archive "vscode-server-win32-$Arch.zip" $Version true $ServerZip } +exec { node build/azure-pipelines/common/createAsset.js "$AssetPlatform-archive" archive "VSCode-win32-$Arch-$Version.zip" $Zip } +exec { node build/azure-pipelines/common/createAsset.js "$AssetPlatform" setup "VSCodeSetup-$Arch-$Version.exe" $SystemExe } +exec { node build/azure-pipelines/common/createAsset.js "$AssetPlatform-user" setup "VSCodeUserSetup-$Arch-$Version.exe" $UserExe } +exec { node build/azure-pipelines/common/createAsset.js "server-$AssetPlatform" archive "vscode-server-win32-$Arch.zip" $ServerZip } # publish hockeyapp symbols $hockeyAppId = if ("$Arch" -eq "ia32") { "$env:VSCODE_HOCKEYAPP_ID_WIN32" } else { "$env:VSCODE_HOCKEYAPP_ID_WIN64" } diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index 9054fbd83e..83d3dc6c85 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -57,7 +57,6 @@ var BUNDLED_FILE_HEADER = [ const languages = i18n.defaultLanguages.concat([]); // i18n.defaultLanguages.concat(process.env.VSCODE_QUALITY !== 'stable' ? i18n.extraLanguages : []); const extractEditorSrcTask = task.define('extract-editor-src', () => { - console.log(`If the build fails, consider tweaking shakeLevel below to a lower value.`); const apiusages = monacoapi.execute().usageContent; const extrausages = fs.readFileSync(path.join(root, 'build', 'monaco', 'monaco.usage.recipe')).toString(); standalone.extractEditor({ @@ -71,14 +70,6 @@ const extractEditorSrcTask = task.define('extract-editor-src', () => { apiusages, extrausages ], - typings: [ - 'typings/lib.ie11_safe_es6.d.ts', - 'typings/thenable.d.ts', - 'typings/es6-promise.d.ts', - 'typings/require-monaco.d.ts', - "typings/lib.es2018.promise.d.ts", - 'vs/monaco.d.ts' - ], libs: [ `lib.es5.d.ts`, `lib.dom.d.ts`, @@ -86,7 +77,8 @@ const extractEditorSrcTask = task.define('extract-editor-src', () => { ], shakeLevel: 2, // 0-Files, 1-InnerFile, 2-ClassMembers importIgnorePattern: /(^vs\/css!)|(promise-polyfill\/polyfill)/, - destRoot: path.join(root, 'out-editor-src') + destRoot: path.join(root, 'out-editor-src'), + redirects: [] }); }); @@ -137,18 +129,70 @@ const createESMSourcesAndResourcesTask = task.define('extract-editor-esm', () => }); const compileEditorESMTask = task.define('compile-editor-esm', () => { + console.log(`Launching the TS compiler at ${path.join(__dirname, '../out-editor-esm')}...`); + let result; if (process.platform === 'win32') { - const result = cp.spawnSync(`..\\node_modules\\.bin\\tsc.cmd`, { + result = cp.spawnSync(`..\\node_modules\\.bin\\tsc.cmd`, { cwd: path.join(__dirname, '../out-editor-esm') }); - console.log(result.stdout.toString()); - console.log(result.stderr.toString()); } else { - const result = cp.spawnSync(`node`, [`../node_modules/.bin/tsc`], { + result = cp.spawnSync(`node`, [`../node_modules/.bin/tsc`], { cwd: path.join(__dirname, '../out-editor-esm') }); - console.log(result.stdout.toString()); - console.log(result.stderr.toString()); + } + + console.log(result.stdout.toString()); + console.log(result.stderr.toString()); + + if (result.status !== 0) { + console.log(`The TS Compilation failed, preparing analysis folder...`); + const destPath = path.join(__dirname, '../../vscode-monaco-editor-esm-analysis'); + return util.rimraf(destPath)().then(() => { + fs.mkdirSync(destPath); + + // initialize a new repository + cp.spawnSync(`git`, [`init`], { + cwd: destPath + }); + + // build a list of files to copy + const files = util.rreddir(path.join(__dirname, '../out-editor-esm')); + + // copy files from src + for (const file of files) { + const srcFilePath = path.join(__dirname, '../src', file); + const dstFilePath = path.join(destPath, file); + if (fs.existsSync(srcFilePath)) { + util.ensureDir(path.dirname(dstFilePath)); + const contents = fs.readFileSync(srcFilePath).toString().replace(/\r\n|\r|\n/g, '\n'); + fs.writeFileSync(dstFilePath, contents); + } + } + + // create an initial commit to diff against + cp.spawnSync(`git`, [`add`, `.`], { + cwd: destPath + }); + + // create the commit + cp.spawnSync(`git`, [`commit`, `-m`, `"original sources"`, `--no-gpg-sign`], { + cwd: destPath + }); + + // copy files from esm + for (const file of files) { + const srcFilePath = path.join(__dirname, '../out-editor-esm', file); + const dstFilePath = path.join(destPath, file); + if (fs.existsSync(srcFilePath)) { + util.ensureDir(path.dirname(dstFilePath)); + const contents = fs.readFileSync(srcFilePath).toString().replace(/\r\n|\r|\n/g, '\n'); + fs.writeFileSync(dstFilePath, contents); + } + } + + console.log(`Open in VS Code the folder at '${destPath}' and you can alayze the compilation error`); + throw new Error('Standalone Editor compilation failed. If this is the build machine, simply launch `yarn run gulp editor-distro` on your machine to further analyze the compilation problem.'); + }); } }); diff --git a/build/gulpfile.extensions.js b/build/gulpfile.extensions.js index 334899a955..dae86fd9cb 100644 --- a/build/gulpfile.extensions.js +++ b/build/gulpfile.extensions.js @@ -115,7 +115,8 @@ const tasks = compilations.map(function (tsconfigFile) { const compileTask = task.define(`compile-extension:${name}`, task.series(cleanTask, () => { const pipeline = createPipeline(sqlLocalizedExtensions.includes(name), true); // {{SQL CARBON EDIT}} - const input = pipeline.tsProjectSrc(); + const nonts = gulp.src(src, srcOpts).pipe(filter(['**', '!**/*.ts'])); + const input = es.merge(nonts, pipeline.tsProjectSrc()); return input .pipe(pipeline()) @@ -124,7 +125,8 @@ const tasks = compilations.map(function (tsconfigFile) { const watchTask = task.define(`watch-extension:${name}`, task.series(cleanTask, () => { const pipeline = createPipeline(false); - const input = pipeline.tsProjectSrc(); + const nonts = gulp.src(src, srcOpts).pipe(filter(['**', '!**/*.ts'])); + const input = es.merge(nonts, pipeline.tsProjectSrc()); const watchInput = watcher(src, { ...srcOpts, ...{ readDelay: 200 } }); return watchInput @@ -134,7 +136,8 @@ const tasks = compilations.map(function (tsconfigFile) { const compileBuildTask = task.define(`compile-build-extension-${name}`, task.series(cleanTask, () => { const pipeline = createPipeline(true, true); - const input = pipeline.tsProjectSrc(); + const nonts = gulp.src(src, srcOpts).pipe(filter(['**', '!**/*.ts'])); + const input = es.merge(nonts, pipeline.tsProjectSrc()); return input .pipe(pipeline()) diff --git a/build/gulpfile.hygiene.js b/build/gulpfile.hygiene.js index 27a592b413..e94eefb36c 100644 --- a/build/gulpfile.hygiene.js +++ b/build/gulpfile.hygiene.js @@ -125,6 +125,7 @@ const copyrightFilter = [ '!**/*.opts', '!**/*.disabled', '!**/*.code-workspace', + '!**/*.js.map', '!**/promise-polyfill/polyfill.js', '!build/**/*.init', '!resources/linux/snap/snapcraft.yaml', @@ -194,7 +195,8 @@ const tslintBaseFilter = [ '!extensions/**/*.test.ts', '!extensions/html-language-features/server/lib/jquery.d.ts', '!extensions/big-data-cluster/src/bigDataCluster/controller/apiGenerated.ts', // {{SQL CARBON EDIT}}, - '!extensions/big-data-cluster/src/bigDataCluster/controller/tokenApiGenerated.ts' // {{SQL CARBON EDIT}}, + '!extensions/big-data-cluster/src/bigDataCluster/controller/tokenApiGenerated.ts', // {{SQL CARBON EDIT}}, + '!src/vs/workbench/services/themes/common/textMateScopeMatcher.ts' // {{SQL CARBON EDIT}} skip this because we have no plans on touching this and its not ours ]; // {{SQL CARBON EDIT}} @@ -424,7 +426,12 @@ function hygiene(some) { let input; if (Array.isArray(some) || typeof some === 'string' || !some) { - input = vfs.src(some || all, { base: '.', follow: true, allowEmpty: true }); + const options = { base: '.', follow: true, allowEmpty: true }; + if (some) { + input = vfs.src(some, options).pipe(filter(all)); // split this up to not unnecessarily filter all a second time + } else { + input = vfs.src(all, options); + } } else { input = some; } diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index f51ae290f1..40b4f8db5a 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -91,8 +91,7 @@ const vscodeResources = [ 'out-build/vs/code/electron-browser/sharedProcess/sharedProcess.js', 'out-build/vs/code/electron-browser/issue/issueReporter.js', 'out-build/vs/code/electron-browser/processExplorer/processExplorer.js', - // {{SQL CARBON EDIT}} - 'out-build/sql/workbench/electron-browser/splashscreen/*', + 'out-build/sql/workbench/electron-browser/splashscreen/*', // {{SQL CARBON EDIT}} STart 'out-build/sql/**/*.{svg,png,cur,html}', 'out-build/sql/base/browser/ui/table/media/*.{gif,png,svg}', 'out-build/sql/base/browser/ui/checkbox/media/*.{gif,png,svg}', @@ -110,7 +109,8 @@ const vscodeResources = [ 'out-build/sql/media/objectTypes/*.svg', 'out-build/sql/media/icons/*.svg', 'out-build/sql/workbench/parts/notebook/media/**/*.svg', - 'out-build/sql/setup.js', + 'out-build/sql/setup.js', // {{SQL CARBON EDIT}} end + 'out-build/vs/platform/auth/common/auth.css', '!**/test/**' ]; diff --git a/build/lib/builtInExtensions.js b/build/lib/builtInExtensions.js index 85fa091a5b..31551040b9 100644 --- a/build/lib/builtInExtensions.js +++ b/build/lib/builtInExtensions.js @@ -23,6 +23,13 @@ const quality = process.env['VSCODE_QUALITY']; const builtInExtensions = quality && quality === 'stable' ? require('../builtInExtensions.json') : require('../builtInExtensions-insiders.json'); // {{SQL CARBON EDIT}} - END const controlFilePath = path.join(os.homedir(), '.vscode-oss-dev', 'extensions', 'control.json'); +const ENABLE_LOGGING = !process.env['VSCODE_BUILD_BUILTIN_EXTENSIONS_SILENCE_PLEASE']; + +function log() { + if (ENABLE_LOGGING) { + fancyLog.apply(this, arguments); + } +} function getExtensionPath(extension) { return path.join(root, '.build', 'builtInExtensions', extension.name); @@ -47,7 +54,7 @@ function isUpToDate(extension) { function syncMarketplaceExtension(extension) { if (isUpToDate(extension)) { - fancyLog(ansiColors.blue('[marketplace]'), `${extension.name}@${extension.version}`, ansiColors.green('✔︎')); + log(ansiColors.blue('[marketplace]'), `${extension.name}@${extension.version}`, ansiColors.green('✔︎')); return es.readArray([]); } @@ -56,13 +63,13 @@ function syncMarketplaceExtension(extension) { return ext.fromMarketplace(extension.name, extension.version, extension.metadata) .pipe(rename(p => p.dirname = `${extension.name}/${p.dirname}`)) .pipe(vfs.dest('.build/builtInExtensions')) - .on('end', () => fancyLog(ansiColors.blue('[marketplace]'), extension.name, ansiColors.green('✔︎'))); + .on('end', () => log(ansiColors.blue('[marketplace]'), extension.name, ansiColors.green('✔︎'))); } function syncExtension(extension, controlState) { switch (controlState) { case 'disabled': - fancyLog(ansiColors.blue('[disabled]'), ansiColors.gray(extension.name)); + log(ansiColors.blue('[disabled]'), ansiColors.gray(extension.name)); return es.readArray([]); case 'marketplace': @@ -70,15 +77,15 @@ function syncExtension(extension, controlState) { default: if (!fs.existsSync(controlState)) { - fancyLog(ansiColors.red(`Error: Built-in extension '${extension.name}' is configured to run from '${controlState}' but that path does not exist.`)); + log(ansiColors.red(`Error: Built-in extension '${extension.name}' is configured to run from '${controlState}' but that path does not exist.`)); return es.readArray([]); } else if (!fs.existsSync(path.join(controlState, 'package.json'))) { - fancyLog(ansiColors.red(`Error: Built-in extension '${extension.name}' is configured to run from '${controlState}' but there is no 'package.json' file in that directory.`)); + log(ansiColors.red(`Error: Built-in extension '${extension.name}' is configured to run from '${controlState}' but there is no 'package.json' file in that directory.`)); return es.readArray([]); } - fancyLog(ansiColors.blue('[local]'), `${extension.name}: ${ansiColors.cyan(controlState)}`, ansiColors.green('✔︎')); + log(ansiColors.blue('[local]'), `${extension.name}: ${ansiColors.cyan(controlState)}`, ansiColors.green('✔︎')); return es.readArray([]); } } @@ -97,8 +104,8 @@ function writeControlFile(control) { } function main() { - fancyLog('Syncronizing built-in extensions...'); - fancyLog(`You can manage built-in extensions with the ${ansiColors.cyan('--builtin')} flag`); + log('Syncronizing built-in extensions...'); + log(`You can manage built-in extensions with the ${ansiColors.cyan('--builtin')} flag`); const control = readControlFile(); const streams = []; diff --git a/build/lib/compilation.js b/build/lib/compilation.js index 6b65b88e2d..903de37a70 100644 --- a/build/lib/compilation.js +++ b/build/lib/compilation.js @@ -44,7 +44,7 @@ function createCompile(src, build, emitError) { const input = es.through(); const output = input .pipe(utf8Filter) - .pipe(bom()) + .pipe(bom()) // this is required to preserve BOM in test files that loose it otherwise .pipe(utf8Filter.restore) .pipe(tsFilter) .pipe(util.loadSourcemaps()) diff --git a/build/lib/compilation.ts b/build/lib/compilation.ts index 4f5a7b8e80..6409322743 100644 --- a/build/lib/compilation.ts +++ b/build/lib/compilation.ts @@ -54,7 +54,7 @@ function createCompile(src: string, build: boolean, emitError?: boolean) { const input = es.through(); const output = input .pipe(utf8Filter) - .pipe(bom()) + .pipe(bom()) // this is required to preserve BOM in test files that loose it otherwise .pipe(utf8Filter.restore) .pipe(tsFilter) .pipe(util.loadSourcemaps()) diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 3f8c3cec51..6a3f89dc47 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -42,10 +42,18 @@ "name": "vs/workbench/contrib/callHierarchy", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/codeActions", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/comments", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/testCustomEditors", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/debug", "project": "vscode-workbench" @@ -135,7 +143,7 @@ "project": "vscode-workbench" }, { - "name": "vs/workbench/contrib/stats", + "name": "vs/workbench/contrib/tags", "project": "vscode-workbench" }, { @@ -194,6 +202,10 @@ "name": "vs/workbench/services/actions", "project": "vscode-workbench" }, + { + "name": "vs/workbench/services/authToken", + "project": "vscode-workbench" + }, { "name": "vs/workbench/services/bulkEdit", "project": "vscode-workbench" diff --git a/build/lib/standalone.js b/build/lib/standalone.js index e374a22eff..3f5a44ae09 100644 --- a/build/lib/standalone.js +++ b/build/lib/standalone.js @@ -43,7 +43,9 @@ function extractEditor(options) { compilerOptions.declaration = false; compilerOptions.moduleResolution = ts.ModuleResolutionKind.Classic; options.compilerOptions = compilerOptions; - console.log(`Running with shakeLevel ${tss.toStringShakeLevel(options.shakeLevel)}`); + console.log(`Running tree shaker with shakeLevel ${tss.toStringShakeLevel(options.shakeLevel)}`); + // Take the extra included .d.ts files from `tsconfig.monaco.json` + options.typings = tsConfig.include.filter(includedFile => /\.d\.ts$/.test(includedFile)); let result = tss.shake(options); for (let fileName in result) { if (result.hasOwnProperty(fileName)) { diff --git a/build/lib/standalone.ts b/build/lib/standalone.ts index ec579ddad6..f157b7bba7 100644 --- a/build/lib/standalone.ts +++ b/build/lib/standalone.ts @@ -50,7 +50,10 @@ export function extractEditor(options: tss.ITreeShakingOptions & { destRoot: str options.compilerOptions = compilerOptions; - console.log(`Running with shakeLevel ${tss.toStringShakeLevel(options.shakeLevel)}`); + console.log(`Running tree shaker with shakeLevel ${tss.toStringShakeLevel(options.shakeLevel)}`); + + // Take the extra included .d.ts files from `tsconfig.monaco.json` + options.typings = (tsConfig.include).filter(includedFile => /\.d\.ts$/.test(includedFile)); let result = tss.shake(options); for (let fileName in result) { diff --git a/build/lib/treeshaking.js b/build/lib/treeshaking.js index c8bc24dbde..ffcffea8a8 100644 --- a/build/lib/treeshaking.js +++ b/build/lib/treeshaking.js @@ -25,17 +25,17 @@ function toStringShakeLevel(shakeLevel) { } } exports.toStringShakeLevel = toStringShakeLevel; -function printDiagnostics(diagnostics) { +function printDiagnostics(options, diagnostics) { for (const diag of diagnostics) { let result = ''; if (diag.file) { - result += `${diag.file.fileName}: `; + result += `${path.join(options.sourcesRoot, diag.file.fileName)}`; } if (diag.file && diag.start) { let location = diag.file.getLineAndCharacterOfPosition(diag.start); - result += `- ${location.line + 1},${location.character} - `; + result += `:${location.line + 1}:${location.character}`; } - result += JSON.stringify(diag.messageText); + result += ` - ` + JSON.stringify(diag.messageText); console.log(result); } } @@ -44,17 +44,17 @@ function shake(options) { const program = languageService.getProgram(); const globalDiagnostics = program.getGlobalDiagnostics(); if (globalDiagnostics.length > 0) { - printDiagnostics(globalDiagnostics); + printDiagnostics(options, globalDiagnostics); throw new Error(`Compilation Errors encountered.`); } const syntacticDiagnostics = program.getSyntacticDiagnostics(); if (syntacticDiagnostics.length > 0) { - printDiagnostics(syntacticDiagnostics); + printDiagnostics(options, syntacticDiagnostics); throw new Error(`Compilation Errors encountered.`); } const semanticDiagnostics = program.getSemanticDiagnostics(); if (semanticDiagnostics.length > 0) { - printDiagnostics(semanticDiagnostics); + printDiagnostics(options, semanticDiagnostics); throw new Error(`Compilation Errors encountered.`); } markNodes(languageService, options); @@ -358,7 +358,7 @@ function markNodes(languageService, options) { ++step; let node; if (step % 100 === 0) { - console.log(`${step}/${step + black_queue.length + gray_queue.length} (${black_queue.length}, ${gray_queue.length})`); + console.log(`Treeshaking - ${Math.floor(100 * step / (step + black_queue.length + gray_queue.length))}% - ${step}/${step + black_queue.length + gray_queue.length} (${black_queue.length}, ${gray_queue.length})`); } if (black_queue.length === 0) { for (let i = 0; i < gray_queue.length; i++) { diff --git a/build/lib/treeshaking.ts b/build/lib/treeshaking.ts index 87b6f42dc3..e187507c87 100644 --- a/build/lib/treeshaking.ts +++ b/build/lib/treeshaking.ts @@ -71,17 +71,17 @@ export interface ITreeShakingResult { [file: string]: string; } -function printDiagnostics(diagnostics: ReadonlyArray): void { +function printDiagnostics(options: ITreeShakingOptions, diagnostics: ReadonlyArray): void { for (const diag of diagnostics) { let result = ''; if (diag.file) { - result += `${diag.file.fileName}: `; + result += `${path.join(options.sourcesRoot, diag.file.fileName)}`; } if (diag.file && diag.start) { let location = diag.file.getLineAndCharacterOfPosition(diag.start); - result += `- ${location.line + 1},${location.character} - `; + result += `:${location.line + 1}:${location.character}`; } - result += JSON.stringify(diag.messageText); + result += ` - ` + JSON.stringify(diag.messageText); console.log(result); } } @@ -92,19 +92,19 @@ export function shake(options: ITreeShakingOptions): ITreeShakingResult { const globalDiagnostics = program.getGlobalDiagnostics(); if (globalDiagnostics.length > 0) { - printDiagnostics(globalDiagnostics); + printDiagnostics(options, globalDiagnostics); throw new Error(`Compilation Errors encountered.`); } const syntacticDiagnostics = program.getSyntacticDiagnostics(); if (syntacticDiagnostics.length > 0) { - printDiagnostics(syntacticDiagnostics); + printDiagnostics(options, syntacticDiagnostics); throw new Error(`Compilation Errors encountered.`); } const semanticDiagnostics = program.getSemanticDiagnostics(); if (semanticDiagnostics.length > 0) { - printDiagnostics(semanticDiagnostics); + printDiagnostics(options, semanticDiagnostics); throw new Error(`Compilation Errors encountered.`); } @@ -471,7 +471,7 @@ function markNodes(languageService: ts.LanguageService, options: ITreeShakingOpt let node: ts.Node; if (step % 100 === 0) { - console.log(`${step}/${step + black_queue.length + gray_queue.length} (${black_queue.length}, ${gray_queue.length})`); + console.log(`Treeshaking - ${Math.floor(100 * step / (step + black_queue.length + gray_queue.length))}% - ${step}/${step + black_queue.length + gray_queue.length} (${black_queue.length}, ${gray_queue.length})`); } if (black_queue.length === 0) { diff --git a/build/lib/util.js b/build/lib/util.js index ea7b512e63..79cbe51ec6 100644 --- a/build/lib/util.js +++ b/build/lib/util.js @@ -185,6 +185,31 @@ function rimraf(dir) { return result; } exports.rimraf = rimraf; +function _rreaddir(dirPath, prepend, result) { + const entries = fs.readdirSync(dirPath, { withFileTypes: true }); + for (const entry of entries) { + if (entry.isDirectory()) { + _rreaddir(path.join(dirPath, entry.name), `${prepend}/${entry.name}`, result); + } + else { + result.push(`${prepend}/${entry.name}`); + } + } +} +function rreddir(dirPath) { + let result = []; + _rreaddir(dirPath, '', result); + return result; +} +exports.rreddir = rreddir; +function ensureDir(dirPath) { + if (fs.existsSync(dirPath)) { + return; + } + ensureDir(path.dirname(dirPath)); + fs.mkdirSync(dirPath); +} +exports.ensureDir = ensureDir; function getVersion(root) { let version = process.env['BUILD_SOURCEVERSION']; if (!version || !/^[0-9a-f]{40}$/i.test(version)) { diff --git a/build/lib/util.ts b/build/lib/util.ts index 9b63c9fd7e..806173699f 100644 --- a/build/lib/util.ts +++ b/build/lib/util.ts @@ -243,6 +243,31 @@ export function rimraf(dir: string): () => Promise { return result; } +function _rreaddir(dirPath: string, prepend: string, result: string[]): void { + const entries = fs.readdirSync(dirPath, { withFileTypes: true }); + for (const entry of entries) { + if (entry.isDirectory()) { + _rreaddir(path.join(dirPath, entry.name), `${prepend}/${entry.name}`, result); + } else { + result.push(`${prepend}/${entry.name}`); + } + } +} + +export function rreddir(dirPath: string): string[] { + let result: string[] = []; + _rreaddir(dirPath, '', result); + return result; +} + +export function ensureDir(dirPath: string): void { + if (fs.existsSync(dirPath)) { + return; + } + ensureDir(path.dirname(dirPath)); + fs.mkdirSync(dirPath); +} + export function getVersion(root: string): string | undefined { let version = process.env['BUILD_SOURCEVERSION']; diff --git a/build/lib/watch/index.js b/build/lib/watch/index.js index beb7abbe42..d07d6efdde 100644 --- a/build/lib/watch/index.js +++ b/build/lib/watch/index.js @@ -3,14 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -const es = require('event-stream'); - - -let watch = undefined; - -if (!watch) { - watch = process.platform === 'win32' ? require('./watch-win32') : require('gulp-watch'); -} +const watch = process.platform === 'win32' ? require('./watch-win32') : require('vscode-gulp-watch'); module.exports = function () { return watch.apply(null, arguments); diff --git a/build/lib/watch/package.json b/build/lib/watch/package.json index b26f589ce0..d509f873e8 100644 --- a/build/lib/watch/package.json +++ b/build/lib/watch/package.json @@ -5,7 +5,8 @@ "author": "Microsoft ", "private": true, "license": "MIT", - "devDependencies": { - "gulp-watch": "5.0.1" + "devDependencies": {}, + "dependencies": { + "vscode-gulp-watch": "^5.0.2" } } diff --git a/build/lib/watch/yarn.lock b/build/lib/watch/yarn.lock index f7d5d976b1..3f330da176 100644 --- a/build/lib/watch/yarn.lock +++ b/build/lib/watch/yarn.lock @@ -2,11 +2,6 @@ # yarn lockfile v1 -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== - ansi-colors@1.1.0, ansi-colors@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-1.1.0.tgz#6374b4dd5d4718ff3ce27a671a3b1cad077132a9" @@ -21,11 +16,6 @@ ansi-gray@^0.1.1: dependencies: ansi-wrap "0.1.0" -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - ansi-wrap@0.1.0, ansi-wrap@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" @@ -39,26 +29,13 @@ anymatch@^1.3.0: micromatch "^2.1.5" normalize-path "^2.0.0" -anymatch@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" - integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== +anymatch@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" + integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" - -aproba@^1.0.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== - -are-we-there-yet@~1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d" - integrity sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0= - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" + normalize-path "^3.0.0" + picomatch "^2.0.4" arr-diff@^2.0.0: version "2.0.0" @@ -72,7 +49,7 @@ arr-diff@^4.0.0: resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= -arr-flatten@^1.0.1, arr-flatten@^1.1.0: +arr-flatten@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== @@ -87,56 +64,15 @@ array-unique@^0.2.1: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" integrity sha1-odl8yvy8JiXMcPrc6zalDFiwGlM= -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= - assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= -async-each@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" - integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== - -atob@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - -binary-extensions@^1.0.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.10.0.tgz#9aeb9a6c5e88638aad171e167f5900abe24835d0" - integrity sha1-muuabF6IY4qtFx4Wf1kAq+JINdA= - -brace-expansion@^1.1.7: - version "1.1.8" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" - integrity sha1-wHshHHyVLsH479Uad+8NHTmQopI= - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" +binary-extensions@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" + integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== braces@^1.8.2: version "1.8.5" @@ -147,70 +83,27 @@ braces@^1.8.2: preserve "^0.2.0" repeat-element "^1.1.2" -braces@^2.3.1, braces@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== +braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" + fill-range "^7.0.1" -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== +chokidar@3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.0.tgz#12c0714668c55800f659e262d4962a97faf554a6" + integrity sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A== dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - -chokidar@^2.0.0: - version "2.1.6" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.6.tgz#b6cad653a929e244ce8a834244164d241fa954c5" - integrity sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g== - dependencies: - anymatch "^2.0.0" - async-each "^1.0.1" - braces "^2.3.2" - glob-parent "^3.1.0" - inherits "^2.0.3" - is-binary-path "^1.0.0" - is-glob "^4.0.0" - normalize-path "^3.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.2.1" - upath "^1.1.1" + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.2.0" optionalDependencies: - fsevents "^1.2.7" - -chownr@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.2.tgz#a18f1e0b269c8a6a5d3c86eb298beb14c3dd7bf6" - integrity sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A== - -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" + fsevents "~2.1.1" clone-buffer@^1.0.0: version "1.0.0" @@ -228,9 +121,9 @@ clone-stats@^1.0.0: integrity sha1-s3gt/4u1R04Yuba/D9/ngvh3doA= clone@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.3.tgz#298d7e2231660f40c003c2ed3140decf3f53085f" - integrity sha1-KY1+IjFmD0DAA8LtMUDezz9TCF8= + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= clone@^2.1.1: version "2.1.2" @@ -246,105 +139,16 @@ cloneable-readable@^1.0.0: process-nextick-args "^2.0.0" readable-stream "^2.3.5" -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= - -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - color-support@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== -component-emitter@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -console-control-strings@^1.0.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= - -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= - 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= -debug@^2.2.0, debug@^2.3.3: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@^3.2.6: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== - dependencies: - ms "^2.1.1" - -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= - -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= - -detect-libc@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.2.tgz#71ad5d204bf17a6a6ca8f450c61454066ef461e1" - integrity sha1-ca1dIEvxempsqPRQxhRUBm70YeE= - expand-brackets@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" @@ -352,19 +156,6 @@ expand-brackets@^0.1.4: dependencies: is-posix-bracket "^0.1.0" -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - expand-range@^1.8.1: version "1.8.2" resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" @@ -372,14 +163,7 @@ expand-range@^1.8.1: dependencies: fill-range "^2.1.0" -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: +extend-shallow@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= @@ -394,20 +178,6 @@ extglob@^0.3.1: dependencies: is-extglob "^1.0.0" -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - fancy-log@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.2.tgz#f41125e3d84f2e7d89a43d06d958c8f78be16be1" @@ -423,25 +193,22 @@ filename-regex@^2.0.0: integrity sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY= fill-range@^2.1.0: - version "2.2.3" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" - integrity sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM= + version "2.2.4" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565" + integrity sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q== dependencies: is-number "^2.1.0" isobject "^2.0.0" - randomatic "^1.1.3" + randomatic "^3.0.0" repeat-element "^1.1.2" repeat-string "^1.5.2" -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" + to-regex-range "^5.0.1" first-chunk-stream@^2.0.0: version "2.0.0" @@ -450,7 +217,7 @@ first-chunk-stream@^2.0.0: dependencies: readable-stream "^2.0.2" -for-in@^1.0.1, for-in@^1.0.2: +for-in@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= @@ -462,51 +229,10 @@ for-own@^0.1.4: dependencies: for-in "^1.0.1" -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= - dependencies: - map-cache "^0.2.2" - -fs-minipass@^1.2.5: - version "1.2.6" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07" - integrity sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ== - dependencies: - minipass "^2.2.1" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -fsevents@^1.2.7: - version "1.2.9" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.9.tgz#3f5ed66583ccd6f400b5a00db6f7e861363e388f" - integrity sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw== - dependencies: - nan "^2.12.1" - node-pre-gyp "^0.12.0" - -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= +fsevents@~2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.1.tgz#74c64e21df71721845d0c44fe54b7f56b82995a9" + integrity sha512-4FRPXWETxtigtJW/gxzEDsX1LVbPAM93VleB83kZB+ellqbHMkyt2aJfuzNLRvFPnGi6bcE5SvfxgbXPeKteJw== glob-base@^0.3.0: version "0.3.0" @@ -523,7 +249,7 @@ glob-parent@^2.0.0: dependencies: is-glob "^2.0.0" -glob-parent@^3.0.1, glob-parent@^3.1.0: +glob-parent@^3.0.1: version "3.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= @@ -531,177 +257,35 @@ glob-parent@^3.0.1, glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob@^7.0.5: - version "7.1.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" - integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ== +glob-parent@~5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" + integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw== dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -graceful-fs@^4.1.11: - version "4.2.0" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.0.tgz#8d8fdc73977cb04104721cb53666c1ca64cd328b" - integrity sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg== + is-glob "^4.0.1" graceful-fs@^4.1.2: - version "4.1.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" - integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg= + version "4.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" + integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== -gulp-watch@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/gulp-watch/-/gulp-watch-5.0.1.tgz#83d378752f5bfb46da023e73c17ed1da7066215d" - integrity sha512-HnTSBdzAOFIT4wmXYPDUn783TaYAq9bpaN05vuZNP5eni3z3aRx0NAKbjhhMYtcq76x4R1wf4oORDGdlrEjuog== - dependencies: - ansi-colors "1.1.0" - anymatch "^1.3.0" - chokidar "^2.0.0" - fancy-log "1.3.2" - glob-parent "^3.0.1" - object-assign "^4.1.0" - path-is-absolute "^1.0.1" - plugin-error "1.0.1" - readable-stream "^2.2.2" - slash "^1.0.0" - vinyl "^2.1.0" - vinyl-file "^2.0.0" - -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= - -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - -iconv-lite@^0.4.4: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -ignore-walk@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" - integrity sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ== - dependencies: - minimatch "^3.0.4" - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.1, inherits@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - -inherits@^2.0.3: +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== -ini@~1.3.0: - version "1.3.4" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" - integrity sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4= - -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== dependencies: - kind-of "^3.0.2" - -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== - dependencies: - kind-of "^6.0.0" - -is-binary-path@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" - integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= - dependencies: - binary-extensions "^1.0.0" + binary-extensions "^2.0.0" is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= - dependencies: - kind-of "^3.0.2" - -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" - integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== - dependencies: - kind-of "^6.0.0" - -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - is-dotfile@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" @@ -714,7 +298,7 @@ is-equal-shallow@^0.1.3: dependencies: is-primitive "^2.0.0" -is-extendable@^0.1.0, is-extendable@^0.1.1: +is-extendable@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= @@ -736,13 +320,6 @@ is-extglob@^2.1.0, is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - dependencies: - number-is-nan "^1.0.0" - is-glob@^2.0.0, is-glob@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" @@ -757,7 +334,7 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" -is-glob@^4.0.0: +is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== @@ -771,14 +348,17 @@ is-number@^2.1.0: dependencies: kind-of "^3.0.2" -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= - dependencies: - kind-of "^3.0.2" +is-number@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" + integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ== -is-plain-object@^2.0.3, is-plain-object@^2.0.4: +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== @@ -800,11 +380,6 @@ is-utf8@^0.2.0: resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= -is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - isarray@1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -817,46 +392,27 @@ isobject@^2.0.0: dependencies: isarray "1.0.0" -isobject@^3.0.0, isobject@^3.0.1: +isobject@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: +kind-of@^3.0.2: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= dependencies: is-buffer "^1.1.5" -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= - dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== - -kind-of@^6.0.0, kind-of@^6.0.2: +kind-of@^6.0.0: version "6.0.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= - dependencies: - object-visit "^1.0.0" +math-random@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c" + integrity sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A== micromatch@^2.1.5: version "2.3.11" @@ -877,198 +433,23 @@ micromatch@^2.1.5: parse-glob "^3.0.4" regex-cache "^0.4.2" -micromatch@^3.1.10, micromatch@^3.1.4: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= - -minimist@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= - -minipass@^2.2.1, minipass@^2.3.5: - version "2.3.5" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" - integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA== - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - -minizlib@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" - integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA== - dependencies: - minipass "^2.2.1" - -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - -mkdirp@^0.5.0, mkdirp@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= - dependencies: - minimist "0.0.8" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - -ms@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -nan@^2.12.1: - version "2.14.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" - integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== - -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -needle@^2.2.1: - version "2.4.0" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c" - integrity sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg== - dependencies: - debug "^3.2.6" - iconv-lite "^0.4.4" - sax "^1.2.4" - -node-pre-gyp@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149" - integrity sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A== - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.1" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.2.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4" - -nopt@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" - integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= - dependencies: - abbrev "1" - osenv "^0.1.4" - -normalize-path@^2.0.0, normalize-path@^2.0.1, normalize-path@^2.1.1: +normalize-path@^2.0.0, normalize-path@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= dependencies: remove-trailing-separator "^1.0.1" -normalize-path@^3.0.0: +normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -npm-bundled@^1.0.1: - version "1.0.6" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" - integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g== - -npm-packlist@^1.1.6: - version "1.4.4" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.4.tgz#866224233850ac534b63d1a6e76050092b5d2f44" - integrity sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw== - dependencies: - ignore-walk "^3.0.1" - npm-bundled "^1.0.1" - -npmlog@^4.0.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= - object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= - dependencies: - isobject "^3.0.0" - object.omit@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" @@ -1077,38 +458,6 @@ object.omit@^2.0.0: for-own "^0.1.4" is-extendable "^0.1.1" -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= - dependencies: - isobject "^3.0.1" - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= - -os-tmpdir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= - -osenv@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644" - integrity sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ= - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - parse-glob@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" @@ -1119,21 +468,21 @@ parse-glob@^3.0.4: is-extglob "^1.0.0" is-glob "^2.0.0" -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= - path-dirname@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= -path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: +path-is-absolute@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= +picomatch@^2.0.4: + version "2.1.0" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.1.0.tgz#0fd042f568d08b1ad9ff2d3ec0f0bfb3cb80e177" + integrity sha512-uhnEDzAbrcJ8R3g2fANnSuXZMBtkpSjxTTgn2LeSiQlfmq72enQJWdQllXW24MBLYnA1SBD2vfvx2o0Zw3Ielw== + pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -1161,11 +510,6 @@ plugin-error@1.0.1: arr-union "^3.1.0" extend-shallow "^3.0.2" -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= - preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" @@ -1176,43 +520,16 @@ 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= - -randomatic@^1.1.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" - integrity sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how== +randomatic@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed" + integrity sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw== dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" + is-number "^4.0.0" + kind-of "^6.0.0" + math-random "^1.0.1" -rc@^1.2.7: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.2.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" - integrity sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - safe-buffer "~5.1.1" - string_decoder "~1.0.3" - util-deprecate "~1.0.1" - -readable-stream@^2.3.5: +readable-stream@^2.0.2, readable-stream@^2.2.2, readable-stream@^2.3.5: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== @@ -1225,14 +542,12 @@ readable-stream@^2.3.5: string_decoder "~1.1.1" 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" - integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== +readdirp@~3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.2.0.tgz#c30c33352b12c96dfb4b895421a49fd5a9593839" + integrity sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ== dependencies: - graceful-fs "^4.1.11" - micromatch "^3.1.10" - readable-stream "^2.0.2" + picomatch "^2.0.4" regex-cache@^0.4.2: version "0.4.4" @@ -1241,25 +556,17 @@ regex-cache@^0.4.2: dependencies: is-equal-shallow "^0.1.3" -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - 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" integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= repeat-element@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" - integrity sha1-7wiaF40Ug7quTZPrmLT55OEdmQo= + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" + integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== -repeat-string@^1.5.2, repeat-string@^1.6.1: +repeat-string@^1.5.2: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= @@ -1274,161 +581,10 @@ replace-ext@^1.0.0: resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs= -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= - -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - -rimraf@^2.6.1: - version "2.6.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" - integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w== - dependencies: - glob "^7.0.5" - -safe-buffer@^5.1.2: - version "5.2.0" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" - integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== - safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" - integrity sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg== - -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= - dependencies: - ret "~0.1.10" - -"safer-buffer@>= 2.1.2 < 3": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sax@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - -semver@^5.3.0: - version "5.4.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" - integrity sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg== - -set-blocking@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= - -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - -signal-exit@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" - integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= - -slash@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" - integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= - -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - -source-map-resolve@^0.5.0: - version "0.5.2" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" - integrity sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA== - dependencies: - atob "^2.1.1" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - -source-map-url@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" - integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= - -source-map@^0.5.6: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= - -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" - -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - -string-width@^1.0.1, string-width@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -string_decoder@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" - integrity sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ== - dependencies: - safe-buffer "~5.1.0" + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== string_decoder@~1.1.1: version "1.1.1" @@ -1437,13 +593,6 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - strip-bom-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-bom-stream/-/strip-bom-stream-2.0.0.tgz#f87db5ef2613f6968aa545abfe1ec728b6a829ca" @@ -1459,86 +608,17 @@ strip-bom@^2.0.0: dependencies: is-utf8 "^0.2.0" -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - -tar@^4: - version "4.4.10" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.10.tgz#946b2810b9a5e0b26140cf78bea6b0b0d689eba1" - integrity sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA== - dependencies: - chownr "^1.1.1" - fs-minipass "^1.2.5" - minipass "^2.3.5" - minizlib "^1.2.1" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.3" - time-stamp@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" integrity sha1-dkpaEa9QVhkhsTPztE5hhofg9cM= -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" - -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - -upath@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.2.tgz#3db658600edaeeccbe6db5e684d67ee8c2acd068" - integrity sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q== - -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= - -use@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + is-number "^7.0.0" util-deprecate@~1.0.1: version "1.0.2" @@ -1578,19 +658,20 @@ vinyl@^2.1.0: remove-trailing-separator "^1.0.1" replace-ext "^1.0.0" -wide-align@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710" - integrity sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w== +vscode-gulp-watch@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/vscode-gulp-watch/-/vscode-gulp-watch-5.0.2.tgz#0060ba8d091284a6fbd7e608aa318a9c1d73b840" + integrity sha512-l2v+W3iQvxpX2ny2C7eJTd+83rQXiZ85KGY0mub/QRqUxgDc+KH/EYiw4mttzIhPzVBmxrUO4RcLNbPdccg0mQ== dependencies: - string-width "^1.0.2" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -yallist@^3.0.0, yallist@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" - integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== + ansi-colors "1.1.0" + anymatch "^1.3.0" + chokidar "3.3.0" + fancy-log "1.3.2" + glob-parent "^3.0.1" + normalize-path "^3.0.0" + object-assign "^4.1.0" + path-is-absolute "^1.0.1" + plugin-error "1.0.1" + readable-stream "^2.2.2" + vinyl "^2.1.0" + vinyl-file "^2.0.0" diff --git a/build/monaco/monaco.d.ts.recipe b/build/monaco/monaco.d.ts.recipe index b45cc8b620..57a446cda1 100644 --- a/build/monaco/monaco.d.ts.recipe +++ b/build/monaco/monaco.d.ts.recipe @@ -43,7 +43,7 @@ declare namespace monaco { } declare namespace monaco.editor { - +#include(vs/editor/browser/widget/diffNavigator): IDiffNavigator #includeAll(vs/editor/standalone/browser/standaloneEditor;modes.=>languages.;editorCommon.=>): #include(vs/editor/standalone/common/standaloneThemeService): BuiltinTheme, IStandaloneThemeData, IColors #include(vs/editor/common/modes/supports/tokenization): ITokenThemeRule diff --git a/build/monaco/monaco.usage.recipe b/build/monaco/monaco.usage.recipe index f1a74a25e3..3c48da8d85 100644 --- a/build/monaco/monaco.usage.recipe +++ b/build/monaco/monaco.usage.recipe @@ -2,38 +2,17 @@ // This file is adding references to various symbols which should not be removed via tree shaking import { ServiceIdentifier } from './vs/platform/instantiation/common/instantiation'; -import { IContextViewService } from './vs/platform/contextview/browser/contextView'; -import { IHighlight } from './vs/base/parts/quickopen/browser/quickOpenModel'; -import { IWorkspaceContextService } from './vs/platform/workspace/common/workspace'; -import { IEnvironmentService } from './vs/platform/environment/common/environment'; -import { CountBadge } from './vs/base/browser/ui/countBadge/countBadge'; -import { SimpleWorkerClient, create as create1 } from './vs/base/common/worker/simpleWorker'; +import { create as create1 } from './vs/base/common/worker/simpleWorker'; import { create as create2 } from './vs/editor/common/services/editorSimpleWorker'; -import { QuickOpenWidget } from './vs/base/parts/quickopen/browser/quickOpenWidget'; -import { WorkbenchAsyncDataTree } from './vs/platform/list/browser/listService'; import { SyncDescriptor0, SyncDescriptor1, SyncDescriptor2, SyncDescriptor3, SyncDescriptor4, SyncDescriptor5, SyncDescriptor6, SyncDescriptor7, SyncDescriptor8 } from './vs/platform/instantiation/common/descriptors'; -import { DiffNavigator } from './vs/editor/browser/widget/diffNavigator'; -import { DocumentRangeFormattingEditProvider } from './vs/editor/common/modes'; import * as editorAPI from './vs/editor/editor.api'; (function () { var a: any; var b: any; - a = (b).layout; // IContextViewProvider - a = (b).getWorkspaceFolder; // IWorkspaceFolderProvider - a = (b).getWorkspace; // IWorkspaceFolderProvider - a = (b).style; // IThemable - a = (b).style; // IThemable - a = (>b).style; // IThemable - a = (b).userHome; // IUserHomeProvider - a = (b).previous; // IDiffNavigator a = (>b).type; - a = (b).start; - a = (b).end; - a = (>b).getProxyObject; // IWorkerClient a = create1; a = create2; - a = (b).extensionId; // injection madness a = (>b).ctor; diff --git a/build/npm/postinstall.js b/build/npm/postinstall.js index 5a6a2bc2a3..86f41ba3dc 100644 --- a/build/npm/postinstall.js +++ b/build/npm/postinstall.js @@ -73,48 +73,3 @@ yarnInstall(`build`); // node modules required for build yarnInstall('test/automation'); // node modules required for smoketest yarnInstall('test/smoke'); // node modules required for smoketest yarnInstallBuildDependencies(); // node modules for watching, specific to host node version, not electron - -// Remove the windows process tree typings as this causes duplicate identifier errors in tsc builds -const processTreeDts = path.join('node_modules', 'windows-process-tree', 'typings', 'windows-process-tree.d.ts'); -if (fs.existsSync(processTreeDts)) { - console.log('Removing windows-process-tree.d.ts'); - fs.unlinkSync(processTreeDts); -} - -function getInstalledVersion(packageName, cwd) { - const opts = {}; - if (cwd) { - opts.cwd = cwd; - } - - const result = cp.spawnSync(yarn, ['list', '--pattern', packageName], opts); - const stdout = result.stdout.toString(); - const match = stdout.match(new RegExp(packageName + '@(\\S+)')); - if (!match || !match[1]) { - throw new Error('Unexpected output from yarn list: ' + stdout); - } - - return match[1]; -} - -function assertSameVersionsBetweenFolders(packageName, otherFolder) { - const baseVersion = getInstalledVersion(packageName); - const otherVersion = getInstalledVersion(packageName, otherFolder); - - if (baseVersion !== otherVersion) { - throw new Error(`Mismatched versions installed for ${packageName}: root has ${baseVersion}, ./${otherFolder} has ${otherVersion}. These should be the same!`); - } -} - -// Check that modules in both the base package.json and remote/ have the same version installed -const requireSameVersionsInRemote = [ - 'xterm', - 'xterm-addon-search', - 'xterm-addon-web-links', - 'node-pty', - 'vscode-ripgrep' -]; - -requireSameVersionsInRemote.forEach(packageName => { - assertSameVersionsBetweenFolders(packageName, 'remote'); -}); diff --git a/build/npm/preinstall.js b/build/npm/preinstall.js index 6b1f5982b9..77f8eb52ce 100644 --- a/build/npm/preinstall.js +++ b/build/npm/preinstall.js @@ -7,8 +7,8 @@ let err = false; const majorNodeVersion = parseInt(/^(\d+)\./.exec(process.versions.node)[1]); -if (majorNodeVersion < 8 || majorNodeVersion >= 11) { - console.error('\033[1;31m*** Please use node >=8 and <11.\033[0;0m'); +if (majorNodeVersion < 10 || majorNodeVersion >= 13) { + console.error('\033[1;31m*** Please use node >=10 and <=12.\033[0;0m'); err = true; } diff --git a/build/package.json b/build/package.json index 50116ab295..2893570167 100644 --- a/build/package.json +++ b/build/package.json @@ -6,7 +6,7 @@ "@types/ansi-colors": "^3.2.0", "@types/azure": "0.9.19", "@types/debounce": "^1.0.0", - "@types/documentdb": "1.10.2", + "@types/documentdb": "^1.10.5", "@types/fancy-log": "^1.3.0", "@types/glob": "^7.1.1", "@types/gulp": "^4.0.5", @@ -44,10 +44,10 @@ "rollup": "^1.20.3", "rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-node-resolve": "^5.2.0", + "service-downloader": "github:anthonydresser/service-downloader#0.1.7", "terser": "4.3.8", "tslint": "^5.9.1", - "service-downloader": "github:anthonydresser/service-downloader#0.1.7", - "typescript": "3.7.0-dev.20191017", + "typescript": "3.7.2", "vsce": "1.48.0", "vscode-telemetry-extractor": "^1.5.4", "xml2js": "^0.4.17" @@ -57,5 +57,8 @@ "watch": "tsc -p tsconfig.build.json --watch", "postinstall": "npm run compile", "npmCheckJs": "tsc --noEmit" + }, + "dependencies": { + "@azure/cosmos": "^3.4.0" } } diff --git a/build/win32/i18n/messages.de.isl b/build/win32/i18n/messages.de.isl index 755652bcdb..6a9f29aa9c 100644 --- a/build/win32/i18n/messages.de.isl +++ b/build/win32/i18n/messages.de.isl @@ -5,4 +5,5 @@ AssociateWithFiles=%1 als Editor f AddToPath=Zu PATH hinzufgen (nach dem Neustart verfgbar) RunAfter=%1 nach der Installation ausfhren Other=Andere: -SourceFile=%1-Quelldatei \ No newline at end of file +SourceFile=%1-Quelldatei +OpenWithCodeContextMenu=Mit %1 ffnen \ No newline at end of file diff --git a/build/win32/i18n/messages.en.isl b/build/win32/i18n/messages.en.isl index a6aab59b95..6535824eed 100644 --- a/build/win32/i18n/messages.en.isl +++ b/build/win32/i18n/messages.en.isl @@ -5,4 +5,5 @@ AssociateWithFiles=Register %1 as an editor for supported file types AddToPath=Add to PATH (requires shell restart) RunAfter=Run %1 after installation Other=Other: -SourceFile=%1 Source File \ No newline at end of file +SourceFile=%1 Source File +OpenWithCodeContextMenu=Open with %1 \ No newline at end of file diff --git a/build/win32/i18n/messages.es.isl b/build/win32/i18n/messages.es.isl index e8d5af64b6..e51f099f9a 100644 --- a/build/win32/i18n/messages.es.isl +++ b/build/win32/i18n/messages.es.isl @@ -5,4 +5,5 @@ AssociateWithFiles=Registrar %1 como editor para tipos de archivo admitidos AddToPath=Agregar a PATH (disponible despus de reiniciar) RunAfter=Ejecutar %1 despus de la instalacin Other=Otros: -SourceFile=Archivo de origen %1 \ No newline at end of file +SourceFile=Archivo de origen %1 +OpenWithCodeContextMenu=Abrir con %1 \ No newline at end of file diff --git a/build/win32/i18n/messages.fr.isl b/build/win32/i18n/messages.fr.isl index 3e17036f80..df14041862 100644 --- a/build/win32/i18n/messages.fr.isl +++ b/build/win32/i18n/messages.fr.isl @@ -5,4 +5,5 @@ AssociateWithFiles=Inscrire %1 en tant qu' AddToPath=Ajouter PATH (disponible aprs le redmarrage) RunAfter=Excuter %1 aprs l'installation Other=Autre: -SourceFile=Fichier source %1 \ No newline at end of file +SourceFile=Fichier source %1 +OpenWithCodeContextMenu=Ouvrir avec %1 \ No newline at end of file diff --git a/build/win32/i18n/messages.hu.isl b/build/win32/i18n/messages.hu.isl index f141ad1b24..b64553da8e 100644 --- a/build/win32/i18n/messages.hu.isl +++ b/build/win32/i18n/messages.hu.isl @@ -5,4 +5,5 @@ AssociateWithFiles=%1 regisztr AddToPath=Hozzads a PATH-hoz (jraindts utn lesz elrhet) RunAfter=%1 indtsa a telepts utn Other=Egyb: -SourceFile=%1 forrsfjl \ No newline at end of file +SourceFile=%1 forrsfjl +OpenWithCodeContextMenu=Megnyits a kvetkezvel: %1 \ No newline at end of file diff --git a/build/win32/i18n/messages.it.isl b/build/win32/i18n/messages.it.isl index 7ad8a0622d..08248c4ce1 100644 --- a/build/win32/i18n/messages.it.isl +++ b/build/win32/i18n/messages.it.isl @@ -5,4 +5,5 @@ AssociateWithFiles=Registra %1 come editor per i tipi di file supportati AddToPath=Aggiungi a PATH (disponibile dopo il riavvio) RunAfter=Esegui %1 dopo l'installazione Other=Altro: -SourceFile=File di origine %1 \ No newline at end of file +SourceFile=File di origine %1 +OpenWithCodeContextMenu=Apri con %1 \ No newline at end of file diff --git a/build/win32/i18n/messages.ja.isl b/build/win32/i18n/messages.ja.isl index 3a16aaa204..9675060e94 100644 --- a/build/win32/i18n/messages.ja.isl +++ b/build/win32/i18n/messages.ja.isl @@ -5,4 +5,5 @@ AssociateWithFiles= AddToPath=PATH ւ̒ljiċNɎgp”\j RunAfter=CXg[ %1 s Other=̑: -SourceFile=%1 \[X t@C \ No newline at end of file +SourceFile=%1 \[X t@C +OpenWithCodeContextMenu=%1 ŊJ \ No newline at end of file diff --git a/build/win32/i18n/messages.ko.isl b/build/win32/i18n/messages.ko.isl index 28860c36b6..5a510558bb 100644 --- a/build/win32/i18n/messages.ko.isl +++ b/build/win32/i18n/messages.ko.isl @@ -5,4 +5,5 @@ AssociateWithFiles=%1 AddToPath=PATH ߰(ٽ ) RunAfter=ġ %1 Other=Ÿ: -SourceFile=%1 \ No newline at end of file +SourceFile=%1 +OpenWithCodeContextMenu=%1() \ No newline at end of file diff --git a/build/win32/i18n/messages.pt-br.isl b/build/win32/i18n/messages.pt-br.isl index d534637f8b..e327e8fd1a 100644 --- a/build/win32/i18n/messages.pt-br.isl +++ b/build/win32/i18n/messages.pt-br.isl @@ -5,4 +5,5 @@ AssociateWithFiles=Registre %1 como um editor para tipos de arquivos suportados AddToPath=Adicione em PATH (disponvel aps reiniciar) RunAfter=Executar %1 aps a instalao Other=Outros: -SourceFile=Arquivo Fonte %1 \ No newline at end of file +SourceFile=Arquivo Fonte %1 +OpenWithCodeContextMenu=Abrir com %1 \ No newline at end of file diff --git a/build/win32/i18n/messages.ru.isl b/build/win32/i18n/messages.ru.isl index 4d83466362..bca3b864a5 100644 --- a/build/win32/i18n/messages.ru.isl +++ b/build/win32/i18n/messages.ru.isl @@ -5,4 +5,5 @@ AssociateWithFiles= AddToPath= PATH ( ) RunAfter= %1 Other=: -SourceFile= %1 \ No newline at end of file +SourceFile= %1 +OpenWithCodeContextMenu= %1 \ No newline at end of file diff --git a/build/win32/i18n/messages.tr.isl b/build/win32/i18n/messages.tr.isl index dc241d924c..b13e5e27bd 100644 --- a/build/win32/i18n/messages.tr.isl +++ b/build/win32/i18n/messages.tr.isl @@ -5,4 +5,5 @@ AssociateWithFiles=%1 uygulamas AddToPath=PATH'e ekle (yeniden balattktan sonra kullanlabilir) RunAfter=Kurulumdan sonra %1 uygulamasn altr. Other=Dier: -SourceFile=%1 Kaynak Dosyas \ No newline at end of file +SourceFile=%1 Kaynak Dosyas +OpenWithCodeContextMenu=%1 le A \ No newline at end of file diff --git a/build/win32/i18n/messages.zh-cn.isl b/build/win32/i18n/messages.zh-cn.isl index 349fc2ccc2..8fa136f6d5 100644 --- a/build/win32/i18n/messages.zh-cn.isl +++ b/build/win32/i18n/messages.zh-cn.isl @@ -5,4 +5,5 @@ AssociateWithFiles= AddToPath=ӵ PATH (Ч) RunAfter=װ %1 Other=: -SourceFile=%1 Դļ \ No newline at end of file +SourceFile=%1 Դļ +OpenWithCodeContextMenu=ͨ %1 \ No newline at end of file diff --git a/build/win32/i18n/messages.zh-tw.isl b/build/win32/i18n/messages.zh-tw.isl index 7c3f84aa13..40c5fa92d7 100644 --- a/build/win32/i18n/messages.zh-tw.isl +++ b/build/win32/i18n/messages.zh-tw.isl @@ -5,4 +5,5 @@ AssociateWithFiles= AddToPath=[J PATH (sҰʫͮ) RunAfter=w˫ %1 Other=L: -SourceFile=%1 ӷɮ \ No newline at end of file +SourceFile=%1 ӷɮ +OpenWithCodeContextMenu=H %1 } \ No newline at end of file diff --git a/build/yarn.lock b/build/yarn.lock index a32f725051..e39467f9f3 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -2,6 +2,22 @@ # yarn lockfile v1 +"@azure/cosmos@^3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@azure/cosmos/-/cosmos-3.4.0.tgz#96f36a4522be23e1389d0516ea4d77e5fc153221" + integrity sha512-4ym+ezk7qBe4s7/tb6IJ5kmXE4xgEbAPbraT3382oeCRlYpGrblIZIDoWbthMCJfLyLBDX5T05Fhm18QeY1R/w== + dependencies: + "@types/debug" "^4.1.4" + debug "^4.1.1" + fast-json-stable-stringify "^2.0.0" + node-abort-controller "^1.0.4" + node-fetch "^2.6.0" + os-name "^3.1.0" + priorityqueuejs "^1.0.0" + semaphore "^1.0.5" + tslib "^1.9.3" + uuid "^3.3.2" + "@dsherret/to-absolute-glob@^2.0.2": version "2.0.2" resolved "https://registry.yarnpkg.com/@dsherret/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1f6475dc8bd974cea07a2daf3864b317b1dd332c" @@ -61,10 +77,15 @@ resolved "https://registry.yarnpkg.com/@types/debounce/-/debounce-1.0.0.tgz#417560200331e1bb84d72da85391102c2fcd61b7" integrity sha1-QXVgIAMx4buE1y2oU5EQLC/NYbc= -"@types/documentdb@1.10.2": - version "1.10.2" - resolved "https://registry.yarnpkg.com/@types/documentdb/-/documentdb-1.10.2.tgz#6795025cdc51577af5ed531b6f03bd44404f5350" - integrity sha512-A+AsxkjKB/mpnuNl2L/YP7azlpd/n/55rtBTTKYj203g5hSrDfv06AnN8v+pO1Qo6vCxm6JsKV/BaEBmgx4gaQ== +"@types/debug@^4.1.4": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd" + integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ== + +"@types/documentdb@^1.10.5": + version "1.10.5" + resolved "https://registry.yarnpkg.com/@types/documentdb/-/documentdb-1.10.5.tgz#86c6ec9be9ce07ff9fcac5ca3c17570c385d40a4" + integrity sha512-FHQV9Nc1ffrLkQxO0zFlDCRPyHZtuKmAAuJIi278COhtkKBuBRuKOzoO3JlT0yfUrivPjAzNae+gh9fS++r0Ag== dependencies: "@types/node" "*" @@ -950,6 +971,17 @@ core-util-is@1.0.2, core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= +cross-spawn@^6.0.0: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + cryptiles@2.x.x: version "2.0.5" resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" @@ -1031,6 +1063,13 @@ debug@^3.1.0: dependencies: ms "^2.1.1" +debug@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" @@ -1254,6 +1293,13 @@ end-of-stream@^1.0.0: dependencies: once "^1.4.0" +end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + entities@^1.1.1, entities@~1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" @@ -1296,6 +1342,19 @@ eventemitter2@^5.0.1: resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-5.0.1.tgz#6197a095d5fb6b57e8942f6fd7eaad63a09c9452" integrity sha1-YZegldX7a1folC9v1+qtY6CclFI= +execa@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== + dependencies: + cross-spawn "^6.0.0" + get-stream "^4.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + expand-brackets@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" @@ -1528,6 +1587,13 @@ get-stream@^2.2.0: object-assign "^4.0.1" pinkie-promise "^2.0.0" +get-stream@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -2152,6 +2218,11 @@ isarray@1.0.0, isarray@~1.0.0: resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + isobject@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" @@ -2388,6 +2459,11 @@ lodash@^4.15.0, lodash@^4.17.10: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== +macos-release@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.3.0.tgz#eb1930b036c0800adebccd5f17bc4c12de8bb71f" + integrity sha512-OHhSbtcviqMPt7yfw5ef5aghS2jzFVKEFyCJndQt2YpSQ9qRVSEv2axSJI1paVThEu+FFGs584h/1YhxjVqajA== + magic-string@^0.25.2: version "0.25.3" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.3.tgz#34b8d2a2c7fec9d9bdf9929a3fd81d271ef35be9" @@ -2569,9 +2645,9 @@ ms@2.0.0: integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= ms@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" - integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== multimatch@^4.0.0: version "4.0.0" @@ -2627,6 +2703,21 @@ needle@^2.2.1: iconv-lite "^0.4.4" sax "^1.2.4" +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +node-abort-controller@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-1.0.4.tgz#4095e41d58b2fae169d2f9892904d603e11c7a39" + integrity sha512-7cNtLKTAg0LrW3ViS2C7UfIzbL3rZd8L0++5MidbKqQVJ8yrH6+1VRSHl33P0ZjBTbOJd37d9EYekvHyKkB0QQ== + +node-fetch@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" + integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== + node-pre-gyp@^0.10.0: version "0.10.3" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc" @@ -2676,6 +2767,13 @@ npm-packlist@^1.1.6: ignore-walk "^3.0.1" npm-bundled "^1.0.1" +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= + dependencies: + path-key "^2.0.0" + npmlog@^4.0.2: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" @@ -2741,7 +2839,7 @@ object.pick@^1.3.0: dependencies: isobject "^3.0.1" -once@^1.3.0, once@^1.4.0: +once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= @@ -2761,6 +2859,14 @@ os-homedir@^1.0.0: resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= +os-name@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/os-name/-/os-name-3.1.0.tgz#dec19d966296e1cd62d701a5a66ee1ddeae70801" + integrity sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg== + dependencies: + macos-release "^2.2.0" + windows-release "^3.1.0" + os-tmpdir@^1.0.0, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -2774,6 +2880,11 @@ osenv@^0.1.3, osenv@^0.1.4: os-homedir "^1.0.0" os-tmpdir "^1.0.0" +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= + p-map@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" @@ -2813,6 +2924,11 @@ path-is-inside@^1.0.1: resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= +path-key@^2.0.0, path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + path-parse@^1.0.5, path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" @@ -2878,7 +2994,7 @@ prettyjson@1.2.1: colors "^1.1.2" minimist "^1.2.0" -priorityqueuejs@1.0.0: +priorityqueuejs@1.0.0, priorityqueuejs@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/priorityqueuejs/-/priorityqueuejs-1.0.0.tgz#2ee4f23c2560913e08c07ce5ccdd6de3df2c5af8" integrity sha1-LuTyPCVgkT4IwHzlzN1t498sWvg= @@ -2893,6 +3009,14 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" @@ -3200,11 +3324,21 @@ semaphore@1.0.5: resolved "https://registry.yarnpkg.com/semaphore/-/semaphore-1.0.5.tgz#b492576e66af193db95d65e25ec53f5f19798d60" integrity sha1-tJJXbmavGT25XWXiXsU/Xxl5jWA= +semaphore@^1.0.5: + version "1.1.0" + resolved "https://registry.yarnpkg.com/semaphore/-/semaphore-1.1.0.tgz#aaad8b86b20fe8e9b32b16dc2ee682a8cd26a8aa" + integrity sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA== + semver@^5.1.0, semver@^5.3.0: version "5.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== +semver@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + "service-downloader@github:anthonydresser/service-downloader#0.1.7": version "0.1.7" resolved "https://codeload.github.com/anthonydresser/service-downloader/tar.gz/c08de456c9f1af6258061fdc524275b21c6db667" @@ -3242,6 +3376,18 @@ set-value@^2.0.0: is-plain-object "^2.0.3" split-string "^3.0.1" +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + signal-exit@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" @@ -3437,6 +3583,11 @@ strip-dirs@^2.0.0: dependencies: is-natural-number "^4.0.1" +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= + strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -3600,6 +3751,11 @@ tslib@^1.8.0, tslib@^1.8.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== +tslib@^1.9.3: + version "1.10.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" + integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== + tslint@^5.9.1: version "5.11.0" resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.11.0.tgz#98f30c02eae3cde7006201e4c33cb08b48581eed" @@ -3650,10 +3806,10 @@ typed-rest-client@^0.9.0: tunnel "0.0.4" underscore "1.8.3" -typescript@3.7.0-dev.20191017: - version "3.7.0-dev.20191017" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.0-dev.20191017.tgz#e61440dd445edea6d7b9a699e7c5d5fbcd1906f2" - integrity sha512-Yi0lCPEN0cn9Gp8TEEkPpgKNR5SWAmx9Hmzzz+oEuivw6amURqRGynaLyFZkMA9iMsvYG5LLqhdlFO3uu5ZT/w== +typescript@3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb" + integrity sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ== typescript@^3.0.1: version "3.5.3" @@ -3759,6 +3915,11 @@ uuid@^3.1.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14" integrity sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA== +uuid@^3.3.2: + version "3.3.3" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866" + integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ== + validator@~3.35.0: version "3.35.0" resolved "https://registry.yarnpkg.com/validator/-/validator-3.35.0.tgz#3f07249402c1fc8fc093c32c6e43d72a79cca1dc" @@ -3845,6 +4006,13 @@ vso-node-api@6.1.2-preview: typed-rest-client "^0.9.0" underscore "^1.8.3" +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + wide-align@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" @@ -3852,6 +4020,13 @@ wide-align@^1.1.0: dependencies: string-width "^1.0.2 || 2" +windows-release@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.2.0.tgz#8122dad5afc303d833422380680a79cdfa91785f" + integrity sha512-QTlz2hKLrdqukrsapKsINzqMgOUpQW268eJ0OaOpJN32h272waxR9fkB9VoWRtK7uKHG5EHJcTXQBD8XZVJkFA== + dependencies: + execa "^1.0.0" + wordwrap@~0.0.2: version "0.0.3" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" diff --git a/cgmanifest.json b/cgmanifest.json index c78562c41d..bc3086c310 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -60,12 +60,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "1e50380fab37f407c4d357e1e30ecbc3d5a703b8" + "commitHash": "6f62f91822a80192cb711c604f1a8f1a176f328d" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "6.0.12" + "version": "6.1.5" }, { "component": { @@ -98,7 +98,7 @@ "git": { "name": "vscode-codicons", "repositoryUrl": "https://github.com/microsoft/vscode-codicons", - "commitHash": "7f14c092f65f658cd520df3f13765efe828d0ba4" + "commitHash": "65d11e0839d0ce09faa1a159dc0b3c0bd1aa50be" } }, "license": "MIT and Creative Commons Attribution 4.0", diff --git a/extensions/admin-tool-ext-win/package.json b/extensions/admin-tool-ext-win/package.json index 48d2890915..6786293805 100644 --- a/extensions/admin-tool-ext-win/package.json +++ b/extensions/admin-tool-ext-win/package.json @@ -74,7 +74,7 @@ "vscode-nls": "^3.2.1" }, "devDependencies": { - "@types/node": "10", + "@types/node": "^12.11.7", "mocha-junit-reporter": "^1.17.0", "mocha-multi-reporters": "^1.1.7", "should": "^13.2.3", diff --git a/extensions/admin-tool-ext-win/yarn.lock b/extensions/admin-tool-ext-win/yarn.lock index 19ae10b456..b7aa7baad0 100644 --- a/extensions/admin-tool-ext-win/yarn.lock +++ b/extensions/admin-tool-ext-win/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@types/node@10": - version "10.17.4" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.4.tgz#8993a4fe3c4022fda66bf4ea660d615fc5659c6f" - integrity sha512-F2pgg+LcIr/elguz+x+fdBX5KeZXGUOp7TV8M0TVIrDezYLFRNt8oMTyps0VQ1kj5WGGoR18RdxnRDHXrIFHMQ== +"@types/node@^12.11.7": + version "12.12.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.7.tgz#01e4ea724d9e3bd50d90c11fd5980ba317d8fa11" + integrity sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w== "ads-extension-telemetry@github:Charles-Gagnon/ads-extension-telemetry#0.1.0": version "0.1.0" diff --git a/extensions/agent/package.json b/extensions/agent/package.json index ff5c21c61a..41ca0fca27 100644 --- a/extensions/agent/package.json +++ b/extensions/agent/package.json @@ -82,7 +82,7 @@ }, "devDependencies": { "@types/mocha": "^5.2.5", - "@types/node": "^10.14.8", + "@types/node": "^12.11.7", "mocha": "^5.2.0", "mocha-junit-reporter": "^1.17.0", "mocha-multi-reporters": "^1.1.7", diff --git a/extensions/agent/yarn.lock b/extensions/agent/yarn.lock index 2db1288c3f..f11b6cd39b 100644 --- a/extensions/agent/yarn.lock +++ b/extensions/agent/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.6.tgz#b8622d50557dd155e9f2f634b7d68fd38de5e94b" integrity sha512-1axi39YdtBI7z957vdqXI4Ac25e7YihYQtJa+Clnxg1zTJEaIRbndt71O3sP4GAMgiAm0pY26/b9BrY4MR/PMw== -"@types/node@^10.14.8": - version "10.14.17" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.17.tgz#b96d4dd3e427382482848948041d3754d40fd5ce" - integrity sha512-p/sGgiPaathCfOtqu2fx5Mu1bcjuP8ALFg4xpGgNkcin7LwRyzUKniEHBKdcE1RPsenq5JVPIpMTJSygLboygQ== +"@types/node@^12.11.7": + version "12.12.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.7.tgz#01e4ea724d9e3bd50d90c11fd5980ba317d8fa11" + integrity sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w== agent-base@4, agent-base@^4.3.0: version "4.3.0" diff --git a/extensions/azurecore/package.json b/extensions/azurecore/package.json index 281dd8672d..9203453187 100644 --- a/extensions/azurecore/package.json +++ b/extensions/azurecore/package.json @@ -186,7 +186,7 @@ }, "devDependencies": { "@types/mocha": "^5.2.5", - "@types/node": "^10.12.12", + "@types/node": "^12.11.7", "@types/request": "^2.48.1", "mocha": "^5.2.0", "mocha-junit-reporter": "^1.17.0", diff --git a/extensions/azurecore/yarn.lock b/extensions/azurecore/yarn.lock index b87a360ff0..ac683128c5 100644 --- a/extensions/azurecore/yarn.lock +++ b/extensions/azurecore/yarn.lock @@ -59,11 +59,16 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.5.tgz#8a4accfc403c124a0bafe8a9fc61a05ec1032073" integrity sha512-lAVp+Kj54ui/vLUFxsJTMtWvZraZxum3w3Nwkble2dNuV5VnPA+Mi2oGX9XYJAaIvZi3tn3cbjS/qcJXRb6Bww== -"@types/node@*", "@types/node@^10.12.12": +"@types/node@*": version "10.14.4" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.4.tgz#1c586b991457cbb58fef51bc4e0cfcfa347714b5" integrity sha512-DT25xX/YgyPKiHFOpNuANIQIVvYEwCWXgK2jYYwqgaMrYE6+tq+DtmMwlD3drl6DJbUwtlIDnn0d7tIn/EbXBg== +"@types/node@^12.11.7": + version "12.12.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.7.tgz#01e4ea724d9e3bd50d90c11fd5980ba317d8fa11" + integrity sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w== + "@types/node@^8.0.47": version "8.10.45" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.45.tgz#4c49ba34106bc7dced77ff6bae8eb6543cde8351" diff --git a/extensions/cms/package.json b/extensions/cms/package.json index d1ee19e30e..7eebb23b7f 100644 --- a/extensions/cms/package.json +++ b/extensions/cms/package.json @@ -633,7 +633,7 @@ }, "devDependencies": { "@types/mocha": "^5.2.5", - "@types/node": "^10.14.8", + "@types/node": "^12.11.7", "mocha-junit-reporter": "^1.17.0", "mocha-multi-reporters": "^1.1.7", "mocha": "^5.2.0", diff --git a/extensions/cms/yarn.lock b/extensions/cms/yarn.lock index 0743d25fc7..06c5ec5132 100644 --- a/extensions/cms/yarn.lock +++ b/extensions/cms/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.5.tgz#8a4accfc403c124a0bafe8a9fc61a05ec1032073" integrity sha512-lAVp+Kj54ui/vLUFxsJTMtWvZraZxum3w3Nwkble2dNuV5VnPA+Mi2oGX9XYJAaIvZi3tn3cbjS/qcJXRb6Bww== -"@types/node@^10.14.8": - version "10.14.17" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.17.tgz#b96d4dd3e427382482848948041d3754d40fd5ce" - integrity sha512-p/sGgiPaathCfOtqu2fx5Mu1bcjuP8ALFg4xpGgNkcin7LwRyzUKniEHBKdcE1RPsenq5JVPIpMTJSygLboygQ== +"@types/node@^12.11.7": + version "12.12.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.7.tgz#01e4ea724d9e3bd50d90c11fd5980ba317d8fa11" + integrity sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w== ajv@^5.3.0: version "5.5.2" diff --git a/extensions/configuration-editing/package.json b/extensions/configuration-editing/package.json index bd10c038f8..5defc86cf5 100644 --- a/extensions/configuration-editing/package.json +++ b/extensions/configuration-editing/package.json @@ -38,7 +38,8 @@ "tasks.json", "keybindings.json", "extensions.json", - "argv.json" + "argv.json", + "profiles.json" ] } ], @@ -114,6 +115,6 @@ ] }, "devDependencies": { - "@types/node": "^10.14.8" + "@types/node": "^12.11.7" } } diff --git a/extensions/configuration-editing/schemas/attachContainer.schema.json b/extensions/configuration-editing/schemas/attachContainer.schema.json index 5f3d28abb8..ba0df4b33d 100644 --- a/extensions/configuration-editing/schemas/attachContainer.schema.json +++ b/extensions/configuration-editing/schemas/attachContainer.schema.json @@ -18,6 +18,20 @@ "type": "integer" } }, + "remoteEnv": { + "type": "object", + "additionalProperties": { + "type": [ + "string", + "null" + ] + }, + "description": "Remote environment variables." + }, + "remoteUser": { + "type": "string", + "description": "The user VS Code Server will be started with. The default is the same user as the container." + }, "extensions": { "type": "array", "description": "An array of extensions that should be installed into the container.", diff --git a/extensions/configuration-editing/schemas/devContainer.schema.json b/extensions/configuration-editing/schemas/devContainer.schema.json index c24857720e..631c58e82b 100644 --- a/extensions/configuration-editing/schemas/devContainer.schema.json +++ b/extensions/configuration-editing/schemas/devContainer.schema.json @@ -22,6 +22,20 @@ "$ref": "vscode://schemas/settings/machine", "description": "Machine specific settings that should be copied into the container." }, + "remoteEnv": { + "type": "object", + "additionalProperties": { + "type": [ + "string", + "null" + ] + }, + "description": "Remote environment variables." + }, + "remoteUser": { + "type": "string", + "description": "The user VS Code Server will be started with. The default is the same user as the container." + }, "postCreateCommand": { "type": [ "string", @@ -55,6 +69,21 @@ ] } }, + "containerEnv": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Container environment variables." + }, + "containerUser": { + "type": "string", + "description": "The user the container will be started with. The default is the user on the Docker image." + }, + "updateRemoteUserUID": { + "type": "boolean", + "description": "Controls whether on Linux the container's user should be updated with the local user's UID and GID. On by default." + }, "runArgs": { "type": "array", "description": "The arguments required when starting in the container.", @@ -68,7 +97,7 @@ "none", "stopContainer" ], - "description": "Action to take when VS Code is shutting down. The default is to stop the container." + "description": "Action to take when the VS Code window is closed. The default is to stop the container." }, "overrideCommand": { "type": "boolean", @@ -146,7 +175,7 @@ "none", "stopCompose" ], - "description": "Action to take when VS Code is shutting down. The default is to stop the containers." + "description": "Action to take when the VS Code window is closed. The default is to stop the containers." } }, "required": [ diff --git a/extensions/configuration-editing/src/extension.ts b/extensions/configuration-editing/src/extension.ts index f67105c60f..8b2574d474 100644 --- a/extensions/configuration-editing/src/extension.ts +++ b/extensions/configuration-editing/src/extension.ts @@ -4,23 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { getLocation, parse, visit } from 'jsonc-parser'; -import * as path from 'path'; import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; import { SettingsDocument } from './settingsDocumentHelper'; const localize = nls.loadMessageBundle(); -const fadedDecoration = vscode.window.createTextEditorDecorationType({ - light: { - color: '#757575' - }, - dark: { - color: '#878787' - } -}); - -let pendingLaunchJsonDecoration: NodeJS.Timer; - export function activate(context: vscode.ExtensionContext): void { //settings.json suggestions context.subscriptions.push(registerSettingsCompletions()); @@ -33,18 +21,6 @@ export function activate(context: vscode.ExtensionContext): void { // task.json variable suggestions context.subscriptions.push(registerVariableCompletions('**/tasks.json')); - - // launch.json decorations - context.subscriptions.push(vscode.window.onDidChangeActiveTextEditor(editor => updateLaunchJsonDecorations(editor), null, context.subscriptions)); - context.subscriptions.push(vscode.workspace.onDidChangeTextDocument(event => { - if (vscode.window.activeTextEditor && event.document === vscode.window.activeTextEditor.document) { - if (pendingLaunchJsonDecoration) { - clearTimeout(pendingLaunchJsonDecoration); - } - pendingLaunchJsonDecoration = setTimeout(() => updateLaunchJsonDecorations(vscode.window.activeTextEditor), 1000); - } - }, null, context.subscriptions)); - updateLaunchJsonDecorations(vscode.window.activeTextEditor); } function registerSettingsCompletions(): vscode.Disposable { @@ -153,39 +129,6 @@ function provideInstalledExtensionProposals(extensionsContent: IExtensionsConten return undefined; } -function updateLaunchJsonDecorations(editor: vscode.TextEditor | undefined): void { - if (!editor || path.basename(editor.document.fileName) !== 'launch.json') { - return; - } - - const ranges: vscode.Range[] = []; - let addPropertyAndValue = false; - let depthInArray = 0; - visit(editor.document.getText(), { - onObjectProperty: (property, offset, length) => { - // Decorate attributes which are unlikely to be edited by the user. - // Only decorate "configurations" if it is not inside an array (compounds have a configurations property which should not be decorated). - addPropertyAndValue = property === 'version' || property === 'type' || property === 'request' || property === 'compounds' || (property === 'configurations' && depthInArray === 0); - if (addPropertyAndValue) { - ranges.push(new vscode.Range(editor.document.positionAt(offset), editor.document.positionAt(offset + length))); - } - }, - onLiteralValue: (_value, offset, length) => { - if (addPropertyAndValue) { - ranges.push(new vscode.Range(editor.document.positionAt(offset), editor.document.positionAt(offset + length))); - } - }, - onArrayBegin: (_offset: number, _length: number) => { - depthInArray++; - }, - onArrayEnd: (_offset: number, _length: number) => { - depthInArray--; - } - }); - - editor.setDecorations(fadedDecoration, ranges); -} - vscode.languages.registerDocumentSymbolProvider({ pattern: '**/launch.json', language: 'jsonc' }, { provideDocumentSymbols(document: vscode.TextDocument, _token: vscode.CancellationToken): vscode.ProviderResult { const result: vscode.SymbolInformation[] = []; diff --git a/extensions/configuration-editing/yarn.lock b/extensions/configuration-editing/yarn.lock index 39ad233731..d5aafed118 100644 --- a/extensions/configuration-editing/yarn.lock +++ b/extensions/configuration-editing/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@types/node@^10.14.8": - version "10.14.8" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9" - integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw== +"@types/node@^12.11.7": + version "12.11.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a" + integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA== jsonc-parser@^2.1.1: version "2.1.1" diff --git a/extensions/dacpac/package.json b/extensions/dacpac/package.json index a8eb43b53e..5b606a8629 100644 --- a/extensions/dacpac/package.json +++ b/extensions/dacpac/package.json @@ -57,7 +57,7 @@ }, "devDependencies": { "@types/mocha": "^5.2.5", - "@types/node": "10", + "@types/node": "^12.11.7", "mocha": "^5.2.0", "mocha-junit-reporter": "^1.17.0", "mocha-multi-reporters": "^1.1.7", diff --git a/extensions/dacpac/yarn.lock b/extensions/dacpac/yarn.lock index b02627ca80..4ababcfc7a 100644 --- a/extensions/dacpac/yarn.lock +++ b/extensions/dacpac/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.6.tgz#b8622d50557dd155e9f2f634b7d68fd38de5e94b" integrity sha512-1axi39YdtBI7z957vdqXI4Ac25e7YihYQtJa+Clnxg1zTJEaIRbndt71O3sP4GAMgiAm0pY26/b9BrY4MR/PMw== -"@types/node@10": - version "10.17.4" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.4.tgz#8993a4fe3c4022fda66bf4ea660d615fc5659c6f" - integrity sha512-F2pgg+LcIr/elguz+x+fdBX5KeZXGUOp7TV8M0TVIrDezYLFRNt8oMTyps0VQ1kj5WGGoR18RdxnRDHXrIFHMQ== +"@types/node@^12.11.7": + version "12.12.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.7.tgz#01e4ea724d9e3bd50d90c11fd5980ba317d8fa11" + integrity sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w== agent-base@4, agent-base@^4.3.0: version "4.3.0" diff --git a/extensions/extension-editing/package.json b/extensions/extension-editing/package.json index 50c97fb26f..54edf5ccbd 100644 --- a/extensions/extension-editing/package.json +++ b/extensions/extension-editing/package.json @@ -54,6 +54,6 @@ }, "devDependencies": { "@types/markdown-it": "0.0.2", - "@types/node": "^10.14.8" + "@types/node": "^12.11.7" } } diff --git a/extensions/extension-editing/yarn.lock b/extensions/extension-editing/yarn.lock index fb98728f23..d2f69a169d 100644 --- a/extensions/extension-editing/yarn.lock +++ b/extensions/extension-editing/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.2.tgz#5d9ad19e6e6508cdd2f2596df86fd0aade598660" integrity sha1-XZrRnm5lCM3S8llt+G/Qqt5ZhmA= -"@types/node@^10.14.8": - version "10.14.8" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9" - integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw== +"@types/node@^12.11.7": + version "12.11.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a" + integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA== "@types/node@^6.0.46": version "6.0.78" diff --git a/extensions/git-ui/package.json b/extensions/git-ui/package.json index 2f1ab43f89..b08942888e 100644 --- a/extensions/git-ui/package.json +++ b/extensions/git-ui/package.json @@ -7,7 +7,9 @@ "engines": { "vscode": "^1.5.0" }, - "extensionKind": "ui", + "extensionKind": [ + "ui" + ], "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", "enableProposedApi": true, "categories": [ @@ -23,6 +25,6 @@ "watch": "gulp watch-extension:git-ui" }, "devDependencies": { - "@types/node": "^10.14.8" + "@types/node": "^12.11.7" } -} \ No newline at end of file +} diff --git a/extensions/git-ui/src/main.ts b/extensions/git-ui/src/main.ts index 057074fae6..12eedaa4d8 100644 --- a/extensions/git-ui/src/main.ts +++ b/extensions/git-ui/src/main.ts @@ -41,12 +41,12 @@ export function exec(command: string, options: cp.ExecOptions & { stdin?: string (error ? reject : resolve)({ error, stdout, stderr }); }); if (options.stdin) { - child.stdin.write(options.stdin, (err: any) => { + child.stdin!.write(options.stdin, (err: any) => { if (err) { reject(err); return; } - child.stdin.end((err: any) => { + child.stdin!.end((err: any) => { if (err) { reject(err); } diff --git a/extensions/git-ui/yarn.lock b/extensions/git-ui/yarn.lock index b23b0ac039..40784952b8 100644 --- a/extensions/git-ui/yarn.lock +++ b/extensions/git-ui/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -"@types/node@^10.14.8": - version "10.14.8" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9" - integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw== +"@types/node@^12.11.7": + version "12.11.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a" + integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA== diff --git a/extensions/git/package.json b/extensions/git/package.json index eafc491643..d801ec64f9 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -109,6 +109,24 @@ "dark": "resources/icons/dark/stage.svg" } }, + { + "command": "git.stageAllTracked", + "title": "%command.stageAllTracked%", + "category": "Git", + "icon": { + "light": "resources/icons/light/stage.svg", + "dark": "resources/icons/dark/stage.svg" + } + }, + { + "command": "git.stageAllUntracked", + "title": "%command.stageAllUntracked%", + "category": "Git", + "icon": { + "light": "resources/icons/light/stage.svg", + "dark": "resources/icons/dark/stage.svg" + } + }, { "command": "git.stageSelectedRanges", "title": "%command.stageSelectedRanges%", @@ -178,6 +196,24 @@ "dark": "resources/icons/dark/clean.svg" } }, + { + "command": "git.cleanAllTracked", + "title": "%command.cleanAllTracked%", + "category": "Git", + "icon": { + "light": "resources/icons/light/clean.svg", + "dark": "resources/icons/dark/clean.svg" + } + }, + { + "command": "git.cleanAllUntracked", + "title": "%command.cleanAllUntracked%", + "category": "Git", + "icon": { + "light": "resources/icons/light/clean.svg", + "dark": "resources/icons/dark/clean.svg" + } + }, { "command": "git.commit", "title": "%command.commit%", @@ -267,6 +303,11 @@ "title": "%command.createTag%", "category": "Git" }, + { + "command": "git.deleteTag", + "title": "%command.deleteTag%", + "category": "Git" + }, { "command": "git.fetch", "title": "%command.fetch%", @@ -396,6 +437,11 @@ "command": "git.stashApplyLatest", "title": "%command.stashApplyLatest%", "category": "Git" + }, + { + "command": "git.stashDrop", + "title": "%command.stashDrop%", + "category": "Git" } ], "menus": { @@ -440,6 +486,14 @@ "command": "git.stageAll", "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, + { + "command": "git.stageAllTracked", + "when": "config.git.enabled && gitOpenRepositoryCount != 0" + }, + { + "command": "git.stageAllUntracked", + "when": "config.git.enabled && gitOpenRepositoryCount != 0" + }, { "command": "git.stageSelectedRanges", "when": "config.git.enabled && gitOpenRepositoryCount != 0" @@ -564,6 +618,10 @@ "command": "git.createTag", "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, + { + "command": "git.deleteTag", + "when": "config.git.enabled && gitOpenRepositoryCount != 0" + }, { "command": "git.fetch", "when": "config.git.enabled && gitOpenRepositoryCount != 0" @@ -651,6 +709,10 @@ { "command": "git.stashApplyLatest", "when": "config.git.enabled && gitOpenRepositoryCount != 0" + }, + { + "command": "git.stashDrop", + "when": "config.git.enabled && gitOpenRepositoryCount != 0" } ], "scm/title": [ @@ -804,6 +866,11 @@ "group": "6_stash", "when": "scmProvider == git" }, + { + "command": "git.stashDrop", + "group": "6_stash", + "when": "scmProvider == git" + }, { "command": "git.showOutput", "group": "7_repository", @@ -840,22 +907,62 @@ }, { "command": "git.cleanAll", - "when": "scmProvider == git && scmResourceGroup == workingTree", + "when": "scmProvider == git && scmResourceGroup == workingTree && config.git.untrackedChanges == mixed", "group": "1_modification" }, { "command": "git.stageAll", - "when": "scmProvider == git && scmResourceGroup == workingTree", + "when": "scmProvider == git && scmResourceGroup == workingTree && config.git.untrackedChanges == mixed", "group": "1_modification" }, { "command": "git.cleanAll", - "when": "scmProvider == git && scmResourceGroup == workingTree", + "when": "scmProvider == git && scmResourceGroup == workingTree && config.git.untrackedChanges == mixed", "group": "inline" }, { "command": "git.stageAll", - "when": "scmProvider == git && scmResourceGroup == workingTree", + "when": "scmProvider == git && scmResourceGroup == workingTree && config.git.untrackedChanges == mixed", + "group": "inline" + }, + { + "command": "git.cleanAllTracked", + "when": "scmProvider == git && scmResourceGroup == workingTree && config.git.untrackedChanges != mixed", + "group": "1_modification" + }, + { + "command": "git.stageAllTracked", + "when": "scmProvider == git && scmResourceGroup == workingTree && config.git.untrackedChanges != mixed", + "group": "1_modification" + }, + { + "command": "git.cleanAllTracked", + "when": "scmProvider == git && scmResourceGroup == workingTree && config.git.untrackedChanges != mixed", + "group": "inline" + }, + { + "command": "git.stageAllTracked", + "when": "scmProvider == git && scmResourceGroup == workingTree && config.git.untrackedChanges != mixed", + "group": "inline" + }, + { + "command": "git.cleanAllUntracked", + "when": "scmProvider == git && scmResourceGroup == untracked", + "group": "1_modification" + }, + { + "command": "git.stageAllUntracked", + "when": "scmProvider == git && scmResourceGroup == untracked", + "group": "1_modification" + }, + { + "command": "git.cleanAllUntracked", + "when": "scmProvider == git && scmResourceGroup == untracked", + "group": "inline" + }, + { + "command": "git.stageAllUntracked", + "when": "scmProvider == git && scmResourceGroup == untracked", "group": "inline" } ], @@ -1031,13 +1138,63 @@ "command": "git.revealInExplorer", "when": "scmProvider == git && scmResourceGroup == workingTree", "group": "2_view" + }, + { + "command": "git.openChange", + "when": "scmProvider == git && scmResourceGroup == untracked", + "group": "navigation" + }, + { + "command": "git.openHEADFile", + "when": "scmProvider == git && scmResourceGroup == untracked", + "group": "navigation" + }, + { + "command": "git.openFile", + "when": "scmProvider == git && scmResourceGroup == untracked", + "group": "navigation" + }, + { + "command": "git.stage", + "when": "scmProvider == git && scmResourceGroup == untracked", + "group": "1_modification" + }, + { + "command": "git.clean", + "when": "scmProvider == git && scmResourceGroup == untracked && !gitFreshRepository", + "group": "1_modification" + }, + { + "command": "git.clean", + "when": "scmProvider == git && scmResourceGroup == untracked && !gitFreshRepository", + "group": "inline" + }, + { + "command": "git.stage", + "when": "scmProvider == git && scmResourceGroup == untracked", + "group": "inline" + }, + { + "command": "git.openFile2", + "when": "scmProvider == git && scmResourceGroup == untracked && config.git.showInlineOpenFileAction && config.git.openDiffOnClick", + "group": "inline0" + }, + { + "command": "git.openChange", + "when": "scmProvider == git && scmResourceGroup == untracked && config.git.showInlineOpenFileAction && !config.git.openDiffOnClick", + "group": "inline0" + }, + { + "command": "git.ignore", + "when": "scmProvider == git && scmResourceGroup == untracked", + "group": "1_modification@3" } ], "editor/title": [ { "command": "git.openFile", "group": "navigation", - "when": "config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^gitfs$|^file$/" }, { "command": "git.openChange", @@ -1047,44 +1204,44 @@ { "command": "git.stageSelectedRanges", "group": "2_git@1", - "when": "config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^gitfs$|^file$/" }, { "command": "git.unstageSelectedRanges", "group": "2_git@2", - "when": "config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^gitfs$|^file$/" }, { "command": "git.revertSelectedRanges", "group": "2_git@3", - "when": "config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^gitfs$|^file$/" } ], "editor/context": [ { "command": "git.stageSelectedRanges", "group": "2_git@1", - "when": "isInDiffRightEditor && config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "isInDiffRightEditor && config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^gitfs$|^file$/" }, { "command": "git.unstageSelectedRanges", "group": "2_git@2", - "when": "isInDiffRightEditor && config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "isInDiffRightEditor && config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^gitfs$|^file$/" }, { "command": "git.revertSelectedRanges", "group": "2_git@3", - "when": "isInDiffRightEditor && config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "isInDiffRightEditor && config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^gitfs$|^file$/" } ], "scm/change/title": [ { "command": "git.stageChange", - "when": "originalResourceScheme == git" + "when": "originalResourceScheme == gitfs" }, { "command": "git.revertChange", - "when": "originalResourceScheme == git" + "when": "originalResourceScheme == gitfs" } ] }, @@ -1174,7 +1331,8 @@ "%config.countBadge.off%" ], "description": "%config.countBadge%", - "default": "all" + "default": "all", + "scope": "resource" }, "git.checkoutType": { "type": "string", @@ -1434,6 +1592,22 @@ ], "default": "committerdate", "description": "%config.branchSortOrder%" + }, + "git.untrackedChanges": { + "type": "string", + "enum": [ + "mixed", + "separate", + "hidden" + ], + "enumDescriptions": [ + "%config.untrackedChanges.mixed%", + "%config.untrackedChanges.separate%", + "%config.untrackedChanges.hidden%" + ], + "default": "mixed", + "description": "%config.untrackedChanges%", + "scope": "resource" } } }, @@ -1585,7 +1759,7 @@ "byline": "^5.0.0", "file-type": "^7.2.0", "iconv-lite": "^0.4.24", - "jschardet": "^1.6.0", + "jschardet": "2.1.1", "vscode-extension-telemetry": "0.1.1", "vscode-nls": "^4.0.0", "vscode-uri": "^2.0.0", @@ -1595,7 +1769,7 @@ "@types/byline": "4.2.31", "@types/file-type": "^5.2.1", "@types/mocha": "2.2.43", - "@types/node": "^10.14.8", + "@types/node": "^12.11.7", "@types/which": "^1.0.28", "mocha": "^3.2.0" } diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 358f166058..5c357b40eb 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -11,6 +11,8 @@ "command.openHEADFile": "Open File (HEAD)", "command.stage": "Stage Changes", "command.stageAll": "Stage All Changes", + "command.stageAllTracked": "Stage All Tracked Changes", + "command.stageAllUntracked": "Stage All Untracked Changes", "command.stageSelectedRanges": "Stage Selected Ranges", "command.revertSelectedRanges": "Revert Selected Ranges", "command.stageChange": "Stage Change", @@ -20,6 +22,8 @@ "command.unstageSelectedRanges": "Unstage Selected Ranges", "command.clean": "Discard Changes", "command.cleanAll": "Discard All Changes", + "command.cleanAllTracked": "Discard All Tracked Changes", + "command.cleanAllUntracked": "Discard All Untracked Changes", "command.commit": "Commit", "command.commitStaged": "Commit Staged", "command.commitEmpty": "Commit Empty", @@ -37,6 +41,7 @@ "command.renameBranch": "Rename Branch...", "command.merge": "Merge Branch...", "command.createTag": "Create Tag", + "command.deleteTag": "Delete Tag", "command.fetch": "Fetch", "command.fetchPrune": "Fetch (Prune)", "command.fetchAll": "Fetch From All Remotes", @@ -56,13 +61,14 @@ "command.publish": "Publish Branch...", "command.showOutput": "Show Git Output", "command.ignore": "Add to .gitignore", - "command.revealInExplorer": "Reveal in Explorer", + "command.revealInExplorer": "Reveal in Side Bar", "command.stashIncludeUntracked": "Stash (Include Untracked)", "command.stash": "Stash", "command.stashPop": "Pop Stash...", "command.stashPopLatest": "Pop Latest Stash", "command.stashApply": "Apply Stash...", "command.stashApplyLatest": "Apply Latest Stash", + "command.stashDrop": "Drop Stash...", "config.enabled": "Whether git is enabled.", "config.path": "Path and filename of the git executable, e.g. `C:\\Program Files\\Git\\bin\\git.exe` (Windows).", "config.autoRepositoryDetection": "Configures when repositories should be automatically detected.", @@ -119,7 +125,7 @@ "config.scanRepositories": "List of paths to search for git repositories in.", "config.showProgress": "Controls whether git actions should show progress.", "config.rebaseWhenSync": "Force git to use rebase when running the sync command.", - "config.confirmEmptyCommits": "Always confirm the creation of empty commits.", + "config.confirmEmptyCommits": "Always confirm the creation of empty commits for the 'Git: Commit Empty' command.", "config.fetchOnPull": "Fetch all branches when pulling or just the current one.", "config.pullTags": "Fetch all tags when pulling.", "config.autoStash": "Stash any changes before pulling and restore them after successful pull.", @@ -129,6 +135,10 @@ "config.openDiffOnClick": "Controls whether the diff editor should be opened when clicking a change. Otherwise the regular editor will be opened.", "config.supportCancellation": "Controls whether a notification comes up when running the Sync action, which allows the user to cancel the operation.", "config.branchSortOrder": "Controls the sort order for branches.", + "config.untrackedChanges": "Controls how untracked changes behave.", + "config.untrackedChanges.mixed": "All changes, tracked and untracked, appear together and behave equally.", + "config.untrackedChanges.separate": "Untracked changes appear separately in the Source Control view. They are also excluded from several actions.", + "config.untrackedChanges.hidden": "Untracked changes are hidden and excluded from several actions.", "colors.added": "Color for added resources.", "colors.modified": "Color for modified resources.", "colors.deleted": "Color for deleted resources.", diff --git a/extensions/git/src/api/api1.ts b/extensions/git/src/api/api1.ts index 6e47ea6eaf..c51ae898c0 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -8,6 +8,7 @@ import { Repository as BaseRepository, Resource } from '../repository'; import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState } from './git'; import { Event, SourceControlInputBox, Uri, SourceControl } from 'vscode'; import { mapEvent } from '../util'; +import { toGitUri } from '../uri'; class ApiInputBox implements InputBox { set value(value: string) { this._inputBox.value = value; } @@ -234,5 +235,9 @@ export class ApiImpl implements API { return this._model.repositories.map(r => new ApiRepository(r)); } + toGitUri(uri: Uri, ref: string): Uri { + return toGitUri(uri, ref); + } + constructor(private _model: Model) { } } diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index e8362f68c5..d0fb3b9134 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -185,6 +185,8 @@ export interface API { readonly repositories: Repository[]; readonly onDidOpenRepository: Event; readonly onDidCloseRepository: Event; + + toGitUri(uri: Uri, ref: string): Uri; } export interface GitExtension { diff --git a/extensions/git/src/askpass-main.ts b/extensions/git/src/askpass-main.ts index ec0b062ca5..2da27b1586 100644 --- a/extensions/git/src/askpass-main.ts +++ b/extensions/git/src/askpass-main.ts @@ -3,9 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as http from 'http'; import * as fs from 'fs'; import * as nls from 'vscode-nls'; +import { IPCClient } from './ipc/ipcClient'; const localize = nls.loadMessageBundle(); @@ -20,10 +20,6 @@ function main(argv: string[]): void { return fatal('Wrong number of arguments'); } - if (!process.env['VSCODE_GIT_ASKPASS_HANDLE']) { - return fatal('Missing handle'); - } - if (!process.env['VSCODE_GIT_ASKPASS_PIPE']) { return fatal('Missing pipe'); } @@ -33,40 +29,14 @@ function main(argv: string[]): void { } const output = process.env['VSCODE_GIT_ASKPASS_PIPE'] as string; - const socketPath = process.env['VSCODE_GIT_ASKPASS_HANDLE'] as string; const request = argv[2]; const host = argv[4].substring(1, argv[4].length - 2); - const opts: http.RequestOptions = { - socketPath, - path: '/', - method: 'POST' - }; + const ipcClient = new IPCClient('askpass'); - const req = http.request(opts, res => { - if (res.statusCode !== 200) { - return fatal(`Bad status code: ${res.statusCode}`); - } - - const chunks: string[] = []; - res.setEncoding('utf8'); - res.on('data', (d: string) => chunks.push(d)); - res.on('end', () => { - const raw = chunks.join(''); - - try { - const result = JSON.parse(raw); - fs.writeFileSync(output, result + '\n'); - } catch (err) { - return fatal(`Error parsing response`); - } - - setTimeout(() => process.exit(0), 0); - }); - }); - - req.on('error', () => fatal('Error in request')); - req.write(JSON.stringify({ request, host })); - req.end(); + ipcClient.call({ request, host }).then(res => { + fs.writeFileSync(output, res + '\n'); + setTimeout(() => process.exit(0), 0); + }).catch(err => fatal(err)); } main(process.argv); diff --git a/extensions/git/src/askpass.ts b/extensions/git/src/askpass.ts index 1ee9388c44..14d7e7f260 100644 --- a/extensions/git/src/askpass.ts +++ b/extensions/git/src/askpass.ts @@ -3,15 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable, window, InputBoxOptions } from 'vscode'; -import { denodeify } from './util'; +import { window, InputBoxOptions } from 'vscode'; +import { IDisposable } from './util'; import * as path from 'path'; -import * as http from 'http'; -import * as os from 'os'; -import * as fs from 'fs'; -import * as crypto from 'crypto'; - -const randomBytes = denodeify(crypto.randomBytes); +import { IIPCHandler, IIPCServer } from './ipc/ipcServer'; export interface AskpassEnvironment { GIT_ASKPASS: string; @@ -21,68 +16,21 @@ export interface AskpassEnvironment { VSCODE_GIT_ASKPASS_HANDLE?: string; } -function getIPCHandlePath(nonce: string): string { - if (process.platform === 'win32') { - return `\\\\.\\pipe\\vscode-git-askpass-${nonce}-sock`; +export class Askpass implements IIPCHandler { + + private disposable: IDisposable; + + static getDisabledEnv(): AskpassEnvironment { + return { + GIT_ASKPASS: path.join(__dirname, 'askpass-empty.sh') + }; } - if (process.env['XDG_RUNTIME_DIR']) { - return path.join(process.env['XDG_RUNTIME_DIR'] as string, `vscode-git-askpass-${nonce}.sock`); + constructor(ipc: IIPCServer) { + this.disposable = ipc.registerHandler('askpass', this); } - return path.join(os.tmpdir(), `vscode-git-askpass-${nonce}.sock`); -} - -export class Askpass implements Disposable { - - private server: http.Server; - private ipcHandlePathPromise: Promise; - private ipcHandlePath: string | undefined; - private enabled = true; - - constructor() { - this.server = http.createServer((req, res) => this.onRequest(req, res)); - this.ipcHandlePathPromise = this.setup().catch(err => { - console.error(err); - return ''; - }); - } - - private async setup(): Promise { - const buffer = await randomBytes(20); - const nonce = buffer.toString('hex'); - const ipcHandlePath = getIPCHandlePath(nonce); - this.ipcHandlePath = ipcHandlePath; - - try { - this.server.listen(ipcHandlePath); - this.server.on('error', err => console.error(err)); - } catch (err) { - console.error('Could not launch git askpass helper.'); - this.enabled = false; - } - - return ipcHandlePath; - } - - private onRequest(req: http.IncomingMessage, res: http.ServerResponse): void { - const chunks: string[] = []; - req.setEncoding('utf8'); - req.on('data', (d: string) => chunks.push(d)); - req.on('end', () => { - const { request, host } = JSON.parse(chunks.join('')); - - this.prompt(host, request).then(result => { - res.writeHead(200); - res.end(JSON.stringify(result)); - }, () => { - res.writeHead(500); - res.end(); - }); - }); - } - - private async prompt(host: string, request: string): Promise { + async handle({ request, host }: { request: string, host: string }): Promise { const options: InputBoxOptions = { password: /password/i.test(request), placeHolder: request, @@ -93,27 +41,16 @@ export class Askpass implements Disposable { return await window.showInputBox(options) || ''; } - async getEnv(): Promise { - if (!this.enabled) { - return { - GIT_ASKPASS: path.join(__dirname, 'askpass-empty.sh') - }; - } - + getEnv(): AskpassEnvironment { return { ELECTRON_RUN_AS_NODE: '1', GIT_ASKPASS: path.join(__dirname, 'askpass.sh'), VSCODE_GIT_ASKPASS_NODE: process.execPath, - VSCODE_GIT_ASKPASS_MAIN: path.join(__dirname, 'askpass-main.js'), - VSCODE_GIT_ASKPASS_HANDLE: await this.ipcHandlePathPromise + VSCODE_GIT_ASKPASS_MAIN: path.join(__dirname, 'askpass-main.js') }; } dispose(): void { - this.server.close(); - - if (this.ipcHandlePath && process.platform !== 'win32') { - fs.unlinkSync(this.ipcHandlePath); - } + this.disposable.dispose(); } -} \ No newline at end of file +} diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 0db1b8a022..e43affda74 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -3,19 +3,19 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Uri, commands, Disposable, window, workspace, QuickPickItem, OutputChannel, Range, WorkspaceEdit, Position, LineChange, SourceControlResourceState, TextDocumentShowOptions, ViewColumn, ProgressLocation, TextEditor, MessageOptions, WorkspaceFolder } from 'vscode'; -import { Git, CommitOptions, Stash, ForcePushMode } from './git'; -import { Repository, Resource, ResourceGroupType } from './repository'; -import { Model } from './model'; -import { toGitUri, fromGitUri } from './uri'; -import { grep, isDescendant, pathEquals } from './util'; -import { applyLineChanges, intersectDiffWithRange, toLineRanges, invertLineChange, getModifiedRange } from './staging'; -import * as path from 'path'; import { lstat, Stats } from 'fs'; import * as os from 'os'; +import * as path from 'path'; +import { commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder } from 'vscode'; import TelemetryReporter from 'vscode-extension-telemetry'; import * as nls from 'vscode-nls'; -import { Ref, RefType, Branch, GitErrorCodes, Status } from './api/git'; +import { Branch, GitErrorCodes, Ref, RefType, Status } from './api/git'; +import { CommitOptions, ForcePushMode, Git, Stash } from './git'; +import { Model } from './model'; +import { Repository, Resource, ResourceGroupType } from './repository'; +import { applyLineChanges, getModifiedRange, intersectDiffWithRange, invertLineChange, toLineRanges } from './staging'; +import { fromGitUri, toGitUri, isGitUri } from './uri'; +import { grep, isDescendant, pathEquals } from './util'; const localize = nls.loadMessageBundle(); @@ -99,7 +99,7 @@ class CreateBranchItem implements QuickPickItem { constructor(private cc: CommandCenter) { } - get label(): string { return localize('create branch', '$(plus) Create new branch...'); } + get label(): string { return '$(plus) ' + localize('create branch', 'Create new branch...'); } get description(): string { return ''; } get alwaysShow(): boolean { return true; } @@ -113,7 +113,7 @@ class CreateBranchFromItem implements QuickPickItem { constructor(private cc: CommandCenter) { } - get label(): string { return localize('create branch from', '$(plus) Create new branch from...'); } + get label(): string { return '$(plus) ' + localize('create branch from', 'Create new branch from...'); } get description(): string { return ''; } get alwaysShow(): boolean { return true; } @@ -136,7 +136,7 @@ class AddRemoteItem implements QuickPickItem { constructor(private cc: CommandCenter) { } - get label(): string { return localize('add remote', '$(plus) Add a new remote...'); } + get label(): string { return '$(plus) ' + localize('add remote', 'Add a new remote...'); } get description(): string { return ''; } get alwaysShow(): boolean { return true; } @@ -170,14 +170,14 @@ function command(commandId: string, options: CommandOptions = {}): Function { }; } -const ImageMimetypes = [ - 'image/png', - 'image/gif', - 'image/jpeg', - 'image/webp', - 'image/tiff', - 'image/bmp' -]; +// const ImageMimetypes = [ +// 'image/png', +// 'image/gif', +// 'image/jpeg', +// 'image/webp', +// 'image/tiff', +// 'image/bmp' +// ]; async function categorizeResourceByResolution(resources: Resource[]): Promise<{ merge: Resource[], resolved: Resource[], unresolved: Resource[], deletionConflicts: Resource[] }> { const selection = resources.filter(s => s instanceof Resource) as Resource[]; @@ -213,6 +213,12 @@ function createCheckoutItems(repository: Repository): CheckoutItem[] { return [...heads, ...tags, ...remoteHeads]; } +class TagItem implements QuickPickItem { + get label(): string { return this.ref.name ?? ''; } + get description(): string { return this.ref.commit?.substr(0, 8) ?? ''; } + constructor(readonly ref: Ref) { } +} + enum PushType { Push, PushTo, @@ -289,10 +295,10 @@ export class CommandCenter { } } else { if (resource.type !== Status.DELETED_BY_THEM) { - left = await this.getLeftResource(resource); + left = this.getLeftResource(resource); } - right = await this.getRightResource(resource); + right = this.getRightResource(resource); } const title = this.getTitle(resource); @@ -324,79 +330,40 @@ export class CommandCenter { } } - private async getURI(uri: Uri, ref: string): Promise { - const repository = this.model.getRepository(uri); - - if (!repository) { - return toGitUri(uri, ref); - } - - try { - let gitRef = ref; - - if (gitRef === '~') { - const uriString = uri.toString(); - const [indexStatus] = repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString); - gitRef = indexStatus ? '' : 'HEAD'; - } - - const { size, object } = await repository.getObjectDetails(gitRef, uri.fsPath); - const { mimetype } = await repository.detectObjectType(object); - - if (mimetype === 'text/plain') { - return toGitUri(uri, ref); - } - - if (size > 1000000) { // 1 MB - return Uri.parse(`data:;label:${path.basename(uri.fsPath)};description:${gitRef},`); - } - - if (ImageMimetypes.indexOf(mimetype) > -1) { - const contents = await repository.buffer(gitRef, uri.fsPath); - return Uri.parse(`data:${mimetype};label:${path.basename(uri.fsPath)};description:${gitRef};size:${size};base64,${contents.toString('base64')}`); - } - - return Uri.parse(`data:;label:${path.basename(uri.fsPath)};description:${gitRef},`); - - } catch (err) { - return toGitUri(uri, ref); - } - } - - private async getLeftResource(resource: Resource): Promise { + private getLeftResource(resource: Resource): Uri | undefined { switch (resource.type) { case Status.INDEX_MODIFIED: case Status.INDEX_RENAMED: case Status.INDEX_ADDED: - return this.getURI(resource.original, 'HEAD'); + return toGitUri(resource.original, 'HEAD'); case Status.MODIFIED: case Status.UNTRACKED: - return this.getURI(resource.resourceUri, '~'); + return toGitUri(resource.resourceUri, '~'); case Status.DELETED_BY_THEM: - return this.getURI(resource.resourceUri, ''); + return toGitUri(resource.resourceUri, ''); } return undefined; } - private async getRightResource(resource: Resource): Promise { + private getRightResource(resource: Resource): Uri | undefined { switch (resource.type) { case Status.INDEX_MODIFIED: case Status.INDEX_ADDED: case Status.INDEX_COPIED: case Status.INDEX_RENAMED: - return this.getURI(resource.resourceUri, ''); + return toGitUri(resource.resourceUri, ''); case Status.INDEX_DELETED: case Status.DELETED: - return this.getURI(resource.resourceUri, 'HEAD'); + return toGitUri(resource.resourceUri, 'HEAD'); case Status.DELETED_BY_US: - return this.getURI(resource.resourceUri, '~3'); + return toGitUri(resource.resourceUri, '~3'); case Status.DELETED_BY_THEM: - return this.getURI(resource.resourceUri, '~2'); + return toGitUri(resource.resourceUri, '~2'); case Status.MODIFIED: case Status.UNTRACKED: @@ -453,7 +420,7 @@ export class CommandCenter { } @command('git.clone') - async clone(url?: string): Promise { + async clone(url?: string, parentPath?: string): Promise { if (!url) { url = await window.showInputBox({ prompt: localize('repourl', "Repository URL"), @@ -473,31 +440,33 @@ export class CommandCenter { url = url.trim().replace(/^git\s+clone\s+/, ''); - const config = workspace.getConfiguration('git'); - let defaultCloneDirectory = config.get('defaultCloneDirectory') || os.homedir(); - defaultCloneDirectory = defaultCloneDirectory.replace(/^~/, os.homedir()); + if (!parentPath) { + const config = workspace.getConfiguration('git'); + let defaultCloneDirectory = config.get('defaultCloneDirectory') || os.homedir(); + defaultCloneDirectory = defaultCloneDirectory.replace(/^~/, os.homedir()); - const uris = await window.showOpenDialog({ - canSelectFiles: false, - canSelectFolders: true, - canSelectMany: false, - defaultUri: Uri.file(defaultCloneDirectory), - openLabel: localize('selectFolder', "Select Repository Location") - }); + const uris = await window.showOpenDialog({ + canSelectFiles: false, + canSelectFolders: true, + canSelectMany: false, + defaultUri: Uri.file(defaultCloneDirectory), + openLabel: localize('selectFolder', "Select Repository Location") + }); - if (!uris || uris.length === 0) { - /* __GDPR__ - "clone" : { - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_directory' }); - return; + if (!uris || uris.length === 0) { + /* __GDPR__ + "clone" : { + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_directory' }); + return; + } + + const uri = uris[0]; + parentPath = uri.fsPath; } - const uri = uris[0]; - const parentPath = uri.fsPath; - try { const opts = { location: ProgressLocation.Notification, @@ -507,7 +476,7 @@ export class CommandCenter { const repositoryPath = await window.withProgress( opts, - (progress, token) => this.git.clone(url!, parentPath, progress, token) + (progress, token) => this.git.clone(url!, parentPath!, progress, token) ); let message = localize('proposeopen', "Would you like to open the cloned repository?"); @@ -686,7 +655,7 @@ export class CommandCenter { let uris: Uri[] | undefined; if (arg instanceof Uri) { - if (arg.scheme === 'git') { + if (isGitUri(arg)) { uris = [Uri.file(fromGitUri(arg).path)]; } else if (arg.scheme === 'file') { uris = [arg]; @@ -765,7 +734,7 @@ export class CommandCenter { return; } - const HEAD = await this.getLeftResource(resource); + const HEAD = this.getLeftResource(resource); const basename = path.basename(resource.resourceUri.fsPath); const title = `${basename} (HEAD)`; @@ -866,7 +835,8 @@ export class CommandCenter { } const workingTree = selection.filter(s => s.resourceGroupType === ResourceGroupType.WorkingTree); - const scmResources = [...workingTree, ...resolved, ...unresolved]; + const untracked = selection.filter(s => s.resourceGroupType === ResourceGroupType.Untracked); + const scmResources = [...workingTree, ...untracked, ...resolved, ...unresolved]; this.outputChannel.appendLine(`git.stage.scmResources ${scmResources.length}`); if (!scmResources.length) { @@ -907,7 +877,9 @@ export class CommandCenter { } } - await repository.add([]); + const config = workspace.getConfiguration('git', Uri.file(repository.root)); + const untrackedChanges = config.get<'mixed' | 'separate' | 'hidden'>('untrackedChanges'); + await repository.add([], untrackedChanges === 'mixed' ? undefined : { update: true }); } private async _stageDeletionConflict(repository: Repository, uri: Uri): Promise { @@ -945,6 +917,24 @@ export class CommandCenter { } } + @command('git.stageAllTracked', { repository: true }) + async stageAllTracked(repository: Repository): Promise { + const resources = repository.workingTreeGroup.resourceStates + .filter(r => r.type !== Status.UNTRACKED && r.type !== Status.IGNORED); + const uris = resources.map(r => r.resourceUri); + + await repository.add(uris); + } + + @command('git.stageAllUntracked', { repository: true }) + async stageAllUntracked(repository: Repository): Promise { + const resources = [...repository.workingTreeGroup.resourceStates, ...repository.untrackedGroup.resourceStates] + .filter(r => r.type === Status.UNTRACKED || r.type === Status.IGNORED); + const uris = resources.map(r => r.resourceUri); + + await repository.add(uris); + } + @command('git.stageChange') async stageChange(uri: Uri, changes: LineChange[], index: number): Promise { const textEditor = window.visibleTextEditors.filter(e => e.document.uri.toString() === uri.toString())[0]; @@ -1090,7 +1080,7 @@ export class CommandCenter { const modifiedDocument = textEditor.document; const modifiedUri = modifiedDocument.uri; - if (modifiedUri.scheme !== 'git') { + if (!isGitUri(modifiedUri)) { return; } @@ -1131,8 +1121,8 @@ export class CommandCenter { resourceStates = [resource]; } - const scmResources = resourceStates - .filter(s => s instanceof Resource && s.resourceGroupType === ResourceGroupType.WorkingTree) as Resource[]; + const scmResources = resourceStates.filter(s => s instanceof Resource + && (s.resourceGroupType === ResourceGroupType.WorkingTree || s.resourceGroupType === ResourceGroupType.Untracked)) as Resource[]; if (!scmResources.length) { return; @@ -1189,41 +1179,11 @@ export class CommandCenter { const untrackedResources = resources.filter(r => r.type === Status.UNTRACKED || r.type === Status.IGNORED); if (untrackedResources.length === 0) { - const message = resources.length === 1 - ? localize('confirm discard all single', "Are you sure you want to discard changes in {0}?", path.basename(resources[0].resourceUri.fsPath)) - : localize('confirm discard all', "Are you sure you want to discard ALL changes in {0} files?\nThis is IRREVERSIBLE!\nYour current working set will be FOREVER LOST.", resources.length); - const yes = resources.length === 1 - ? localize('discardAll multiple', "Discard 1 File") - : localize('discardAll', "Discard All {0} Files", resources.length); - const pick = await window.showWarningMessage(message, { modal: true }, yes); - - if (pick !== yes) { - return; - } - - await repository.clean(resources.map(r => r.resourceUri)); - return; + await this._cleanTrackedChanges(repository, resources); } else if (resources.length === 1) { - const message = localize('confirm delete', "Are you sure you want to DELETE {0}?\nThis is IRREVERSIBLE!\nThis file will be FOREVER LOST.", path.basename(resources[0].resourceUri.fsPath)); - const yes = localize('delete file', "Delete file"); - const pick = await window.showWarningMessage(message, { modal: true }, yes); - - if (pick !== yes) { - return; - } - - await repository.clean(resources.map(r => r.resourceUri)); + await this._cleanUntrackedChange(repository, resources[0]); } else if (trackedResources.length === 0) { - const message = localize('confirm delete multiple', "Are you sure you want to DELETE {0} files?", resources.length); - const yes = localize('delete files', "Delete Files"); - const pick = await window.showWarningMessage(message, { modal: true }, yes); - - if (pick !== yes) { - return; - } - - await repository.clean(resources.map(r => r.resourceUri)); - + await this._cleanUntrackedChanges(repository, resources); } else { // resources.length > 1 && untrackedResources.length > 0 && trackedResources.length > 0 const untrackedMessage = untrackedResources.length === 1 ? localize('there are untracked files single', "The following untracked file will be DELETED FROM DISK if discarded: {0}.", path.basename(untrackedResources[0].resourceUri.fsPath)) @@ -1248,6 +1208,74 @@ export class CommandCenter { } } + @command('git.cleanAllTracked', { repository: true }) + async cleanAllTracked(repository: Repository): Promise { + const resources = repository.workingTreeGroup.resourceStates + .filter(r => r.type !== Status.UNTRACKED && r.type !== Status.IGNORED); + + if (resources.length === 0) { + return; + } + + await this._cleanTrackedChanges(repository, resources); + } + + @command('git.cleanAllUntracked', { repository: true }) + async cleanAllUntracked(repository: Repository): Promise { + const resources = [...repository.workingTreeGroup.resourceStates, ...repository.untrackedGroup.resourceStates] + .filter(r => r.type === Status.UNTRACKED || r.type === Status.IGNORED); + + if (resources.length === 0) { + return; + } + + if (resources.length === 1) { + await this._cleanUntrackedChange(repository, resources[0]); + } else { + await this._cleanUntrackedChanges(repository, resources); + } + } + + private async _cleanTrackedChanges(repository: Repository, resources: Resource[]): Promise { + const message = resources.length === 1 + ? localize('confirm discard all single', "Are you sure you want to discard changes in {0}?", path.basename(resources[0].resourceUri.fsPath)) + : localize('confirm discard all', "Are you sure you want to discard ALL changes in {0} files?\nThis is IRREVERSIBLE!\nYour current working set will be FOREVER LOST.", resources.length); + const yes = resources.length === 1 + ? localize('discardAll multiple', "Discard 1 File") + : localize('discardAll', "Discard All {0} Files", resources.length); + const pick = await window.showWarningMessage(message, { modal: true }, yes); + + if (pick !== yes) { + return; + } + + await repository.clean(resources.map(r => r.resourceUri)); + } + + private async _cleanUntrackedChange(repository: Repository, resource: Resource): Promise { + const message = localize('confirm delete', "Are you sure you want to DELETE {0}?\nThis is IRREVERSIBLE!\nThis file will be FOREVER LOST.", path.basename(resource.resourceUri.fsPath)); + const yes = localize('delete file', "Delete file"); + const pick = await window.showWarningMessage(message, { modal: true }, yes); + + if (pick !== yes) { + return; + } + + await repository.clean([resource.resourceUri]); + } + + private async _cleanUntrackedChanges(repository: Repository, resources: Resource[]): Promise { + const message = localize('confirm delete multiple', "Are you sure you want to DELETE {0} files?", resources.length); + const yes = localize('delete files', "Delete Files"); + const pick = await window.showWarningMessage(message, { modal: true }, yes); + + if (pick !== yes) { + return; + } + + await repository.clean(resources.map(r => r.resourceUri)); + } + private async smartCommit( repository: Repository, getCommitMessage: () => Promise, @@ -1271,7 +1299,7 @@ export class CommandCenter { if (promptToSaveFilesBeforeCommit === 'staged' || repository.indexGroup.resourceStates.length > 0) { documents = documents - .filter(d => repository.indexGroup.resourceStates.some(s => s.resourceUri.path === d.uri.fsPath)); + .filter(d => repository.indexGroup.resourceStates.some(s => pathEquals(s.resourceUri.fsPath, d.uri.fsPath))); } if (documents.length > 0) { @@ -1356,6 +1384,10 @@ export class CommandCenter { opts.all = 'tracked'; } + if (opts.all && config.get<'mixed' | 'separate' | 'hidden'>('untrackedChanges') !== 'mixed') { + opts.all = 'tracked'; + } + await repository.commit(message, opts); const postCommitCommand = config.get<'none' | 'push' | 'sync'>('postCommitCommand'); @@ -1376,6 +1408,7 @@ export class CommandCenter { const message = repository.inputBox.value; const getCommitMessage = async () => { let _message: string | undefined = message; + if (!_message) { let value: string | undefined = undefined; @@ -1400,7 +1433,7 @@ export class CommandCenter { }); } - return _message ? repository.cleanUpCommitEditMessage(_message) : _message; + return _message; }; const didCommit = await this.smartCommit(repository, getCommitMessage, opts); @@ -1485,7 +1518,7 @@ export class CommandCenter { if (commit.parents.length > 1) { const yes = localize('undo commit', "Undo merge commit"); - const result = await window.showWarningMessage(localize('merge commit', "The last commit was a merge commit. Are you sure you want to undo it?"), yes); + const result = await window.showWarningMessage(localize('merge commit', "The last commit was a merge commit. Are you sure you want to undo it?"), { modal: true }, yes); if (result !== yes) { return; @@ -1705,6 +1738,26 @@ export class CommandCenter { await repository.tag(name, message); } + @command('git.deleteTag', { repository: true }) + async deleteTag(repository: Repository): Promise { + const picks = repository.refs.filter(ref => ref.type === RefType.Tag) + .map(ref => new TagItem(ref)); + + if (picks.length === 0) { + window.showWarningMessage(localize('no tags', "This repository has no tags.")); + return; + } + + const placeHolder = localize('select a tag to delete', 'Select a tag to delete'); + const choice = await window.showQuickPick(picks, { placeHolder }); + + if (!choice) { + return; + } + + await repository.deleteTag(choice.label); + } + @command('git.fetch', { repository: true }) async fetch(repository: Repository): Promise { if (repository.remotes.length === 0) { @@ -2073,9 +2126,14 @@ export class CommandCenter { return; } + const branchName = repository.HEAD && repository.HEAD.name || ''; + + if (remotes.length === 1) { + return await repository.pushTo(remotes[0].name, branchName, true); + } + const addRemote = new AddRemoteItem(this); const picks = [...repository.remotes.map(r => ({ label: r.name, description: r.pushUrl })), addRemote]; - const branchName = repository.HEAD && repository.HEAD.name || ''; const placeHolder = localize('pick remote', "Pick a remote to publish the branch '{0}' to:", branchName); const choice = await window.showQuickPick(picks, { placeHolder }); @@ -2133,7 +2191,8 @@ export class CommandCenter { } private async _stash(repository: Repository, includeUntracked = false): Promise { - const noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0; + const noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0 + && (!includeUntracked || repository.untrackedGroup.resourceStates.length === 0); const noStagedChanges = repository.indexGroup.resourceStates.length === 0; if (noUnstagedChanges && noStagedChanges) { @@ -2215,6 +2274,18 @@ export class CommandCenter { await repository.applyStash(); } + @command('git.stashDrop', { repository: true }) + async stashDrop(repository: Repository): Promise { + const placeHolder = localize('pick stash to drop', "Pick a stash to drop"); + const stash = await this.pickStash(repository, placeHolder); + + if (!stash) { + return; + } + + await repository.dropStash(stash.index); + } + private async pickStash(repository: Repository, placeHolder: string): Promise { const stashes = await repository.getStashes(); @@ -2352,7 +2423,7 @@ export class CommandCenter { return undefined; } - if (uri.scheme === 'git') { + if (isGitUri(uri)) { const { path } = fromGitUri(uri); uri = Uri.file(path); } diff --git a/extensions/git/src/contentProvider.ts b/extensions/git/src/contentProvider.ts index b94847eec2..1f394f9cd9 100644 --- a/extensions/git/src/contentProvider.ts +++ b/extensions/git/src/contentProvider.ts @@ -147,4 +147,4 @@ export class GitContentProvider { dispose(): void { this.disposables.forEach(d => d.dispose()); } -} \ No newline at end of file +} diff --git a/extensions/git/src/decorationProvider.ts b/extensions/git/src/decorationProvider.ts index 1f0a02567a..d3ee8bb27b 100644 --- a/extensions/git/src/decorationProvider.ts +++ b/extensions/git/src/decorationProvider.ts @@ -113,6 +113,7 @@ class GitDecorationProvider implements DecorationProvider { this.collectSubmoduleDecorationData(newDecorations); this.collectDecorationData(this.repository.indexGroup, newDecorations); + this.collectDecorationData(this.repository.untrackedGroup, newDecorations); this.collectDecorationData(this.repository.workingTreeGroup, newDecorations); this.collectDecorationData(this.repository.mergeGroup, newDecorations); diff --git a/extensions/git/src/encoding.ts b/extensions/git/src/encoding.ts index 48df276e43..424f5312bb 100644 --- a/extensions/git/src/encoding.ts +++ b/extensions/git/src/encoding.ts @@ -5,8 +5,6 @@ import * as jschardet from 'jschardet'; -jschardet.Constants.MINIMUM_THRESHOLD = 0.2; - function detectEncodingByBOM(buffer: Buffer): string | null { if (!buffer || buffer.length < 2) { return null; diff --git a/extensions/git/src/fileSystemProvider.ts b/extensions/git/src/fileSystemProvider.ts new file mode 100644 index 0000000000..3ea7558b9a --- /dev/null +++ b/extensions/git/src/fileSystemProvider.ts @@ -0,0 +1,199 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { workspace, Uri, Disposable, Event, EventEmitter, window, FileSystemProvider, FileChangeEvent, FileStat, FileType, FileChangeType, FileSystemError } from 'vscode'; +import { debounce, throttle } from './decorators'; +import { fromGitUri, toGitUri } from './uri'; +import { Model, ModelChangeEvent, OriginalResourceChangeEvent } from './model'; +import { filterEvent, eventToPromise, isDescendant, pathEquals, EmptyDisposable } from './util'; + +interface CacheRow { + uri: Uri; + timestamp: number; +} + +const THREE_MINUTES = 1000 * 60 * 3; +const FIVE_MINUTES = 1000 * 60 * 5; + +export class GitFileSystemProvider implements FileSystemProvider { + + private _onDidChangeFile = new EventEmitter(); + readonly onDidChangeFile: Event = this._onDidChangeFile.event; + + private changedRepositoryRoots = new Set(); + private cache = new Map(); + private mtime = new Date().getTime(); + private disposables: Disposable[] = []; + + constructor(private model: Model) { + this.disposables.push( + model.onDidChangeRepository(this.onDidChangeRepository, this), + model.onDidChangeOriginalResource(this.onDidChangeOriginalResource, this), + workspace.registerFileSystemProvider('gitfs', this, { isReadonly: true, isCaseSensitive: true }), + workspace.registerResourceLabelFormatter({ + scheme: 'gitfs', + formatting: { + label: '${path} (git)', + separator: '/' + } + }) + ); + + setInterval(() => this.cleanup(), FIVE_MINUTES); + } + + private onDidChangeRepository({ repository }: ModelChangeEvent): void { + this.changedRepositoryRoots.add(repository.root); + this.eventuallyFireChangeEvents(); + } + + private onDidChangeOriginalResource({ uri }: OriginalResourceChangeEvent): void { + if (uri.scheme !== 'file') { + return; + } + + const gitUri = toGitUri(uri, '', { replaceFileExtension: true }); + this.mtime = new Date().getTime(); + this._onDidChangeFile.fire([{ type: FileChangeType.Changed, uri: gitUri }]); + } + + @debounce(1100) + private eventuallyFireChangeEvents(): void { + this.fireChangeEvents(); + } + + @throttle + private async fireChangeEvents(): Promise { + if (!window.state.focused) { + const onDidFocusWindow = filterEvent(window.onDidChangeWindowState, e => e.focused); + await eventToPromise(onDidFocusWindow); + } + + const events: FileChangeEvent[] = []; + + for (const { uri } of this.cache.values()) { + const fsPath = uri.fsPath; + + for (const root of this.changedRepositoryRoots) { + if (isDescendant(root, fsPath)) { + events.push({ type: FileChangeType.Changed, uri }); + break; + } + } + } + + if (events.length > 0) { + this.mtime = new Date().getTime(); + this._onDidChangeFile.fire(events); + } + + this.changedRepositoryRoots.clear(); + } + + private cleanup(): void { + const now = new Date().getTime(); + const cache = new Map(); + + for (const row of this.cache.values()) { + const { path } = fromGitUri(row.uri); + const isOpen = workspace.textDocuments + .filter(d => d.uri.scheme === 'file') + .some(d => pathEquals(d.uri.fsPath, path)); + + if (isOpen || now - row.timestamp < THREE_MINUTES) { + cache.set(row.uri.toString(), row); + } else { + // TODO: should fire delete events? + } + } + + this.cache = cache; + } + + watch(): Disposable { + return EmptyDisposable; + } + + stat(uri: Uri): FileStat { + const { submoduleOf } = fromGitUri(uri); + const repository = submoduleOf ? this.model.getRepository(submoduleOf) : this.model.getRepository(uri); + + if (!repository) { + throw FileSystemError.FileNotFound(); + } + + return { type: FileType.File, size: 0, mtime: this.mtime, ctime: 0 }; + } + + readDirectory(): Thenable<[string, FileType][]> { + throw new Error('Method not implemented.'); + } + + createDirectory(): void { + throw new Error('Method not implemented.'); + } + + async readFile(uri: Uri): Promise { + let { path, ref, submoduleOf } = fromGitUri(uri); + + if (submoduleOf) { + const repository = this.model.getRepository(submoduleOf); + + if (!repository) { + throw FileSystemError.FileNotFound(); + } + + const encoder = new TextEncoder(); + + if (ref === 'index') { + return encoder.encode(await repository.diffIndexWithHEAD(path)); + } else { + return encoder.encode(await repository.diffWithHEAD(path)); + } + } + + const repository = this.model.getRepository(uri); + + if (!repository) { + throw FileSystemError.FileNotFound(); + } + + const timestamp = new Date().getTime(); + const cacheValue: CacheRow = { uri, timestamp }; + + this.cache.set(uri.toString(), cacheValue); + + if (ref === '~') { + const fileUri = Uri.file(path); + const uriString = fileUri.toString(); + const [indexStatus] = repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString); + ref = indexStatus ? '' : 'HEAD'; + } else if (/^~\d$/.test(ref)) { + ref = `:${ref[1]}`; + } + + try { + return await repository.buffer(ref, path); + } catch (err) { + return new Uint8Array(0); + } + } + + writeFile(): void { + throw new Error('Method not implemented.'); + } + + delete(): void { + throw new Error('Method not implemented.'); + } + + rename(): void { + throw new Error('Method not implemented.'); + } + + dispose(): void { + this.disposables.forEach(d => d.dispose()); + } +} diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index ca90587eaa..6be990e828 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as fs from 'fs'; +import { promises as fs, exists } from 'fs'; import * as path from 'path'; import * as os from 'os'; import * as cp from 'child_process'; @@ -11,7 +11,7 @@ import * as which from 'which'; import { EventEmitter } from 'events'; import iconv = require('iconv-lite'); import * as filetype from 'file-type'; -import { assign, groupBy, denodeify, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent, splitInChunks, Limiter } from './util'; +import { assign, groupBy, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent, splitInChunks, Limiter } from './util'; import { CancellationToken, Progress } from 'vscode'; import { URI } from 'vscode-uri'; import { detectEncoding } from './encoding'; @@ -22,8 +22,6 @@ import { StringDecoder } from 'string_decoder'; // https://github.com/microsoft/vscode/issues/65693 const MAX_CLI_LENGTH = 30000; -const readfile = denodeify(fs.readFile); - export interface IGit { path: string; version: string; @@ -196,13 +194,13 @@ async function exec(child: cp.ChildProcess, cancellationToken?: CancellationToke }), new Promise(c => { const buffers: Buffer[] = []; - on(child.stdout, 'data', (b: Buffer) => buffers.push(b)); - once(child.stdout, 'close', () => c(Buffer.concat(buffers))); + on(child.stdout!, 'data', (b: Buffer) => buffers.push(b)); + once(child.stdout!, 'close', () => c(Buffer.concat(buffers))); }), new Promise(c => { const buffers: Buffer[] = []; - on(child.stderr, 'data', (b: Buffer) => buffers.push(b)); - once(child.stderr, 'close', () => c(Buffer.concat(buffers).toString('utf8'))); + on(child.stderr!, 'data', (b: Buffer) => buffers.push(b)); + once(child.stderr!, 'close', () => c(Buffer.concat(buffers).toString('utf8'))); }) ]) as Promise<[number, Buffer, string]>; @@ -350,7 +348,7 @@ export class Git { let folderPath = path.join(parentPath, folderName); let count = 1; - while (count < 20 && await new Promise(c => fs.exists(folderPath, c))) { + while (count < 20 && await new Promise(c => exists(folderPath, c))) { folderName = `${baseFolderName}-${count++}`; folderPath = path.join(parentPath, folderName); } @@ -360,7 +358,7 @@ export class Git { const onSpawn = (child: cp.ChildProcess) => { const decoder = new StringDecoder('utf8'); const lineStream = new byline.LineStream({ encoding: 'utf8' }); - child.stderr.on('data', (buffer: Buffer) => lineStream.write(decoder.write(buffer))); + child.stderr!.on('data', (buffer: Buffer) => lineStream.write(decoder.write(buffer))); let totalProgress = 0; let previousProgress = 0; @@ -438,7 +436,7 @@ export class Git { } if (options.input) { - child.stdin.end(options.input, 'utf8'); + child.stdin!.end(options.input, 'utf8'); } const bufferResult = await exec(child, options.cancellationToken); @@ -763,9 +761,10 @@ export class Repository { async log(options?: LogOptions): Promise { const maxEntries = options && typeof options.maxEntries === 'number' && options.maxEntries > 0 ? options.maxEntries : 32; const args = ['log', '-' + maxEntries, `--pretty=format:${COMMIT_FORMAT}%x00%x00`]; + const gitResult = await this.run(args); if (gitResult.exitCode) { - // An empty repo. + // An empty repo return []; } @@ -882,7 +881,7 @@ export class Repository { async detectObjectType(object: string): Promise<{ mimetype: string, encoding?: string }> { const child = await this.stream(['show', object]); - const buffer = await readBytes(child.stdout, 4100); + const buffer = await readBytes(child.stdout!, 4100); try { child.kill(); @@ -1151,7 +1150,7 @@ export class Repository { async stage(path: string, data: string): Promise { const child = this.stream(['hash-object', '--stdin', '-w', '--path', path], { stdio: [null, null, null] }); - child.stdin.end(data, 'utf8'); + child.stdin!.end(data, 'utf8'); const { exitCode, stdout } = await exec(child); const hash = stdout.toString('utf8'); @@ -1163,11 +1162,12 @@ export class Repository { }); } + const treeish = await this.getCommit('HEAD').then(() => 'HEAD', () => ''); let mode: string; let add: string = ''; try { - const details = await this.getObjectDetails('HEAD', path); + const details = await this.getObjectDetails(treeish, path); mode = details.mode; } catch (err) { if (err.gitErrorCode !== GitErrorCodes.UnknownPath) { @@ -1327,6 +1327,11 @@ export class Repository { await this.run(args); } + async deleteTag(name: string): Promise { + let args = ['tag', '-d', name]; + await this.run(args); + } + async clean(paths: string[]): Promise { const pathsByGroup = groupBy(paths, p => path.dirname(p)); const groups = Object.keys(pathsByGroup).map(k => pathsByGroup[k]); @@ -1592,6 +1597,24 @@ export class Repository { } } + async dropStash(index?: number): Promise { + const args = ['stash', 'drop']; + + if (typeof index === 'number') { + args.push(`stash@{${index}}`); + } + + try { + await this.run(args); + } catch (err) { + if (/No stash found/.test(err.stderr || '')) { + err.gitErrorCode = GitErrorCodes.NoStashFound; + } + + throw err; + } + } + getStatus(limit = 5000): Promise<{ status: IFileStatus[]; didHitLimit: boolean; }> { return new Promise<{ status: IFileStatus[]; didHitLimit: boolean; }>((c, e) => { const parser = new GitStatusParser(); @@ -1618,19 +1641,19 @@ export class Repository { if (parser.status.length > limit) { child.removeListener('exit', onExit); - child.stdout.removeListener('data', onStdoutData); + child.stdout!.removeListener('data', onStdoutData); child.kill(); c({ status: parser.status.slice(0, limit), didHitLimit: true }); } }; - child.stdout.setEncoding('utf8'); - child.stdout.on('data', onStdoutData); + child.stdout!.setEncoding('utf8'); + child.stdout!.on('data', onStdoutData); const stderrData: string[] = []; - child.stderr.setEncoding('utf8'); - child.stderr.on('data', raw => stderrData.push(raw as string)); + child.stderr!.setEncoding('utf8'); + child.stderr!.on('data', raw => stderrData.push(raw as string)); child.on('error', cpErrorHandler(e)); child.on('exit', onExit); @@ -1789,18 +1812,17 @@ export class Repository { } } - cleanupCommitEditMessage(message: string): string { - //TODO: Support core.commentChar + // TODO: Support core.commentChar + stripCommitMessageComments(message: string): string { return message.replace(/^\s*#.*$\n?/gm, '').trim(); } - async getMergeMessage(): Promise { const mergeMsgPath = path.join(this.repositoryRoot, '.git', 'MERGE_MSG'); try { - const raw = await readfile(mergeMsgPath, 'utf8'); - return raw.trim(); + const raw = await fs.readFile(mergeMsgPath, 'utf8'); + return this.stripCommitMessageComments(raw); } catch { return undefined; } @@ -1823,9 +1845,8 @@ export class Repository { templatePath = path.join(this.repositoryRoot, templatePath); } - const raw = await readfile(templatePath, 'utf8'); - return raw.trim(); - + const raw = await fs.readFile(templatePath, 'utf8'); + return this.stripCommitMessageComments(raw); } catch (err) { return ''; } @@ -1848,7 +1869,7 @@ export class Repository { const gitmodulesPath = path.join(this.root, '.gitmodules'); try { - const gitmodulesRaw = await readfile(gitmodulesPath, 'utf8'); + const gitmodulesRaw = await fs.readFile(gitmodulesPath, 'utf8'); return parseGitmodules(gitmodulesRaw); } catch (err) { if (/ENOENT/.test(err.message)) { diff --git a/extensions/git/src/ipc/ipcClient.ts b/extensions/git/src/ipc/ipcClient.ts new file mode 100644 index 0000000000..54b07dd45f --- /dev/null +++ b/extensions/git/src/ipc/ipcClient.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as http from 'http'; + +export class IPCClient { + + private ipcHandlePath: string; + + constructor(private handlerName: string) { + const ipcHandlePath = process.env['VSCODE_GIT_IPC_HANDLE']; + + if (!ipcHandlePath) { + throw new Error('Missing VSCODE_GIT_IPC_HANDLE'); + } + + this.ipcHandlePath = ipcHandlePath; + } + + call(request: any): Promise { + const opts: http.RequestOptions = { + socketPath: this.ipcHandlePath, + path: `/${this.handlerName}`, + method: 'POST' + }; + + return new Promise((c, e) => { + const req = http.request(opts, res => { + if (res.statusCode !== 200) { + return e(new Error(`Bad status code: ${res.statusCode}`)); + } + + const chunks: Buffer[] = []; + res.on('data', d => chunks.push(d)); + res.on('end', () => c(JSON.parse(Buffer.concat(chunks).toString('utf8')))); + }); + + req.on('error', err => e(err)); + req.write(JSON.stringify(request)); + req.end(); + }); + } +} diff --git a/extensions/git/src/ipc/ipcServer.ts b/extensions/git/src/ipc/ipcServer.ts new file mode 100644 index 0000000000..218a7c1a53 --- /dev/null +++ b/extensions/git/src/ipc/ipcServer.ts @@ -0,0 +1,106 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vscode'; +import { toDisposable } from '../util'; +import * as path from 'path'; +import * as http from 'http'; +import * as os from 'os'; +import * as fs from 'fs'; +import * as crypto from 'crypto'; + +function getIPCHandlePath(nonce: string): string { + if (process.platform === 'win32') { + return `\\\\.\\pipe\\vscode-git-ipc-${nonce}-sock`; + } + + if (process.env['XDG_RUNTIME_DIR']) { + return path.join(process.env['XDG_RUNTIME_DIR'] as string, `vscode-git-ipc-${nonce}.sock`); + } + + return path.join(os.tmpdir(), `vscode-git-ipc-${nonce}.sock`); +} + +export interface IIPCHandler { + handle(request: any): Promise; +} + +export async function createIPCServer(): Promise { + const server = http.createServer(); + const buffer = await new Promise((c, e) => crypto.randomBytes(20, (err, buf) => err ? e(err) : c(buf))); + const nonce = buffer.toString('hex'); + const ipcHandlePath = getIPCHandlePath(nonce); + + return new Promise((c, e) => { + try { + server.on('error', err => e(err)); + server.listen(ipcHandlePath); + c(new IPCServer(server, ipcHandlePath)); + } catch (err) { + e(err); + } + }); +} + +export interface IIPCServer extends Disposable { + readonly ipcHandlePath: string | undefined; + getEnv(): any; + registerHandler(name: string, handler: IIPCHandler): Disposable; +} + +class IPCServer implements IIPCServer, Disposable { + + private handlers = new Map(); + get ipcHandlePath(): string { return this._ipcHandlePath; } + + constructor(private server: http.Server, private _ipcHandlePath: string) { + this.server.on('request', this.onRequest.bind(this)); + } + + registerHandler(name: string, handler: IIPCHandler): Disposable { + this.handlers.set(`/${name}`, handler); + return toDisposable(() => this.handlers.delete(name)); + } + + private onRequest(req: http.IncomingMessage, res: http.ServerResponse): void { + if (!req.url) { + console.warn(`Request lacks url`); + return; + } + + const handler = this.handlers.get(req.url); + + if (!handler) { + console.warn(`IPC handler for ${req.url} not found`); + return; + } + + const chunks: Buffer[] = []; + req.on('data', d => chunks.push(d)); + req.on('end', () => { + const request = JSON.parse(Buffer.concat(chunks).toString('utf8')); + handler.handle(request).then(result => { + res.writeHead(200); + res.end(JSON.stringify(result)); + }, () => { + res.writeHead(500); + res.end(); + }); + }); + } + + getEnv(): any { + return { VSCODE_GIT_IPC_HANDLE: this.ipcHandlePath }; + } + + dispose(): void { + this.handlers.clear(); + this.server.close(); + + if (this._ipcHandlePath && process.platform !== 'win32') { + fs.unlinkSync(this._ipcHandlePath); + } + } +} diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index fdc0f87416..5e85b3420b 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -11,6 +11,7 @@ import { findGit, Git, IGit } from './git'; import { Model } from './model'; import { CommandCenter } from './commands'; import { GitContentProvider } from './contentProvider'; +import { GitFileSystemProvider } from './fileSystemProvider'; import { GitDecorations } from './decorationProvider'; import { Askpass } from './askpass'; import { toDisposable, filterEvent, eventToPromise } from './util'; @@ -18,9 +19,9 @@ import TelemetryReporter from 'vscode-extension-telemetry'; import { GitExtension } from './api/git'; import { GitProtocolHandler } from './protocolHandler'; import { GitExtensionImpl } from './api/extension'; -// {{SQL CARBON EDIT}} - remove unused imports // import * as path from 'path'; // import * as fs from 'fs'; +import { createIPCServer, IIPCServer } from './ipc/ipcServer'; const deactivateTasks: { (): Promise; }[] = []; @@ -33,10 +34,26 @@ export async function deactivate(): Promise { async function createModel(context: ExtensionContext, outputChannel: OutputChannel, telemetryReporter: TelemetryReporter, disposables: Disposable[]): Promise { const pathHint = workspace.getConfiguration('git').get('path'); const info = await findGit(pathHint, path => outputChannel.appendLine(localize('looking', "Looking for git in: {0}", path))); - const askpass = new Askpass(); - disposables.push(askpass); - const env = await askpass.getEnv(); + let env: any = {}; + let ipc: IIPCServer | undefined; + + try { + ipc = await createIPCServer(); + disposables.push(ipc); + env = { ...env, ...ipc.getEnv() }; + } catch { + // noop + } + + if (ipc) { + const askpass = new Askpass(ipc); + disposables.push(askpass); + env = { ...env, ...askpass.getEnv() }; + } else { + env = { ...env, ...Askpass.getDisabledEnv() }; + } + const git = new Git({ gitPath: info.path, version: info.version, env }); const model = new Model(git, context.globalState, outputChannel); disposables.push(model); @@ -63,6 +80,7 @@ async function createModel(context: ExtensionContext, outputChannel: OutputChann disposables.push( new CommandCenter(git, model, outputChannel, telemetryReporter), new GitContentProvider(model), + new GitFileSystemProvider(model), new GitDecorations(model), new GitProtocolHandler() ); @@ -198,4 +216,4 @@ async function checkGitVersion(_info: IGit): Promise { // await config.update('ignoreLegacyWarning', true, true); // } // {{SQL CARBON EDIT}} - End -} \ No newline at end of file +} diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 064c218642..560c4a5d01 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -6,7 +6,7 @@ import { workspace, WorkspaceFoldersChangeEvent, Uri, window, Event, EventEmitter, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup, TextEditor, Memento, OutputChannel } from 'vscode'; import { Repository, RepositoryState } from './repository'; import { memoize, sequentialize, debounce } from './decorators'; -import { dispose, anyEvent, filterEvent, isDescendant, firstIndex } from './util'; +import { dispose, anyEvent, filterEvent, isDescendant, firstIndex, pathEquals } from './util'; import { Git } from './git'; import * as path from 'path'; import * as fs from 'fs'; @@ -240,10 +240,7 @@ export class Model { return; } - const config = workspace.getConfiguration('git'); - const ignoredRepos = new Set(config.get>('ignoredRepositories')); - - if (ignoredRepos.has(rawRoot)) { + if (this.shouldRepositoryBeIgnored(rawRoot)) { return; } @@ -261,6 +258,27 @@ export class Model { } } + private shouldRepositoryBeIgnored(repositoryRoot: string): boolean { + const config = workspace.getConfiguration('git'); + const ignoredRepos = config.get('ignoredRepositories') || []; + + for (const ignoredRepo of ignoredRepos) { + if (path.isAbsolute(ignoredRepo)) { + if (pathEquals(ignoredRepo, repositoryRoot)) { + return true; + } + } else { + for (const folder of workspace.workspaceFolders || []) { + if (pathEquals(path.join(folder.uri.fsPath, ignoredRepo), repositoryRoot)) { + return true; + } + } + } + } + + return false; + } + private open(repository: Repository): void { this.outputChannel.appendLine(`Open repository: ${repository.root}`); diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 29a8876d99..b0bf44b165 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -3,17 +3,17 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Uri, Command, EventEmitter, Event, scm, SourceControl, SourceControlInputBox, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, SourceControlInputBoxValidation, Disposable, ProgressLocation, window, workspace, WorkspaceEdit, ThemeColor, Decoration, Memento, SourceControlInputBoxValidationType, OutputChannel, LogLevel, env, ProgressOptions, CancellationToken } from 'vscode'; -import { Repository as BaseRepository, Commit, Stash, GitError, Submodule, CommitOptions, ForcePushMode } from './git'; -import { anyEvent, filterEvent, eventToPromise, dispose, find, isDescendant, IDisposable, onceEvent, EmptyDisposable, debounceEvent, combinedDisposable } from './util'; -import { memoize, throttle, debounce } from './decorators'; -import { toGitUri } from './uri'; -import { AutoFetcher } from './autofetch'; -import * as path from 'path'; -import * as nls from 'vscode-nls'; import * as fs from 'fs'; +import * as path from 'path'; +import { CancellationToken, Command, Disposable, env, Event, EventEmitter, LogLevel, Memento, OutputChannel, ProgressLocation, ProgressOptions, scm, SourceControl, SourceControlInputBox, SourceControlInputBoxValidation, SourceControlInputBoxValidationType, SourceControlResourceDecorations, SourceControlResourceGroup, SourceControlResourceState, ThemeColor, Uri, window, workspace, WorkspaceEdit, Decoration } from 'vscode'; +import * as nls from 'vscode-nls'; +import { Branch, Change, GitErrorCodes, LogOptions, Ref, RefType, Remote, Status } from './api/git'; +import { AutoFetcher } from './autofetch'; +import { debounce, memoize, throttle } from './decorators'; +import { Commit, CommitOptions, ForcePushMode, GitError, Repository as BaseRepository, Stash, Submodule } from './git'; import { StatusBarCommands } from './statusbar'; -import { Branch, Ref, Remote, RefType, GitErrorCodes, Status, LogOptions, Change } from './api/git'; +import { toGitUri } from './uri'; +import { anyEvent, combinedDisposable, debounceEvent, dispose, EmptyDisposable, eventToPromise, filterEvent, find, IDisposable, isDescendant, onceEvent } from './util'; import { IFileWatcher, watch } from './watch'; const timeout = (millis: number) => new Promise(c => setTimeout(c, millis)); @@ -33,7 +33,8 @@ export const enum RepositoryState { export const enum ResourceGroupType { Merge, Index, - WorkingTree + WorkingTree, + Untracked } export class Resource implements SourceControlResourceState { @@ -292,6 +293,7 @@ export const enum Operation { Merge = 'Merge', Ignore = 'Ignore', Tag = 'Tag', + DeleteTag = 'DeleteTag', Stash = 'Stash', CheckIgnore = 'CheckIgnore', GetObjectDetails = 'GetObjectDetails', @@ -569,6 +571,9 @@ export class Repository implements Disposable { private _workingTreeGroup: SourceControlResourceGroup; get workingTreeGroup(): GitResourceGroup { return this._workingTreeGroup as GitResourceGroup; } + private _untrackedGroup: SourceControlResourceGroup; + get untrackedGroup(): GitResourceGroup { return this._untrackedGroup as GitResourceGroup; } + private _HEAD: Branch | undefined; get HEAD(): Branch | undefined { return this._HEAD; @@ -641,6 +646,7 @@ export class Repository implements Disposable { this.mergeGroup.resourceStates = []; this.indexGroup.resourceStates = []; this.workingTreeGroup.resourceStates = []; + this.untrackedGroup.resourceStates = []; this._sourceControl.count = 0; } @@ -708,6 +714,7 @@ export class Repository implements Disposable { this._mergeGroup = this._sourceControl.createResourceGroup('merge', localize('merge changes', "MERGE CHANGES")); this._indexGroup = this._sourceControl.createResourceGroup('index', localize('staged changes', "STAGED CHANGES")); this._workingTreeGroup = this._sourceControl.createResourceGroup('workingTree', localize('changes', "CHANGES")); + this._untrackedGroup = this._sourceControl.createResourceGroup('untracked', localize('untracked changes', "UNTRACKED CHANGES")); const updateIndexGroupVisibility = () => { const config = workspace.getConfiguration('git', root); @@ -721,11 +728,16 @@ export class Repository implements Disposable { const onConfigListenerForBranchSortOrder = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.branchSortOrder', root)); onConfigListenerForBranchSortOrder(this.updateModelState, this, this.disposables); + const onConfigListenerForUntracked = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.untrackedChanges', root)); + onConfigListenerForUntracked(this.updateModelState, this, this.disposables); + this.mergeGroup.hideWhenEmpty = true; + this.untrackedGroup.hideWhenEmpty = true; this.disposables.push(this.mergeGroup); this.disposables.push(this.indexGroup); this.disposables.push(this.workingTreeGroup); + this.disposables.push(this.untrackedGroup); this.disposables.push(new AutoFetcher(this, globalState)); @@ -911,8 +923,8 @@ export class Repository implements Disposable { return this.run(Operation.HashObject, () => this.repository.hashObject(data)); } - async add(resources: Uri[]): Promise { - await this.run(Operation.Add, () => this.repository.add(resources.map(r => r.fsPath))); + async add(resources: Uri[], opts?: { update?: boolean }): Promise { + await this.run(Operation.Add, () => this.repository.add(resources.map(r => r.fsPath), opts)); } async rm(resources: Uri[]): Promise { @@ -957,6 +969,7 @@ export class Repository implements Disposable { const toClean: string[] = []; const toCheckout: string[] = []; const submodulesToUpdate: string[] = []; + const resourceStates = [...this.workingTreeGroup.resourceStates, ...this.untrackedGroup.resourceStates]; resources.forEach(r => { const fsPath = r.fsPath; @@ -969,7 +982,7 @@ export class Repository implements Disposable { } const raw = r.toString(); - const scmResource = find(this.workingTreeGroup.resourceStates, sr => sr.resourceUri.toString() === raw); + const scmResource = find(resourceStates, sr => sr.resourceUri.toString() === raw); if (!scmResource) { return; @@ -1021,6 +1034,10 @@ export class Repository implements Disposable { await this.run(Operation.Tag, () => this.repository.tag(name, message)); } + async deleteTag(name: string): Promise { + await this.run(Operation.DeleteTag, () => this.repository.deleteTag(name)); + } + async checkout(treeish: string): Promise { await this.run(Operation.Checkout, () => this.repository.checkout(treeish, [])); } @@ -1249,6 +1266,10 @@ export class Repository implements Disposable { return await this.run(Operation.Stash, () => this.repository.popStash(index)); } + async dropStash(index?: number): Promise { + return await this.run(Operation.Stash, () => this.repository.dropStash(index)); + } + async applyStash(index?: number): Promise { return await this.run(Operation.Stash, () => this.repository.applyStash(index)); } @@ -1257,10 +1278,6 @@ export class Repository implements Disposable { return await this.run(Operation.GetCommitTemplate, async () => this.repository.getCommitTemplate()); } - async cleanUpCommitEditMessage(editMessage: string): Promise { - return this.repository.cleanupCommitEditMessage(editMessage); - } - async ignore(files: Uri[]): Promise { return await this.run(Operation.Ignore, async () => { const ignoreFile = `${this.repository.root}${path.sep}.gitignore`; @@ -1298,7 +1315,7 @@ export class Repository implements Disposable { // https://git-scm.com/docs/git-check-ignore#git-check-ignore--z const child = this.repository.stream(['check-ignore', '-v', '-z', '--stdin'], { stdio: [null, null, null] }); - child.stdin.end(filePaths.join('\0'), 'utf8'); + child.stdin!.end(filePaths.join('\0'), 'utf8'); const onExit = (exitCode: number) => { if (exitCode === 1) { @@ -1320,12 +1337,12 @@ export class Repository implements Disposable { data += raw; }; - child.stdout.setEncoding('utf8'); - child.stdout.on('data', onStdoutData); + child.stdout!.setEncoding('utf8'); + child.stdout!.on('data', onStdoutData); let stderr: string = ''; - child.stderr.setEncoding('utf8'); - child.stderr.on('data', raw => stderr += raw); + child.stderr!.setEncoding('utf8'); + child.stderr!.on('data', raw => stderr += raw); child.on('error', reject); child.on('exit', onExit); @@ -1427,6 +1444,7 @@ export class Repository implements Disposable { private async updateModelState(): Promise { const { status, didHitLimit } = await this.repository.getStatus(); const config = workspace.getConfiguration('git'); + const scopedConfig = workspace.getConfiguration('git', Uri.file(this.repository.root)); const shouldIgnore = config.get('ignoreLimitWarning') === true; const useIcons = !config.get('decorations.enabled', true); this.isRepositoryHuge = didHitLimit; @@ -1487,17 +1505,29 @@ export class Repository implements Disposable { this._submodules = submodules!; this.rebaseCommit = rebaseCommit; + const untrackedChanges = scopedConfig.get<'mixed' | 'separate' | 'hidden'>('untrackedChanges'); const index: Resource[] = []; const workingTree: Resource[] = []; const merge: Resource[] = []; + const untracked: Resource[] = []; status.forEach(raw => { const uri = Uri.file(path.join(this.repository.root, raw.path)); - const renameUri = raw.rename ? Uri.file(path.join(this.repository.root, raw.rename)) : undefined; + const renameUri = raw.rename + ? Uri.file(path.join(this.repository.root, raw.rename)) + : undefined; switch (raw.x + raw.y) { - case '??': return workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.UNTRACKED, useIcons)); - case '!!': return workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.IGNORED, useIcons)); + case '??': switch (untrackedChanges) { + case 'mixed': return workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.UNTRACKED, useIcons)); + case 'separate': return untracked.push(new Resource(ResourceGroupType.Untracked, uri, Status.UNTRACKED, useIcons)); + default: return undefined; + } + case '!!': switch (untrackedChanges) { + case 'mixed': return workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.IGNORED, useIcons)); + case 'separate': return untracked.push(new Resource(ResourceGroupType.Untracked, uri, Status.IGNORED, useIcons)); + default: return undefined; + } case 'DD': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.BOTH_DELETED, useIcons)); case 'AU': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.ADDED_BY_US, useIcons)); case 'UD': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.DELETED_BY_THEM, useIcons)); @@ -1520,6 +1550,7 @@ export class Repository implements Disposable { case 'D': workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.DELETED, useIcons, renameUri)); break; case 'A': workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.INTENT_TO_ADD, useIcons, renameUri)); break; } + return undefined; }); @@ -1527,6 +1558,7 @@ export class Repository implements Disposable { this.mergeGroup.resourceStates = merge; this.indexGroup.resourceStates = index; this.workingTreeGroup.resourceStates = workingTree; + this.untrackedGroup.resourceStates = untracked; // set count badge this.setCountBadge(); @@ -1537,12 +1569,27 @@ export class Repository implements Disposable { } private setCountBadge(): void { - const countBadge = workspace.getConfiguration('git').get('countBadge'); - let count = this.mergeGroup.resourceStates.length + this.indexGroup.resourceStates.length + this.workingTreeGroup.resourceStates.length; + const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); + const countBadge = config.get<'all' | 'tracked' | 'off'>('countBadge'); + const untrackedChanges = config.get<'mixed' | 'separate' | 'hidden'>('untrackedChanges'); + + let count = + this.mergeGroup.resourceStates.length + + this.indexGroup.resourceStates.length + + this.workingTreeGroup.resourceStates.length; switch (countBadge) { case 'off': count = 0; break; - case 'tracked': count = count - this.workingTreeGroup.resourceStates.filter(r => r.type === Status.UNTRACKED || r.type === Status.IGNORED).length; break; + case 'tracked': + if (untrackedChanges === 'mixed') { + count -= this.workingTreeGroup.resourceStates.filter(r => r.type === Status.UNTRACKED || r.type === Status.IGNORED).length; + } + break; + case 'all': + if (untrackedChanges === 'separate') { + count += this.untrackedGroup.resourceStates.length; + } + break; } this._sourceControl.count = count; @@ -1644,7 +1691,7 @@ export class Repository implements Disposable { const head = HEAD.name || tagName || (HEAD.commit || '').substr(0, 8); return head - + (this.workingTreeGroup.resourceStates.length > 0 ? '*' : '') + + (this.workingTreeGroup.resourceStates.length + this.untrackedGroup.resourceStates.length > 0 ? '*' : '') + (this.indexGroup.resourceStates.length > 0 ? '+' : '') + (this.mergeGroup.resourceStates.length > 0 ? '!' : ''); } diff --git a/extensions/git/src/typings/jschardet.d.ts b/extensions/git/src/typings/jschardet.d.ts deleted file mode 100644 index f252a47fd0..0000000000 --- a/extensions/git/src/typings/jschardet.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -declare module 'jschardet' { - export interface IDetectedMap { - encoding: string, - confidence: number - } - export function detect(buffer: Buffer): IDetectedMap; - - export const Constants: { - MINIMUM_THRESHOLD: number, - } -} \ No newline at end of file diff --git a/extensions/git/src/uri.ts b/extensions/git/src/uri.ts index f23f8fbd87..a66877b07b 100644 --- a/extensions/git/src/uri.ts +++ b/extensions/git/src/uri.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Uri } from 'vscode'; +import * as qs from 'querystring'; export interface GitUriParams { path: string; @@ -11,8 +12,26 @@ export interface GitUriParams { submoduleOf?: string; } +export function isGitUri(uri: Uri): boolean { + return /^git(fs)?$/.test(uri.scheme); +} + export function fromGitUri(uri: Uri): GitUriParams { - return JSON.parse(uri.query); + const result = qs.parse(uri.query) as any; + + if (!result) { + throw new Error('Invalid git URI: empty query'); + } + + if (typeof result.path !== 'string') { + throw new Error('Invalid git URI: missing path'); + } + + if (typeof result.ref !== 'string') { + throw new Error('Invalid git URI: missing ref'); + } + + return result; } export interface GitUriOptions { @@ -42,8 +61,8 @@ export function toGitUri(uri: Uri, ref: string, options: GitUriOptions = {}): Ur } return uri.with({ - scheme: 'git', + scheme: 'gitfs', path, - query: JSON.stringify(params) + query: qs.stringify(params as any) }); -} \ No newline at end of file +} diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index 8f9a0e0841..742bcbfa05 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -6,7 +6,7 @@ import { Event } from 'vscode'; import { dirname, sep } from 'path'; import { Readable } from 'stream'; -import * as fs from 'fs'; +import { promises as fs, createReadStream } from 'fs'; import * as byline from 'byline'; export function log(...args: any[]): void { @@ -140,25 +140,14 @@ export function groupBy(arr: T[], fn: (el: T) => string): { [key: string]: T[ }, Object.create(null)); } -export function denodeify(fn: Function): (a: A, b: B, c: C) => Promise; -export function denodeify(fn: Function): (a: A, b: B) => Promise; -export function denodeify(fn: Function): (a: A) => Promise; -export function denodeify(fn: Function): (...args: any[]) => Promise; -export function denodeify(fn: Function): (...args: any[]) => Promise { - return (...args) => new Promise((c, e) => fn(...args, (err: any, r: any) => err ? e(err) : c(r))); -} - -export function nfcall(fn: Function, ...args: any[]): Promise { - return new Promise((c, e) => fn(...args, (err: any, r: any) => err ? e(err) : c(r))); -} export async function mkdirp(path: string, mode?: number): Promise { const mkdir = async () => { try { - await nfcall(fs.mkdir, path, mode); + await fs.mkdir(path, mode); } catch (err) { if (err.code === 'EEXIST') { - const stat = await nfcall(fs.stat, path); + const stat = await fs.stat(path); if (stat.isDirectory()) { return; @@ -232,7 +221,7 @@ export function find(array: T[], fn: (t: T) => boolean): T | undefined { export async function grep(filename: string, pattern: RegExp): Promise { return new Promise((c, e) => { - const fileStream = fs.createReadStream(filename, { encoding: 'utf8' }); + const fileStream = createReadStream(filename, { encoding: 'utf8' }); const stream = byline(fileStream); stream.on('data', (line: string) => { if (pattern.test(line)) { diff --git a/extensions/git/yarn.lock b/extensions/git/yarn.lock index 1f2ea8aed4..58a82bbe41 100644 --- a/extensions/git/yarn.lock +++ b/extensions/git/yarn.lock @@ -26,10 +26,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.51.tgz#b31d716fb8d58eeb95c068a039b9b6292817d5fb" integrity sha512-El3+WJk2D/ppWNd2X05aiP5l2k4EwF7KwheknQZls+I26eSICoWRhRIJ56jGgw2dqNGQ5LtNajmBU2ajS28EvQ== -"@types/node@^10.14.8": - version "10.14.8" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9" - integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw== +"@types/node@^12.11.7": + version "12.11.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a" + integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA== "@types/which@^1.0.28": version "1.0.28" @@ -176,10 +176,10 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= -jschardet@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-1.6.0.tgz#c7d1a71edcff2839db2f9ec30fc5d5ebd3c1a678" - integrity sha512-xYuhvQ7I9PDJIGBWev9xm0+SMSed3ZDBAmvVjbFR1ZRLAF+vlXcQu6cRI9uAlj81rzikElRVteehwV7DuX2ZmQ== +jschardet@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-2.1.1.tgz#af6f8fd0b3b0f5d46a8fd9614a4fce490575c184" + integrity sha512-pA5qG9Zwm8CBpGlK/lo2GE9jPxwqRgMV7Lzc/1iaPccw6v4Rhj8Zg2BTyrdmHmxlJojnbLupLeRnaPLsq03x6Q== json3@3.3.2: version "3.3.2" diff --git a/extensions/image-preview/media/main.css b/extensions/image-preview/media/main.css index 67c4a53049..f363cb0a54 100644 --- a/extensions/image-preview/media/main.css +++ b/extensions/image-preview/media/main.css @@ -21,6 +21,7 @@ body img { .container { padding: 5px 0 0 10px; box-sizing: border-box; + -webkit-user-select: none; user-select: none; } diff --git a/extensions/image-preview/media/main.js b/extensions/image-preview/media/main.js index 8843c26bdf..eb42624fd5 100644 --- a/extensions/image-preview/media/main.js +++ b/extensions/image-preview/media/main.js @@ -70,7 +70,8 @@ let ctrlPressed = false; let altPressed = false; let hasLoadedImage = false; - let consumeClick = false; + let consumeClick = true; + let isActive = false; // Elements const container = document.body; @@ -117,10 +118,16 @@ }); } - function changeActive(value) { + function setActive(value) { + isActive = value; if (value) { - container.classList.add('zoom-in'); - consumeClick = true; + if (isMac ? altPressed : ctrlPressed) { + container.classList.remove('zoom-in'); + container.classList.add('zoom-out'); + } else { + container.classList.remove('zoom-out'); + container.classList.add('zoom-in'); + } } else { ctrlPressed = false; altPressed = false; @@ -202,7 +209,10 @@ return; } - consumeClick = false; + ctrlPressed = e.ctrlKey; + altPressed = e.altKey; + + consumeClick = !isActive; }); container.addEventListener('click', (/** @type {MouseEvent} */ e) => { @@ -214,14 +224,6 @@ return; } - ctrlPressed = e.ctrlKey; - altPressed = e.altKey; - - if (isMac ? altPressed : ctrlPressed) { - container.classList.remove('zoom-in'); - container.classList.add('zoom-out'); - } - if (consumeClick) { consumeClick = false; return; @@ -239,6 +241,11 @@ }); container.addEventListener('wheel', (/** @type {WheelEvent} */ e) => { + // Prevent pinch to zoom + if (e.ctrlKey) { + e.preventDefault(); + } + if (!image || !hasLoadedImage) { return; } @@ -254,9 +261,9 @@ let delta = e.deltaY > 0 ? 1 : -1; updateScale(scale * (1 - delta * SCALE_PINCH_FACTOR)); - }); + }, { passive: false }); - window.addEventListener('scroll', () => { + window.addEventListener('scroll', e => { if (!image || !hasLoadedImage || !image.parentElement || scale === 'fit') { return; } @@ -265,7 +272,7 @@ if (entry) { vscode.setState({ scale: entry.scale, offsetX: window.scrollX, offsetY: window.scrollY }); } - }); + }, { passive: true }); container.classList.add('image'); @@ -296,7 +303,7 @@ document.body.classList.remove('loading'); }); - image.src = decodeURI(settings.src); + image.src = settings.src; window.addEventListener('message', e => { switch (e.data.type) { @@ -305,7 +312,7 @@ break; case 'setActive': - changeActive(e.data.value); + setActive(e.data.value); break; case 'zoomIn': diff --git a/extensions/image-preview/package.json b/extensions/image-preview/package.json index c6149de85d..8be8486a54 100644 --- a/extensions/image-preview/package.json +++ b/extensions/image-preview/package.json @@ -29,8 +29,7 @@ "priority": "builtin", "selector": [ { - "filenamePattern": "*.{jpg,jpe,jpeg,png,bmp,gif,ico,tga,webp}", - "mime": "image/*" + "filenamePattern": "*.{jpg,jpe,jpeg,png,bmp,gif,ico,webp}" } ] } diff --git a/extensions/image-preview/src/binarySizeStatusBarEntry.ts b/extensions/image-preview/src/binarySizeStatusBarEntry.ts new file mode 100644 index 0000000000..c339a84de5 --- /dev/null +++ b/extensions/image-preview/src/binarySizeStatusBarEntry.ts @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import * as nls from 'vscode-nls'; +import { PreviewStatusBarEntry } from './ownedStatusBarEntry'; + +const localize = nls.loadMessageBundle(); + +class BinarySize { + static readonly KB = 1024; + static readonly MB = BinarySize.KB * BinarySize.KB; + static readonly GB = BinarySize.MB * BinarySize.KB; + static readonly TB = BinarySize.GB * BinarySize.KB; + + static formatSize(size: number): string { + if (size < BinarySize.KB) { + return localize('sizeB', "{0}B", size); + } + + if (size < BinarySize.MB) { + return localize('sizeKB', "{0}KB", (size / BinarySize.KB).toFixed(2)); + } + + if (size < BinarySize.GB) { + return localize('sizeMB', "{0}MB", (size / BinarySize.MB).toFixed(2)); + } + + if (size < BinarySize.TB) { + return localize('sizeGB', "{0}GB", (size / BinarySize.GB).toFixed(2)); + } + + return localize('sizeTB', "{0}TB", (size / BinarySize.TB).toFixed(2)); + } +} + +export class BinarySizeStatusBarEntry extends PreviewStatusBarEntry { + + constructor() { + super({ + id: 'imagePreview.binarySize', + name: localize('sizeStatusBar.name', "Image Binary Size"), + alignment: vscode.StatusBarAlignment.Right, + priority: 100, + }); + } + + public show(owner: string, size: number | undefined) { + if (typeof size === 'number') { + super.showItem(owner, BinarySize.formatSize(size)); + } else { + this.hide(owner); + } + } +} diff --git a/extensions/image-preview/src/extension.ts b/extensions/image-preview/src/extension.ts index 2126e9c1cb..1804cbd561 100644 --- a/extensions/image-preview/src/extension.ts +++ b/extensions/image-preview/src/extension.ts @@ -6,6 +6,7 @@ import * as vscode from 'vscode'; import { PreviewManager } from './preview'; import { SizeStatusBarEntry } from './sizeStatusBarEntry'; +import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry'; import { ZoomStatusBarEntry } from './zoomStatusBarEntry'; export function activate(context: vscode.ExtensionContext) { @@ -14,18 +15,15 @@ export function activate(context: vscode.ExtensionContext) { const sizeStatusBarEntry = new SizeStatusBarEntry(); context.subscriptions.push(sizeStatusBarEntry); + const binarySizeStatusBarEntry = new BinarySizeStatusBarEntry(); + context.subscriptions.push(binarySizeStatusBarEntry); + const zoomStatusBarEntry = new ZoomStatusBarEntry(); context.subscriptions.push(zoomStatusBarEntry); - const previewManager = new PreviewManager(extensionRoot, sizeStatusBarEntry, zoomStatusBarEntry); + const previewManager = new PreviewManager(extensionRoot, sizeStatusBarEntry, binarySizeStatusBarEntry, zoomStatusBarEntry); - context.subscriptions.push(vscode.window.registerWebviewEditorProvider( - PreviewManager.viewType, - { - async resolveWebviewEditor(resource: vscode.Uri, editor: vscode.WebviewEditor): Promise { - previewManager.resolve(resource, editor); - } - })); + context.subscriptions.push(vscode.window.registerWebviewEditorProvider(PreviewManager.viewType, previewManager)); context.subscriptions.push(vscode.commands.registerCommand('imagePreview.zoomIn', () => { previewManager.activePreview?.zoomIn(); diff --git a/extensions/image-preview/src/ownedStatusBarEntry.ts b/extensions/image-preview/src/ownedStatusBarEntry.ts new file mode 100644 index 0000000000..46852aa4e4 --- /dev/null +++ b/extensions/image-preview/src/ownedStatusBarEntry.ts @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { Disposable } from './dispose'; + +export abstract class PreviewStatusBarEntry extends Disposable { + private _showOwner: string | undefined; + + protected readonly entry: vscode.StatusBarItem; + + constructor(options: vscode.window.StatusBarItemOptions) { + super(); + this.entry = this._register(vscode.window.createStatusBarItem(options)); + } + + protected showItem(owner: string, text: string) { + this._showOwner = owner; + this.entry.text = text; + this.entry.show(); + } + + public hide(owner: string) { + if (owner === this._showOwner) { + this.entry.hide(); + this._showOwner = undefined; + } + } +} diff --git a/extensions/image-preview/src/preview.ts b/extensions/image-preview/src/preview.ts index 2809c8ac74..a2a36edbb6 100644 --- a/extensions/image-preview/src/preview.ts +++ b/extensions/image-preview/src/preview.ts @@ -8,11 +8,12 @@ import * as nls from 'vscode-nls'; import { Disposable } from './dispose'; import { SizeStatusBarEntry } from './sizeStatusBarEntry'; import { Scale, ZoomStatusBarEntry } from './zoomStatusBarEntry'; +import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry'; const localize = nls.loadMessageBundle(); -export class PreviewManager { +export class PreviewManager implements vscode.WebviewEditorProvider { public static readonly viewType = 'imagePreview.previewEditor'; @@ -22,14 +23,15 @@ export class PreviewManager { constructor( private readonly extensionRoot: vscode.Uri, private readonly sizeStatusBarEntry: SizeStatusBarEntry, + private readonly binarySizeStatusBarEntry: BinarySizeStatusBarEntry, private readonly zoomStatusBarEntry: ZoomStatusBarEntry, ) { } - public resolve( - resource: vscode.Uri, - webviewEditor: vscode.WebviewEditor, - ) { - const preview = new Preview(this.extensionRoot, resource, webviewEditor, this.sizeStatusBarEntry, this.zoomStatusBarEntry); + public async resolveWebviewEditor( + input: { readonly resource: vscode.Uri, }, + webviewEditor: vscode.WebviewPanel, + ): Promise { + const preview = new Preview(this.extensionRoot, input.resource, webviewEditor, this.sizeStatusBarEntry, this.binarySizeStatusBarEntry, this.zoomStatusBarEntry); this._previews.add(preview); this.setActivePreview(preview); @@ -42,6 +44,8 @@ export class PreviewManager { this.setActivePreview(undefined); } }); + + return {}; } public get activePreview() { return this._activePreview; } @@ -68,13 +72,15 @@ class Preview extends Disposable { private _previewState = PreviewState.Visible; private _imageSize: string | undefined; + private _imageBinarySize: number | undefined; private _imageZoom: Scale | undefined; constructor( private readonly extensionRoot: vscode.Uri, private readonly resource: vscode.Uri, - private readonly webviewEditor: vscode.WebviewEditor, + private readonly webviewEditor: vscode.WebviewPanel, private readonly sizeStatusBarEntry: SizeStatusBarEntry, + private readonly binarySizeStatusBarEntry: BinarySizeStatusBarEntry, private readonly zoomStatusBarEntry: ZoomStatusBarEntry, ) { super(); @@ -115,11 +121,13 @@ class Preview extends Disposable { this._register(webviewEditor.onDidChangeViewState(() => { this.update(); + this.webviewEditor.webview.postMessage({ type: 'setActive', value: this.webviewEditor.active }); })); this._register(webviewEditor.onDidDispose(() => { if (this._previewState === PreviewState.Active) { this.sizeStatusBarEntry.hide(this.id); + this.binarySizeStatusBarEntry.hide(this.id); this.zoomStatusBarEntry.hide(this.id); } this._previewState = PreviewState.Disposed; @@ -137,8 +145,14 @@ class Preview extends Disposable { } })); + vscode.workspace.fs.stat(resource).then(({ size }) => { + this._imageBinarySize = size; + this.update(); + }); + this.render(); this.update(); + this.webviewEditor.webview.postMessage({ type: 'setActive', value: this.webviewEditor.active }); } public zoomIn() { @@ -167,15 +181,16 @@ class Preview extends Disposable { if (this.webviewEditor.active) { this._previewState = PreviewState.Active; this.sizeStatusBarEntry.show(this.id, this._imageSize || ''); + this.binarySizeStatusBarEntry.show(this.id, this._imageBinarySize); this.zoomStatusBarEntry.show(this.id, this._imageZoom || 'fit'); } else { if (this._previewState === PreviewState.Active) { this.sizeStatusBarEntry.hide(this.id); + this.binarySizeStatusBarEntry.hide(this.id); this.zoomStatusBarEntry.hide(this.id); } this._previewState = PreviewState.Visible; } - this.webviewEditor.webview.postMessage({ type: 'setActive', value: this.webviewEditor.active }); } private getWebiewContents(): string { @@ -191,8 +206,11 @@ class Preview extends Disposable { - - + + + + Image Preview @@ -208,18 +226,21 @@ class Preview extends Disposable { `; } - private getResourcePath(webviewEditor: vscode.WebviewEditor, resource: vscode.Uri, version: string) { + private getResourcePath(webviewEditor: vscode.WebviewPanel, resource: vscode.Uri, version: string) { switch (resource.scheme) { case 'data': - return encodeURI(resource.toString(true)); + return resource.toString(true); case 'git': // Show blank image - return encodeURI('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAEElEQVR42gEFAPr/AP///wAI/AL+Sr4t6gAAAABJRU5ErkJggg=='); - + return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAEElEQVR42gEFAPr/AP///wAI/AL+Sr4t6gAAAABJRU5ErkJggg=='; default: - return encodeURI(webviewEditor.webview.asWebviewUri(resource).toString(true) + `?version=${version}`); + // Avoid adding cache busting if there is already a query string + if (resource.query) { + return webviewEditor.webview.asWebviewUri(resource).toString(true); + } + return webviewEditor.webview.asWebviewUri(resource).with({ query: `version=${version}` }).toString(true); } } diff --git a/extensions/image-preview/src/sizeStatusBarEntry.ts b/extensions/image-preview/src/sizeStatusBarEntry.ts index cc166dec6c..a2e7e50a89 100644 --- a/extensions/image-preview/src/sizeStatusBarEntry.ts +++ b/extensions/image-preview/src/sizeStatusBarEntry.ts @@ -4,36 +4,23 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { Disposable } from './dispose'; import * as nls from 'vscode-nls'; +import { PreviewStatusBarEntry } from './ownedStatusBarEntry'; const localize = nls.loadMessageBundle(); -export class SizeStatusBarEntry extends Disposable { - private readonly _entry: vscode.StatusBarItem; - - private _showingOwner: string | undefined; +export class SizeStatusBarEntry extends PreviewStatusBarEntry { constructor() { - super(); - this._entry = this._register(vscode.window.createStatusBarItem({ + super({ id: 'imagePreview.size', name: localize('sizeStatusBar.name', "Image Size"), alignment: vscode.StatusBarAlignment.Right, priority: 101 /* to the left of editor status (100) */, - })); + }); } public show(owner: string, text: string) { - this._showingOwner = owner; - this._entry.text = text; - this._entry.show(); - } - - public hide(owner: string) { - if (owner === this._showingOwner) { - this._entry.hide(); - this._showingOwner = undefined; - } + this.showItem(owner, text); } } diff --git a/extensions/image-preview/src/zoomStatusBarEntry.ts b/extensions/image-preview/src/zoomStatusBarEntry.ts index 42976b8dc5..dfe166ae3b 100644 --- a/extensions/image-preview/src/zoomStatusBarEntry.ts +++ b/extensions/image-preview/src/zoomStatusBarEntry.ts @@ -5,7 +5,7 @@ import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; -import { Disposable } from './dispose'; +import { PreviewStatusBarEntry as OwnedStatusBarEntry } from './ownedStatusBarEntry'; const localize = nls.loadMessageBundle(); @@ -13,22 +13,18 @@ const selectZoomLevelCommandId = '_imagePreview.selectZoomLevel'; export type Scale = number | 'fit'; -export class ZoomStatusBarEntry extends Disposable { - private readonly _entry: vscode.StatusBarItem; +export class ZoomStatusBarEntry extends OwnedStatusBarEntry { private readonly _onDidChangeScale = this._register(new vscode.EventEmitter<{ scale: Scale }>()); public readonly onDidChangeScale = this._onDidChangeScale.event; - private _showOwner: string | undefined; - constructor() { - super(); - this._entry = this._register(vscode.window.createStatusBarItem({ + super({ id: 'imagePreview.zoom', name: localize('zoomStatusBar.name', "Image Zoom"), alignment: vscode.StatusBarAlignment.Right, priority: 102 /* to the left of editor size entry (101) */, - })); + }); this._register(vscode.commands.registerCommand(selectZoomLevelCommandId, async () => { type MyPickItem = vscode.QuickPickItem & { scale: Scale }; @@ -47,20 +43,11 @@ export class ZoomStatusBarEntry extends Disposable { } })); - this._entry.command = selectZoomLevelCommandId; + this.entry.command = selectZoomLevelCommandId; } public show(owner: string, scale: Scale) { - this._showOwner = owner; - this._entry.text = this.zoomLabel(scale); - this._entry.show(); - } - - public hide(owner: string) { - if (owner === this._showOwner) { - this._entry.hide(); - this._showOwner = undefined; - } + this.showItem(owner, this.zoomLabel(scale)); } private zoomLabel(scale: Scale): string { diff --git a/extensions/import/package.json b/extensions/import/package.json index f3b0fb56af..1ba9d05717 100644 --- a/extensions/import/package.json +++ b/extensions/import/package.json @@ -71,7 +71,7 @@ "vscode-nls": "^3.2.1" }, "devDependencies": { - "@types/node": "10" + "@types/node": "^12.11.7" }, "__metadata": { "id": "23", diff --git a/extensions/import/yarn.lock b/extensions/import/yarn.lock index 8866bfc1d3..b17a263ef5 100644 --- a/extensions/import/yarn.lock +++ b/extensions/import/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@types/node@10": - version "10.17.4" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.4.tgz#8993a4fe3c4022fda66bf4ea660d615fc5659c6f" - integrity sha512-F2pgg+LcIr/elguz+x+fdBX5KeZXGUOp7TV8M0TVIrDezYLFRNt8oMTyps0VQ1kj5WGGoR18RdxnRDHXrIFHMQ== +"@types/node@^12.11.7": + version "12.12.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.7.tgz#01e4ea724d9e3bd50d90c11fd5980ba317d8fa11" + integrity sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w== agent-base@4: version "4.2.1" diff --git a/extensions/integration-tests/.vscode/launch.json b/extensions/integration-tests/.vscode/launch.json index 73c3753c75..f858d00591 100644 --- a/extensions/integration-tests/.vscode/launch.json +++ b/extensions/integration-tests/.vscode/launch.json @@ -14,4 +14,4 @@ "preLaunchTask": "npm" } ] -} \ No newline at end of file +} diff --git a/extensions/json-language-features/client/src/jsonMain.ts b/extensions/json-language-features/client/src/jsonMain.ts index eac6eaefe5..c51dbb7c0c 100644 --- a/extensions/json-language-features/client/src/jsonMain.ts +++ b/extensions/json-language-features/client/src/jsonMain.ts @@ -46,6 +46,7 @@ interface Settings { json?: { schemas?: JSONSchemaSettings[]; format?: { enable: boolean; }; + resultLimit?: number; }; http?: { proxy?: string; @@ -142,12 +143,6 @@ export function activate(context: ExtensionContext) { let disposable = client.start(); toDispose.push(disposable); client.onReady().then(() => { - disposable = client.onTelemetry(e => { - if (telemetryReporter) { - telemetryReporter.sendTelemetryEvent(e.key, e.data); - } - }); - const schemaDocuments: { [uri: string]: boolean } = {}; // handle content request @@ -161,11 +156,23 @@ export function activate(context: ExtensionContext) { return Promise.reject(error); }); } else { + if (telemetryReporter && uri.authority === 'schema.management.azure.com') { + /* __GDPR__ + "json.schema" : { + "schemaURL" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + telemetryReporter.sendTelemetryEvent('json.schema', { schemaURL: uriPath }); + } const headers = { 'Accept-Encoding': 'gzip, deflate' }; return xhr({ url: uriPath, followRedirects: 5, headers }).then(response => { return response.responseText; }, (error: XHRResponse) => { - return Promise.reject(new ResponseError(error.status, error.responseText || getErrorStatusDescription(error.status) || error.toString())); + let extraInfo = error.responseText || error.toString(); + if (extraInfo.length > 256) { + extraInfo = `${extraInfo.substr(0, 256)}...`; + } + return Promise.reject(new ResponseError(error.status, getErrorStatusDescription(error.status) + '\n' + extraInfo)); }); } }); @@ -320,6 +327,7 @@ function getSettings(): Settings { }, json: { schemas: [], + resultLimit: 5000 } }; let schemaSettingsById: { [schemaId: string]: JSONSchemaSettings } = Object.create(null); diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index cc61c9163c..833059f3c8 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -102,12 +102,12 @@ } }, "dependencies": { - "request-light": "^0.2.4", + "request-light": "^0.2.5", "vscode-extension-telemetry": "0.1.1", - "vscode-languageclient": "^6.0.0-next.1", + "vscode-languageclient": "^6.0.0-next.3", "vscode-nls": "^4.1.1" }, "devDependencies": { - "@types/node": "^10.14.8" + "@types/node": "^12.11.7" } } diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index f3ddc4293b..6dc132cc74 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -12,16 +12,15 @@ }, "main": "./out/jsonServerMain", "dependencies": { - "jsonc-parser": "^2.1.1", - "request-light": "^0.2.4", - "vscode-json-languageservice": "^3.3.5", - "vscode-languageserver": "^6.0.0-next.1", - "vscode-nls": "^4.1.1", - "vscode-uri": "^2.0.3" + "jsonc-parser": "^2.2.0", + "request-light": "^0.2.5", + "vscode-json-languageservice": "^3.4.7", + "vscode-languageserver": "^6.0.0-next.3", + "vscode-uri": "^2.1.1" }, "devDependencies": { "@types/mocha": "2.2.33", - "@types/node": "^10.14.8" + "@types/node": "^12.11.7" }, "scripts": { "prepublishOnly": "npm run clean && npm run compile", diff --git a/extensions/json-language-features/server/src/jsonServerMain.ts b/extensions/json-language-features/server/src/jsonServerMain.ts index 52427693a4..183ab87c8a 100644 --- a/extensions/json-language-features/server/src/jsonServerMain.ts +++ b/extensions/json-language-features/server/src/jsonServerMain.ts @@ -5,16 +5,18 @@ import { createConnection, IConnection, - TextDocuments, TextDocument, InitializeParams, InitializeResult, NotificationType, RequestType, - DocumentRangeFormattingRequest, Disposable, ServerCapabilities, Diagnostic + TextDocuments, InitializeParams, InitializeResult, NotificationType, RequestType, + DocumentRangeFormattingRequest, Disposable, ServerCapabilities, TextDocumentSyncKind } from 'vscode-languageserver'; import { xhr, XHRResponse, configure as configureHttpRequests, getErrorStatusDescription } from 'request-light'; import * as fs from 'fs'; import { URI } from 'vscode-uri'; import * as URL from 'url'; +import { posix } from 'path'; +import { setTimeout, clearTimeout } from 'timers'; import { formatError, runSafe, runSafeAsync } from './utils/runner'; -import { JSONDocument, JSONSchema, getLanguageService, DocumentLanguageSettings, SchemaConfiguration, ClientCapabilities } from 'vscode-json-languageservice'; +import { TextDocument, JSONDocument, JSONSchema, getLanguageService, DocumentLanguageSettings, SchemaConfiguration, ClientCapabilities, SchemaRequestService, Diagnostic } from 'vscode-json-languageservice'; import { getLanguageModelCache } from './languageModelCache'; interface ISchemaAssociations { @@ -56,40 +58,40 @@ const workspaceContext = { return URL.resolve(resource, relativePath); } }; -function getSchemaRequestService(handledSchemas: { [schema: string]: boolean }) { +const fileRequestService: SchemaRequestService = (uri: string) => { + const fsPath = URI.parse(uri).fsPath; + return new Promise((c, e) => { + fs.readFile(fsPath, 'UTF-8', (err, result) => { + err ? e(err.message || err.toString()) : c(result.toString()); + }); + }); +}; + +const httpRequestService: SchemaRequestService = (uri: string) => { + const headers = { 'Accept-Encoding': 'gzip, deflate' }; + return xhr({ url: uri, followRedirects: 5, headers }).then(response => { + return response.responseText; + }, (error: XHRResponse) => { + return Promise.reject(error.responseText || getErrorStatusDescription(error.status) || error.toString()); + }); +}; + +function getSchemaRequestService(handledSchemas: string[] = ['https', 'http', 'file']) { + const builtInHandlers: { [protocol: string]: SchemaRequestService } = {}; + for (let protocol of handledSchemas) { + if (protocol === 'file') { + builtInHandlers[protocol] = fileRequestService; + } else if (protocol === 'http' || protocol === 'https') { + builtInHandlers[protocol] = httpRequestService; + } + } return (uri: string): Thenable => { const protocol = uri.substr(0, uri.indexOf(':')); - if (!handledSchemas || handledSchemas[protocol]) { - if (protocol === 'file') { - const fsPath = URI.parse(uri).fsPath; - return new Promise((c, e) => { - fs.readFile(fsPath, 'UTF-8', (err, result) => { - err ? e(err.message || err.toString()) : c(result.toString()); - }); - }); - } else if (protocol === 'http' || protocol === 'https') { - if (uri.indexOf('//schema.management.azure.com/') !== -1) { - /* __GDPR__ - "json.schema" : { - "schemaURL" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - connection.telemetry.logEvent({ - key: 'json.schema', - value: { - schemaURL: uri - } - }); - } - const headers = { 'Accept-Encoding': 'gzip, deflate' }; - return xhr({ url: uri, followRedirects: 5, headers }).then(response => { - return response.responseText; - }, (error: XHRResponse) => { - return Promise.reject(error.responseText || getErrorStatusDescription(error.status) || error.toString()); - }); - } + const builtInHandler = builtInHandlers[protocol]; + if (builtInHandler) { + return builtInHandler(uri); } return connection.sendRequest(VSCodeContentRequest.type, uri).then(responseText => { return responseText; @@ -107,7 +109,7 @@ let languageService = getLanguageService({ }); // Create a text document manager. -const documents: TextDocuments = new TextDocuments(); +const documents = new TextDocuments(TextDocument); // Make the text document manager listen on the connection // for open, change and close text document events @@ -115,9 +117,12 @@ documents.listen(connection); let clientSnippetSupport = false; let dynamicFormatterRegistration = false; -let foldingRangeLimit = Number.MAX_VALUE; let hierarchicalDocumentSymbolSupport = false; +let foldingRangeLimitDefault = Number.MAX_VALUE; +let foldingRangeLimit = Number.MAX_VALUE; +let resultLimit = Number.MAX_VALUE; + // After the server has started the client sends an initialize request. The server receives // in the passed params the rootPath of the workspace plus the client capabilities. connection.onInitialize((params: InitializeParams): InitializeResult => { @@ -145,11 +150,10 @@ connection.onInitialize((params: InitializeParams): InitializeResult => { clientSnippetSupport = getClientCapability('textDocument.completion.completionItem.snippetSupport', false); dynamicFormatterRegistration = getClientCapability('textDocument.rangeFormatting.dynamicRegistration', false) && (typeof params.initializationOptions.provideFormatter !== 'boolean'); - foldingRangeLimit = getClientCapability('textDocument.foldingRange.rangeLimit', Number.MAX_VALUE); + foldingRangeLimitDefault = getClientCapability('textDocument.foldingRange.rangeLimit', Number.MAX_VALUE); hierarchicalDocumentSymbolSupport = getClientCapability('textDocument.documentSymbol.hierarchicalDocumentSymbolSupport', false); const capabilities: ServerCapabilities = { - // Tell the client that the server works in FULL text document sync mode - textDocumentSync: documents.syncKind, + textDocumentSync: TextDocumentSyncKind.Incremental, completionProvider: clientSnippetSupport ? { resolveProvider: true, triggerCharacters: ['"', ':'] } : undefined, hoverProvider: true, documentSymbolProvider: true, @@ -169,6 +173,7 @@ interface Settings { json: { schemas: JSONSchemaSettings[]; format: { enable: boolean; }; + resultLimit?: number; }; http: { proxy: string; @@ -182,6 +187,39 @@ interface JSONSchemaSettings { schema?: JSONSchema; } +namespace LimitExceededWarnings { + const pendingWarnings: { [uri: string]: { features: { [name: string]: string }; timeout?: NodeJS.Timeout; } } = {}; + + export function cancel(uri: string) { + const warning = pendingWarnings[uri]; + if (warning && warning.timeout) { + clearTimeout(warning.timeout); + delete pendingWarnings[uri]; + } + } + + export function onResultLimitExceeded(uri: string, resultLimit: number, name: string) { + return () => { + let warning = pendingWarnings[uri]; + if (warning) { + if (!warning.timeout) { + // already shown + return; + } + warning.features[name] = name; + warning.timeout.refresh(); + } else { + warning = { features: { [name]: name } }; + warning.timeout = setTimeout(() => { + connection.window.showInformationMessage(`${posix.basename(uri)}: For performance reasons, ${Object.keys(warning.features).join(' and ')} have been limited to ${resultLimit} items.`); + warning.timeout = undefined; + }, 2000); + pendingWarnings[uri] = warning; + } + }; + } +} + let jsonConfigurationSettings: JSONSchemaSettings[] | undefined = undefined; let schemaAssociations: ISchemaAssociations | undefined = undefined; let formatterRegistration: Thenable | null = null; @@ -194,6 +232,9 @@ connection.onDidChangeConfiguration((change) => { jsonConfigurationSettings = settings.json && settings.json.schemas; updateConfiguration(); + foldingRangeLimit = Math.trunc(Math.max(settings.json && settings.json.resultLimit || foldingRangeLimitDefault, 0)); + resultLimit = Math.trunc(Math.max(settings.json && settings.json.resultLimit || Number.MAX_VALUE, 0)); + // dynamically enable & disable the formatter if (dynamicFormatterRegistration) { const enableFormatter = settings && settings.json && settings.json.format && settings.json.format.enable; @@ -270,11 +311,13 @@ function updateConfiguration() { // The content of a text document has changed. This event is emitted // when the text document first opened or when its content has changed. documents.onDidChangeContent((change) => { + LimitExceededWarnings.cancel(change.document.uri); triggerValidation(change.document); }); // a document has closed: clear all diagnostics documents.onDidClose(event => { + LimitExceededWarnings.cancel(event.document.uri); cleanPendingValidation(event.document); connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] }); }); @@ -383,10 +426,11 @@ connection.onDocumentSymbol((documentSymbolParams, token) => { const document = documents.get(documentSymbolParams.textDocument.uri); if (document) { const jsonDocument = getJSONDocument(document); + const onResultLimitExceeded = LimitExceededWarnings.onResultLimitExceeded(document.uri, resultLimit, 'document symbols'); if (hierarchicalDocumentSymbolSupport) { - return languageService.findDocumentSymbols2(document, jsonDocument); + return languageService.findDocumentSymbols2(document, jsonDocument, { resultLimit, onResultLimitExceeded }); } else { - return languageService.findDocumentSymbols(document, jsonDocument); + return languageService.findDocumentSymbols(document, jsonDocument, { resultLimit, onResultLimitExceeded }); } } return []; @@ -407,8 +451,9 @@ connection.onDocumentColor((params, token) => { return runSafeAsync(async () => { const document = documents.get(params.textDocument.uri); if (document) { + const onResultLimitExceeded = LimitExceededWarnings.onResultLimitExceeded(document.uri, resultLimit, 'document colors'); const jsonDocument = getJSONDocument(document); - return languageService.findDocumentColors(document, jsonDocument); + return languageService.findDocumentColors(document, jsonDocument, { resultLimit, onResultLimitExceeded }); } return []; }, [], `Error while computing document colors for ${params.textDocument.uri}`, token); @@ -429,7 +474,8 @@ connection.onFoldingRanges((params, token) => { return runSafe(() => { const document = documents.get(params.textDocument.uri); if (document) { - return languageService.getFoldingRanges(document, { rangeLimit: foldingRangeLimit }); + const onRangeLimitExceeded = LimitExceededWarnings.onResultLimitExceeded(document.uri, foldingRangeLimit, 'folding ranges'); + return languageService.getFoldingRanges(document, { rangeLimit: foldingRangeLimit, onRangeLimitExceeded }); } return null; }, null, `Error while computing folding ranges for ${params.textDocument.uri}`, token); diff --git a/extensions/json-language-features/server/yarn.lock b/extensions/json-language-features/server/yarn.lock index 7cb33a9e77..f0fde79ab0 100644 --- a/extensions/json-language-features/server/yarn.lock +++ b/extensions/json-language-features/server/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.33.tgz#d79a0061ec270379f4d9e225f4096fb436669def" integrity sha1-15oAYewnA3n02eIl9AlvtDZmne8= -"@types/node@^10.14.8": - version "10.14.8" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9" - integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw== +"@types/node@^12.11.7": + version "12.11.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a" + integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA== agent-base@4: version "4.1.2" @@ -53,7 +53,7 @@ http-proxy-agent@^2.1.0: agent-base "4" debug "3.1.0" -https-proxy-agent@^2.2.1: +https-proxy-agent@^2.2.3: version "2.2.4" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== @@ -61,77 +61,77 @@ https-proxy-agent@^2.2.1: agent-base "^4.3.0" debug "^3.1.0" -jsonc-parser@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.1.1.tgz#83dc3d7a6e7186346b889b1280eefa04446c6d3e" - integrity sha512-VC0CjnWJylKB1iov4u76/W/5Ef0ydDkjtYWxoZ9t3HdWlSnZQwZL5MgFikaB/EtQ4RmMEw3tmQzuYnZA2/Ja1g== +jsonc-parser@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.0.tgz#f206f87f9d49d644b7502052c04e82dd6392e9ef" + integrity sha512-4fLQxW1j/5fWj6p78vAlAafoCKtuBm6ghv+Ij5W2DrDx0qE+ZdEl2c6Ko1mgJNF5ftX1iEWQQ4Ap7+3GlhjkOA== ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= -request-light@^0.2.4: - version "0.2.4" - resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.2.4.tgz#3cea29c126682e6bcadf7915353322eeba01a755" - integrity sha512-pM9Fq5jRnSb+82V7M97rp8FE9/YNeP2L9eckB4Szd7lyeclSIx02aIpPO/6e4m6Dy31+FBN/zkFMTd2HkNO3ow== +request-light@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.2.5.tgz#38a3da7b2e56f7af8cbba57e8a94930ee2380746" + integrity sha512-eBEh+GzJAftUnex6tcL6eV2JCifY0+sZMIUpUPOVXbs2nV5hla4ZMmO3icYKGuGVuQ2zHE9evh4OrRcH4iyYYw== dependencies: http-proxy-agent "^2.1.0" - https-proxy-agent "^2.2.1" - vscode-nls "^4.0.0" - -vscode-json-languageservice@^3.3.5: - version "3.3.5" - resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.3.5.tgz#e222e8391beeb23cfa40cf17fd57d1594d295fc7" - integrity sha512-Le6SG5aRdrRc5jVeVMRkYbGH9rrVaZHCW0Oa8zCFQ0T8viUud9qdZ29lSv5NPNLwTB8mn4pYucFyyEPM2YWvLA== - dependencies: - jsonc-parser "^2.1.1" - vscode-languageserver-types "^3.15.0-next.5" + https-proxy-agent "^2.2.3" vscode-nls "^4.1.1" - vscode-uri "^2.0.3" + +vscode-json-languageservice@^3.4.7: + version "3.4.7" + resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.4.7.tgz#8d85f3c1d46a1e58e9867d747552fb8c83d934fd" + integrity sha512-y3MN2+/yph3yoIHGmHu4ScYpm285L58XVvfGkd49xTQzLja4apxSbwzsYcP9QsqS0W7KuvoyiPhqksiudoMwjg== + dependencies: + jsonc-parser "^2.2.0" + vscode-languageserver-textdocument "^1.0.0-next.4" + vscode-languageserver-types "^3.15.0-next.6" + vscode-nls "^4.1.1" + vscode-uri "^2.1.0" vscode-jsonrpc@^5.0.0-next.2: version "5.0.0-next.2" resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-5.0.0-next.2.tgz#a44bc03f67069e53f8d8beb88b96c0cacbfefbca" integrity sha512-Q3/jabZUNviCG9hhF6hHWjhrABevPF9mv0aiE2j8BYCAP2k+aHTpjMyk+04MzaAqWYwXdQuZkLSbcYCCqbzJLg== -vscode-languageserver-protocol@^3.15.0-next.9: - version "3.15.0-next.9" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.0-next.9.tgz#e768256bd5b580b25bfbc8099bc03bc4c42ebf30" - integrity sha512-b9PAxouMmtsLEe8ZjbIMPb7wRWPhckGfgjwZLmp/dWnaAuRPYtY3lGO0/rNbLc3jKIqCVlnEyYVFKalzDAzj0g== +vscode-languageserver-protocol@^3.15.0-next.10: + version "3.15.0-next.10" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.0-next.10.tgz#f1382f0c270ae5d0c2c7e552483285fb75810914" + integrity sha512-TmbBhKrBoYNX+/pQGwoXmy2qlOfjGBUhwUGIzQoWpj8qtDzYuLof8bi19rGLZ9sVuSHh3anvIyVpGJEqT0QODQ== dependencies: vscode-jsonrpc "^5.0.0-next.2" - vscode-languageserver-types "^3.15.0-next.5" + vscode-languageserver-types "^3.15.0-next.6" -vscode-languageserver-types@^3.15.0-next.5: - version "3.15.0-next.5" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.0-next.5.tgz#863d711bf47b338ff5e63ae19fb20d4fcd4d713b" - integrity sha512-7hrELhTeWieUgex3+6692KjCkcmO/+V/bFItM5MHGcBotzwmjEuXjapLLYTYhIspuJ1ibRSik5MhX5YwLpsPiw== +vscode-languageserver-textdocument@^1.0.0-next.4: + version "1.0.0-next.4" + resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.0-next.4.tgz#8f7afdfe3e81411f57baaa29bb3214d1907160cd" + integrity sha512-LJ5WfoBO54nqinjlLJKnjoo2Im4bIvPJ8bFT7R0C84ZI36iK8M29ddslfe5jUeWNSTtCda7YuKdKsDIq38HpgA== -vscode-languageserver@^6.0.0-next.1: - version "6.0.0-next.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-6.0.0-next.1.tgz#4d71886d4a17d22eafc61b3a5fbf84e8e27c191f" - integrity sha512-LSF6bXoFeXfMPRNyqzI3yFX/kD2DzXBemqvyj1kDWNVraiWttm4xKF4YXsvJ7Z3s9sVt/Dpu3CFU3w61PGNZMg== +vscode-languageserver-types@^3.15.0-next.6: + version "3.15.0-next.6" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.0-next.6.tgz#7a990d00c39ad4e744335afb4cc422a3e687ff25" + integrity sha512-+4jfvmZ26oFMSX6EgPRB75PWHoT8pzyWuSSWk0erC4hTzmJq2gWxVLh20bZutZjMmiivawvPshtM3XZhX2SttA== + +vscode-languageserver@^6.0.0-next.3: + version "6.0.0-next.3" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-6.0.0-next.3.tgz#41e2fda6417939792f6a19fc19ecbb2f080e2072" + integrity sha512-Q6T+KwYuoXV9KRHD6x7RfTU13pV0xAX2BtcuvSC/LBCiVAnEIOe7jKZjzya+B9gDvSk4hpfvhPefy5IdQK1mpQ== dependencies: - vscode-languageserver-protocol "^3.15.0-next.9" - vscode-textbuffer "^1.0.0" - -vscode-nls@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002" - integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw== + vscode-languageserver-protocol "^3.15.0-next.10" vscode-nls@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c" integrity sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A== -vscode-textbuffer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/vscode-textbuffer/-/vscode-textbuffer-1.0.0.tgz#1faee638c8e0e4131c8d5c353993a1874acda086" - integrity sha512-zPaHo4urgpwsm+PrJWfNakolRpryNja18SUip/qIIsfhuEqEIPEXMxHOlFPjvDC4JgTaimkncNW7UMXRJTY6ow== +vscode-uri@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-2.1.0.tgz#475a4269e63edbc13914b40c84bc1416e3398156" + integrity sha512-3voe44nOhb6OdKlpZShVsmVvY2vFQHMe6REP3Ky9RVJuPyM/XidsjH6HncCIDdSmbcF5YQHrTC/Q+Q2loJGkOw== -vscode-uri@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-2.0.3.tgz#25e5f37f552fbee3cec7e5f80cef8469cefc6543" - integrity sha512-4D3DI3F4uRy09WNtDGD93H9q034OHImxiIcSq664Hq1Y1AScehlP3qqZyTkX/RWxeu0MRMHGkrxYqm2qlDF/aw== +vscode-uri@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-2.1.1.tgz#5aa1803391b6ebdd17d047f51365cf62c38f6e90" + integrity sha512-eY9jmGoEnVf8VE8xr5znSah7Qt1P/xsCdErz+g8HYZtJ7bZqKH5E3d+6oVNm1AC/c6IHUDokbmVXKOi4qPAC9A== diff --git a/extensions/json-language-features/yarn.lock b/extensions/json-language-features/yarn.lock index ae47010432..27e4f7cf88 100644 --- a/extensions/json-language-features/yarn.lock +++ b/extensions/json-language-features/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@types/node@^10.14.8": - version "10.14.8" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9" - integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw== +"@types/node@^12.11.7": + version "12.11.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a" + integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA== agent-base@4: version "4.2.1" @@ -76,7 +76,7 @@ http-proxy-agent@^2.1.0: agent-base "4" debug "3.1.0" -https-proxy-agent@^2.2.1: +https-proxy-agent@^2.2.3: version "2.2.4" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== @@ -94,14 +94,14 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== -request-light@^0.2.4: - version "0.2.4" - resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.2.4.tgz#3cea29c126682e6bcadf7915353322eeba01a755" - integrity sha512-pM9Fq5jRnSb+82V7M97rp8FE9/YNeP2L9eckB4Szd7lyeclSIx02aIpPO/6e4m6Dy31+FBN/zkFMTd2HkNO3ow== +request-light@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.2.5.tgz#38a3da7b2e56f7af8cbba57e8a94930ee2380746" + integrity sha512-eBEh+GzJAftUnex6tcL6eV2JCifY0+sZMIUpUPOVXbs2nV5hla4ZMmO3icYKGuGVuQ2zHE9evh4OrRcH4iyYYw== dependencies: http-proxy-agent "^2.1.0" - https-proxy-agent "^2.2.1" - vscode-nls "^4.0.0" + https-proxy-agent "^2.2.3" + vscode-nls "^4.1.1" semver@^5.3.0: version "5.5.0" @@ -125,31 +125,26 @@ vscode-jsonrpc@^5.0.0-next.2: resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-5.0.0-next.2.tgz#a44bc03f67069e53f8d8beb88b96c0cacbfefbca" integrity sha512-Q3/jabZUNviCG9hhF6hHWjhrABevPF9mv0aiE2j8BYCAP2k+aHTpjMyk+04MzaAqWYwXdQuZkLSbcYCCqbzJLg== -vscode-languageclient@^6.0.0-next.1: - version "6.0.0-next.1" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-6.0.0-next.1.tgz#deca1743afd20da092e04e40ef73cedbbd978455" - integrity sha512-eJ9VjLFNINArgRzLbQ11YlWry7dM93GEODkQBXTRfrSypksiO9qSGr4SHhWgxxP26p4FRSpzc/17+N+Egnnchg== +vscode-languageclient@^6.0.0-next.3: + version "6.0.0-next.3" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-6.0.0-next.3.tgz#41b701d963fc7affc01e9279532a747fcd4f3810" + integrity sha512-SuSaG9xjqkROm4Ie0jQig0CFDuU/WxHERegl3kRsFHDbhMSK4dH45ZeBY5zMWUgZ+LrIrEbwf8qWNlrTRBlUgg== dependencies: semver "^6.3.0" - vscode-languageserver-protocol "^3.15.0-next.9" + vscode-languageserver-protocol "^3.15.0-next.10" -vscode-languageserver-protocol@^3.15.0-next.9: - version "3.15.0-next.9" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.0-next.9.tgz#e768256bd5b580b25bfbc8099bc03bc4c42ebf30" - integrity sha512-b9PAxouMmtsLEe8ZjbIMPb7wRWPhckGfgjwZLmp/dWnaAuRPYtY3lGO0/rNbLc3jKIqCVlnEyYVFKalzDAzj0g== +vscode-languageserver-protocol@^3.15.0-next.10: + version "3.15.0-next.10" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.0-next.10.tgz#f1382f0c270ae5d0c2c7e552483285fb75810914" + integrity sha512-TmbBhKrBoYNX+/pQGwoXmy2qlOfjGBUhwUGIzQoWpj8qtDzYuLof8bi19rGLZ9sVuSHh3anvIyVpGJEqT0QODQ== dependencies: vscode-jsonrpc "^5.0.0-next.2" - vscode-languageserver-types "^3.15.0-next.5" + vscode-languageserver-types "^3.15.0-next.6" -vscode-languageserver-types@^3.15.0-next.5: - version "3.15.0-next.5" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.0-next.5.tgz#863d711bf47b338ff5e63ae19fb20d4fcd4d713b" - integrity sha512-7hrELhTeWieUgex3+6692KjCkcmO/+V/bFItM5MHGcBotzwmjEuXjapLLYTYhIspuJ1ibRSik5MhX5YwLpsPiw== - -vscode-nls@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002" - integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw== +vscode-languageserver-types@^3.15.0-next.6: + version "3.15.0-next.6" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.0-next.6.tgz#7a990d00c39ad4e744335afb4cc422a3e687ff25" + integrity sha512-+4jfvmZ26oFMSX6EgPRB75PWHoT8pzyWuSSWk0erC4hTzmJq2gWxVLh20bZutZjMmiivawvPshtM3XZhX2SttA== vscode-nls@^4.1.1: version "4.1.1" diff --git a/extensions/json/syntaxes/JSON.tmLanguage.json b/extensions/json/syntaxes/JSON.tmLanguage.json index d296aac33e..910045be39 100644 --- a/extensions/json/syntaxes/JSON.tmLanguage.json +++ b/extensions/json/syntaxes/JSON.tmLanguage.json @@ -5,7 +5,7 @@ "Once accepted there, we are happy to receive an update request." ], "version": "https://github.com/Microsoft/vscode-JSON.tmLanguage/commit/9bd83f1c252b375e957203f21793316203f61f70", - "name": "JSON (JavaScript Next)", + "name": "JSON (Javascript Next)", "scopeName": "source.json", "patterns": [ { @@ -210,4 +210,4 @@ ] } } -} +} \ No newline at end of file diff --git a/extensions/liveshare/package.json b/extensions/liveshare/package.json index 1f2b11c96d..f0daa36908 100644 --- a/extensions/liveshare/package.json +++ b/extensions/liveshare/package.json @@ -21,7 +21,7 @@ "compile": "gulp compile-extension:liveshare" }, "devDependencies": { - "@types/node": "12.0.9", + "@types/node": "^12.11.7", "ts-loader": "^5.3.3", "tslint": "^5.12.1", "typescript": "^3.3.1" diff --git a/extensions/liveshare/yarn.lock b/extensions/liveshare/yarn.lock index d01bdeabdc..aa202a3d63 100644 --- a/extensions/liveshare/yarn.lock +++ b/extensions/liveshare/yarn.lock @@ -18,10 +18,10 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@types/node@12.0.9": - version "12.0.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.9.tgz#18e0fc7bc6acc71f43a1a6ec9096c30d3954dd5c" - integrity sha512-xxrghIb6jMoEkNtdzGMUezwCEGuBd4QSA/Fko1XaUYpn6P/LwVw7UGpf4NzwGZXRC96fDgBJcBX7bXU0T52nWA== +"@types/node@^12.11.7": + version "12.12.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.7.tgz#01e4ea724d9e3bd50d90c11fd5980ba317d8fa11" + integrity sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w== ansi-styles@^3.2.1: version "3.2.1" diff --git a/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json b/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json index 269140f643..477fff2313 100644 --- a/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json +++ b/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/46724e2885f9557400ed91727d75c3574ceded3a", + "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/e3091a421bdcad527018c897652ded47585cbd12", "name": "Markdown", "scopeName": "text.html.markdown", "patterns": [ @@ -63,7 +63,7 @@ "while": "(^|\\G)\\s*(>) ?" }, "fenced_code_block_css": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(css|css.erb)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(css|css.erb)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -96,7 +96,7 @@ ] }, "fenced_code_block_basic": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(html|htm|shtml|xhtml|inc|tmpl|tpl)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(html|htm|shtml|xhtml|inc|tmpl|tpl)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -129,7 +129,7 @@ ] }, "fenced_code_block_ini": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(ini|conf)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(ini|conf)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -162,7 +162,7 @@ ] }, "fenced_code_block_java": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(java|bsh)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(java|bsh)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -195,7 +195,7 @@ ] }, "fenced_code_block_lua": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(lua)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(lua)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -228,7 +228,7 @@ ] }, "fenced_code_block_makefile": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(Makefile|makefile|GNUmakefile|OCamlMakefile)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(Makefile|makefile|GNUmakefile|OCamlMakefile)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -261,7 +261,7 @@ ] }, "fenced_code_block_perl": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(perl|pl|pm|pod|t|PL|psgi|vcl)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(perl|pl|pm|pod|t|PL|psgi|vcl)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -294,7 +294,7 @@ ] }, "fenced_code_block_r": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(R|r|s|S|Rprofile|\\{\\.r.+?\\})(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(R|r|s|S|Rprofile|\\{\\.r.+?\\})((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -327,7 +327,7 @@ ] }, "fenced_code_block_ruby": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(ruby|rb|rbx|rjs|Rakefile|rake|cgi|fcgi|gemspec|irbrc|Capfile|ru|prawn|Cheffile|Gemfile|Guardfile|Hobofile|Vagrantfile|Appraisals|Rantfile|Berksfile|Berksfile.lock|Thorfile|Puppetfile)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(ruby|rb|rbx|rjs|Rakefile|rake|cgi|fcgi|gemspec|irbrc|Capfile|ru|prawn|Cheffile|Gemfile|Guardfile|Hobofile|Vagrantfile|Appraisals|Rantfile|Berksfile|Berksfile.lock|Thorfile|Puppetfile)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -360,7 +360,7 @@ ] }, "fenced_code_block_php": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(php|php3|php4|php5|phpt|phtml|aw|ctp)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(php|php3|php4|php5|phpt|phtml|aw|ctp)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -396,7 +396,7 @@ ] }, "fenced_code_block_sql": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(sql|ddl|dml)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(sql|ddl|dml)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -429,7 +429,7 @@ ] }, "fenced_code_block_vs_net": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(vb)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(vb)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -462,7 +462,7 @@ ] }, "fenced_code_block_xml": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(xml|xsd|tld|jsp|pt|cpt|dtml|rss|opml)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(xml|xsd|tld|jsp|pt|cpt|dtml|rss|opml)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -495,7 +495,7 @@ ] }, "fenced_code_block_xsl": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(xsl|xslt)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(xsl|xslt)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -528,7 +528,7 @@ ] }, "fenced_code_block_yaml": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(yaml|yml)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(yaml|yml)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -561,7 +561,7 @@ ] }, "fenced_code_block_dosbatch": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(bat|batch)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(bat|batch)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -594,7 +594,7 @@ ] }, "fenced_code_block_clojure": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(clj|cljs|clojure)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(clj|cljs|clojure)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -627,7 +627,7 @@ ] }, "fenced_code_block_coffee": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(coffee|Cakefile|coffee.erb)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(coffee|Cakefile|coffee.erb)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -660,7 +660,7 @@ ] }, "fenced_code_block_c": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(c|h)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(c|h)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -693,7 +693,7 @@ ] }, "fenced_code_block_cpp": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(cpp|c\\+\\+|cxx)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(cpp|c\\+\\+|cxx)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -726,7 +726,7 @@ ] }, "fenced_code_block_diff": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(patch|diff|rej)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(patch|diff|rej)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -759,7 +759,7 @@ ] }, "fenced_code_block_dockerfile": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(dockerfile|Dockerfile)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(dockerfile|Dockerfile)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -792,7 +792,7 @@ ] }, "fenced_code_block_git_commit": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(COMMIT_EDITMSG|MERGE_MSG)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(COMMIT_EDITMSG|MERGE_MSG)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -825,7 +825,7 @@ ] }, "fenced_code_block_git_rebase": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(git-rebase-todo)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(git-rebase-todo)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -858,7 +858,7 @@ ] }, "fenced_code_block_go": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(go|golang)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(go|golang)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -891,7 +891,7 @@ ] }, "fenced_code_block_groovy": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(groovy|gvy)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(groovy|gvy)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -924,7 +924,7 @@ ] }, "fenced_code_block_pug": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(jade|pug)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(jade|pug)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -957,7 +957,7 @@ ] }, "fenced_code_block_js": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(js|jsx|javascript|es6|mjs|\\{\\.js.+?\\})(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(js|jsx|javascript|es6|mjs|cjs|\\{\\.js.+?\\})((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -990,7 +990,7 @@ ] }, "fenced_code_block_js_regexp": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(regexp)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(regexp)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1023,7 +1023,7 @@ ] }, "fenced_code_block_json": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(json|json5|sublime-settings|sublime-menu|sublime-keymap|sublime-mousemap|sublime-theme|sublime-build|sublime-project|sublime-completions)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(json|json5|sublime-settings|sublime-menu|sublime-keymap|sublime-mousemap|sublime-theme|sublime-build|sublime-project|sublime-completions)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1056,7 +1056,7 @@ ] }, "fenced_code_block_jsonc": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(jsonc)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(jsonc)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1089,7 +1089,7 @@ ] }, "fenced_code_block_less": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(less)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(less)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1122,7 +1122,7 @@ ] }, "fenced_code_block_objc": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(objectivec|objective-c|mm|objc|obj-c|m|h)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(objectivec|objective-c|mm|objc|obj-c|m|h)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1155,7 +1155,7 @@ ] }, "fenced_code_block_swift": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(swift)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(swift)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1188,7 +1188,7 @@ ] }, "fenced_code_block_scss": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(scss)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(scss)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1221,7 +1221,7 @@ ] }, "fenced_code_block_perl6": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(perl6|p6|pl6|pm6|nqp)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(perl6|p6|pl6|pm6|nqp)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1254,7 +1254,7 @@ ] }, "fenced_code_block_powershell": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(powershell|ps1|psm1|psd1)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(powershell|ps1|psm1|psd1)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1287,7 +1287,7 @@ ] }, "fenced_code_block_python": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(python|py|py3|rpy|pyw|cpy|SConstruct|Sconstruct|sconstruct|SConscript|gyp|gypi|\\{\\.python.+?\\})(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(python|py|py3|rpy|pyw|cpy|SConstruct|Sconstruct|sconstruct|SConscript|gyp|gypi|\\{\\.python.+?\\})((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1320,7 +1320,7 @@ ] }, "fenced_code_block_regexp_python": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(re)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(re)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1353,7 +1353,7 @@ ] }, "fenced_code_block_rust": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(rust|rs|\\{\\.rust.+?\\})(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(rust|rs|\\{\\.rust.+?\\})((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1386,7 +1386,7 @@ ] }, "fenced_code_block_scala": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(scala|sbt)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(scala|sbt)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1419,7 +1419,7 @@ ] }, "fenced_code_block_shell": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(shell|sh|bash|zsh|bashrc|bash_profile|bash_login|profile|bash_logout|.textmate_init|\\{\\.bash.+?\\})(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(shell|sh|bash|zsh|bashrc|bash_profile|bash_login|profile|bash_logout|.textmate_init|\\{\\.bash.+?\\})((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1452,7 +1452,7 @@ ] }, "fenced_code_block_ts": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(typescript|ts)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(typescript|ts)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1485,7 +1485,7 @@ ] }, "fenced_code_block_tsx": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(tsx)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(tsx)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1518,7 +1518,7 @@ ] }, "fenced_code_block_csharp": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(cs|csharp|c#)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(cs|csharp|c#)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1551,7 +1551,7 @@ ] }, "fenced_code_block_fsharp": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(fs|fsharp|f#)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(fs|fsharp|f#)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1584,7 +1584,7 @@ ] }, "fenced_code_block_dart": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(dart)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(dart)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1617,7 +1617,7 @@ ] }, "fenced_code_block_handlebars": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(handlebars|hbs)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(handlebars|hbs)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1650,7 +1650,7 @@ ] }, "fenced_code_block_markdown": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(markdown|md)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(markdown|md)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1683,7 +1683,7 @@ ] }, "fenced_code_block_log": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(log)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(log)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -2623,4 +2623,4 @@ "name": "markup.inline.raw.string.markdown" } } -} \ No newline at end of file +} diff --git a/extensions/markdown-language-features/media/index.js b/extensions/markdown-language-features/media/index.js index a274cb72d8..1aa7530d34 100644 --- a/extensions/markdown-language-features/media/index.js +++ b/extensions/markdown-language-features/media/index.js @@ -1,2 +1,2 @@ -!function(e){var t={};function n(o){if(t[o])return t[o].exports;var i=t[o]={i:o,l:!1,exports:{}};return e[o].call(i.exports,i,i.exports,n),i.l=!0,i.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:o})},n.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=11)}([function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});let o=void 0;function i(e){const t=document.getElementById("vscode-markdown-preview-data");if(t){const n=t.getAttribute(e);if(n)return JSON.parse(n)}throw new Error(`Could not load data for ${e}`)}t.getData=i,t.getSettings=function(){if(o)return o;if(o=i("data-settings"))return o;throw new Error("Could not load settings")}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(0);function i(e){return t=0,n=o.getSettings().lineCount-1,i=e,Math.min(n,Math.max(t,i));var t,n,i}const r=(()=>{let e;return()=>{if(!e){e=[{element:document.body,line:0}];for(const t of document.getElementsByClassName("code-line")){const n=+t.getAttribute("data-line");isNaN(n)||e.push({element:t,line:n})}}return e}})();function s(e){const t=Math.floor(e),n=r();let o=n[0]||null;for(const e of n){if(e.line===t)return{previous:e,next:void 0};if(e.line>t)return{previous:o,next:e};o=e}return{previous:o}}function c(e){const t=r(),n=e-window.scrollY;let o=-1,i=t.length-1;for(;o+1=n?i=e:o=e}const s=t[i],c=s.element.getBoundingClientRect();if(i>=1&&c.top>n){return{previous:t[o],next:s}}return{previous:s}}t.getElementsForSourceLine=s,t.getLineElementsAtPageOffset=c,t.scrollToRevealSourceLine=function(e){if(!o.getSettings().scrollPreviewWithEditor)return;if(e<=0)return void window.scroll(window.scrollX,0);const{previous:t,next:n}=s(e);if(!t)return;let i=0;const r=t.element.getBoundingClientRect(),c=r.top;if(n&&n.line!==t.line)i=c+(e-t.line)/(n.line-t.line)*(n.element.getBoundingClientRect().top-c);else{const t=e-Math.floor(e);i=c+r.height*t}window.scroll(window.scrollX,Math.max(1,window.scrollY+i))},t.getEditorLineNumberForPageOffset=function(e){const{previous:t,next:n}=c(e);if(t){const o=t.element.getBoundingClientRect(),r=e-window.scrollY-o.top;if(n){const e=r/(n.element.getBoundingClientRect().top-o.top);return i(t.line+e*(n.line-t.line))}{const e=r/o.height;return i(t.line+e)}}return null},t.getLineElementForFragment=function(e){return r().find(t=>t.element.id===e)}},,,,,function(e,t){var n;n=function(){return this}();try{n=n||Function("return this")()||(0,eval)("this")}catch(e){"object"==typeof window&&(n=window)}e.exports=n},function(e,t,n){(function(t){var n="Expected a function",o=NaN,i="[object Symbol]",r=/^\s+|\s+$/g,s=/^[-+]0x[0-9a-f]+$/i,c=/^0b[01]+$/i,a=/^0o[0-7]+$/i,u=parseInt,l="object"==typeof t&&t&&t.Object===Object&&t,d="object"==typeof self&&self&&self.Object===Object&&self,f=l||d||Function("return this")(),g=Object.prototype.toString,p=Math.max,m=Math.min,v=function(){return f.Date.now()};function h(e,t,o){var i,r,s,c,a,u,l=0,d=!1,f=!1,g=!0;if("function"!=typeof e)throw new TypeError(n);function h(t){var n=i,o=r;return i=r=void 0,l=t,c=e.apply(o,n)}function y(e){var n=e-u;return void 0===u||n>=t||n<0||f&&e-l>=s}function E(){var e=v();if(y(e))return L(e);a=setTimeout(E,function(e){var n=t-(e-u);return f?m(n,s-(e-l)):n}(e))}function L(e){return a=void 0,g&&i?h(e):(i=r=void 0,c)}function M(){var e=v(),n=y(e);if(i=arguments,r=this,u=e,n){if(void 0===a)return function(e){return l=e,a=setTimeout(E,t),d?h(e):c}(u);if(f)return a=setTimeout(E,t),h(u)}return void 0===a&&(a=setTimeout(E,t)),c}return t=b(t)||0,w(o)&&(d=!!o.leading,s=(f="maxWait"in o)?p(b(o.maxWait)||0,t):s,g="trailing"in o?!!o.trailing:g),M.cancel=function(){void 0!==a&&clearTimeout(a),l=0,i=u=r=a=void 0},M.flush=function(){return void 0===a?c:L(v())},M}function w(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}function b(e){if("number"==typeof e)return e;if(function(e){return"symbol"==typeof e||function(e){return!!e&&"object"==typeof e}(e)&&g.call(e)==i}(e))return o;if(w(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=w(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=e.replace(r,"");var n=c.test(e);return n||a.test(e)?u(e.slice(2),n?2:8):s.test(e)?o:+e}e.exports=function(e,t,o){var i=!0,r=!0;if("function"!=typeof e)throw new TypeError(n);return w(o)&&(i="leading"in o?!!o.leading:i,r="trailing"in o?!!o.trailing:r),h(e,t,{leading:i,maxWait:t,trailing:r})}}).call(this,n(6))},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(0);t.createPosterForVsCode=(e=>new class{postMessage(t,n){e.postMessage({type:t,source:o.getSettings().source,body:n})}})},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.onceDocumentLoaded=function(e){"loading"===document.readyState||"uninitialized"===document.readyState?document.addEventListener("DOMContentLoaded",e):e()}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(1);t.ActiveLineMarker=class{onDidChangeTextEditorSelection(e){const{previous:t}=o.getElementsForSourceLine(e);this._update(t&&t.element)}_update(e){this._unmarkActiveElement(this._current),this._markActiveElement(e),this._current=e}_unmarkActiveElement(e){e&&(e.className=e.className.replace(/\bcode-active-line\b/g,""))}_markActiveElement(e){e&&(e.className+=" code-active-line")}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(10),i=n(9),r=n(8),s=n(1),c=n(0),a=n(7);let u=!0;const l=new o.ActiveLineMarker,d=c.getSettings(),f=acquireVsCodeApi();let g=c.getData("data-state");f.setState(g);const p=r.createPosterForVsCode(f);window.cspAlerter.setPoster(p),window.styleLoadingMonitor.setPoster(p),window.onload=(()=>{v()}),i.onceDocumentLoaded(()=>{d.scrollPreviewWithEditor&&setTimeout(()=>{if(g.fragment){const e=s.getLineElementForFragment(g.fragment);e&&(u=!0,s.scrollToRevealSourceLine(e.line))}else{const e=+d.line;isNaN(e)||(u=!0,s.scrollToRevealSourceLine(e))}},0)});const m=(()=>{const e=a(e=>{u=!0,s.scrollToRevealSourceLine(e)},50);return(t,n)=>{isNaN(t)||(n.line=t,e(t))}})();let v=a(()=>{const e=[];let t=document.getElementsByTagName("img");if(t){let n;for(n=0;n{u=!0,v()},!0),window.addEventListener("message",e=>{if(e.data.source===d.source)switch(e.data.type){case"onDidChangeTextEditorSelection":l.onDidChangeTextEditorSelection(e.data.line);break;case"updateView":m(e.data.line,d)}},!1),document.addEventListener("dblclick",e=>{if(!d.doubleClickToSwitchToEditor)return;for(let t=e.target;t;t=t.parentNode)if("A"===t.tagName)return;const t=e.pageY,n=s.getEditorLineNumberForPageOffset(t);"number"!=typeof n||isNaN(n)||p.postMessage("didClick",{line:Math.floor(n)})});const h=["http:","https:","mailto:","vscode:","vscode-insiders"];document.addEventListener("click",e=>{if(!e)return;let t=e.target;for(;t;){if(t.tagName&&"A"===t.tagName&&t.href){if(t.getAttribute("href").startsWith("#"))return;if(h.some(e=>t.href.startsWith(e)))return;const n=t.getAttribute("data-href")||t.getAttribute("href");return/^[a-z\-]+:/i.test(n)?void 0:(p.postMessage("openLink",{href:n}),e.preventDefault(),void e.stopPropagation())}t=t.parentNode}},!0),window.addEventListener("scroll",a(()=>{if(u)u=!1;else{const e=s.getEditorLineNumberForPageOffset(window.scrollY);"number"!=typeof e||isNaN(e)||(p.postMessage("revealLine",{line:e}),g.line=e,f.setState(g))}},50))}]); -//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvc2V0dGluZ3MudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvc2Nyb2xsLXN5bmMudHMiLCJ3ZWJwYWNrOi8vLyh3ZWJwYWNrKS9idWlsZGluL2dsb2JhbC5qcyIsIndlYnBhY2s6Ly8vLi9ub2RlX21vZHVsZXMvbG9kYXNoLnRocm90dGxlL2luZGV4LmpzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL21lc3NhZ2luZy50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9ldmVudHMudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvYWN0aXZlTGluZU1hcmtlci50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9pbmRleC50cyJdLCJuYW1lcyI6WyJpbnN0YWxsZWRNb2R1bGVzIiwiX193ZWJwYWNrX3JlcXVpcmVfXyIsIm1vZHVsZUlkIiwiZXhwb3J0cyIsIm1vZHVsZSIsImkiLCJsIiwibW9kdWxlcyIsImNhbGwiLCJtIiwiYyIsImQiLCJuYW1lIiwiZ2V0dGVyIiwibyIsIk9iamVjdCIsImRlZmluZVByb3BlcnR5IiwiY29uZmlndXJhYmxlIiwiZW51bWVyYWJsZSIsImdldCIsInIiLCJ2YWx1ZSIsIm4iLCJfX2VzTW9kdWxlIiwib2JqZWN0IiwicHJvcGVydHkiLCJwcm90b3R5cGUiLCJoYXNPd25Qcm9wZXJ0eSIsInAiLCJzIiwiY2FjaGVkU2V0dGluZ3MiLCJ1bmRlZmluZWQiLCJnZXREYXRhIiwia2V5IiwiZWxlbWVudCIsImRvY3VtZW50IiwiZ2V0RWxlbWVudEJ5SWQiLCJkYXRhIiwiZ2V0QXR0cmlidXRlIiwiSlNPTiIsInBhcnNlIiwiRXJyb3IiLCJnZXRTZXR0aW5ncyIsInNldHRpbmdzXzEiLCJjbGFtcExpbmUiLCJsaW5lIiwibWluIiwibWF4IiwibGluZUNvdW50IiwiTWF0aCIsImdldENvZGVMaW5lRWxlbWVudHMiLCJlbGVtZW50cyIsImJvZHkiLCJnZXRFbGVtZW50c0J5Q2xhc3NOYW1lIiwiaXNOYU4iLCJwdXNoIiwiZ2V0RWxlbWVudHNGb3JTb3VyY2VMaW5lIiwidGFyZ2V0TGluZSIsImxpbmVOdW1iZXIiLCJmbG9vciIsImxpbmVzIiwicHJldmlvdXMiLCJlbnRyeSIsIm5leHQiLCJnZXRMaW5lRWxlbWVudHNBdFBhZ2VPZmZzZXQiLCJvZmZzZXQiLCJwb3NpdGlvbiIsIndpbmRvdyIsInNjcm9sbFkiLCJsbyIsImhpIiwibGVuZ3RoIiwibWlkIiwiYm91bmRzIiwiZ2V0Qm91bmRpbmdDbGllbnRSZWN0IiwidG9wIiwiaGVpZ2h0IiwiaGlFbGVtZW50IiwiaGlCb3VuZHMiLCJzY3JvbGxUb1JldmVhbFNvdXJjZUxpbmUiLCJzY3JvbGxQcmV2aWV3V2l0aEVkaXRvciIsInNjcm9sbCIsInNjcm9sbFgiLCJzY3JvbGxUbyIsInJlY3QiLCJwcmV2aW91c1RvcCIsInByb2dyZXNzSW5FbGVtZW50IiwiZ2V0RWRpdG9yTGluZU51bWJlckZvclBhZ2VPZmZzZXQiLCJwcmV2aW91c0JvdW5kcyIsIm9mZnNldEZyb21QcmV2aW91cyIsInByb2dyZXNzQmV0d2VlbkVsZW1lbnRzIiwicHJvZ3Jlc3NXaXRoaW5FbGVtZW50IiwiZ2V0TGluZUVsZW1lbnRGb3JGcmFnbWVudCIsImZyYWdtZW50IiwiZmluZCIsImlkIiwiZyIsInRoaXMiLCJGdW5jdGlvbiIsImV2YWwiLCJlIiwiZ2xvYmFsIiwiRlVOQ19FUlJPUl9URVhUIiwiTkFOIiwic3ltYm9sVGFnIiwicmVUcmltIiwicmVJc0JhZEhleCIsInJlSXNCaW5hcnkiLCJyZUlzT2N0YWwiLCJmcmVlUGFyc2VJbnQiLCJwYXJzZUludCIsImZyZWVHbG9iYWwiLCJmcmVlU2VsZiIsInNlbGYiLCJyb290Iiwib2JqZWN0VG9TdHJpbmciLCJ0b1N0cmluZyIsIm5hdGl2ZU1heCIsIm5hdGl2ZU1pbiIsIm5vdyIsIkRhdGUiLCJkZWJvdW5jZSIsImZ1bmMiLCJ3YWl0Iiwib3B0aW9ucyIsImxhc3RBcmdzIiwibGFzdFRoaXMiLCJtYXhXYWl0IiwicmVzdWx0IiwidGltZXJJZCIsImxhc3RDYWxsVGltZSIsImxhc3RJbnZva2VUaW1lIiwibGVhZGluZyIsIm1heGluZyIsInRyYWlsaW5nIiwiVHlwZUVycm9yIiwiaW52b2tlRnVuYyIsInRpbWUiLCJhcmdzIiwidGhpc0FyZyIsImFwcGx5Iiwic2hvdWxkSW52b2tlIiwidGltZVNpbmNlTGFzdENhbGwiLCJ0aW1lckV4cGlyZWQiLCJ0cmFpbGluZ0VkZ2UiLCJzZXRUaW1lb3V0IiwicmVtYWluaW5nV2FpdCIsImRlYm91bmNlZCIsImlzSW52b2tpbmciLCJhcmd1bWVudHMiLCJsZWFkaW5nRWRnZSIsInRvTnVtYmVyIiwiaXNPYmplY3QiLCJjYW5jZWwiLCJjbGVhclRpbWVvdXQiLCJmbHVzaCIsInR5cGUiLCJpc09iamVjdExpa2UiLCJpc1N5bWJvbCIsIm90aGVyIiwidmFsdWVPZiIsInJlcGxhY2UiLCJpc0JpbmFyeSIsInRlc3QiLCJzbGljZSIsImNyZWF0ZVBvc3RlckZvclZzQ29kZSIsInZzY29kZSIsIltvYmplY3QgT2JqZWN0XSIsInBvc3RNZXNzYWdlIiwic291cmNlIiwib25jZURvY3VtZW50TG9hZGVkIiwiZiIsInJlYWR5U3RhdGUiLCJhZGRFdmVudExpc3RlbmVyIiwic2Nyb2xsX3N5bmNfMSIsIkFjdGl2ZUxpbmVNYXJrZXIiLCJfdXBkYXRlIiwiYmVmb3JlIiwiX3VubWFya0FjdGl2ZUVsZW1lbnQiLCJfY3VycmVudCIsIl9tYXJrQWN0aXZlRWxlbWVudCIsImNsYXNzTmFtZSIsImFjdGl2ZUxpbmVNYXJrZXJfMSIsImV2ZW50c18xIiwibWVzc2FnaW5nXzEiLCJ0aHJvdHRsZSIsInNjcm9sbERpc2FibGVkIiwibWFya2VyIiwic2V0dGluZ3MiLCJhY3F1aXJlVnNDb2RlQXBpIiwic3RhdGUiLCJzZXRTdGF0ZSIsIm1lc3NhZ2luZyIsImNzcEFsZXJ0ZXIiLCJzZXRQb3N0ZXIiLCJzdHlsZUxvYWRpbmdNb25pdG9yIiwib25sb2FkIiwidXBkYXRlSW1hZ2VTaXplcyIsImluaXRpYWxMaW5lIiwib25VcGRhdGVWaWV3IiwiZG9TY3JvbGwiLCJpbWFnZUluZm8iLCJpbWFnZXMiLCJnZXRFbGVtZW50c0J5VGFnTmFtZSIsImltZyIsImNsYXNzTGlzdCIsImNvbnRhaW5zIiwicmVtb3ZlIiwid2lkdGgiLCJldmVudCIsIm9uRGlkQ2hhbmdlVGV4dEVkaXRvclNlbGVjdGlvbiIsImRvdWJsZUNsaWNrVG9Td2l0Y2hUb0VkaXRvciIsIm5vZGUiLCJ0YXJnZXQiLCJwYXJlbnROb2RlIiwidGFnTmFtZSIsInBhZ2VZIiwicGFzc1Rocm91Z2hMaW5rU2NoZW1lcyIsImhyZWYiLCJzdGFydHNXaXRoIiwic29tZSIsInNjaGVtZSIsImhyZWZUZXh0IiwicHJldmVudERlZmF1bHQiLCJzdG9wUHJvcGFnYXRpb24iXSwibWFwcGluZ3MiOiJhQUNBLElBQUFBLEtBR0EsU0FBQUMsRUFBQUMsR0FHQSxHQUFBRixFQUFBRSxHQUNBLE9BQUFGLEVBQUFFLEdBQUFDLFFBR0EsSUFBQUMsRUFBQUosRUFBQUUsSUFDQUcsRUFBQUgsRUFDQUksR0FBQSxFQUNBSCxZQVVBLE9BTkFJLEVBQUFMLEdBQUFNLEtBQUFKLEVBQUFELFFBQUFDLElBQUFELFFBQUFGLEdBR0FHLEVBQUFFLEdBQUEsRUFHQUYsRUFBQUQsUUFLQUYsRUFBQVEsRUFBQUYsRUFHQU4sRUFBQVMsRUFBQVYsRUFHQUMsRUFBQVUsRUFBQSxTQUFBUixFQUFBUyxFQUFBQyxHQUNBWixFQUFBYSxFQUFBWCxFQUFBUyxJQUNBRyxPQUFBQyxlQUFBYixFQUFBUyxHQUNBSyxjQUFBLEVBQ0FDLFlBQUEsRUFDQUMsSUFBQU4sS0FNQVosRUFBQW1CLEVBQUEsU0FBQWpCLEdBQ0FZLE9BQUFDLGVBQUFiLEVBQUEsY0FBaURrQixPQUFBLEtBSWpEcEIsRUFBQXFCLEVBQUEsU0FBQWxCLEdBQ0EsSUFBQVMsRUFBQVQsS0FBQW1CLFdBQ0EsV0FBMkIsT0FBQW5CLEVBQUEsU0FDM0IsV0FBaUMsT0FBQUEsR0FFakMsT0FEQUgsRUFBQVUsRUFBQUUsRUFBQSxJQUFBQSxHQUNBQSxHQUlBWixFQUFBYSxFQUFBLFNBQUFVLEVBQUFDLEdBQXNELE9BQUFWLE9BQUFXLFVBQUFDLGVBQUFuQixLQUFBZ0IsRUFBQUMsSUFHdER4QixFQUFBMkIsRUFBQSxHQUlBM0IsSUFBQTRCLEVBQUEsbUNDOURBZCxPQUFBQyxlQUFBYixFQUFBLGNBQThDa0IsT0FBQSxJQUM5QyxJQUFBUyxPQUFBQyxFQUNBLFNBQUFDLEVBQUFDLEdBQ0EsTUFBQUMsRUFBQUMsU0FBQUMsZUFBQSxnQ0FDQSxHQUFBRixFQUFBLENBQ0EsTUFBQUcsRUFBQUgsRUFBQUksYUFBQUwsR0FDQSxHQUFBSSxFQUNBLE9BQUFFLEtBQUFDLE1BQUFILEdBR0EsVUFBQUksaUNBQStDUixLQUUvQzlCLEVBQUE2QixVQVdBN0IsRUFBQXVDLFlBVkEsV0FDQSxHQUFBWixFQUNBLE9BQUFBLEVBR0EsR0FEQUEsRUFBQUUsRUFBQSxpQkFFQSxPQUFBRixFQUVBLFVBQUFXLE1BQUEsMERDckJBMUIsT0FBQUMsZUFBQWIsRUFBQSxjQUE4Q2tCLE9BQUEsSUFDOUMsTUFBQXNCLEVBQUExQyxFQUFBLEdBSUEsU0FBQTJDLEVBQUFDLEdBQ0EsT0FKQUMsRUFJQSxFQUpBQyxFQUlBSixFQUFBRCxjQUFBTSxVQUFBLEVBSkEzQixFQUlBd0IsRUFIQUksS0FBQUgsSUFBQUMsRUFBQUUsS0FBQUYsSUFBQUQsRUFBQXpCLElBREEsSUFBQXlCLEVBQUFDLEVBQUExQixFQU1BLE1BQUE2QixFQUFBLE1BQ0EsSUFBQUMsRUFDQSxXQUNBLElBQUFBLEVBQUEsQ0FDQUEsSUFBeUJqQixRQUFBQyxTQUFBaUIsS0FBQVAsS0FBQSxJQUN6QixVQUFBWCxLQUFBQyxTQUFBa0IsdUJBQUEsY0FDQSxNQUFBUixHQUFBWCxFQUFBSSxhQUFBLGFBQ0FnQixNQUFBVCxJQUNBTSxFQUFBSSxNQUFtQ3JCLFVBQUFXLFVBSW5DLE9BQUFNLElBWkEsR0FxQkEsU0FBQUssRUFBQUMsR0FDQSxNQUFBQyxFQUFBVCxLQUFBVSxNQUFBRixHQUNBRyxFQUFBVixJQUNBLElBQUFXLEVBQUFELEVBQUEsU0FDQSxVQUFBRSxLQUFBRixFQUFBLENBQ0EsR0FBQUUsRUFBQWpCLE9BQUFhLEVBQ0EsT0FBb0JHLFNBQUFDLEVBQUFDLFVBQUFoQyxHQUVwQixHQUFBK0IsRUFBQWpCLEtBQUFhLEVBQ0EsT0FBb0JHLFdBQUFFLEtBQUFELEdBRXBCRCxFQUFBQyxFQUVBLE9BQVlELFlBTVosU0FBQUcsRUFBQUMsR0FDQSxNQUFBTCxFQUFBVixJQUNBZ0IsRUFBQUQsRUFBQUUsT0FBQUMsUUFDQSxJQUFBQyxHQUFBLEVBQ0FDLEVBQUFWLEVBQUFXLE9BQUEsRUFDQSxLQUFBRixFQUFBLEVBQUFDLEdBQUEsQ0FDQSxNQUFBRSxFQUFBdkIsS0FBQVUsT0FBQVUsRUFBQUMsR0FBQSxHQUNBRyxFQUFBYixFQUFBWSxHQUFBdEMsUUFBQXdDLHdCQUNBRCxFQUFBRSxJQUFBRixFQUFBRyxRQUFBVixFQUNBSSxFQUFBRSxFQUdBSCxFQUFBRyxFQUdBLE1BQUFLLEVBQUFqQixFQUFBVSxHQUNBUSxFQUFBRCxFQUFBM0MsUUFBQXdDLHdCQUNBLEdBQUFKLEdBQUEsR0FBQVEsRUFBQUgsSUFBQVQsRUFBQSxDQUVBLE9BQWdCTCxTQURoQkQsRUFBQVMsR0FDZ0JOLEtBQUFjLEdBRWhCLE9BQVloQixTQUFBZ0IsR0F6QloxRSxFQUFBcUQsMkJBMkJBckQsRUFBQTZELDhCQStCQTdELEVBQUE0RSx5QkEzQkEsU0FBQWxDLEdBQ0EsSUFBQUYsRUFBQUQsY0FBQXNDLHdCQUNBLE9BRUEsR0FBQW5DLEdBQUEsRUFFQSxZQURBc0IsT0FBQWMsT0FBQWQsT0FBQWUsUUFBQSxHQUdBLE1BQUFyQixTQUFXQSxFQUFBRSxRQUFpQlAsRUFBQVgsR0FDNUIsSUFBQWdCLEVBQ0EsT0FFQSxJQUFBc0IsRUFBQSxFQUNBLE1BQUFDLEVBQUF2QixFQUFBM0IsUUFBQXdDLHdCQUNBVyxFQUFBRCxFQUFBVCxJQUNBLEdBQUFaLEtBQUFsQixPQUFBZ0IsRUFBQWhCLEtBSUFzQyxFQUFBRSxHQUZBeEMsRUFBQWdCLEVBQUFoQixPQUFBa0IsRUFBQWxCLEtBQUFnQixFQUFBaEIsT0FDQWtCLEVBQUE3QixRQUFBd0Msd0JBQUFDLElBQUFVLE9BR0EsQ0FDQSxNQUFBQyxFQUFBekMsRUFBQUksS0FBQVUsTUFBQWQsR0FDQXNDLEVBQUFFLEVBQUFELEVBQUFSLE9BQUFVLEVBRUFuQixPQUFBYyxPQUFBZCxPQUFBZSxRQUFBakMsS0FBQUYsSUFBQSxFQUFBb0IsT0FBQUMsUUFBQWUsS0FxQkFoRixFQUFBb0YsaUNBbEJBLFNBQUF0QixHQUNBLE1BQUFKLFNBQVdBLEVBQUFFLFFBQWlCQyxFQUFBQyxHQUM1QixHQUFBSixFQUFBLENBQ0EsTUFBQTJCLEVBQUEzQixFQUFBM0IsUUFBQXdDLHdCQUNBZSxFQUFBeEIsRUFBQUUsT0FBQUMsUUFBQW9CLEVBQUFiLElBQ0EsR0FBQVosRUFBQSxDQUNBLE1BQUEyQixFQUFBRCxHQUFBMUIsRUFBQTdCLFFBQUF3Qyx3QkFBQUMsSUFBQWEsRUFBQWIsS0FFQSxPQUFBL0IsRUFEQWlCLEVBQUFoQixLQUFBNkMsR0FBQTNCLEVBQUFsQixLQUFBZ0IsRUFBQWhCLE9BR0EsQ0FDQSxNQUFBOEMsRUFBQUYsRUFBQUQsRUFBQSxPQUVBLE9BQUE1QyxFQURBaUIsRUFBQWhCLEtBQUE4QyxJQUlBLGFBV0F4RixFQUFBeUYsMEJBTEEsU0FBQUMsR0FDQSxPQUFBM0MsSUFBQTRDLEtBQUE1RCxHQUNBQSxVQUFBNkQsS0FBQUYsdUJDcElBLElBQUFHLEVBR0FBLEVBQUEsV0FDQSxPQUFBQyxLQURBLEdBSUEsSUFFQUQsS0FBQUUsU0FBQSxjQUFBQSxLQUFBLEVBQUFDLE1BQUEsUUFDQyxNQUFBQyxHQUVELGlCQUFBakMsU0FBQTZCLEVBQUE3QixRQU9BL0QsRUFBQUQsUUFBQTZGLG9CQ25CQSxTQUFBSyxHQVVBLElBQUFDLEVBQUEsc0JBR0FDLEVBQUEsSUFHQUMsRUFBQSxrQkFHQUMsRUFBQSxhQUdBQyxFQUFBLHFCQUdBQyxFQUFBLGFBR0FDLEVBQUEsY0FHQUMsRUFBQUMsU0FHQUMsRUFBQSxpQkFBQVYsUUFBQXRGLGlCQUFBc0YsRUFHQVcsRUFBQSxpQkFBQUMsaUJBQUFsRyxpQkFBQWtHLEtBR0FDLEVBQUFILEdBQUFDLEdBQUFkLFNBQUEsY0FBQUEsR0FVQWlCLEVBUEFwRyxPQUFBVyxVQU9BMEYsU0FHQUMsRUFBQXBFLEtBQUFGLElBQ0F1RSxFQUFBckUsS0FBQUgsSUFrQkF5RSxFQUFBLFdBQ0EsT0FBQUwsRUFBQU0sS0FBQUQsT0F5REEsU0FBQUUsRUFBQUMsRUFBQUMsRUFBQUMsR0FDQSxJQUFBQyxFQUNBQyxFQUNBQyxFQUNBQyxFQUNBQyxFQUNBQyxFQUNBQyxFQUFBLEVBQ0FDLEdBQUEsRUFDQUMsR0FBQSxFQUNBQyxHQUFBLEVBRUEsc0JBQUFaLEVBQ0EsVUFBQWEsVUFBQWpDLEdBVUEsU0FBQWtDLEVBQUFDLEdBQ0EsSUFBQUMsRUFBQWIsRUFDQWMsRUFBQWIsRUFLQSxPQUhBRCxFQUFBQyxPQUFBL0YsRUFDQW9HLEVBQUFNLEVBQ0FULEVBQUFOLEVBQUFrQixNQUFBRCxFQUFBRCxHQXFCQSxTQUFBRyxFQUFBSixHQUNBLElBQUFLLEVBQUFMLEVBQUFQLEVBTUEsWUFBQW5HLElBQUFtRyxHQUFBWSxHQUFBbkIsR0FDQW1CLEVBQUEsR0FBQVQsR0FOQUksRUFBQU4sR0FNQUosRUFHQSxTQUFBZ0IsSUFDQSxJQUFBTixFQUFBbEIsSUFDQSxHQUFBc0IsRUFBQUosR0FDQSxPQUFBTyxFQUFBUCxHQUdBUixFQUFBZ0IsV0FBQUYsRUF6QkEsU0FBQU4sR0FDQSxJQUVBVCxFQUFBTCxHQUZBYyxFQUFBUCxHQUlBLE9BQUFHLEVBQUFmLEVBQUFVLEVBQUFELEdBSEFVLEVBQUFOLElBR0FILEVBb0JBa0IsQ0FBQVQsSUFHQSxTQUFBTyxFQUFBUCxHQUtBLE9BSkFSLE9BQUFsRyxFQUlBdUcsR0FBQVQsRUFDQVcsRUFBQUMsSUFFQVosRUFBQUMsT0FBQS9GLEVBQ0FpRyxHQWVBLFNBQUFtQixJQUNBLElBQUFWLEVBQUFsQixJQUNBNkIsRUFBQVAsRUFBQUosR0FNQSxHQUpBWixFQUFBd0IsVUFDQXZCLEVBQUE3QixLQUNBaUMsRUFBQU8sRUFFQVcsRUFBQSxDQUNBLFFBQUFySCxJQUFBa0csRUFDQSxPQXZFQSxTQUFBUSxHQU1BLE9BSkFOLEVBQUFNLEVBRUFSLEVBQUFnQixXQUFBRixFQUFBcEIsR0FFQVMsRUFBQUksRUFBQUMsR0FBQVQsRUFpRUFzQixDQUFBcEIsR0FFQSxHQUFBRyxFQUdBLE9BREFKLEVBQUFnQixXQUFBRixFQUFBcEIsR0FDQWEsRUFBQU4sR0FNQSxZQUhBbkcsSUFBQWtHLElBQ0FBLEVBQUFnQixXQUFBRixFQUFBcEIsSUFFQUssRUFJQSxPQXhHQUwsRUFBQTRCLEVBQUE1QixJQUFBLEVBQ0E2QixFQUFBNUIsS0FDQVEsSUFBQVIsRUFBQVEsUUFFQUwsR0FEQU0sRUFBQSxZQUFBVCxHQUNBUCxFQUFBa0MsRUFBQTNCLEVBQUFHLFVBQUEsRUFBQUosR0FBQUksRUFDQU8sRUFBQSxhQUFBVixNQUFBVSxZQWlHQWEsRUFBQU0sT0FuQ0EsZ0JBQ0ExSCxJQUFBa0csR0FDQXlCLGFBQUF6QixHQUVBRSxFQUFBLEVBQ0FOLEVBQUFLLEVBQUFKLEVBQUFHLE9BQUFsRyxHQStCQW9ILEVBQUFRLE1BNUJBLFdBQ0EsWUFBQTVILElBQUFrRyxFQUFBRCxFQUFBZ0IsRUFBQXpCLE1BNEJBNEIsRUEwRkEsU0FBQUssRUFBQW5JLEdBQ0EsSUFBQXVJLFNBQUF2SSxFQUNBLFFBQUFBLElBQUEsVUFBQXVJLEdBQUEsWUFBQUEsR0E0RUEsU0FBQUwsRUFBQWxJLEdBQ0Esb0JBQUFBLEVBQ0EsT0FBQUEsRUFFQSxHQWhDQSxTQUFBQSxHQUNBLHVCQUFBQSxHQXRCQSxTQUFBQSxHQUNBLFFBQUFBLEdBQUEsaUJBQUFBLEVBc0JBd0ksQ0FBQXhJLElBQUE4RixFQUFBM0csS0FBQWEsSUFBQW1GLEVBOEJBc0QsQ0FBQXpJLEdBQ0EsT0FBQWtGLEVBRUEsR0FBQWlELEVBQUFuSSxHQUFBLENBQ0EsSUFBQTBJLEVBQUEsbUJBQUExSSxFQUFBMkksUUFBQTNJLEVBQUEySSxVQUFBM0ksRUFDQUEsRUFBQW1JLEVBQUFPLEtBQUEsR0FBQUEsRUFFQSxvQkFBQTFJLEVBQ0EsV0FBQUEsT0FFQUEsSUFBQTRJLFFBQUF4RCxFQUFBLElBQ0EsSUFBQXlELEVBQUF2RCxFQUFBd0QsS0FBQTlJLEdBQ0EsT0FBQTZJLEdBQUF0RCxFQUFBdUQsS0FBQTlJLEdBQ0F3RixFQUFBeEYsRUFBQStJLE1BQUEsR0FBQUYsRUFBQSxLQUNBeEQsRUFBQXlELEtBQUE5SSxHQUFBa0YsR0FBQWxGLEVBR0FqQixFQUFBRCxRQTlJQSxTQUFBdUgsRUFBQUMsRUFBQUMsR0FDQSxJQUFBUSxHQUFBLEVBQ0FFLEdBQUEsRUFFQSxzQkFBQVosRUFDQSxVQUFBYSxVQUFBakMsR0FNQSxPQUpBa0QsRUFBQTVCLEtBQ0FRLEVBQUEsWUFBQVIsTUFBQVEsVUFDQUUsRUFBQSxhQUFBVixNQUFBVSxZQUVBYixFQUFBQyxFQUFBQyxHQUNBUyxVQUNBTCxRQUFBSixFQUNBVyw4RENqVEF2SCxPQUFBQyxlQUFBYixFQUFBLGNBQThDa0IsT0FBQSxJQUM5QyxNQUFBc0IsRUFBQTFDLEVBQUEsR0FDQUUsRUFBQWtLLHNCQUFBLENBQUFDLEdBQ0EsVUFDQUMsWUFBQVgsRUFBQXhHLEdBQ0FrSCxFQUFBRSxhQUNBWixPQUNBYSxPQUFBOUgsRUFBQUQsY0FBQStILE9BQ0FySCwwQ0NSQXJDLE9BQUFDLGVBQUFiLEVBQUEsY0FBOENrQixPQUFBLElBUzlDbEIsRUFBQXVLLG1CQVJBLFNBQUFDLEdBQ0EsWUFBQXhJLFNBQUF5SSxZQUFBLGtCQUFBekksU0FBQXlJLFdBQ0F6SSxTQUFBMEksaUJBQUEsbUJBQUFGLEdBR0FBLG1DQ1ZBNUosT0FBQUMsZUFBQWIsRUFBQSxjQUE4Q2tCLE9BQUEsSUFLOUMsTUFBQXlKLEVBQUE3SyxFQUFBLEdBd0JBRSxFQUFBNEssdUJBdEJBUiwrQkFBQTFILEdBQ0EsTUFBQWdCLFNBQWVBLEdBQVdpSCxFQUFBdEgseUJBQUFYLEdBQzFCb0QsS0FBQStFLFFBQUFuSCxLQUFBM0IsU0FFQXFJLFFBQUFVLEdBQ0FoRixLQUFBaUYscUJBQUFqRixLQUFBa0YsVUFDQWxGLEtBQUFtRixtQkFBQUgsR0FDQWhGLEtBQUFrRixTQUFBRixFQUVBVixxQkFBQXJJLEdBQ0FBLElBR0FBLEVBQUFtSixVQUFBbkosRUFBQW1KLFVBQUFwQixRQUFBLDZCQUVBTSxtQkFBQXJJLEdBQ0FBLElBR0FBLEVBQUFtSixXQUFBLHFEQ3RCQXRLLE9BQUFDLGVBQUFiLEVBQUEsY0FBOENrQixPQUFBLElBQzlDLE1BQUFpSyxFQUFBckwsRUFBQSxJQUNBc0wsRUFBQXRMLEVBQUEsR0FDQXVMLEVBQUF2TCxFQUFBLEdBQ0E2SyxFQUFBN0ssRUFBQSxHQUNBMEMsRUFBQTFDLEVBQUEsR0FDQXdMLEVBQUF4TCxFQUFBLEdBQ0EsSUFBQXlMLEdBQUEsRUFDQSxNQUFBQyxFQUFBLElBQUFMLEVBQUFQLGlCQUNBYSxFQUFBakosRUFBQUQsY0FDQTRILEVBQUF1QixtQkFFQSxJQUFBQyxFQUFBbkosRUFBQVgsUUFBQSxjQUNBc0ksRUFBQXlCLFNBQUFELEdBQ0EsTUFBQUUsRUFBQVIsRUFBQW5CLHNCQUFBQyxHQUNBbkcsT0FBQThILFdBQUFDLFVBQUFGLEdBQ0E3SCxPQUFBZ0ksb0JBQUFELFVBQUFGLEdBQ0E3SCxPQUFBaUksT0FBQSxNQUNBQyxNQUVBZCxFQUFBYixtQkFBQSxLQUNBa0IsRUFBQTVHLHlCQUNBaUUsV0FBQSxLQUVBLEdBQUE2QyxFQUFBakcsU0FBQSxDQUNBLE1BQUEzRCxFQUFBNEksRUFBQWxGLDBCQUFBa0csRUFBQWpHLFVBQ0EzRCxJQUNBd0osR0FBQSxFQUNBWixFQUFBL0YseUJBQUE3QyxFQUFBVyxXQUdBLENBQ0EsTUFBQXlKLEdBQUFWLEVBQUEvSSxLQUNBUyxNQUFBZ0osS0FDQVosR0FBQSxFQUNBWixFQUFBL0YseUJBQUF1SCxNQUdTLEtBR1QsTUFBQUMsRUFBQSxNQUNBLE1BQUFDLEVBQUFmLEVBQUE1SSxJQUNBNkksR0FBQSxFQUNBWixFQUFBL0YseUJBQUFsQyxJQUNLLElBQ0wsT0FBQUEsRUFBQStJLEtBQ0F0SSxNQUFBVCxLQUNBK0ksRUFBQS9JLE9BQ0EySixFQUFBM0osTUFSQSxHQVlBLElBQUF3SixFQUFBWixFQUFBLEtBQ0EsTUFBQWdCLEtBQ0EsSUFBQUMsRUFBQXZLLFNBQUF3SyxxQkFBQSxPQUNBLEdBQUFELEVBQUEsQ0FDQSxJQUFBck0sRUFDQSxJQUFBQSxFQUFBLEVBQW1CQSxFQUFBcU0sRUFBQW5JLE9BQW1CbEUsSUFBQSxDQUN0QyxNQUFBdU0sRUFBQUYsRUFBQXJNLEdBQ0F1TSxFQUFBQyxVQUFBQyxTQUFBLFlBQ0FGLEVBQUFDLFVBQUFFLE9BQUEsV0FFQU4sRUFBQWxKLE1BQ0F3QyxHQUFBNkcsRUFBQTdHLEdBQ0FuQixPQUFBZ0ksRUFBQWhJLE9BQ0FvSSxNQUFBSixFQUFBSSxRQUdBaEIsRUFBQXhCLFlBQUEsa0JBQUFpQyxLQUVDLElBQ0R0SSxPQUFBMEcsaUJBQUEsY0FDQWEsR0FBQSxFQUNBVyxNQUNDLEdBQ0RsSSxPQUFBMEcsaUJBQUEsVUFBQW9DLElBQ0EsR0FBQUEsRUFBQTVLLEtBQUFvSSxTQUFBbUIsRUFBQW5CLE9BR0EsT0FBQXdDLEVBQUE1SyxLQUFBdUgsTUFDQSxxQ0FDQStCLEVBQUF1QiwrQkFBQUQsRUFBQTVLLEtBQUFRLE1BQ0EsTUFDQSxpQkFDQTBKLEVBQUFVLEVBQUE1SyxLQUFBUSxLQUFBK0ksTUFHQyxHQUNEekosU0FBQTBJLGlCQUFBLFdBQUFvQyxJQUNBLElBQUFyQixFQUFBdUIsNEJBQ0EsT0FHQSxRQUFBQyxFQUFBSCxFQUFBSSxPQUFpQ0QsRUFBTUEsSUFBQUUsV0FDdkMsU0FBQUYsRUFBQUcsUUFDQSxPQUdBLE1BQUF0SixFQUFBZ0osRUFBQU8sTUFDQTNLLEVBQUFpSSxFQUFBdkYsaUNBQUF0QixHQUNBLGlCQUFBcEIsR0FBQVMsTUFBQVQsSUFDQW1KLEVBQUF4QixZQUFBLFlBQTJDM0gsS0FBQUksS0FBQVUsTUFBQWQsT0FHM0MsTUFBQTRLLEdBQUEsd0RBQ0F0TCxTQUFBMEksaUJBQUEsUUFBQW9DLElBQ0EsSUFBQUEsRUFDQSxPQUVBLElBQUFHLEVBQUFILEVBQUFJLE9BQ0EsS0FBQUQsR0FBQSxDQUNBLEdBQUFBLEVBQUFHLFNBQUEsTUFBQUgsRUFBQUcsU0FBQUgsRUFBQU0sS0FBQSxDQUNBLEdBQUFOLEVBQUE5SyxhQUFBLFFBQUFxTCxXQUFBLEtBQ0EsT0FHQSxHQUFBRixFQUFBRyxLQUFBQyxHQUFBVCxFQUFBTSxLQUFBQyxXQUFBRSxJQUNBLE9BRUEsTUFBQUMsRUFBQVYsRUFBQTlLLGFBQUEsY0FBQThLLEVBQUE5SyxhQUFBLFFBRUEsb0JBQUE2SCxLQUFBMkQsUUFNQSxHQUxBOUIsRUFBQXhCLFlBQUEsWUFBbURrRCxLQUFBSSxJQUNuRGIsRUFBQWMsc0JBQ0FkLEVBQUFlLG1CQUtBWixJQUFBRSxjQUVDLEdBQ0RuSixPQUFBMEcsaUJBQUEsU0FBQVksRUFBQSxLQUNBLEdBQUFDLEVBQ0FBLEdBQUEsTUFFQSxDQUNBLE1BQUE3SSxFQUFBaUksRUFBQXZGLGlDQUFBcEIsT0FBQUMsU0FDQSxpQkFBQXZCLEdBQUFTLE1BQUFULEtBQ0FtSixFQUFBeEIsWUFBQSxjQUFpRDNILFNBQ2pEaUosRUFBQWpKLE9BQ0F5SCxFQUFBeUIsU0FBQUQsTUFHQyIsImZpbGUiOiJpbmRleC5qcyIsInNvdXJjZXNDb250ZW50IjpbIiBcdC8vIFRoZSBtb2R1bGUgY2FjaGVcbiBcdHZhciBpbnN0YWxsZWRNb2R1bGVzID0ge307XG5cbiBcdC8vIFRoZSByZXF1aXJlIGZ1bmN0aW9uXG4gXHRmdW5jdGlvbiBfX3dlYnBhY2tfcmVxdWlyZV9fKG1vZHVsZUlkKSB7XG5cbiBcdFx0Ly8gQ2hlY2sgaWYgbW9kdWxlIGlzIGluIGNhY2hlXG4gXHRcdGlmKGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdKSB7XG4gXHRcdFx0cmV0dXJuIGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdLmV4cG9ydHM7XG4gXHRcdH1cbiBcdFx0Ly8gQ3JlYXRlIGEgbmV3IG1vZHVsZSAoYW5kIHB1dCBpdCBpbnRvIHRoZSBjYWNoZSlcbiBcdFx0dmFyIG1vZHVsZSA9IGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdID0ge1xuIFx0XHRcdGk6IG1vZHVsZUlkLFxuIFx0XHRcdGw6IGZhbHNlLFxuIFx0XHRcdGV4cG9ydHM6IHt9XG4gXHRcdH07XG5cbiBcdFx0Ly8gRXhlY3V0ZSB0aGUgbW9kdWxlIGZ1bmN0aW9uXG4gXHRcdG1vZHVsZXNbbW9kdWxlSWRdLmNhbGwobW9kdWxlLmV4cG9ydHMsIG1vZHVsZSwgbW9kdWxlLmV4cG9ydHMsIF9fd2VicGFja19yZXF1aXJlX18pO1xuXG4gXHRcdC8vIEZsYWcgdGhlIG1vZHVsZSBhcyBsb2FkZWRcbiBcdFx0bW9kdWxlLmwgPSB0cnVlO1xuXG4gXHRcdC8vIFJldHVybiB0aGUgZXhwb3J0cyBvZiB0aGUgbW9kdWxlXG4gXHRcdHJldHVybiBtb2R1bGUuZXhwb3J0cztcbiBcdH1cblxuXG4gXHQvLyBleHBvc2UgdGhlIG1vZHVsZXMgb2JqZWN0IChfX3dlYnBhY2tfbW9kdWxlc19fKVxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5tID0gbW9kdWxlcztcblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGUgY2FjaGVcbiBcdF9fd2VicGFja19yZXF1aXJlX18uYyA9IGluc3RhbGxlZE1vZHVsZXM7XG5cbiBcdC8vIGRlZmluZSBnZXR0ZXIgZnVuY3Rpb24gZm9yIGhhcm1vbnkgZXhwb3J0c1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5kID0gZnVuY3Rpb24oZXhwb3J0cywgbmFtZSwgZ2V0dGVyKSB7XG4gXHRcdGlmKCFfX3dlYnBhY2tfcmVxdWlyZV9fLm8oZXhwb3J0cywgbmFtZSkpIHtcbiBcdFx0XHRPYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgbmFtZSwge1xuIFx0XHRcdFx0Y29uZmlndXJhYmxlOiBmYWxzZSxcbiBcdFx0XHRcdGVudW1lcmFibGU6IHRydWUsXG4gXHRcdFx0XHRnZXQ6IGdldHRlclxuIFx0XHRcdH0pO1xuIFx0XHR9XG4gXHR9O1xuXG4gXHQvLyBkZWZpbmUgX19lc01vZHVsZSBvbiBleHBvcnRzXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLnIgPSBmdW5jdGlvbihleHBvcnRzKSB7XG4gXHRcdE9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCAnX19lc01vZHVsZScsIHsgdmFsdWU6IHRydWUgfSk7XG4gXHR9O1xuXG4gXHQvLyBnZXREZWZhdWx0RXhwb3J0IGZ1bmN0aW9uIGZvciBjb21wYXRpYmlsaXR5IHdpdGggbm9uLWhhcm1vbnkgbW9kdWxlc1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5uID0gZnVuY3Rpb24obW9kdWxlKSB7XG4gXHRcdHZhciBnZXR0ZXIgPSBtb2R1bGUgJiYgbW9kdWxlLl9fZXNNb2R1bGUgP1xuIFx0XHRcdGZ1bmN0aW9uIGdldERlZmF1bHQoKSB7IHJldHVybiBtb2R1bGVbJ2RlZmF1bHQnXTsgfSA6XG4gXHRcdFx0ZnVuY3Rpb24gZ2V0TW9kdWxlRXhwb3J0cygpIHsgcmV0dXJuIG1vZHVsZTsgfTtcbiBcdFx0X193ZWJwYWNrX3JlcXVpcmVfXy5kKGdldHRlciwgJ2EnLCBnZXR0ZXIpO1xuIFx0XHRyZXR1cm4gZ2V0dGVyO1xuIFx0fTtcblxuIFx0Ly8gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLm8gPSBmdW5jdGlvbihvYmplY3QsIHByb3BlcnR5KSB7IHJldHVybiBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwob2JqZWN0LCBwcm9wZXJ0eSk7IH07XG5cbiBcdC8vIF9fd2VicGFja19wdWJsaWNfcGF0aF9fXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLnAgPSBcIlwiO1xuXG5cbiBcdC8vIExvYWQgZW50cnkgbW9kdWxlIGFuZCByZXR1cm4gZXhwb3J0c1xuIFx0cmV0dXJuIF9fd2VicGFja19yZXF1aXJlX18oX193ZWJwYWNrX3JlcXVpcmVfXy5zID0gMTEpO1xuIiwiXCJ1c2Ugc3RyaWN0XCI7XG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBcIl9fZXNNb2R1bGVcIiwgeyB2YWx1ZTogdHJ1ZSB9KTtcbmxldCBjYWNoZWRTZXR0aW5ncyA9IHVuZGVmaW5lZDtcbmZ1bmN0aW9uIGdldERhdGEoa2V5KSB7XG4gICAgY29uc3QgZWxlbWVudCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCd2c2NvZGUtbWFya2Rvd24tcHJldmlldy1kYXRhJyk7XG4gICAgaWYgKGVsZW1lbnQpIHtcbiAgICAgICAgY29uc3QgZGF0YSA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKGtleSk7XG4gICAgICAgIGlmIChkYXRhKSB7XG4gICAgICAgICAgICByZXR1cm4gSlNPTi5wYXJzZShkYXRhKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICB0aHJvdyBuZXcgRXJyb3IoYENvdWxkIG5vdCBsb2FkIGRhdGEgZm9yICR7a2V5fWApO1xufVxuZXhwb3J0cy5nZXREYXRhID0gZ2V0RGF0YTtcbmZ1bmN0aW9uIGdldFNldHRpbmdzKCkge1xuICAgIGlmIChjYWNoZWRTZXR0aW5ncykge1xuICAgICAgICByZXR1cm4gY2FjaGVkU2V0dGluZ3M7XG4gICAgfVxuICAgIGNhY2hlZFNldHRpbmdzID0gZ2V0RGF0YSgnZGF0YS1zZXR0aW5ncycpO1xuICAgIGlmIChjYWNoZWRTZXR0aW5ncykge1xuICAgICAgICByZXR1cm4gY2FjaGVkU2V0dGluZ3M7XG4gICAgfVxuICAgIHRocm93IG5ldyBFcnJvcignQ291bGQgbm90IGxvYWQgc2V0dGluZ3MnKTtcbn1cbmV4cG9ydHMuZ2V0U2V0dGluZ3MgPSBnZXRTZXR0aW5ncztcbiIsIlwidXNlIHN0cmljdFwiO1xuLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAqICBDb3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbiAqICBMaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIFNlZSBMaWNlbnNlLnR4dCBpbiB0aGUgcHJvamVjdCByb290IGZvciBsaWNlbnNlIGluZm9ybWF0aW9uLlxuICotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5PYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgXCJfX2VzTW9kdWxlXCIsIHsgdmFsdWU6IHRydWUgfSk7XG5jb25zdCBzZXR0aW5nc18xID0gcmVxdWlyZShcIi4vc2V0dGluZ3NcIik7XG5mdW5jdGlvbiBjbGFtcChtaW4sIG1heCwgdmFsdWUpIHtcbiAgICByZXR1cm4gTWF0aC5taW4obWF4LCBNYXRoLm1heChtaW4sIHZhbHVlKSk7XG59XG5mdW5jdGlvbiBjbGFtcExpbmUobGluZSkge1xuICAgIHJldHVybiBjbGFtcCgwLCBzZXR0aW5nc18xLmdldFNldHRpbmdzKCkubGluZUNvdW50IC0gMSwgbGluZSk7XG59XG5jb25zdCBnZXRDb2RlTGluZUVsZW1lbnRzID0gKCgpID0+IHtcbiAgICBsZXQgZWxlbWVudHM7XG4gICAgcmV0dXJuICgpID0+IHtcbiAgICAgICAgaWYgKCFlbGVtZW50cykge1xuICAgICAgICAgICAgZWxlbWVudHMgPSBbeyBlbGVtZW50OiBkb2N1bWVudC5ib2R5LCBsaW5lOiAwIH1dO1xuICAgICAgICAgICAgZm9yIChjb25zdCBlbGVtZW50IG9mIGRvY3VtZW50LmdldEVsZW1lbnRzQnlDbGFzc05hbWUoJ2NvZGUtbGluZScpKSB7XG4gICAgICAgICAgICAgICAgY29uc3QgbGluZSA9ICtlbGVtZW50LmdldEF0dHJpYnV0ZSgnZGF0YS1saW5lJyk7XG4gICAgICAgICAgICAgICAgaWYgKCFpc05hTihsaW5lKSkge1xuICAgICAgICAgICAgICAgICAgICBlbGVtZW50cy5wdXNoKHsgZWxlbWVudDogZWxlbWVudCwgbGluZSB9KTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIGVsZW1lbnRzO1xuICAgIH07XG59KSgpO1xuLyoqXG4gKiBGaW5kIHRoZSBodG1sIGVsZW1lbnRzIHRoYXQgbWFwIHRvIGEgc3BlY2lmaWMgdGFyZ2V0IGxpbmUgaW4gdGhlIGVkaXRvci5cbiAqXG4gKiBJZiBhbiBleGFjdCBtYXRjaCwgcmV0dXJucyBhIHNpbmdsZSBlbGVtZW50LiBJZiB0aGUgbGluZSBpcyBiZXR3ZWVuIGVsZW1lbnRzLFxuICogcmV0dXJucyB0aGUgZWxlbWVudCBwcmlvciB0byBhbmQgdGhlIGVsZW1lbnQgYWZ0ZXIgdGhlIGdpdmVuIGxpbmUuXG4gKi9cbmZ1bmN0aW9uIGdldEVsZW1lbnRzRm9yU291cmNlTGluZSh0YXJnZXRMaW5lKSB7XG4gICAgY29uc3QgbGluZU51bWJlciA9IE1hdGguZmxvb3IodGFyZ2V0TGluZSk7XG4gICAgY29uc3QgbGluZXMgPSBnZXRDb2RlTGluZUVsZW1lbnRzKCk7XG4gICAgbGV0IHByZXZpb3VzID0gbGluZXNbMF0gfHwgbnVsbDtcbiAgICBmb3IgKGNvbnN0IGVudHJ5IG9mIGxpbmVzKSB7XG4gICAgICAgIGlmIChlbnRyeS5saW5lID09PSBsaW5lTnVtYmVyKSB7XG4gICAgICAgICAgICByZXR1cm4geyBwcmV2aW91czogZW50cnksIG5leHQ6IHVuZGVmaW5lZCB9O1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKGVudHJ5LmxpbmUgPiBsaW5lTnVtYmVyKSB7XG4gICAgICAgICAgICByZXR1cm4geyBwcmV2aW91cywgbmV4dDogZW50cnkgfTtcbiAgICAgICAgfVxuICAgICAgICBwcmV2aW91cyA9IGVudHJ5O1xuICAgIH1cbiAgICByZXR1cm4geyBwcmV2aW91cyB9O1xufVxuZXhwb3J0cy5nZXRFbGVtZW50c0ZvclNvdXJjZUxpbmUgPSBnZXRFbGVtZW50c0ZvclNvdXJjZUxpbmU7XG4vKipcbiAqIEZpbmQgdGhlIGh0bWwgZWxlbWVudHMgdGhhdCBhcmUgYXQgYSBzcGVjaWZpYyBwaXhlbCBvZmZzZXQgb24gdGhlIHBhZ2UuXG4gKi9cbmZ1bmN0aW9uIGdldExpbmVFbGVtZW50c0F0UGFnZU9mZnNldChvZmZzZXQpIHtcbiAgICBjb25zdCBsaW5lcyA9IGdldENvZGVMaW5lRWxlbWVudHMoKTtcbiAgICBjb25zdCBwb3NpdGlvbiA9IG9mZnNldCAtIHdpbmRvdy5zY3JvbGxZO1xuICAgIGxldCBsbyA9IC0xO1xuICAgIGxldCBoaSA9IGxpbmVzLmxlbmd0aCAtIDE7XG4gICAgd2hpbGUgKGxvICsgMSA8IGhpKSB7XG4gICAgICAgIGNvbnN0IG1pZCA9IE1hdGguZmxvb3IoKGxvICsgaGkpIC8gMik7XG4gICAgICAgIGNvbnN0IGJvdW5kcyA9IGxpbmVzW21pZF0uZWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtcbiAgICAgICAgaWYgKGJvdW5kcy50b3AgKyBib3VuZHMuaGVpZ2h0ID49IHBvc2l0aW9uKSB7XG4gICAgICAgICAgICBoaSA9IG1pZDtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIGxvID0gbWlkO1xuICAgICAgICB9XG4gICAgfVxuICAgIGNvbnN0IGhpRWxlbWVudCA9IGxpbmVzW2hpXTtcbiAgICBjb25zdCBoaUJvdW5kcyA9IGhpRWxlbWVudC5lbGVtZW50LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO1xuICAgIGlmIChoaSA+PSAxICYmIGhpQm91bmRzLnRvcCA+IHBvc2l0aW9uKSB7XG4gICAgICAgIGNvbnN0IGxvRWxlbWVudCA9IGxpbmVzW2xvXTtcbiAgICAgICAgcmV0dXJuIHsgcHJldmlvdXM6IGxvRWxlbWVudCwgbmV4dDogaGlFbGVtZW50IH07XG4gICAgfVxuICAgIHJldHVybiB7IHByZXZpb3VzOiBoaUVsZW1lbnQgfTtcbn1cbmV4cG9ydHMuZ2V0TGluZUVsZW1lbnRzQXRQYWdlT2Zmc2V0ID0gZ2V0TGluZUVsZW1lbnRzQXRQYWdlT2Zmc2V0O1xuLyoqXG4gKiBBdHRlbXB0IHRvIHJldmVhbCB0aGUgZWxlbWVudCBmb3IgYSBzb3VyY2UgbGluZSBpbiB0aGUgZWRpdG9yLlxuICovXG5mdW5jdGlvbiBzY3JvbGxUb1JldmVhbFNvdXJjZUxpbmUobGluZSkge1xuICAgIGlmICghc2V0dGluZ3NfMS5nZXRTZXR0aW5ncygpLnNjcm9sbFByZXZpZXdXaXRoRWRpdG9yKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYgKGxpbmUgPD0gMCkge1xuICAgICAgICB3aW5kb3cuc2Nyb2xsKHdpbmRvdy5zY3JvbGxYLCAwKTtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCB7IHByZXZpb3VzLCBuZXh0IH0gPSBnZXRFbGVtZW50c0ZvclNvdXJjZUxpbmUobGluZSk7XG4gICAgaWYgKCFwcmV2aW91cykge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGxldCBzY3JvbGxUbyA9IDA7XG4gICAgY29uc3QgcmVjdCA9IHByZXZpb3VzLmVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7XG4gICAgY29uc3QgcHJldmlvdXNUb3AgPSByZWN0LnRvcDtcbiAgICBpZiAobmV4dCAmJiBuZXh0LmxpbmUgIT09IHByZXZpb3VzLmxpbmUpIHtcbiAgICAgICAgLy8gQmV0d2VlbiB0d28gZWxlbWVudHMuIEdvIHRvIHBlcmNlbnRhZ2Ugb2Zmc2V0IGJldHdlZW4gdGhlbS5cbiAgICAgICAgY29uc3QgYmV0d2VlblByb2dyZXNzID0gKGxpbmUgLSBwcmV2aW91cy5saW5lKSAvIChuZXh0LmxpbmUgLSBwcmV2aW91cy5saW5lKTtcbiAgICAgICAgY29uc3QgZWxlbWVudE9mZnNldCA9IG5leHQuZWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKS50b3AgLSBwcmV2aW91c1RvcDtcbiAgICAgICAgc2Nyb2xsVG8gPSBwcmV2aW91c1RvcCArIGJldHdlZW5Qcm9ncmVzcyAqIGVsZW1lbnRPZmZzZXQ7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICBjb25zdCBwcm9ncmVzc0luRWxlbWVudCA9IGxpbmUgLSBNYXRoLmZsb29yKGxpbmUpO1xuICAgICAgICBzY3JvbGxUbyA9IHByZXZpb3VzVG9wICsgKHJlY3QuaGVpZ2h0ICogcHJvZ3Jlc3NJbkVsZW1lbnQpO1xuICAgIH1cbiAgICB3aW5kb3cuc2Nyb2xsKHdpbmRvdy5zY3JvbGxYLCBNYXRoLm1heCgxLCB3aW5kb3cuc2Nyb2xsWSArIHNjcm9sbFRvKSk7XG59XG5leHBvcnRzLnNjcm9sbFRvUmV2ZWFsU291cmNlTGluZSA9IHNjcm9sbFRvUmV2ZWFsU291cmNlTGluZTtcbmZ1bmN0aW9uIGdldEVkaXRvckxpbmVOdW1iZXJGb3JQYWdlT2Zmc2V0KG9mZnNldCkge1xuICAgIGNvbnN0IHsgcHJldmlvdXMsIG5leHQgfSA9IGdldExpbmVFbGVtZW50c0F0UGFnZU9mZnNldChvZmZzZXQpO1xuICAgIGlmIChwcmV2aW91cykge1xuICAgICAgICBjb25zdCBwcmV2aW91c0JvdW5kcyA9IHByZXZpb3VzLmVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7XG4gICAgICAgIGNvbnN0IG9mZnNldEZyb21QcmV2aW91cyA9IChvZmZzZXQgLSB3aW5kb3cuc2Nyb2xsWSAtIHByZXZpb3VzQm91bmRzLnRvcCk7XG4gICAgICAgIGlmIChuZXh0KSB7XG4gICAgICAgICAgICBjb25zdCBwcm9ncmVzc0JldHdlZW5FbGVtZW50cyA9IG9mZnNldEZyb21QcmV2aW91cyAvIChuZXh0LmVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkudG9wIC0gcHJldmlvdXNCb3VuZHMudG9wKTtcbiAgICAgICAgICAgIGNvbnN0IGxpbmUgPSBwcmV2aW91cy5saW5lICsgcHJvZ3Jlc3NCZXR3ZWVuRWxlbWVudHMgKiAobmV4dC5saW5lIC0gcHJldmlvdXMubGluZSk7XG4gICAgICAgICAgICByZXR1cm4gY2xhbXBMaW5lKGxpbmUpO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgY29uc3QgcHJvZ3Jlc3NXaXRoaW5FbGVtZW50ID0gb2Zmc2V0RnJvbVByZXZpb3VzIC8gKHByZXZpb3VzQm91bmRzLmhlaWdodCk7XG4gICAgICAgICAgICBjb25zdCBsaW5lID0gcHJldmlvdXMubGluZSArIHByb2dyZXNzV2l0aGluRWxlbWVudDtcbiAgICAgICAgICAgIHJldHVybiBjbGFtcExpbmUobGluZSk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIG51bGw7XG59XG5leHBvcnRzLmdldEVkaXRvckxpbmVOdW1iZXJGb3JQYWdlT2Zmc2V0ID0gZ2V0RWRpdG9yTGluZU51bWJlckZvclBhZ2VPZmZzZXQ7XG4vKipcbiAqIFRyeSB0byBmaW5kIHRoZSBodG1sIGVsZW1lbnQgYnkgdXNpbmcgYSBmcmFnbWVudCBpZFxuICovXG5mdW5jdGlvbiBnZXRMaW5lRWxlbWVudEZvckZyYWdtZW50KGZyYWdtZW50KSB7XG4gICAgcmV0dXJuIGdldENvZGVMaW5lRWxlbWVudHMoKS5maW5kKChlbGVtZW50KSA9PiB7XG4gICAgICAgIHJldHVybiBlbGVtZW50LmVsZW1lbnQuaWQgPT09IGZyYWdtZW50O1xuICAgIH0pO1xufVxuZXhwb3J0cy5nZXRMaW5lRWxlbWVudEZvckZyYWdtZW50ID0gZ2V0TGluZUVsZW1lbnRGb3JGcmFnbWVudDtcbiIsInZhciBnO1xyXG5cclxuLy8gVGhpcyB3b3JrcyBpbiBub24tc3RyaWN0IG1vZGVcclxuZyA9IChmdW5jdGlvbigpIHtcclxuXHRyZXR1cm4gdGhpcztcclxufSkoKTtcclxuXHJcbnRyeSB7XHJcblx0Ly8gVGhpcyB3b3JrcyBpZiBldmFsIGlzIGFsbG93ZWQgKHNlZSBDU1ApXHJcblx0ZyA9IGcgfHwgRnVuY3Rpb24oXCJyZXR1cm4gdGhpc1wiKSgpIHx8ICgxLCBldmFsKShcInRoaXNcIik7XHJcbn0gY2F0Y2ggKGUpIHtcclxuXHQvLyBUaGlzIHdvcmtzIGlmIHRoZSB3aW5kb3cgcmVmZXJlbmNlIGlzIGF2YWlsYWJsZVxyXG5cdGlmICh0eXBlb2Ygd2luZG93ID09PSBcIm9iamVjdFwiKSBnID0gd2luZG93O1xyXG59XHJcblxyXG4vLyBnIGNhbiBzdGlsbCBiZSB1bmRlZmluZWQsIGJ1dCBub3RoaW5nIHRvIGRvIGFib3V0IGl0Li4uXHJcbi8vIFdlIHJldHVybiB1bmRlZmluZWQsIGluc3RlYWQgb2Ygbm90aGluZyBoZXJlLCBzbyBpdCdzXHJcbi8vIGVhc2llciB0byBoYW5kbGUgdGhpcyBjYXNlLiBpZighZ2xvYmFsKSB7IC4uLn1cclxuXHJcbm1vZHVsZS5leHBvcnRzID0gZztcclxuIiwiLyoqXG4gKiBsb2Rhc2ggKEN1c3RvbSBCdWlsZCkgPGh0dHBzOi8vbG9kYXNoLmNvbS8+XG4gKiBCdWlsZDogYGxvZGFzaCBtb2R1bGFyaXplIGV4cG9ydHM9XCJucG1cIiAtbyAuL2BcbiAqIENvcHlyaWdodCBqUXVlcnkgRm91bmRhdGlvbiBhbmQgb3RoZXIgY29udHJpYnV0b3JzIDxodHRwczovL2pxdWVyeS5vcmcvPlxuICogUmVsZWFzZWQgdW5kZXIgTUlUIGxpY2Vuc2UgPGh0dHBzOi8vbG9kYXNoLmNvbS9saWNlbnNlPlxuICogQmFzZWQgb24gVW5kZXJzY29yZS5qcyAxLjguMyA8aHR0cDovL3VuZGVyc2NvcmVqcy5vcmcvTElDRU5TRT5cbiAqIENvcHlyaWdodCBKZXJlbXkgQXNoa2VuYXMsIERvY3VtZW50Q2xvdWQgYW5kIEludmVzdGlnYXRpdmUgUmVwb3J0ZXJzICYgRWRpdG9yc1xuICovXG5cbi8qKiBVc2VkIGFzIHRoZSBgVHlwZUVycm9yYCBtZXNzYWdlIGZvciBcIkZ1bmN0aW9uc1wiIG1ldGhvZHMuICovXG52YXIgRlVOQ19FUlJPUl9URVhUID0gJ0V4cGVjdGVkIGEgZnVuY3Rpb24nO1xuXG4vKiogVXNlZCBhcyByZWZlcmVuY2VzIGZvciB2YXJpb3VzIGBOdW1iZXJgIGNvbnN0YW50cy4gKi9cbnZhciBOQU4gPSAwIC8gMDtcblxuLyoqIGBPYmplY3QjdG9TdHJpbmdgIHJlc3VsdCByZWZlcmVuY2VzLiAqL1xudmFyIHN5bWJvbFRhZyA9ICdbb2JqZWN0IFN5bWJvbF0nO1xuXG4vKiogVXNlZCB0byBtYXRjaCBsZWFkaW5nIGFuZCB0cmFpbGluZyB3aGl0ZXNwYWNlLiAqL1xudmFyIHJlVHJpbSA9IC9eXFxzK3xcXHMrJC9nO1xuXG4vKiogVXNlZCB0byBkZXRlY3QgYmFkIHNpZ25lZCBoZXhhZGVjaW1hbCBzdHJpbmcgdmFsdWVzLiAqL1xudmFyIHJlSXNCYWRIZXggPSAvXlstK10weFswLTlhLWZdKyQvaTtcblxuLyoqIFVzZWQgdG8gZGV0ZWN0IGJpbmFyeSBzdHJpbmcgdmFsdWVzLiAqL1xudmFyIHJlSXNCaW5hcnkgPSAvXjBiWzAxXSskL2k7XG5cbi8qKiBVc2VkIHRvIGRldGVjdCBvY3RhbCBzdHJpbmcgdmFsdWVzLiAqL1xudmFyIHJlSXNPY3RhbCA9IC9eMG9bMC03XSskL2k7XG5cbi8qKiBCdWlsdC1pbiBtZXRob2QgcmVmZXJlbmNlcyB3aXRob3V0IGEgZGVwZW5kZW5jeSBvbiBgcm9vdGAuICovXG52YXIgZnJlZVBhcnNlSW50ID0gcGFyc2VJbnQ7XG5cbi8qKiBEZXRlY3QgZnJlZSB2YXJpYWJsZSBgZ2xvYmFsYCBmcm9tIE5vZGUuanMuICovXG52YXIgZnJlZUdsb2JhbCA9IHR5cGVvZiBnbG9iYWwgPT0gJ29iamVjdCcgJiYgZ2xvYmFsICYmIGdsb2JhbC5PYmplY3QgPT09IE9iamVjdCAmJiBnbG9iYWw7XG5cbi8qKiBEZXRlY3QgZnJlZSB2YXJpYWJsZSBgc2VsZmAuICovXG52YXIgZnJlZVNlbGYgPSB0eXBlb2Ygc2VsZiA9PSAnb2JqZWN0JyAmJiBzZWxmICYmIHNlbGYuT2JqZWN0ID09PSBPYmplY3QgJiYgc2VsZjtcblxuLyoqIFVzZWQgYXMgYSByZWZlcmVuY2UgdG8gdGhlIGdsb2JhbCBvYmplY3QuICovXG52YXIgcm9vdCA9IGZyZWVHbG9iYWwgfHwgZnJlZVNlbGYgfHwgRnVuY3Rpb24oJ3JldHVybiB0aGlzJykoKTtcblxuLyoqIFVzZWQgZm9yIGJ1aWx0LWluIG1ldGhvZCByZWZlcmVuY2VzLiAqL1xudmFyIG9iamVjdFByb3RvID0gT2JqZWN0LnByb3RvdHlwZTtcblxuLyoqXG4gKiBVc2VkIHRvIHJlc29sdmUgdGhlXG4gKiBbYHRvU3RyaW5nVGFnYF0oaHR0cDovL2VjbWEtaW50ZXJuYXRpb25hbC5vcmcvZWNtYS0yNjIvNy4wLyNzZWMtb2JqZWN0LnByb3RvdHlwZS50b3N0cmluZylcbiAqIG9mIHZhbHVlcy5cbiAqL1xudmFyIG9iamVjdFRvU3RyaW5nID0gb2JqZWN0UHJvdG8udG9TdHJpbmc7XG5cbi8qIEJ1aWx0LWluIG1ldGhvZCByZWZlcmVuY2VzIGZvciB0aG9zZSB3aXRoIHRoZSBzYW1lIG5hbWUgYXMgb3RoZXIgYGxvZGFzaGAgbWV0aG9kcy4gKi9cbnZhciBuYXRpdmVNYXggPSBNYXRoLm1heCxcbiAgICBuYXRpdmVNaW4gPSBNYXRoLm1pbjtcblxuLyoqXG4gKiBHZXRzIHRoZSB0aW1lc3RhbXAgb2YgdGhlIG51bWJlciBvZiBtaWxsaXNlY29uZHMgdGhhdCBoYXZlIGVsYXBzZWQgc2luY2VcbiAqIHRoZSBVbml4IGVwb2NoICgxIEphbnVhcnkgMTk3MCAwMDowMDowMCBVVEMpLlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAc2luY2UgMi40LjBcbiAqIEBjYXRlZ29yeSBEYXRlXG4gKiBAcmV0dXJucyB7bnVtYmVyfSBSZXR1cm5zIHRoZSB0aW1lc3RhbXAuXG4gKiBAZXhhbXBsZVxuICpcbiAqIF8uZGVmZXIoZnVuY3Rpb24oc3RhbXApIHtcbiAqICAgY29uc29sZS5sb2coXy5ub3coKSAtIHN0YW1wKTtcbiAqIH0sIF8ubm93KCkpO1xuICogLy8gPT4gTG9ncyB0aGUgbnVtYmVyIG9mIG1pbGxpc2Vjb25kcyBpdCB0b29rIGZvciB0aGUgZGVmZXJyZWQgaW52b2NhdGlvbi5cbiAqL1xudmFyIG5vdyA9IGZ1bmN0aW9uKCkge1xuICByZXR1cm4gcm9vdC5EYXRlLm5vdygpO1xufTtcblxuLyoqXG4gKiBDcmVhdGVzIGEgZGVib3VuY2VkIGZ1bmN0aW9uIHRoYXQgZGVsYXlzIGludm9raW5nIGBmdW5jYCB1bnRpbCBhZnRlciBgd2FpdGBcbiAqIG1pbGxpc2Vjb25kcyBoYXZlIGVsYXBzZWQgc2luY2UgdGhlIGxhc3QgdGltZSB0aGUgZGVib3VuY2VkIGZ1bmN0aW9uIHdhc1xuICogaW52b2tlZC4gVGhlIGRlYm91bmNlZCBmdW5jdGlvbiBjb21lcyB3aXRoIGEgYGNhbmNlbGAgbWV0aG9kIHRvIGNhbmNlbFxuICogZGVsYXllZCBgZnVuY2AgaW52b2NhdGlvbnMgYW5kIGEgYGZsdXNoYCBtZXRob2QgdG8gaW1tZWRpYXRlbHkgaW52b2tlIHRoZW0uXG4gKiBQcm92aWRlIGBvcHRpb25zYCB0byBpbmRpY2F0ZSB3aGV0aGVyIGBmdW5jYCBzaG91bGQgYmUgaW52b2tlZCBvbiB0aGVcbiAqIGxlYWRpbmcgYW5kL29yIHRyYWlsaW5nIGVkZ2Ugb2YgdGhlIGB3YWl0YCB0aW1lb3V0LiBUaGUgYGZ1bmNgIGlzIGludm9rZWRcbiAqIHdpdGggdGhlIGxhc3QgYXJndW1lbnRzIHByb3ZpZGVkIHRvIHRoZSBkZWJvdW5jZWQgZnVuY3Rpb24uIFN1YnNlcXVlbnRcbiAqIGNhbGxzIHRvIHRoZSBkZWJvdW5jZWQgZnVuY3Rpb24gcmV0dXJuIHRoZSByZXN1bHQgb2YgdGhlIGxhc3QgYGZ1bmNgXG4gKiBpbnZvY2F0aW9uLlxuICpcbiAqICoqTm90ZToqKiBJZiBgbGVhZGluZ2AgYW5kIGB0cmFpbGluZ2Agb3B0aW9ucyBhcmUgYHRydWVgLCBgZnVuY2AgaXNcbiAqIGludm9rZWQgb24gdGhlIHRyYWlsaW5nIGVkZ2Ugb2YgdGhlIHRpbWVvdXQgb25seSBpZiB0aGUgZGVib3VuY2VkIGZ1bmN0aW9uXG4gKiBpcyBpbnZva2VkIG1vcmUgdGhhbiBvbmNlIGR1cmluZyB0aGUgYHdhaXRgIHRpbWVvdXQuXG4gKlxuICogSWYgYHdhaXRgIGlzIGAwYCBhbmQgYGxlYWRpbmdgIGlzIGBmYWxzZWAsIGBmdW5jYCBpbnZvY2F0aW9uIGlzIGRlZmVycmVkXG4gKiB1bnRpbCB0byB0aGUgbmV4dCB0aWNrLCBzaW1pbGFyIHRvIGBzZXRUaW1lb3V0YCB3aXRoIGEgdGltZW91dCBvZiBgMGAuXG4gKlxuICogU2VlIFtEYXZpZCBDb3JiYWNobydzIGFydGljbGVdKGh0dHBzOi8vY3NzLXRyaWNrcy5jb20vZGVib3VuY2luZy10aHJvdHRsaW5nLWV4cGxhaW5lZC1leGFtcGxlcy8pXG4gKiBmb3IgZGV0YWlscyBvdmVyIHRoZSBkaWZmZXJlbmNlcyBiZXR3ZWVuIGBfLmRlYm91bmNlYCBhbmQgYF8udGhyb3R0bGVgLlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAc2luY2UgMC4xLjBcbiAqIEBjYXRlZ29yeSBGdW5jdGlvblxuICogQHBhcmFtIHtGdW5jdGlvbn0gZnVuYyBUaGUgZnVuY3Rpb24gdG8gZGVib3VuY2UuXG4gKiBAcGFyYW0ge251bWJlcn0gW3dhaXQ9MF0gVGhlIG51bWJlciBvZiBtaWxsaXNlY29uZHMgdG8gZGVsYXkuXG4gKiBAcGFyYW0ge09iamVjdH0gW29wdGlvbnM9e31dIFRoZSBvcHRpb25zIG9iamVjdC5cbiAqIEBwYXJhbSB7Ym9vbGVhbn0gW29wdGlvbnMubGVhZGluZz1mYWxzZV1cbiAqICBTcGVjaWZ5IGludm9raW5nIG9uIHRoZSBsZWFkaW5nIGVkZ2Ugb2YgdGhlIHRpbWVvdXQuXG4gKiBAcGFyYW0ge251bWJlcn0gW29wdGlvbnMubWF4V2FpdF1cbiAqICBUaGUgbWF4aW11bSB0aW1lIGBmdW5jYCBpcyBhbGxvd2VkIHRvIGJlIGRlbGF5ZWQgYmVmb3JlIGl0J3MgaW52b2tlZC5cbiAqIEBwYXJhbSB7Ym9vbGVhbn0gW29wdGlvbnMudHJhaWxpbmc9dHJ1ZV1cbiAqICBTcGVjaWZ5IGludm9raW5nIG9uIHRoZSB0cmFpbGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0LlxuICogQHJldHVybnMge0Z1bmN0aW9ufSBSZXR1cm5zIHRoZSBuZXcgZGVib3VuY2VkIGZ1bmN0aW9uLlxuICogQGV4YW1wbGVcbiAqXG4gKiAvLyBBdm9pZCBjb3N0bHkgY2FsY3VsYXRpb25zIHdoaWxlIHRoZSB3aW5kb3cgc2l6ZSBpcyBpbiBmbHV4LlxuICogalF1ZXJ5KHdpbmRvdykub24oJ3Jlc2l6ZScsIF8uZGVib3VuY2UoY2FsY3VsYXRlTGF5b3V0LCAxNTApKTtcbiAqXG4gKiAvLyBJbnZva2UgYHNlbmRNYWlsYCB3aGVuIGNsaWNrZWQsIGRlYm91bmNpbmcgc3Vic2VxdWVudCBjYWxscy5cbiAqIGpRdWVyeShlbGVtZW50KS5vbignY2xpY2snLCBfLmRlYm91bmNlKHNlbmRNYWlsLCAzMDAsIHtcbiAqICAgJ2xlYWRpbmcnOiB0cnVlLFxuICogICAndHJhaWxpbmcnOiBmYWxzZVxuICogfSkpO1xuICpcbiAqIC8vIEVuc3VyZSBgYmF0Y2hMb2dgIGlzIGludm9rZWQgb25jZSBhZnRlciAxIHNlY29uZCBvZiBkZWJvdW5jZWQgY2FsbHMuXG4gKiB2YXIgZGVib3VuY2VkID0gXy5kZWJvdW5jZShiYXRjaExvZywgMjUwLCB7ICdtYXhXYWl0JzogMTAwMCB9KTtcbiAqIHZhciBzb3VyY2UgPSBuZXcgRXZlbnRTb3VyY2UoJy9zdHJlYW0nKTtcbiAqIGpRdWVyeShzb3VyY2UpLm9uKCdtZXNzYWdlJywgZGVib3VuY2VkKTtcbiAqXG4gKiAvLyBDYW5jZWwgdGhlIHRyYWlsaW5nIGRlYm91bmNlZCBpbnZvY2F0aW9uLlxuICogalF1ZXJ5KHdpbmRvdykub24oJ3BvcHN0YXRlJywgZGVib3VuY2VkLmNhbmNlbCk7XG4gKi9cbmZ1bmN0aW9uIGRlYm91bmNlKGZ1bmMsIHdhaXQsIG9wdGlvbnMpIHtcbiAgdmFyIGxhc3RBcmdzLFxuICAgICAgbGFzdFRoaXMsXG4gICAgICBtYXhXYWl0LFxuICAgICAgcmVzdWx0LFxuICAgICAgdGltZXJJZCxcbiAgICAgIGxhc3RDYWxsVGltZSxcbiAgICAgIGxhc3RJbnZva2VUaW1lID0gMCxcbiAgICAgIGxlYWRpbmcgPSBmYWxzZSxcbiAgICAgIG1heGluZyA9IGZhbHNlLFxuICAgICAgdHJhaWxpbmcgPSB0cnVlO1xuXG4gIGlmICh0eXBlb2YgZnVuYyAhPSAnZnVuY3Rpb24nKSB7XG4gICAgdGhyb3cgbmV3IFR5cGVFcnJvcihGVU5DX0VSUk9SX1RFWFQpO1xuICB9XG4gIHdhaXQgPSB0b051bWJlcih3YWl0KSB8fCAwO1xuICBpZiAoaXNPYmplY3Qob3B0aW9ucykpIHtcbiAgICBsZWFkaW5nID0gISFvcHRpb25zLmxlYWRpbmc7XG4gICAgbWF4aW5nID0gJ21heFdhaXQnIGluIG9wdGlvbnM7XG4gICAgbWF4V2FpdCA9IG1heGluZyA/IG5hdGl2ZU1heCh0b051bWJlcihvcHRpb25zLm1heFdhaXQpIHx8IDAsIHdhaXQpIDogbWF4V2FpdDtcbiAgICB0cmFpbGluZyA9ICd0cmFpbGluZycgaW4gb3B0aW9ucyA/ICEhb3B0aW9ucy50cmFpbGluZyA6IHRyYWlsaW5nO1xuICB9XG5cbiAgZnVuY3Rpb24gaW52b2tlRnVuYyh0aW1lKSB7XG4gICAgdmFyIGFyZ3MgPSBsYXN0QXJncyxcbiAgICAgICAgdGhpc0FyZyA9IGxhc3RUaGlzO1xuXG4gICAgbGFzdEFyZ3MgPSBsYXN0VGhpcyA9IHVuZGVmaW5lZDtcbiAgICBsYXN0SW52b2tlVGltZSA9IHRpbWU7XG4gICAgcmVzdWx0ID0gZnVuYy5hcHBseSh0aGlzQXJnLCBhcmdzKTtcbiAgICByZXR1cm4gcmVzdWx0O1xuICB9XG5cbiAgZnVuY3Rpb24gbGVhZGluZ0VkZ2UodGltZSkge1xuICAgIC8vIFJlc2V0IGFueSBgbWF4V2FpdGAgdGltZXIuXG4gICAgbGFzdEludm9rZVRpbWUgPSB0aW1lO1xuICAgIC8vIFN0YXJ0IHRoZSB0aW1lciBmb3IgdGhlIHRyYWlsaW5nIGVkZ2UuXG4gICAgdGltZXJJZCA9IHNldFRpbWVvdXQodGltZXJFeHBpcmVkLCB3YWl0KTtcbiAgICAvLyBJbnZva2UgdGhlIGxlYWRpbmcgZWRnZS5cbiAgICByZXR1cm4gbGVhZGluZyA/IGludm9rZUZ1bmModGltZSkgOiByZXN1bHQ7XG4gIH1cblxuICBmdW5jdGlvbiByZW1haW5pbmdXYWl0KHRpbWUpIHtcbiAgICB2YXIgdGltZVNpbmNlTGFzdENhbGwgPSB0aW1lIC0gbGFzdENhbGxUaW1lLFxuICAgICAgICB0aW1lU2luY2VMYXN0SW52b2tlID0gdGltZSAtIGxhc3RJbnZva2VUaW1lLFxuICAgICAgICByZXN1bHQgPSB3YWl0IC0gdGltZVNpbmNlTGFzdENhbGw7XG5cbiAgICByZXR1cm4gbWF4aW5nID8gbmF0aXZlTWluKHJlc3VsdCwgbWF4V2FpdCAtIHRpbWVTaW5jZUxhc3RJbnZva2UpIDogcmVzdWx0O1xuICB9XG5cbiAgZnVuY3Rpb24gc2hvdWxkSW52b2tlKHRpbWUpIHtcbiAgICB2YXIgdGltZVNpbmNlTGFzdENhbGwgPSB0aW1lIC0gbGFzdENhbGxUaW1lLFxuICAgICAgICB0aW1lU2luY2VMYXN0SW52b2tlID0gdGltZSAtIGxhc3RJbnZva2VUaW1lO1xuXG4gICAgLy8gRWl0aGVyIHRoaXMgaXMgdGhlIGZpcnN0IGNhbGwsIGFjdGl2aXR5IGhhcyBzdG9wcGVkIGFuZCB3ZSdyZSBhdCB0aGVcbiAgICAvLyB0cmFpbGluZyBlZGdlLCB0aGUgc3lzdGVtIHRpbWUgaGFzIGdvbmUgYmFja3dhcmRzIGFuZCB3ZSdyZSB0cmVhdGluZ1xuICAgIC8vIGl0IGFzIHRoZSB0cmFpbGluZyBlZGdlLCBvciB3ZSd2ZSBoaXQgdGhlIGBtYXhXYWl0YCBsaW1pdC5cbiAgICByZXR1cm4gKGxhc3RDYWxsVGltZSA9PT0gdW5kZWZpbmVkIHx8ICh0aW1lU2luY2VMYXN0Q2FsbCA+PSB3YWl0KSB8fFxuICAgICAgKHRpbWVTaW5jZUxhc3RDYWxsIDwgMCkgfHwgKG1heGluZyAmJiB0aW1lU2luY2VMYXN0SW52b2tlID49IG1heFdhaXQpKTtcbiAgfVxuXG4gIGZ1bmN0aW9uIHRpbWVyRXhwaXJlZCgpIHtcbiAgICB2YXIgdGltZSA9IG5vdygpO1xuICAgIGlmIChzaG91bGRJbnZva2UodGltZSkpIHtcbiAgICAgIHJldHVybiB0cmFpbGluZ0VkZ2UodGltZSk7XG4gICAgfVxuICAgIC8vIFJlc3RhcnQgdGhlIHRpbWVyLlxuICAgIHRpbWVySWQgPSBzZXRUaW1lb3V0KHRpbWVyRXhwaXJlZCwgcmVtYWluaW5nV2FpdCh0aW1lKSk7XG4gIH1cblxuICBmdW5jdGlvbiB0cmFpbGluZ0VkZ2UodGltZSkge1xuICAgIHRpbWVySWQgPSB1bmRlZmluZWQ7XG5cbiAgICAvLyBPbmx5IGludm9rZSBpZiB3ZSBoYXZlIGBsYXN0QXJnc2Agd2hpY2ggbWVhbnMgYGZ1bmNgIGhhcyBiZWVuXG4gICAgLy8gZGVib3VuY2VkIGF0IGxlYXN0IG9uY2UuXG4gICAgaWYgKHRyYWlsaW5nICYmIGxhc3RBcmdzKSB7XG4gICAgICByZXR1cm4gaW52b2tlRnVuYyh0aW1lKTtcbiAgICB9XG4gICAgbGFzdEFyZ3MgPSBsYXN0VGhpcyA9IHVuZGVmaW5lZDtcbiAgICByZXR1cm4gcmVzdWx0O1xuICB9XG5cbiAgZnVuY3Rpb24gY2FuY2VsKCkge1xuICAgIGlmICh0aW1lcklkICE9PSB1bmRlZmluZWQpIHtcbiAgICAgIGNsZWFyVGltZW91dCh0aW1lcklkKTtcbiAgICB9XG4gICAgbGFzdEludm9rZVRpbWUgPSAwO1xuICAgIGxhc3RBcmdzID0gbGFzdENhbGxUaW1lID0gbGFzdFRoaXMgPSB0aW1lcklkID0gdW5kZWZpbmVkO1xuICB9XG5cbiAgZnVuY3Rpb24gZmx1c2goKSB7XG4gICAgcmV0dXJuIHRpbWVySWQgPT09IHVuZGVmaW5lZCA/IHJlc3VsdCA6IHRyYWlsaW5nRWRnZShub3coKSk7XG4gIH1cblxuICBmdW5jdGlvbiBkZWJvdW5jZWQoKSB7XG4gICAgdmFyIHRpbWUgPSBub3coKSxcbiAgICAgICAgaXNJbnZva2luZyA9IHNob3VsZEludm9rZSh0aW1lKTtcblxuICAgIGxhc3RBcmdzID0gYXJndW1lbnRzO1xuICAgIGxhc3RUaGlzID0gdGhpcztcbiAgICBsYXN0Q2FsbFRpbWUgPSB0aW1lO1xuXG4gICAgaWYgKGlzSW52b2tpbmcpIHtcbiAgICAgIGlmICh0aW1lcklkID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgcmV0dXJuIGxlYWRpbmdFZGdlKGxhc3RDYWxsVGltZSk7XG4gICAgICB9XG4gICAgICBpZiAobWF4aW5nKSB7XG4gICAgICAgIC8vIEhhbmRsZSBpbnZvY2F0aW9ucyBpbiBhIHRpZ2h0IGxvb3AuXG4gICAgICAgIHRpbWVySWQgPSBzZXRUaW1lb3V0KHRpbWVyRXhwaXJlZCwgd2FpdCk7XG4gICAgICAgIHJldHVybiBpbnZva2VGdW5jKGxhc3RDYWxsVGltZSk7XG4gICAgICB9XG4gICAgfVxuICAgIGlmICh0aW1lcklkID09PSB1bmRlZmluZWQpIHtcbiAgICAgIHRpbWVySWQgPSBzZXRUaW1lb3V0KHRpbWVyRXhwaXJlZCwgd2FpdCk7XG4gICAgfVxuICAgIHJldHVybiByZXN1bHQ7XG4gIH1cbiAgZGVib3VuY2VkLmNhbmNlbCA9IGNhbmNlbDtcbiAgZGVib3VuY2VkLmZsdXNoID0gZmx1c2g7XG4gIHJldHVybiBkZWJvdW5jZWQ7XG59XG5cbi8qKlxuICogQ3JlYXRlcyBhIHRocm90dGxlZCBmdW5jdGlvbiB0aGF0IG9ubHkgaW52b2tlcyBgZnVuY2AgYXQgbW9zdCBvbmNlIHBlclxuICogZXZlcnkgYHdhaXRgIG1pbGxpc2Vjb25kcy4gVGhlIHRocm90dGxlZCBmdW5jdGlvbiBjb21lcyB3aXRoIGEgYGNhbmNlbGBcbiAqIG1ldGhvZCB0byBjYW5jZWwgZGVsYXllZCBgZnVuY2AgaW52b2NhdGlvbnMgYW5kIGEgYGZsdXNoYCBtZXRob2QgdG9cbiAqIGltbWVkaWF0ZWx5IGludm9rZSB0aGVtLiBQcm92aWRlIGBvcHRpb25zYCB0byBpbmRpY2F0ZSB3aGV0aGVyIGBmdW5jYFxuICogc2hvdWxkIGJlIGludm9rZWQgb24gdGhlIGxlYWRpbmcgYW5kL29yIHRyYWlsaW5nIGVkZ2Ugb2YgdGhlIGB3YWl0YFxuICogdGltZW91dC4gVGhlIGBmdW5jYCBpcyBpbnZva2VkIHdpdGggdGhlIGxhc3QgYXJndW1lbnRzIHByb3ZpZGVkIHRvIHRoZVxuICogdGhyb3R0bGVkIGZ1bmN0aW9uLiBTdWJzZXF1ZW50IGNhbGxzIHRvIHRoZSB0aHJvdHRsZWQgZnVuY3Rpb24gcmV0dXJuIHRoZVxuICogcmVzdWx0IG9mIHRoZSBsYXN0IGBmdW5jYCBpbnZvY2F0aW9uLlxuICpcbiAqICoqTm90ZToqKiBJZiBgbGVhZGluZ2AgYW5kIGB0cmFpbGluZ2Agb3B0aW9ucyBhcmUgYHRydWVgLCBgZnVuY2AgaXNcbiAqIGludm9rZWQgb24gdGhlIHRyYWlsaW5nIGVkZ2Ugb2YgdGhlIHRpbWVvdXQgb25seSBpZiB0aGUgdGhyb3R0bGVkIGZ1bmN0aW9uXG4gKiBpcyBpbnZva2VkIG1vcmUgdGhhbiBvbmNlIGR1cmluZyB0aGUgYHdhaXRgIHRpbWVvdXQuXG4gKlxuICogSWYgYHdhaXRgIGlzIGAwYCBhbmQgYGxlYWRpbmdgIGlzIGBmYWxzZWAsIGBmdW5jYCBpbnZvY2F0aW9uIGlzIGRlZmVycmVkXG4gKiB1bnRpbCB0byB0aGUgbmV4dCB0aWNrLCBzaW1pbGFyIHRvIGBzZXRUaW1lb3V0YCB3aXRoIGEgdGltZW91dCBvZiBgMGAuXG4gKlxuICogU2VlIFtEYXZpZCBDb3JiYWNobydzIGFydGljbGVdKGh0dHBzOi8vY3NzLXRyaWNrcy5jb20vZGVib3VuY2luZy10aHJvdHRsaW5nLWV4cGxhaW5lZC1leGFtcGxlcy8pXG4gKiBmb3IgZGV0YWlscyBvdmVyIHRoZSBkaWZmZXJlbmNlcyBiZXR3ZWVuIGBfLnRocm90dGxlYCBhbmQgYF8uZGVib3VuY2VgLlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAc2luY2UgMC4xLjBcbiAqIEBjYXRlZ29yeSBGdW5jdGlvblxuICogQHBhcmFtIHtGdW5jdGlvbn0gZnVuYyBUaGUgZnVuY3Rpb24gdG8gdGhyb3R0bGUuXG4gKiBAcGFyYW0ge251bWJlcn0gW3dhaXQ9MF0gVGhlIG51bWJlciBvZiBtaWxsaXNlY29uZHMgdG8gdGhyb3R0bGUgaW52b2NhdGlvbnMgdG8uXG4gKiBAcGFyYW0ge09iamVjdH0gW29wdGlvbnM9e31dIFRoZSBvcHRpb25zIG9iamVjdC5cbiAqIEBwYXJhbSB7Ym9vbGVhbn0gW29wdGlvbnMubGVhZGluZz10cnVlXVxuICogIFNwZWNpZnkgaW52b2tpbmcgb24gdGhlIGxlYWRpbmcgZWRnZSBvZiB0aGUgdGltZW91dC5cbiAqIEBwYXJhbSB7Ym9vbGVhbn0gW29wdGlvbnMudHJhaWxpbmc9dHJ1ZV1cbiAqICBTcGVjaWZ5IGludm9raW5nIG9uIHRoZSB0cmFpbGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0LlxuICogQHJldHVybnMge0Z1bmN0aW9ufSBSZXR1cm5zIHRoZSBuZXcgdGhyb3R0bGVkIGZ1bmN0aW9uLlxuICogQGV4YW1wbGVcbiAqXG4gKiAvLyBBdm9pZCBleGNlc3NpdmVseSB1cGRhdGluZyB0aGUgcG9zaXRpb24gd2hpbGUgc2Nyb2xsaW5nLlxuICogalF1ZXJ5KHdpbmRvdykub24oJ3Njcm9sbCcsIF8udGhyb3R0bGUodXBkYXRlUG9zaXRpb24sIDEwMCkpO1xuICpcbiAqIC8vIEludm9rZSBgcmVuZXdUb2tlbmAgd2hlbiB0aGUgY2xpY2sgZXZlbnQgaXMgZmlyZWQsIGJ1dCBub3QgbW9yZSB0aGFuIG9uY2UgZXZlcnkgNSBtaW51dGVzLlxuICogdmFyIHRocm90dGxlZCA9IF8udGhyb3R0bGUocmVuZXdUb2tlbiwgMzAwMDAwLCB7ICd0cmFpbGluZyc6IGZhbHNlIH0pO1xuICogalF1ZXJ5KGVsZW1lbnQpLm9uKCdjbGljaycsIHRocm90dGxlZCk7XG4gKlxuICogLy8gQ2FuY2VsIHRoZSB0cmFpbGluZyB0aHJvdHRsZWQgaW52b2NhdGlvbi5cbiAqIGpRdWVyeSh3aW5kb3cpLm9uKCdwb3BzdGF0ZScsIHRocm90dGxlZC5jYW5jZWwpO1xuICovXG5mdW5jdGlvbiB0aHJvdHRsZShmdW5jLCB3YWl0LCBvcHRpb25zKSB7XG4gIHZhciBsZWFkaW5nID0gdHJ1ZSxcbiAgICAgIHRyYWlsaW5nID0gdHJ1ZTtcblxuICBpZiAodHlwZW9mIGZ1bmMgIT0gJ2Z1bmN0aW9uJykge1xuICAgIHRocm93IG5ldyBUeXBlRXJyb3IoRlVOQ19FUlJPUl9URVhUKTtcbiAgfVxuICBpZiAoaXNPYmplY3Qob3B0aW9ucykpIHtcbiAgICBsZWFkaW5nID0gJ2xlYWRpbmcnIGluIG9wdGlvbnMgPyAhIW9wdGlvbnMubGVhZGluZyA6IGxlYWRpbmc7XG4gICAgdHJhaWxpbmcgPSAndHJhaWxpbmcnIGluIG9wdGlvbnMgPyAhIW9wdGlvbnMudHJhaWxpbmcgOiB0cmFpbGluZztcbiAgfVxuICByZXR1cm4gZGVib3VuY2UoZnVuYywgd2FpdCwge1xuICAgICdsZWFkaW5nJzogbGVhZGluZyxcbiAgICAnbWF4V2FpdCc6IHdhaXQsXG4gICAgJ3RyYWlsaW5nJzogdHJhaWxpbmdcbiAgfSk7XG59XG5cbi8qKlxuICogQ2hlY2tzIGlmIGB2YWx1ZWAgaXMgdGhlXG4gKiBbbGFuZ3VhZ2UgdHlwZV0oaHR0cDovL3d3dy5lY21hLWludGVybmF0aW9uYWwub3JnL2VjbWEtMjYyLzcuMC8jc2VjLWVjbWFzY3JpcHQtbGFuZ3VhZ2UtdHlwZXMpXG4gKiBvZiBgT2JqZWN0YC4gKGUuZy4gYXJyYXlzLCBmdW5jdGlvbnMsIG9iamVjdHMsIHJlZ2V4ZXMsIGBuZXcgTnVtYmVyKDApYCwgYW5kIGBuZXcgU3RyaW5nKCcnKWApXG4gKlxuICogQHN0YXRpY1xuICogQG1lbWJlck9mIF9cbiAqIEBzaW5jZSAwLjEuMFxuICogQGNhdGVnb3J5IExhbmdcbiAqIEBwYXJhbSB7Kn0gdmFsdWUgVGhlIHZhbHVlIHRvIGNoZWNrLlxuICogQHJldHVybnMge2Jvb2xlYW59IFJldHVybnMgYHRydWVgIGlmIGB2YWx1ZWAgaXMgYW4gb2JqZWN0LCBlbHNlIGBmYWxzZWAuXG4gKiBAZXhhbXBsZVxuICpcbiAqIF8uaXNPYmplY3Qoe30pO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNPYmplY3QoWzEsIDIsIDNdKTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmlzT2JqZWN0KF8ubm9vcCk7XG4gKiAvLyA9PiB0cnVlXG4gKlxuICogXy5pc09iamVjdChudWxsKTtcbiAqIC8vID0+IGZhbHNlXG4gKi9cbmZ1bmN0aW9uIGlzT2JqZWN0KHZhbHVlKSB7XG4gIHZhciB0eXBlID0gdHlwZW9mIHZhbHVlO1xuICByZXR1cm4gISF2YWx1ZSAmJiAodHlwZSA9PSAnb2JqZWN0JyB8fCB0eXBlID09ICdmdW5jdGlvbicpO1xufVxuXG4vKipcbiAqIENoZWNrcyBpZiBgdmFsdWVgIGlzIG9iamVjdC1saWtlLiBBIHZhbHVlIGlzIG9iamVjdC1saWtlIGlmIGl0J3Mgbm90IGBudWxsYFxuICogYW5kIGhhcyBhIGB0eXBlb2ZgIHJlc3VsdCBvZiBcIm9iamVjdFwiLlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAc2luY2UgNC4wLjBcbiAqIEBjYXRlZ29yeSBMYW5nXG4gKiBAcGFyYW0geyp9IHZhbHVlIFRoZSB2YWx1ZSB0byBjaGVjay5cbiAqIEByZXR1cm5zIHtib29sZWFufSBSZXR1cm5zIGB0cnVlYCBpZiBgdmFsdWVgIGlzIG9iamVjdC1saWtlLCBlbHNlIGBmYWxzZWAuXG4gKiBAZXhhbXBsZVxuICpcbiAqIF8uaXNPYmplY3RMaWtlKHt9KTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmlzT2JqZWN0TGlrZShbMSwgMiwgM10pO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNPYmplY3RMaWtlKF8ubm9vcCk7XG4gKiAvLyA9PiBmYWxzZVxuICpcbiAqIF8uaXNPYmplY3RMaWtlKG51bGwpO1xuICogLy8gPT4gZmFsc2VcbiAqL1xuZnVuY3Rpb24gaXNPYmplY3RMaWtlKHZhbHVlKSB7XG4gIHJldHVybiAhIXZhbHVlICYmIHR5cGVvZiB2YWx1ZSA9PSAnb2JqZWN0Jztcbn1cblxuLyoqXG4gKiBDaGVja3MgaWYgYHZhbHVlYCBpcyBjbGFzc2lmaWVkIGFzIGEgYFN5bWJvbGAgcHJpbWl0aXZlIG9yIG9iamVjdC5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDQuMC4wXG4gKiBAY2F0ZWdvcnkgTGFuZ1xuICogQHBhcmFtIHsqfSB2YWx1ZSBUaGUgdmFsdWUgdG8gY2hlY2suXG4gKiBAcmV0dXJucyB7Ym9vbGVhbn0gUmV0dXJucyBgdHJ1ZWAgaWYgYHZhbHVlYCBpcyBhIHN5bWJvbCwgZWxzZSBgZmFsc2VgLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLmlzU3ltYm9sKFN5bWJvbC5pdGVyYXRvcik7XG4gKiAvLyA9PiB0cnVlXG4gKlxuICogXy5pc1N5bWJvbCgnYWJjJyk7XG4gKiAvLyA9PiBmYWxzZVxuICovXG5mdW5jdGlvbiBpc1N5bWJvbCh2YWx1ZSkge1xuICByZXR1cm4gdHlwZW9mIHZhbHVlID09ICdzeW1ib2wnIHx8XG4gICAgKGlzT2JqZWN0TGlrZSh2YWx1ZSkgJiYgb2JqZWN0VG9TdHJpbmcuY2FsbCh2YWx1ZSkgPT0gc3ltYm9sVGFnKTtcbn1cblxuLyoqXG4gKiBDb252ZXJ0cyBgdmFsdWVgIHRvIGEgbnVtYmVyLlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAc2luY2UgNC4wLjBcbiAqIEBjYXRlZ29yeSBMYW5nXG4gKiBAcGFyYW0geyp9IHZhbHVlIFRoZSB2YWx1ZSB0byBwcm9jZXNzLlxuICogQHJldHVybnMge251bWJlcn0gUmV0dXJucyB0aGUgbnVtYmVyLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLnRvTnVtYmVyKDMuMik7XG4gKiAvLyA9PiAzLjJcbiAqXG4gKiBfLnRvTnVtYmVyKE51bWJlci5NSU5fVkFMVUUpO1xuICogLy8gPT4gNWUtMzI0XG4gKlxuICogXy50b051bWJlcihJbmZpbml0eSk7XG4gKiAvLyA9PiBJbmZpbml0eVxuICpcbiAqIF8udG9OdW1iZXIoJzMuMicpO1xuICogLy8gPT4gMy4yXG4gKi9cbmZ1bmN0aW9uIHRvTnVtYmVyKHZhbHVlKSB7XG4gIGlmICh0eXBlb2YgdmFsdWUgPT0gJ251bWJlcicpIHtcbiAgICByZXR1cm4gdmFsdWU7XG4gIH1cbiAgaWYgKGlzU3ltYm9sKHZhbHVlKSkge1xuICAgIHJldHVybiBOQU47XG4gIH1cbiAgaWYgKGlzT2JqZWN0KHZhbHVlKSkge1xuICAgIHZhciBvdGhlciA9IHR5cGVvZiB2YWx1ZS52YWx1ZU9mID09ICdmdW5jdGlvbicgPyB2YWx1ZS52YWx1ZU9mKCkgOiB2YWx1ZTtcbiAgICB2YWx1ZSA9IGlzT2JqZWN0KG90aGVyKSA/IChvdGhlciArICcnKSA6IG90aGVyO1xuICB9XG4gIGlmICh0eXBlb2YgdmFsdWUgIT0gJ3N0cmluZycpIHtcbiAgICByZXR1cm4gdmFsdWUgPT09IDAgPyB2YWx1ZSA6ICt2YWx1ZTtcbiAgfVxuICB2YWx1ZSA9IHZhbHVlLnJlcGxhY2UocmVUcmltLCAnJyk7XG4gIHZhciBpc0JpbmFyeSA9IHJlSXNCaW5hcnkudGVzdCh2YWx1ZSk7XG4gIHJldHVybiAoaXNCaW5hcnkgfHwgcmVJc09jdGFsLnRlc3QodmFsdWUpKVxuICAgID8gZnJlZVBhcnNlSW50KHZhbHVlLnNsaWNlKDIpLCBpc0JpbmFyeSA/IDIgOiA4KVxuICAgIDogKHJlSXNCYWRIZXgudGVzdCh2YWx1ZSkgPyBOQU4gOiArdmFsdWUpO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IHRocm90dGxlO1xuIiwiXCJ1c2Ugc3RyaWN0XCI7XG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBcIl9fZXNNb2R1bGVcIiwgeyB2YWx1ZTogdHJ1ZSB9KTtcbmNvbnN0IHNldHRpbmdzXzEgPSByZXF1aXJlKFwiLi9zZXR0aW5nc1wiKTtcbmV4cG9ydHMuY3JlYXRlUG9zdGVyRm9yVnNDb2RlID0gKHZzY29kZSkgPT4ge1xuICAgIHJldHVybiBuZXcgY2xhc3Mge1xuICAgICAgICBwb3N0TWVzc2FnZSh0eXBlLCBib2R5KSB7XG4gICAgICAgICAgICB2c2NvZGUucG9zdE1lc3NhZ2Uoe1xuICAgICAgICAgICAgICAgIHR5cGUsXG4gICAgICAgICAgICAgICAgc291cmNlOiBzZXR0aW5nc18xLmdldFNldHRpbmdzKCkuc291cmNlLFxuICAgICAgICAgICAgICAgIGJvZHlcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9XG4gICAgfTtcbn07XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuZnVuY3Rpb24gb25jZURvY3VtZW50TG9hZGVkKGYpIHtcbiAgICBpZiAoZG9jdW1lbnQucmVhZHlTdGF0ZSA9PT0gJ2xvYWRpbmcnIHx8IGRvY3VtZW50LnJlYWR5U3RhdGUgPT09ICd1bmluaXRpYWxpemVkJykge1xuICAgICAgICBkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdET01Db250ZW50TG9hZGVkJywgZik7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICBmKCk7XG4gICAgfVxufVxuZXhwb3J0cy5vbmNlRG9jdW1lbnRMb2FkZWQgPSBvbmNlRG9jdW1lbnRMb2FkZWQ7XG4iLCJcInVzZSBzdHJpY3RcIjtcbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBcIl9fZXNNb2R1bGVcIiwgeyB2YWx1ZTogdHJ1ZSB9KTtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuY29uc3Qgc2Nyb2xsX3N5bmNfMSA9IHJlcXVpcmUoXCIuL3Njcm9sbC1zeW5jXCIpO1xuY2xhc3MgQWN0aXZlTGluZU1hcmtlciB7XG4gICAgb25EaWRDaGFuZ2VUZXh0RWRpdG9yU2VsZWN0aW9uKGxpbmUpIHtcbiAgICAgICAgY29uc3QgeyBwcmV2aW91cyB9ID0gc2Nyb2xsX3N5bmNfMS5nZXRFbGVtZW50c0ZvclNvdXJjZUxpbmUobGluZSk7XG4gICAgICAgIHRoaXMuX3VwZGF0ZShwcmV2aW91cyAmJiBwcmV2aW91cy5lbGVtZW50KTtcbiAgICB9XG4gICAgX3VwZGF0ZShiZWZvcmUpIHtcbiAgICAgICAgdGhpcy5fdW5tYXJrQWN0aXZlRWxlbWVudCh0aGlzLl9jdXJyZW50KTtcbiAgICAgICAgdGhpcy5fbWFya0FjdGl2ZUVsZW1lbnQoYmVmb3JlKTtcbiAgICAgICAgdGhpcy5fY3VycmVudCA9IGJlZm9yZTtcbiAgICB9XG4gICAgX3VubWFya0FjdGl2ZUVsZW1lbnQoZWxlbWVudCkge1xuICAgICAgICBpZiAoIWVsZW1lbnQpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICBlbGVtZW50LmNsYXNzTmFtZSA9IGVsZW1lbnQuY2xhc3NOYW1lLnJlcGxhY2UoL1xcYmNvZGUtYWN0aXZlLWxpbmVcXGIvZywgJycpO1xuICAgIH1cbiAgICBfbWFya0FjdGl2ZUVsZW1lbnQoZWxlbWVudCkge1xuICAgICAgICBpZiAoIWVsZW1lbnQpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICBlbGVtZW50LmNsYXNzTmFtZSArPSAnIGNvZGUtYWN0aXZlLWxpbmUnO1xuICAgIH1cbn1cbmV4cG9ydHMuQWN0aXZlTGluZU1hcmtlciA9IEFjdGl2ZUxpbmVNYXJrZXI7XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuY29uc3QgYWN0aXZlTGluZU1hcmtlcl8xID0gcmVxdWlyZShcIi4vYWN0aXZlTGluZU1hcmtlclwiKTtcbmNvbnN0IGV2ZW50c18xID0gcmVxdWlyZShcIi4vZXZlbnRzXCIpO1xuY29uc3QgbWVzc2FnaW5nXzEgPSByZXF1aXJlKFwiLi9tZXNzYWdpbmdcIik7XG5jb25zdCBzY3JvbGxfc3luY18xID0gcmVxdWlyZShcIi4vc2Nyb2xsLXN5bmNcIik7XG5jb25zdCBzZXR0aW5nc18xID0gcmVxdWlyZShcIi4vc2V0dGluZ3NcIik7XG5jb25zdCB0aHJvdHRsZSA9IHJlcXVpcmUoXCJsb2Rhc2gudGhyb3R0bGVcIik7XG5sZXQgc2Nyb2xsRGlzYWJsZWQgPSB0cnVlO1xuY29uc3QgbWFya2VyID0gbmV3IGFjdGl2ZUxpbmVNYXJrZXJfMS5BY3RpdmVMaW5lTWFya2VyKCk7XG5jb25zdCBzZXR0aW5ncyA9IHNldHRpbmdzXzEuZ2V0U2V0dGluZ3MoKTtcbmNvbnN0IHZzY29kZSA9IGFjcXVpcmVWc0NvZGVBcGkoKTtcbi8vIFNldCBWUyBDb2RlIHN0YXRlXG5sZXQgc3RhdGUgPSBzZXR0aW5nc18xLmdldERhdGEoJ2RhdGEtc3RhdGUnKTtcbnZzY29kZS5zZXRTdGF0ZShzdGF0ZSk7XG5jb25zdCBtZXNzYWdpbmcgPSBtZXNzYWdpbmdfMS5jcmVhdGVQb3N0ZXJGb3JWc0NvZGUodnNjb2RlKTtcbndpbmRvdy5jc3BBbGVydGVyLnNldFBvc3RlcihtZXNzYWdpbmcpO1xud2luZG93LnN0eWxlTG9hZGluZ01vbml0b3Iuc2V0UG9zdGVyKG1lc3NhZ2luZyk7XG53aW5kb3cub25sb2FkID0gKCkgPT4ge1xuICAgIHVwZGF0ZUltYWdlU2l6ZXMoKTtcbn07XG5ldmVudHNfMS5vbmNlRG9jdW1lbnRMb2FkZWQoKCkgPT4ge1xuICAgIGlmIChzZXR0aW5ncy5zY3JvbGxQcmV2aWV3V2l0aEVkaXRvcikge1xuICAgICAgICBzZXRUaW1lb3V0KCgpID0+IHtcbiAgICAgICAgICAgIC8vIFRyeSB0byBzY3JvbGwgdG8gZnJhZ21lbnQgaWYgYXZhaWxhYmxlXG4gICAgICAgICAgICBpZiAoc3RhdGUuZnJhZ21lbnQpIHtcbiAgICAgICAgICAgICAgICBjb25zdCBlbGVtZW50ID0gc2Nyb2xsX3N5bmNfMS5nZXRMaW5lRWxlbWVudEZvckZyYWdtZW50KHN0YXRlLmZyYWdtZW50KTtcbiAgICAgICAgICAgICAgICBpZiAoZWxlbWVudCkge1xuICAgICAgICAgICAgICAgICAgICBzY3JvbGxEaXNhYmxlZCA9IHRydWU7XG4gICAgICAgICAgICAgICAgICAgIHNjcm9sbF9zeW5jXzEuc2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lKGVsZW1lbnQubGluZSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgY29uc3QgaW5pdGlhbExpbmUgPSArc2V0dGluZ3MubGluZTtcbiAgICAgICAgICAgICAgICBpZiAoIWlzTmFOKGluaXRpYWxMaW5lKSkge1xuICAgICAgICAgICAgICAgICAgICBzY3JvbGxEaXNhYmxlZCA9IHRydWU7XG4gICAgICAgICAgICAgICAgICAgIHNjcm9sbF9zeW5jXzEuc2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lKGluaXRpYWxMaW5lKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH0sIDApO1xuICAgIH1cbn0pO1xuY29uc3Qgb25VcGRhdGVWaWV3ID0gKCgpID0+IHtcbiAgICBjb25zdCBkb1Njcm9sbCA9IHRocm90dGxlKChsaW5lKSA9PiB7XG4gICAgICAgIHNjcm9sbERpc2FibGVkID0gdHJ1ZTtcbiAgICAgICAgc2Nyb2xsX3N5bmNfMS5zY3JvbGxUb1JldmVhbFNvdXJjZUxpbmUobGluZSk7XG4gICAgfSwgNTApO1xuICAgIHJldHVybiAobGluZSwgc2V0dGluZ3MpID0+IHtcbiAgICAgICAgaWYgKCFpc05hTihsaW5lKSkge1xuICAgICAgICAgICAgc2V0dGluZ3MubGluZSA9IGxpbmU7XG4gICAgICAgICAgICBkb1Njcm9sbChsaW5lKTtcbiAgICAgICAgfVxuICAgIH07XG59KSgpO1xubGV0IHVwZGF0ZUltYWdlU2l6ZXMgPSB0aHJvdHRsZSgoKSA9PiB7XG4gICAgY29uc3QgaW1hZ2VJbmZvID0gW107XG4gICAgbGV0IGltYWdlcyA9IGRvY3VtZW50LmdldEVsZW1lbnRzQnlUYWdOYW1lKCdpbWcnKTtcbiAgICBpZiAoaW1hZ2VzKSB7XG4gICAgICAgIGxldCBpO1xuICAgICAgICBmb3IgKGkgPSAwOyBpIDwgaW1hZ2VzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICBjb25zdCBpbWcgPSBpbWFnZXNbaV07XG4gICAgICAgICAgICBpZiAoaW1nLmNsYXNzTGlzdC5jb250YWlucygnbG9hZGluZycpKSB7XG4gICAgICAgICAgICAgICAgaW1nLmNsYXNzTGlzdC5yZW1vdmUoJ2xvYWRpbmcnKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGltYWdlSW5mby5wdXNoKHtcbiAgICAgICAgICAgICAgICBpZDogaW1nLmlkLFxuICAgICAgICAgICAgICAgIGhlaWdodDogaW1nLmhlaWdodCxcbiAgICAgICAgICAgICAgICB3aWR0aDogaW1nLndpZHRoXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgICAgICBtZXNzYWdpbmcucG9zdE1lc3NhZ2UoJ2NhY2hlSW1hZ2VTaXplcycsIGltYWdlSW5mbyk7XG4gICAgfVxufSwgNTApO1xud2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ3Jlc2l6ZScsICgpID0+IHtcbiAgICBzY3JvbGxEaXNhYmxlZCA9IHRydWU7XG4gICAgdXBkYXRlSW1hZ2VTaXplcygpO1xufSwgdHJ1ZSk7XG53aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbWVzc2FnZScsIGV2ZW50ID0+IHtcbiAgICBpZiAoZXZlbnQuZGF0YS5zb3VyY2UgIT09IHNldHRpbmdzLnNvdXJjZSkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIHN3aXRjaCAoZXZlbnQuZGF0YS50eXBlKSB7XG4gICAgICAgIGNhc2UgJ29uRGlkQ2hhbmdlVGV4dEVkaXRvclNlbGVjdGlvbic6XG4gICAgICAgICAgICBtYXJrZXIub25EaWRDaGFuZ2VUZXh0RWRpdG9yU2VsZWN0aW9uKGV2ZW50LmRhdGEubGluZSk7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgY2FzZSAndXBkYXRlVmlldyc6XG4gICAgICAgICAgICBvblVwZGF0ZVZpZXcoZXZlbnQuZGF0YS5saW5lLCBzZXR0aW5ncyk7XG4gICAgICAgICAgICBicmVhaztcbiAgICB9XG59LCBmYWxzZSk7XG5kb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdkYmxjbGljaycsIGV2ZW50ID0+IHtcbiAgICBpZiAoIXNldHRpbmdzLmRvdWJsZUNsaWNrVG9Td2l0Y2hUb0VkaXRvcikge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIC8vIElnbm9yZSBjbGlja3Mgb24gbGlua3NcbiAgICBmb3IgKGxldCBub2RlID0gZXZlbnQudGFyZ2V0OyBub2RlOyBub2RlID0gbm9kZS5wYXJlbnROb2RlKSB7XG4gICAgICAgIGlmIChub2RlLnRhZ05hbWUgPT09ICdBJykge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgfVxuICAgIGNvbnN0IG9mZnNldCA9IGV2ZW50LnBhZ2VZO1xuICAgIGNvbnN0IGxpbmUgPSBzY3JvbGxfc3luY18xLmdldEVkaXRvckxpbmVOdW1iZXJGb3JQYWdlT2Zmc2V0KG9mZnNldCk7XG4gICAgaWYgKHR5cGVvZiBsaW5lID09PSAnbnVtYmVyJyAmJiAhaXNOYU4obGluZSkpIHtcbiAgICAgICAgbWVzc2FnaW5nLnBvc3RNZXNzYWdlKCdkaWRDbGljaycsIHsgbGluZTogTWF0aC5mbG9vcihsaW5lKSB9KTtcbiAgICB9XG59KTtcbmNvbnN0IHBhc3NUaHJvdWdoTGlua1NjaGVtZXMgPSBbJ2h0dHA6JywgJ2h0dHBzOicsICdtYWlsdG86JywgJ3ZzY29kZTonLCAndnNjb2RlLWluc2lkZXJzJ107XG5kb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsIGV2ZW50ID0+IHtcbiAgICBpZiAoIWV2ZW50KSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgbGV0IG5vZGUgPSBldmVudC50YXJnZXQ7XG4gICAgd2hpbGUgKG5vZGUpIHtcbiAgICAgICAgaWYgKG5vZGUudGFnTmFtZSAmJiBub2RlLnRhZ05hbWUgPT09ICdBJyAmJiBub2RlLmhyZWYpIHtcbiAgICAgICAgICAgIGlmIChub2RlLmdldEF0dHJpYnV0ZSgnaHJlZicpLnN0YXJ0c1dpdGgoJyMnKSkge1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIC8vIFBhc3MgdGhyb3VnaCBrbm93biBzY2hlbWVzXG4gICAgICAgICAgICBpZiAocGFzc1Rocm91Z2hMaW5rU2NoZW1lcy5zb21lKHNjaGVtZSA9PiBub2RlLmhyZWYuc3RhcnRzV2l0aChzY2hlbWUpKSkge1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGNvbnN0IGhyZWZUZXh0ID0gbm9kZS5nZXRBdHRyaWJ1dGUoJ2RhdGEtaHJlZicpIHx8IG5vZGUuZ2V0QXR0cmlidXRlKCdocmVmJyk7XG4gICAgICAgICAgICAvLyBJZiBvcmlnaW5hbCBsaW5rIGRvZXNuJ3QgbG9vayBsaWtlIGEgdXJsLCBkZWxlZ2F0ZSBiYWNrIHRvIFZTIENvZGUgdG8gcmVzb2x2ZVxuICAgICAgICAgICAgaWYgKCEvXlthLXpcXC1dKzovaS50ZXN0KGhyZWZUZXh0KSkge1xuICAgICAgICAgICAgICAgIG1lc3NhZ2luZy5wb3N0TWVzc2FnZSgnb3BlbkxpbmsnLCB7IGhyZWY6IGhyZWZUZXh0IH0pO1xuICAgICAgICAgICAgICAgIGV2ZW50LnByZXZlbnREZWZhdWx0KCk7XG4gICAgICAgICAgICAgICAgZXZlbnQuc3RvcFByb3BhZ2F0aW9uKCk7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgICAgIG5vZGUgPSBub2RlLnBhcmVudE5vZGU7XG4gICAgfVxufSwgdHJ1ZSk7XG53aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignc2Nyb2xsJywgdGhyb3R0bGUoKCkgPT4ge1xuICAgIGlmIChzY3JvbGxEaXNhYmxlZCkge1xuICAgICAgICBzY3JvbGxEaXNhYmxlZCA9IGZhbHNlO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgICAgY29uc3QgbGluZSA9IHNjcm9sbF9zeW5jXzEuZ2V0RWRpdG9yTGluZU51bWJlckZvclBhZ2VPZmZzZXQod2luZG93LnNjcm9sbFkpO1xuICAgICAgICBpZiAodHlwZW9mIGxpbmUgPT09ICdudW1iZXInICYmICFpc05hTihsaW5lKSkge1xuICAgICAgICAgICAgbWVzc2FnaW5nLnBvc3RNZXNzYWdlKCdyZXZlYWxMaW5lJywgeyBsaW5lIH0pO1xuICAgICAgICAgICAgc3RhdGUubGluZSA9IGxpbmU7XG4gICAgICAgICAgICB2c2NvZGUuc2V0U3RhdGUoc3RhdGUpO1xuICAgICAgICB9XG4gICAgfVxufSwgNTApKTtcbiJdLCJzb3VyY2VSb290IjoiIn0= +!function(e){var t={};function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(o,r,function(t){return e[t]}.bind(null,r));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=2)}([function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});let o=void 0;function r(e){const t=document.getElementById("vscode-markdown-preview-data");if(t){const n=t.getAttribute(e);if(n)return JSON.parse(n)}throw new Error(`Could not load data for ${e}`)}t.getData=r,t.getSettings=function(){if(o)return o;if(o=r("data-settings"))return o;throw new Error("Could not load settings")}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(0),r="code-line";function i(e){return t=0,n=o.getSettings().lineCount-1,r=e,Math.min(n,Math.max(t,r));var t,n,r}const s=(()=>{let e;return()=>{if(!e){e=[{element:document.body,line:0}];for(const t of document.getElementsByClassName(r)){const n=+t.getAttribute("data-line");isNaN(n)||("CODE"===t.tagName&&t.parentElement&&"PRE"===t.parentElement.tagName?e.push({element:t.parentElement,line:n}):e.push({element:t,line:n}))}}return e}})();function a(e){const t=Math.floor(e),n=s();let o=n[0]||null;for(const e of n){if(e.line===t)return{previous:e,next:void 0};if(e.line>t)return{previous:o,next:e};o=e}return{previous:o}}function c(e){const t=s(),n=e-window.scrollY;let o=-1,r=t.length-1;for(;o+1=n?r=e:o=e}const i=t[r],a=u(i);if(r>=1&&a.top>n){return{previous:t[o],next:i}}return r>1&&rn?{previous:i,next:t[r+1]}:{previous:i}}function u({element:e}){const t=e.getBoundingClientRect(),n=e.querySelector(`.${r}`);if(n){const e=n.getBoundingClientRect(),o=Math.max(1,e.top-t.top);return{top:t.top,height:o}}return t}t.getElementsForSourceLine=a,t.getLineElementsAtPageOffset=c,t.scrollToRevealSourceLine=function(e){if(!o.getSettings().scrollPreviewWithEditor)return;if(e<=0)return void window.scroll(window.scrollX,0);const{previous:t,next:n}=a(e);if(!t)return;let r=0;const i=u(t),s=i.top;if(n&&n.line!==t.line){r=s+(e-t.line)/(n.line-t.line)*(n.element.getBoundingClientRect().top-s)}else{const t=e-Math.floor(e);r=s+i.height*t}window.scroll(window.scrollX,Math.max(1,window.scrollY+r))},t.getEditorLineNumberForPageOffset=function(e){const{previous:t,next:n}=c(e);if(t){const o=u(t),r=e-window.scrollY-o.top;if(n){const e=r/(u(n).top-o.top);return i(t.line+e*(n.line-t.line))}{const e=r/o.height;return i(t.line+e)}}return null},t.getLineElementForFragment=function(e){return s().find(t=>t.element.id===e)}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(3),r=n(4),i=n(5),s=n(1),a=n(0),c=n(6);let u=!0;const l=new o.ActiveLineMarker,f=a.getSettings(),d=acquireVsCodeApi();let g=a.getData("data-state");d.setState(g);const p=i.createPosterForVsCode(d);window.cspAlerter.setPoster(p),window.styleLoadingMonitor.setPoster(p),window.onload=()=>{v()},r.onceDocumentLoaded(()=>{f.scrollPreviewWithEditor&&setTimeout(()=>{if(g.fragment){const e=s.getLineElementForFragment(g.fragment);e&&(u=!0,s.scrollToRevealSourceLine(e.line))}else{const e=+f.line;isNaN(e)||(u=!0,s.scrollToRevealSourceLine(e))}},0)});const m=(()=>{const e=c(e=>{u=!0,s.scrollToRevealSourceLine(e)},50);return(t,n)=>{isNaN(t)||(n.line=t,e(t))}})();let v=c(()=>{const e=[];let t=document.getElementsByTagName("img");if(t){let n;for(n=0;n{u=!0,v()},!0),window.addEventListener("message",e=>{if(e.data.source===f.source)switch(e.data.type){case"onDidChangeTextEditorSelection":l.onDidChangeTextEditorSelection(e.data.line);break;case"updateView":m(e.data.line,f)}},!1),document.addEventListener("dblclick",e=>{if(!f.doubleClickToSwitchToEditor)return;for(let t=e.target;t;t=t.parentNode)if("A"===t.tagName)return;const t=e.pageY,n=s.getEditorLineNumberForPageOffset(t);"number"!=typeof n||isNaN(n)||p.postMessage("didClick",{line:Math.floor(n)})});const h=["http:","https:","mailto:","vscode:","vscode-insiders:"];document.addEventListener("click",e=>{if(!e)return;let t=e.target;for(;t;){if(t.tagName&&"A"===t.tagName&&t.href){if(t.getAttribute("href").startsWith("#"))return;if(h.some(e=>t.href.startsWith(e)))return;const n=t.getAttribute("data-href")||t.getAttribute("href");return/^[a-z\-]+:/i.test(n)?void 0:(p.postMessage("openLink",{href:n}),e.preventDefault(),void e.stopPropagation())}t=t.parentNode}},!0),window.addEventListener("scroll",c(()=>{if(u)u=!1;else{const e=s.getEditorLineNumberForPageOffset(window.scrollY);"number"!=typeof e||isNaN(e)||(p.postMessage("revealLine",{line:e}),g.line=e,d.setState(g))}},50))},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(1);t.ActiveLineMarker=class{onDidChangeTextEditorSelection(e){const{previous:t}=o.getElementsForSourceLine(e);this._update(t&&t.element)}_update(e){this._unmarkActiveElement(this._current),this._markActiveElement(e),this._current=e}_unmarkActiveElement(e){e&&(e.className=e.className.replace(/\bcode-active-line\b/g,""))}_markActiveElement(e){e&&(e.className+=" code-active-line")}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.onceDocumentLoaded=function(e){"loading"===document.readyState||"uninitialized"===document.readyState?document.addEventListener("DOMContentLoaded",e):e()}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(0);t.createPosterForVsCode=e=>new class{postMessage(t,n){e.postMessage({type:t,source:o.getSettings().source,body:n})}}},function(e,t,n){(function(t){var n="Expected a function",o=NaN,r="[object Symbol]",i=/^\s+|\s+$/g,s=/^[-+]0x[0-9a-f]+$/i,a=/^0b[01]+$/i,c=/^0o[0-7]+$/i,u=parseInt,l="object"==typeof t&&t&&t.Object===Object&&t,f="object"==typeof self&&self&&self.Object===Object&&self,d=l||f||Function("return this")(),g=Object.prototype.toString,p=Math.max,m=Math.min,v=function(){return d.Date.now()};function h(e,t,o){var r,i,s,a,c,u,l=0,f=!1,d=!1,g=!0;if("function"!=typeof e)throw new TypeError(n);function h(t){var n=r,o=i;return r=i=void 0,l=t,a=e.apply(o,n)}function y(e){var n=e-u;return void 0===u||n>=t||n<0||d&&e-l>=s}function E(){var e=v();if(y(e))return M(e);c=setTimeout(E,function(e){var n=t-(e-u);return d?m(n,s-(e-l)):n}(e))}function M(e){return c=void 0,g&&r?h(e):(r=i=void 0,a)}function S(){var e=v(),n=y(e);if(r=arguments,i=this,u=e,n){if(void 0===c)return function(e){return l=e,c=setTimeout(E,t),f?h(e):a}(u);if(d)return c=setTimeout(E,t),h(u)}return void 0===c&&(c=setTimeout(E,t)),a}return t=b(t)||0,w(o)&&(f=!!o.leading,s=(d="maxWait"in o)?p(b(o.maxWait)||0,t):s,g="trailing"in o?!!o.trailing:g),S.cancel=function(){void 0!==c&&clearTimeout(c),l=0,r=u=i=c=void 0},S.flush=function(){return void 0===c?a:M(v())},S}function w(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}function b(e){if("number"==typeof e)return e;if(function(e){return"symbol"==typeof e||function(e){return!!e&&"object"==typeof e}(e)&&g.call(e)==r}(e))return o;if(w(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=w(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=e.replace(i,"");var n=a.test(e);return n||c.test(e)?u(e.slice(2),n?2:8):s.test(e)?o:+e}e.exports=function(e,t,o){var r=!0,i=!0;if("function"!=typeof e)throw new TypeError(n);return w(o)&&(r="leading"in o?!!o.leading:r,i="trailing"in o?!!o.trailing:i),h(e,t,{leading:r,maxWait:t,trailing:i})}}).call(this,n(7))},function(e,t){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(e){"object"==typeof window&&(n=window)}e.exports=n}]); +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvc2V0dGluZ3MudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvc2Nyb2xsLXN5bmMudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvaW5kZXgudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvYWN0aXZlTGluZU1hcmtlci50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9ldmVudHMudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvbWVzc2FnaW5nLnRzIiwid2VicGFjazovLy8uL25vZGVfbW9kdWxlcy9sb2Rhc2gudGhyb3R0bGUvaW5kZXguanMiLCJ3ZWJwYWNrOi8vLyh3ZWJwYWNrKS9idWlsZGluL2dsb2JhbC5qcyJdLCJuYW1lcyI6WyJpbnN0YWxsZWRNb2R1bGVzIiwiX193ZWJwYWNrX3JlcXVpcmVfXyIsIm1vZHVsZUlkIiwiZXhwb3J0cyIsIm1vZHVsZSIsImkiLCJsIiwibW9kdWxlcyIsImNhbGwiLCJtIiwiYyIsImQiLCJuYW1lIiwiZ2V0dGVyIiwibyIsIk9iamVjdCIsImRlZmluZVByb3BlcnR5IiwiZW51bWVyYWJsZSIsImdldCIsInIiLCJTeW1ib2wiLCJ0b1N0cmluZ1RhZyIsInZhbHVlIiwidCIsIm1vZGUiLCJfX2VzTW9kdWxlIiwibnMiLCJjcmVhdGUiLCJrZXkiLCJiaW5kIiwibiIsIm9iamVjdCIsInByb3BlcnR5IiwicHJvdG90eXBlIiwiaGFzT3duUHJvcGVydHkiLCJwIiwicyIsImNhY2hlZFNldHRpbmdzIiwidW5kZWZpbmVkIiwiZ2V0RGF0YSIsImVsZW1lbnQiLCJkb2N1bWVudCIsImdldEVsZW1lbnRCeUlkIiwiZGF0YSIsImdldEF0dHJpYnV0ZSIsIkpTT04iLCJwYXJzZSIsIkVycm9yIiwiZ2V0U2V0dGluZ3MiLCJzZXR0aW5nc18xIiwiY29kZUxpbmVDbGFzcyIsImNsYW1wTGluZSIsImxpbmUiLCJtaW4iLCJtYXgiLCJsaW5lQ291bnQiLCJNYXRoIiwiZ2V0Q29kZUxpbmVFbGVtZW50cyIsImVsZW1lbnRzIiwiYm9keSIsImdldEVsZW1lbnRzQnlDbGFzc05hbWUiLCJpc05hTiIsInRhZ05hbWUiLCJwYXJlbnRFbGVtZW50IiwicHVzaCIsImdldEVsZW1lbnRzRm9yU291cmNlTGluZSIsInRhcmdldExpbmUiLCJsaW5lTnVtYmVyIiwiZmxvb3IiLCJsaW5lcyIsInByZXZpb3VzIiwiZW50cnkiLCJuZXh0IiwiZ2V0TGluZUVsZW1lbnRzQXRQYWdlT2Zmc2V0Iiwib2Zmc2V0IiwicG9zaXRpb24iLCJ3aW5kb3ciLCJzY3JvbGxZIiwibG8iLCJoaSIsImxlbmd0aCIsIm1pZCIsImJvdW5kcyIsImdldEVsZW1lbnRCb3VuZHMiLCJ0b3AiLCJoZWlnaHQiLCJoaUVsZW1lbnQiLCJoaUJvdW5kcyIsIm15Qm91bmRzIiwiZ2V0Qm91bmRpbmdDbGllbnRSZWN0IiwiY29kZUxpbmVDaGlsZCIsInF1ZXJ5U2VsZWN0b3IiLCJjaGlsZEJvdW5kcyIsInNjcm9sbFRvUmV2ZWFsU291cmNlTGluZSIsInNjcm9sbFByZXZpZXdXaXRoRWRpdG9yIiwic2Nyb2xsIiwic2Nyb2xsWCIsInNjcm9sbFRvIiwicmVjdCIsInByZXZpb3VzVG9wIiwicHJvZ3Jlc3NJbkVsZW1lbnQiLCJnZXRFZGl0b3JMaW5lTnVtYmVyRm9yUGFnZU9mZnNldCIsInByZXZpb3VzQm91bmRzIiwib2Zmc2V0RnJvbVByZXZpb3VzIiwicHJvZ3Jlc3NCZXR3ZWVuRWxlbWVudHMiLCJwcm9ncmVzc1dpdGhpbkVsZW1lbnQiLCJnZXRMaW5lRWxlbWVudEZvckZyYWdtZW50IiwiZnJhZ21lbnQiLCJmaW5kIiwiaWQiLCJhY3RpdmVMaW5lTWFya2VyXzEiLCJldmVudHNfMSIsIm1lc3NhZ2luZ18xIiwic2Nyb2xsX3N5bmNfMSIsInRocm90dGxlIiwic2Nyb2xsRGlzYWJsZWQiLCJtYXJrZXIiLCJBY3RpdmVMaW5lTWFya2VyIiwic2V0dGluZ3MiLCJ2c2NvZGUiLCJhY3F1aXJlVnNDb2RlQXBpIiwic3RhdGUiLCJzZXRTdGF0ZSIsIm1lc3NhZ2luZyIsImNyZWF0ZVBvc3RlckZvclZzQ29kZSIsImNzcEFsZXJ0ZXIiLCJzZXRQb3N0ZXIiLCJzdHlsZUxvYWRpbmdNb25pdG9yIiwib25sb2FkIiwidXBkYXRlSW1hZ2VTaXplcyIsIm9uY2VEb2N1bWVudExvYWRlZCIsInNldFRpbWVvdXQiLCJpbml0aWFsTGluZSIsIm9uVXBkYXRlVmlldyIsImRvU2Nyb2xsIiwiaW1hZ2VJbmZvIiwiaW1hZ2VzIiwiZ2V0RWxlbWVudHNCeVRhZ05hbWUiLCJpbWciLCJjbGFzc0xpc3QiLCJjb250YWlucyIsInJlbW92ZSIsIndpZHRoIiwicG9zdE1lc3NhZ2UiLCJhZGRFdmVudExpc3RlbmVyIiwiZXZlbnQiLCJzb3VyY2UiLCJ0eXBlIiwib25EaWRDaGFuZ2VUZXh0RWRpdG9yU2VsZWN0aW9uIiwiZG91YmxlQ2xpY2tUb1N3aXRjaFRvRWRpdG9yIiwibm9kZSIsInRhcmdldCIsInBhcmVudE5vZGUiLCJwYWdlWSIsInBhc3NUaHJvdWdoTGlua1NjaGVtZXMiLCJocmVmIiwic3RhcnRzV2l0aCIsInNvbWUiLCJzY2hlbWUiLCJocmVmVGV4dCIsInRlc3QiLCJwcmV2ZW50RGVmYXVsdCIsInN0b3BQcm9wYWdhdGlvbiIsInRoaXMiLCJfdXBkYXRlIiwiYmVmb3JlIiwiX3VubWFya0FjdGl2ZUVsZW1lbnQiLCJfY3VycmVudCIsIl9tYXJrQWN0aXZlRWxlbWVudCIsImNsYXNzTmFtZSIsInJlcGxhY2UiLCJmIiwicmVhZHlTdGF0ZSIsIkZVTkNfRVJST1JfVEVYVCIsIk5BTiIsInN5bWJvbFRhZyIsInJlVHJpbSIsInJlSXNCYWRIZXgiLCJyZUlzQmluYXJ5IiwicmVJc09jdGFsIiwiZnJlZVBhcnNlSW50IiwicGFyc2VJbnQiLCJmcmVlR2xvYmFsIiwiZ2xvYmFsIiwiZnJlZVNlbGYiLCJzZWxmIiwicm9vdCIsIkZ1bmN0aW9uIiwib2JqZWN0VG9TdHJpbmciLCJ0b1N0cmluZyIsIm5hdGl2ZU1heCIsIm5hdGl2ZU1pbiIsIm5vdyIsIkRhdGUiLCJkZWJvdW5jZSIsImZ1bmMiLCJ3YWl0Iiwib3B0aW9ucyIsImxhc3RBcmdzIiwibGFzdFRoaXMiLCJtYXhXYWl0IiwicmVzdWx0IiwidGltZXJJZCIsImxhc3RDYWxsVGltZSIsImxhc3RJbnZva2VUaW1lIiwibGVhZGluZyIsIm1heGluZyIsInRyYWlsaW5nIiwiVHlwZUVycm9yIiwiaW52b2tlRnVuYyIsInRpbWUiLCJhcmdzIiwidGhpc0FyZyIsImFwcGx5Iiwic2hvdWxkSW52b2tlIiwidGltZVNpbmNlTGFzdENhbGwiLCJ0aW1lckV4cGlyZWQiLCJ0cmFpbGluZ0VkZ2UiLCJyZW1haW5pbmdXYWl0IiwiZGVib3VuY2VkIiwiaXNJbnZva2luZyIsImFyZ3VtZW50cyIsImxlYWRpbmdFZGdlIiwidG9OdW1iZXIiLCJpc09iamVjdCIsImNhbmNlbCIsImNsZWFyVGltZW91dCIsImZsdXNoIiwiaXNPYmplY3RMaWtlIiwiaXNTeW1ib2wiLCJvdGhlciIsInZhbHVlT2YiLCJpc0JpbmFyeSIsInNsaWNlIiwiZyIsImUiXSwibWFwcGluZ3MiOiJhQUNFLElBQUlBLEVBQW1CLEdBR3ZCLFNBQVNDLEVBQW9CQyxHQUc1QixHQUFHRixFQUFpQkUsR0FDbkIsT0FBT0YsRUFBaUJFLEdBQVVDLFFBR25DLElBQUlDLEVBQVNKLEVBQWlCRSxHQUFZLENBQ3pDRyxFQUFHSCxFQUNISSxHQUFHLEVBQ0hILFFBQVMsSUFVVixPQU5BSSxFQUFRTCxHQUFVTSxLQUFLSixFQUFPRCxRQUFTQyxFQUFRQSxFQUFPRCxRQUFTRixHQUcvREcsRUFBT0UsR0FBSSxFQUdKRixFQUFPRCxRQUtmRixFQUFvQlEsRUFBSUYsRUFHeEJOLEVBQW9CUyxFQUFJVixFQUd4QkMsRUFBb0JVLEVBQUksU0FBU1IsRUFBU1MsRUFBTUMsR0FDM0NaLEVBQW9CYSxFQUFFWCxFQUFTUyxJQUNsQ0csT0FBT0MsZUFBZWIsRUFBU1MsRUFBTSxDQUFFSyxZQUFZLEVBQU1DLElBQUtMLEtBS2hFWixFQUFvQmtCLEVBQUksU0FBU2hCLEdBQ1gsb0JBQVhpQixRQUEwQkEsT0FBT0MsYUFDMUNOLE9BQU9DLGVBQWViLEVBQVNpQixPQUFPQyxZQUFhLENBQUVDLE1BQU8sV0FFN0RQLE9BQU9DLGVBQWViLEVBQVMsYUFBYyxDQUFFbUIsT0FBTyxLQVF2RHJCLEVBQW9Cc0IsRUFBSSxTQUFTRCxFQUFPRSxHQUV2QyxHQURVLEVBQVBBLElBQVVGLEVBQVFyQixFQUFvQnFCLElBQy9CLEVBQVBFLEVBQVUsT0FBT0YsRUFDcEIsR0FBVyxFQUFQRSxHQUE4QixpQkFBVkYsR0FBc0JBLEdBQVNBLEVBQU1HLFdBQVksT0FBT0gsRUFDaEYsSUFBSUksRUFBS1gsT0FBT1ksT0FBTyxNQUd2QixHQUZBMUIsRUFBb0JrQixFQUFFTyxHQUN0QlgsT0FBT0MsZUFBZVUsRUFBSSxVQUFXLENBQUVULFlBQVksRUFBTUssTUFBT0EsSUFDdEQsRUFBUEUsR0FBNEIsaUJBQVRGLEVBQW1CLElBQUksSUFBSU0sS0FBT04sRUFBT3JCLEVBQW9CVSxFQUFFZSxFQUFJRSxFQUFLLFNBQVNBLEdBQU8sT0FBT04sRUFBTU0sSUFBUUMsS0FBSyxLQUFNRCxJQUM5SSxPQUFPRixHQUlSekIsRUFBb0I2QixFQUFJLFNBQVMxQixHQUNoQyxJQUFJUyxFQUFTVCxHQUFVQSxFQUFPcUIsV0FDN0IsV0FBd0IsT0FBT3JCLEVBQWdCLFNBQy9DLFdBQThCLE9BQU9BLEdBRXRDLE9BREFILEVBQW9CVSxFQUFFRSxFQUFRLElBQUtBLEdBQzVCQSxHQUlSWixFQUFvQmEsRUFBSSxTQUFTaUIsRUFBUUMsR0FBWSxPQUFPakIsT0FBT2tCLFVBQVVDLGVBQWUxQixLQUFLdUIsRUFBUUMsSUFHekcvQixFQUFvQmtDLEVBQUksR0FJakJsQyxFQUFvQkEsRUFBb0JtQyxFQUFJLEcsK0JDN0VyRHJCLE9BQU9DLGVBQWViLEVBQVMsYUFBYyxDQUFFbUIsT0FBTyxJQUN0RCxJQUFJZSxPQUFpQkMsRUFDckIsU0FBU0MsRUFBUVgsR0FDYixNQUFNWSxFQUFVQyxTQUFTQyxlQUFlLGdDQUN4QyxHQUFJRixFQUFTLENBQ1QsTUFBTUcsRUFBT0gsRUFBUUksYUFBYWhCLEdBQ2xDLEdBQUllLEVBQ0EsT0FBT0UsS0FBS0MsTUFBTUgsR0FHMUIsTUFBTSxJQUFJSSxNQUFNLDJCQUEyQm5CLEtBRS9DekIsRUFBUW9DLFFBQVVBLEVBV2xCcEMsRUFBUTZDLFlBVlIsV0FDSSxHQUFJWCxFQUNBLE9BQU9BLEVBR1gsR0FEQUEsRUFBaUJFLEVBQVEsaUJBRXJCLE9BQU9GLEVBRVgsTUFBTSxJQUFJVSxNQUFNLDZCLDZCQ3JCcEJoQyxPQUFPQyxlQUFlYixFQUFTLGFBQWMsQ0FBRW1CLE9BQU8sSUFDdEQsTUFBTTJCLEVBQWEsRUFBUSxHQUNyQkMsRUFBZ0IsWUFJdEIsU0FBU0MsRUFBVUMsR0FDZixPQUpXQyxFQUlFLEVBSkdDLEVBSUFMLEVBQVdELGNBQWNPLFVBQVksRUFKaENqQyxFQUltQzhCLEVBSGpESSxLQUFLSCxJQUFJQyxFQUFLRSxLQUFLRixJQUFJRCxFQUFLL0IsSUFEdkMsSUFBZStCLEVBQUtDLEVBQUtoQyxFQU16QixNQUFNbUMsRUFBc0IsTUFDeEIsSUFBSUMsRUFDSixNQUFPLEtBQ0gsSUFBS0EsRUFBVSxDQUNYQSxFQUFXLENBQUMsQ0FBRWxCLFFBQVNDLFNBQVNrQixLQUFNUCxLQUFNLElBQzVDLElBQUssTUFBTVosS0FBV0MsU0FBU21CLHVCQUF1QlYsR0FBZ0IsQ0FDbEUsTUFBTUUsR0FBUVosRUFBUUksYUFBYSxhQUMvQmlCLE1BQU1ULEtBR2MsU0FBcEJaLEVBQVFzQixTQUFzQnRCLEVBQVF1QixlQUFtRCxRQUFsQ3ZCLEVBQVF1QixjQUFjRCxRQUc3RUosRUFBU00sS0FBSyxDQUFFeEIsUUFBU0EsRUFBUXVCLGNBQWVYLFNBR2hETSxFQUFTTSxLQUFLLENBQUV4QixRQUFTQSxFQUFTWSxXQUk5QyxPQUFPTSxJQXBCYSxHQTZCNUIsU0FBU08sRUFBeUJDLEdBQzlCLE1BQU1DLEVBQWFYLEtBQUtZLE1BQU1GLEdBQ3hCRyxFQUFRWixJQUNkLElBQUlhLEVBQVdELEVBQU0sSUFBTSxLQUMzQixJQUFLLE1BQU1FLEtBQVNGLEVBQU8sQ0FDdkIsR0FBSUUsRUFBTW5CLE9BQVNlLEVBQ2YsTUFBTyxDQUFFRyxTQUFVQyxFQUFPQyxVQUFNbEMsR0FFL0IsR0FBSWlDLEVBQU1uQixLQUFPZSxFQUNsQixNQUFPLENBQUVHLFdBQVVFLEtBQU1ELEdBRTdCRCxFQUFXQyxFQUVmLE1BQU8sQ0FBRUQsWUFNYixTQUFTRyxFQUE0QkMsR0FDakMsTUFBTUwsRUFBUVosSUFDUmtCLEVBQVdELEVBQVNFLE9BQU9DLFFBQ2pDLElBQUlDLEdBQU0sRUFDTkMsRUFBS1YsRUFBTVcsT0FBUyxFQUN4QixLQUFPRixFQUFLLEVBQUlDLEdBQUksQ0FDaEIsTUFBTUUsRUFBTXpCLEtBQUtZLE9BQU9VLEVBQUtDLEdBQU0sR0FDN0JHLEVBQVNDLEVBQWlCZCxFQUFNWSxJQUNsQ0MsRUFBT0UsSUFBTUYsRUFBT0csUUFBVVYsRUFDOUJJLEVBQUtFLEVBR0xILEVBQUtHLEVBR2IsTUFBTUssRUFBWWpCLEVBQU1VLEdBQ2xCUSxFQUFXSixFQUFpQkcsR0FDbEMsR0FBSVAsR0FBTSxHQUFLUSxFQUFTSCxJQUFNVCxFQUFVLENBRXBDLE1BQU8sQ0FBRUwsU0FEU0QsRUFBTVMsR0FDTU4sS0FBTWMsR0FFeEMsT0FBSVAsRUFBSyxHQUFLQSxFQUFLVixFQUFNVyxRQUFVTyxFQUFTSCxJQUFNRyxFQUFTRixPQUFTVixFQUN6RCxDQUFFTCxTQUFVZ0IsRUFBV2QsS0FBTUgsRUFBTVUsRUFBSyxJQUU1QyxDQUFFVCxTQUFVZ0IsR0FHdkIsU0FBU0gsR0FBaUIsUUFBRTNDLElBQ3hCLE1BQU1nRCxFQUFXaEQsRUFBUWlELHdCQUduQkMsRUFBZ0JsRCxFQUFRbUQsY0FBYyxJQUFJekMsS0FDaEQsR0FBSXdDLEVBQWUsQ0FDZixNQUFNRSxFQUFjRixFQUFjRCx3QkFDNUJKLEVBQVM3QixLQUFLRixJQUFJLEVBQUlzQyxFQUFZUixJQUFNSSxFQUFTSixLQUN2RCxNQUFPLENBQ0hBLElBQUtJLEVBQVNKLElBQ2RDLE9BQVFBLEdBR2hCLE9BQU9HLEVBNUNYckYsRUFBUThELHlCQUEyQkEsRUE4Qm5DOUQsRUFBUXNFLDRCQUE4QkEsRUE4Q3RDdEUsRUFBUTBGLHlCQTNCUixTQUFrQ3pDLEdBQzlCLElBQUtILEVBQVdELGNBQWM4Qyx3QkFDMUIsT0FFSixHQUFJMUMsR0FBUSxFQUVSLFlBREF3QixPQUFPbUIsT0FBT25CLE9BQU9vQixRQUFTLEdBR2xDLE1BQU0sU0FBRTFCLEVBQVEsS0FBRUUsR0FBU1AsRUFBeUJiLEdBQ3BELElBQUtrQixFQUNELE9BRUosSUFBSTJCLEVBQVcsRUFDZixNQUFNQyxFQUFPZixFQUFpQmIsR0FDeEI2QixFQUFjRCxFQUFLZCxJQUN6QixHQUFJWixHQUFRQSxFQUFLcEIsT0FBU2tCLEVBQVNsQixLQUFNLENBSXJDNkMsRUFBV0UsR0FGYy9DLEVBQU9rQixFQUFTbEIsT0FBU29CLEVBQUtwQixLQUFPa0IsRUFBU2xCLE9BQ2pEb0IsRUFBS2hDLFFBQVFpRCx3QkFBd0JMLElBQU1lLE9BR2hFLENBQ0QsTUFBTUMsRUFBb0JoRCxFQUFPSSxLQUFLWSxNQUFNaEIsR0FDNUM2QyxFQUFXRSxFQUFlRCxFQUFLYixPQUFTZSxFQUU1Q3hCLE9BQU9tQixPQUFPbkIsT0FBT29CLFFBQVN4QyxLQUFLRixJQUFJLEVBQUdzQixPQUFPQyxRQUFVb0IsS0FxQi9EOUYsRUFBUWtHLGlDQWxCUixTQUEwQzNCLEdBQ3RDLE1BQU0sU0FBRUosRUFBUSxLQUFFRSxHQUFTQyxFQUE0QkMsR0FDdkQsR0FBSUosRUFBVSxDQUNWLE1BQU1nQyxFQUFpQm5CLEVBQWlCYixHQUNsQ2lDLEVBQXNCN0IsRUFBU0UsT0FBT0MsUUFBVXlCLEVBQWVsQixJQUNyRSxHQUFJWixFQUFNLENBQ04sTUFBTWdDLEVBQTBCRCxHQUFzQnBCLEVBQWlCWCxHQUFNWSxJQUFNa0IsRUFBZWxCLEtBRWxHLE9BQU9qQyxFQURNbUIsRUFBU2xCLEtBQU9vRCxHQUEyQmhDLEVBQUtwQixLQUFPa0IsRUFBU2xCLE9BRzVFLENBQ0QsTUFBTXFELEVBQXdCRixFQUFzQkQsRUFBcUIsT0FFekUsT0FBT25ELEVBRE1tQixFQUFTbEIsS0FBT3FELElBSXJDLE9BQU8sTUFXWHRHLEVBQVF1RywwQkFMUixTQUFtQ0MsR0FDL0IsT0FBT2xELElBQXNCbUQsS0FBTXBFLEdBQ3hCQSxFQUFRQSxRQUFRcUUsS0FBT0YsSyw2QkMxSnRDNUYsT0FBT0MsZUFBZWIsRUFBUyxhQUFjLENBQUVtQixPQUFPLElBQ3RELE1BQU13RixFQUFxQixFQUFRLEdBQzdCQyxFQUFXLEVBQVEsR0FDbkJDLEVBQWMsRUFBUSxHQUN0QkMsRUFBZ0IsRUFBUSxHQUN4QmhFLEVBQWEsRUFBUSxHQUNyQmlFLEVBQVcsRUFBUSxHQUN6QixJQUFJQyxHQUFpQixFQUNyQixNQUFNQyxFQUFTLElBQUlOLEVBQW1CTyxpQkFDaENDLEVBQVdyRSxFQUFXRCxjQUN0QnVFLEVBQVNDLG1CQUVmLElBQUlDLEVBQVF4RSxFQUFXVixRQUFRLGNBQy9CZ0YsRUFBT0csU0FBU0QsR0FDaEIsTUFBTUUsRUFBWVgsRUFBWVksc0JBQXNCTCxHQUNwRDNDLE9BQU9pRCxXQUFXQyxVQUFVSCxHQUM1Qi9DLE9BQU9tRCxvQkFBb0JELFVBQVVILEdBQ3JDL0MsT0FBT29ELE9BQVMsS0FDWkMsS0FFSmxCLEVBQVNtQixtQkFBbUIsS0FDcEJaLEVBQVN4Qix5QkFDVHFDLFdBQVcsS0FFUCxHQUFJVixFQUFNZCxTQUFVLENBQ2hCLE1BQU1uRSxFQUFVeUUsRUFBY1AsMEJBQTBCZSxFQUFNZCxVQUMxRG5FLElBQ0EyRSxHQUFpQixFQUNqQkYsRUFBY3BCLHlCQUF5QnJELEVBQVFZLFdBR2xELENBQ0QsTUFBTWdGLEdBQWVkLEVBQVNsRSxLQUN6QlMsTUFBTXVFLEtBQ1BqQixHQUFpQixFQUNqQkYsRUFBY3BCLHlCQUF5QnVDLE1BR2hELEtBR1gsTUFBTUMsRUFBZSxNQUNqQixNQUFNQyxFQUFXcEIsRUFBVTlELElBQ3ZCK0QsR0FBaUIsRUFDakJGLEVBQWNwQix5QkFBeUJ6QyxJQUN4QyxJQUNILE1BQU8sQ0FBQ0EsRUFBTWtFLEtBQ0x6RCxNQUFNVCxLQUNQa0UsRUFBU2xFLEtBQU9BLEVBQ2hCa0YsRUFBU2xGLE1BUkEsR0FZckIsSUFBSTZFLEVBQW1CZixFQUFTLEtBQzVCLE1BQU1xQixFQUFZLEdBQ2xCLElBQUlDLEVBQVMvRixTQUFTZ0cscUJBQXFCLE9BQzNDLEdBQUlELEVBQVEsQ0FDUixJQUFJbkksRUFDSixJQUFLQSxFQUFJLEVBQUdBLEVBQUltSSxFQUFPeEQsT0FBUTNFLElBQUssQ0FDaEMsTUFBTXFJLEVBQU1GLEVBQU9uSSxHQUNmcUksRUFBSUMsVUFBVUMsU0FBUyxZQUN2QkYsRUFBSUMsVUFBVUUsT0FBTyxXQUV6Qk4sRUFBVXZFLEtBQUssQ0FDWDZDLEdBQUk2QixFQUFJN0IsR0FDUnhCLE9BQVFxRCxFQUFJckQsT0FDWnlELE1BQU9KLEVBQUlJLFFBR25CbkIsRUFBVW9CLFlBQVksa0JBQW1CUixLQUU5QyxJQUNIM0QsT0FBT29FLGlCQUFpQixTQUFVLEtBQzlCN0IsR0FBaUIsRUFDakJjLE1BQ0QsR0FDSHJELE9BQU9vRSxpQkFBaUIsVUFBV0MsSUFDL0IsR0FBSUEsRUFBTXRHLEtBQUt1RyxTQUFXNUIsRUFBUzRCLE9BR25DLE9BQVFELEVBQU10RyxLQUFLd0csTUFDZixJQUFLLGlDQUNEL0IsRUFBT2dDLCtCQUErQkgsRUFBTXRHLEtBQUtTLE1BQ2pELE1BQ0osSUFBSyxhQUNEaUYsRUFBYVksRUFBTXRHLEtBQUtTLEtBQU1rRSxNQUd2QyxHQUNIN0UsU0FBU3VHLGlCQUFpQixXQUFZQyxJQUNsQyxJQUFLM0IsRUFBUytCLDRCQUNWLE9BR0osSUFBSyxJQUFJQyxFQUFPTCxFQUFNTSxPQUFRRCxFQUFNQSxFQUFPQSxFQUFLRSxXQUM1QyxHQUFxQixNQUFqQkYsRUFBS3hGLFFBQ0wsT0FHUixNQUFNWSxFQUFTdUUsRUFBTVEsTUFDZnJHLEVBQU82RCxFQUFjWixpQ0FBaUMzQixHQUN4QyxpQkFBVHRCLEdBQXNCUyxNQUFNVCxJQUNuQ3VFLEVBQVVvQixZQUFZLFdBQVksQ0FBRTNGLEtBQU1JLEtBQUtZLE1BQU1oQixPQUc3RCxNQUFNc0csRUFBeUIsQ0FBQyxRQUFTLFNBQVUsVUFBVyxVQUFXLG9CQUN6RWpILFNBQVN1RyxpQkFBaUIsUUFBU0MsSUFDL0IsSUFBS0EsRUFDRCxPQUVKLElBQUlLLEVBQU9MLEVBQU1NLE9BQ2pCLEtBQU9ELEdBQU0sQ0FDVCxHQUFJQSxFQUFLeEYsU0FBNEIsTUFBakJ3RixFQUFLeEYsU0FBbUJ3RixFQUFLSyxLQUFNLENBQ25ELEdBQUlMLEVBQUsxRyxhQUFhLFFBQVFnSCxXQUFXLEtBQ3JDLE9BR0osR0FBSUYsRUFBdUJHLEtBQUtDLEdBQVVSLEVBQUtLLEtBQUtDLFdBQVdFLElBQzNELE9BRUosTUFBTUMsRUFBV1QsRUFBSzFHLGFBQWEsY0FBZ0IwRyxFQUFLMUcsYUFBYSxRQUVyRSxNQUFLLGNBQWNvSCxLQUFLRCxRQU14QixHQUxJcEMsRUFBVW9CLFlBQVksV0FBWSxDQUFFWSxLQUFNSSxJQUMxQ2QsRUFBTWdCLHNCQUNOaEIsRUFBTWlCLG1CQUtkWixFQUFPQSxFQUFLRSxjQUVqQixHQUNINUUsT0FBT29FLGlCQUFpQixTQUFVOUIsRUFBUyxLQUN2QyxHQUFJQyxFQUNBQSxHQUFpQixNQUVoQixDQUNELE1BQU0vRCxFQUFPNkQsRUFBY1osaUNBQWlDekIsT0FBT0MsU0FDL0MsaUJBQVR6QixHQUFzQlMsTUFBTVQsS0FDbkN1RSxFQUFVb0IsWUFBWSxhQUFjLENBQUUzRixTQUN0Q3FFLEVBQU1yRSxLQUFPQSxFQUNibUUsRUFBT0csU0FBU0QsTUFHekIsTSw2QkNySkgxRyxPQUFPQyxlQUFlYixFQUFTLGFBQWMsQ0FBRW1CLE9BQU8sSUFLdEQsTUFBTTJGLEVBQWdCLEVBQVEsR0F3QjlCOUcsRUFBUWtILGlCQXZCUixNQUNJLCtCQUErQmpFLEdBQzNCLE1BQU0sU0FBRWtCLEdBQWEyQyxFQUFjaEQseUJBQXlCYixHQUM1RCtHLEtBQUtDLFFBQVE5RixHQUFZQSxFQUFTOUIsU0FFdEMsUUFBUTZILEdBQ0pGLEtBQUtHLHFCQUFxQkgsS0FBS0ksVUFDL0JKLEtBQUtLLG1CQUFtQkgsR0FDeEJGLEtBQUtJLFNBQVdGLEVBRXBCLHFCQUFxQjdILEdBQ1pBLElBR0xBLEVBQVFpSSxVQUFZakksRUFBUWlJLFVBQVVDLFFBQVEsd0JBQXlCLEtBRTNFLG1CQUFtQmxJLEdBQ1ZBLElBR0xBLEVBQVFpSSxXQUFhLHdCLDZCQ3RCN0IxSixPQUFPQyxlQUFlYixFQUFTLGFBQWMsQ0FBRW1CLE9BQU8sSUFTdERuQixFQUFRK0gsbUJBUlIsU0FBNEJ5QyxHQUNJLFlBQXhCbEksU0FBU21JLFlBQW9ELGtCQUF4Qm5JLFNBQVNtSSxXQUM5Q25JLFNBQVN1RyxpQkFBaUIsbUJBQW9CMkIsR0FHOUNBLE0sNkJDTlI1SixPQUFPQyxlQUFlYixFQUFTLGFBQWMsQ0FBRW1CLE9BQU8sSUFDdEQsTUFBTTJCLEVBQWEsRUFBUSxHQUMzQjlDLEVBQVF5SCxzQkFBeUJMLEdBQ3RCLElBQUksTUFDUCxZQUFZNEIsRUFBTXhGLEdBQ2Q0RCxFQUFPd0IsWUFBWSxDQUNmSSxPQUNBRCxPQUFRakcsRUFBV0QsY0FBY2tHLE9BQ2pDdkYsWSxpQkNiaEIsWUFVQSxJQUFJa0gsRUFBa0Isc0JBR2xCQyxFQUFNLElBR05DLEVBQVksa0JBR1pDLEVBQVMsYUFHVEMsRUFBYSxxQkFHYkMsRUFBYSxhQUdiQyxFQUFZLGNBR1pDLEVBQWVDLFNBR2ZDLEVBQThCLGlCQUFWQyxHQUFzQkEsR0FBVUEsRUFBT3hLLFNBQVdBLFFBQVV3SyxFQUdoRkMsRUFBMEIsaUJBQVJDLE1BQW9CQSxNQUFRQSxLQUFLMUssU0FBV0EsUUFBVTBLLEtBR3hFQyxFQUFPSixHQUFjRSxHQUFZRyxTQUFTLGNBQVRBLEdBVWpDQyxFQVBjN0ssT0FBT2tCLFVBT1E0SixTQUc3QkMsRUFBWXRJLEtBQUtGLElBQ2pCeUksRUFBWXZJLEtBQUtILElBa0JqQjJJLEVBQU0sV0FDUixPQUFPTixFQUFLTyxLQUFLRCxPQXlEbkIsU0FBU0UsRUFBU0MsRUFBTUMsRUFBTUMsR0FDNUIsSUFBSUMsRUFDQUMsRUFDQUMsRUFDQUMsRUFDQUMsRUFDQUMsRUFDQUMsRUFBaUIsRUFDakJDLEdBQVUsRUFDVkMsR0FBUyxFQUNUQyxHQUFXLEVBRWYsR0FBbUIsbUJBQVJaLEVBQ1QsTUFBTSxJQUFJYSxVQUFVbkMsR0FVdEIsU0FBU29DLEVBQVdDLEdBQ2xCLElBQUlDLEVBQU9iLEVBQ1BjLEVBQVViLEVBS2QsT0FIQUQsRUFBV0MsT0FBV2pLLEVBQ3RCc0ssRUFBaUJNLEVBQ2pCVCxFQUFTTixFQUFLa0IsTUFBTUQsRUFBU0QsR0FxQi9CLFNBQVNHLEVBQWFKLEdBQ3BCLElBQUlLLEVBQW9CTCxFQUFPUCxFQU0vQixZQUF5QnJLLElBQWpCcUssR0FBK0JZLEdBQXFCbkIsR0FDekRtQixFQUFvQixHQUFPVCxHQU5KSSxFQUFPTixHQU04QkosRUFHakUsU0FBU2dCLElBQ1AsSUFBSU4sRUFBT2xCLElBQ1gsR0FBSXNCLEVBQWFKLEdBQ2YsT0FBT08sRUFBYVAsR0FHdEJSLEVBQVV2RSxXQUFXcUYsRUF6QnZCLFNBQXVCTixHQUNyQixJQUVJVCxFQUFTTCxHQUZXYyxFQUFPUCxHQUkvQixPQUFPRyxFQUFTZixFQUFVVSxFQUFRRCxHQUhSVSxFQUFPTixJQUdrQ0gsRUFvQmhDaUIsQ0FBY1IsSUFHbkQsU0FBU08sRUFBYVAsR0FLcEIsT0FKQVIsT0FBVXBLLEVBSU55SyxHQUFZVCxFQUNQVyxFQUFXQyxJQUVwQlosRUFBV0MsT0FBV2pLLEVBQ2ZtSyxHQWVULFNBQVNrQixJQUNQLElBQUlULEVBQU9sQixJQUNQNEIsRUFBYU4sRUFBYUosR0FNOUIsR0FKQVosRUFBV3VCLFVBQ1h0QixFQUFXcEMsS0FDWHdDLEVBQWVPLEVBRVhVLEVBQVksQ0FDZCxRQUFnQnRMLElBQVpvSyxFQUNGLE9BdkVOLFNBQXFCUSxHQU1uQixPQUpBTixFQUFpQk0sRUFFakJSLEVBQVV2RSxXQUFXcUYsRUFBY3BCLEdBRTVCUyxFQUFVSSxFQUFXQyxHQUFRVCxFQWlFekJxQixDQUFZbkIsR0FFckIsR0FBSUcsRUFHRixPQURBSixFQUFVdkUsV0FBV3FGLEVBQWNwQixHQUM1QmEsRUFBV04sR0FNdEIsWUFIZ0JySyxJQUFab0ssSUFDRkEsRUFBVXZFLFdBQVdxRixFQUFjcEIsSUFFOUJLLEVBSVQsT0F4R0FMLEVBQU8yQixFQUFTM0IsSUFBUyxFQUNyQjRCLEVBQVMzQixLQUNYUSxJQUFZUixFQUFRUSxRQUVwQkwsR0FEQU0sRUFBUyxZQUFhVCxHQUNIUCxFQUFVaUMsRUFBUzFCLEVBQVFHLFVBQVksRUFBR0osR0FBUUksRUFDckVPLEVBQVcsYUFBY1YsSUFBWUEsRUFBUVUsU0FBV0EsR0FpRzFEWSxFQUFVTSxPQW5DVixnQkFDa0IzTCxJQUFab0ssR0FDRndCLGFBQWF4QixHQUVmRSxFQUFpQixFQUNqQk4sRUFBV0ssRUFBZUosRUFBV0csT0FBVXBLLEdBK0JqRHFMLEVBQVVRLE1BNUJWLFdBQ0UsWUFBbUI3TCxJQUFab0ssRUFBd0JELEVBQVNnQixFQUFhekIsTUE0QmhEMkIsRUEwRlQsU0FBU0ssRUFBUzFNLEdBQ2hCLElBQUk2SCxTQUFjN0gsRUFDbEIsUUFBU0EsSUFBa0IsVUFBUjZILEdBQTRCLFlBQVJBLEdBNEV6QyxTQUFTNEUsRUFBU3pNLEdBQ2hCLEdBQW9CLGlCQUFUQSxFQUNULE9BQU9BLEVBRVQsR0FoQ0YsU0FBa0JBLEdBQ2hCLE1BQXVCLGlCQUFUQSxHQXRCaEIsU0FBc0JBLEdBQ3BCLFFBQVNBLEdBQXlCLGlCQUFUQSxFQXNCdEI4TSxDQUFhOU0sSUFBVXNLLEVBQWVwTCxLQUFLYyxJQUFVeUosRUE4QnBEc0QsQ0FBUy9NLEdBQ1gsT0FBT3dKLEVBRVQsR0FBSWtELEVBQVMxTSxHQUFRLENBQ25CLElBQUlnTixFQUFnQyxtQkFBakJoTixFQUFNaU4sUUFBd0JqTixFQUFNaU4sVUFBWWpOLEVBQ25FQSxFQUFRME0sRUFBU00sR0FBVUEsRUFBUSxHQUFNQSxFQUUzQyxHQUFvQixpQkFBVGhOLEVBQ1QsT0FBaUIsSUFBVkEsRUFBY0EsR0FBU0EsRUFFaENBLEVBQVFBLEVBQU1vSixRQUFRTSxFQUFRLElBQzlCLElBQUl3RCxFQUFXdEQsRUFBV2xCLEtBQUsxSSxHQUMvQixPQUFRa04sR0FBWXJELEVBQVVuQixLQUFLMUksR0FDL0I4SixFQUFhOUosRUFBTW1OLE1BQU0sR0FBSUQsRUFBVyxFQUFJLEdBQzNDdkQsRUFBV2pCLEtBQUsxSSxHQUFTd0osR0FBT3hKLEVBR3ZDbEIsRUFBT0QsUUE5SVAsU0FBa0JnTSxFQUFNQyxFQUFNQyxHQUM1QixJQUFJUSxHQUFVLEVBQ1ZFLEdBQVcsRUFFZixHQUFtQixtQkFBUlosRUFDVCxNQUFNLElBQUlhLFVBQVVuQyxHQU10QixPQUpJbUQsRUFBUzNCLEtBQ1hRLEVBQVUsWUFBYVIsSUFBWUEsRUFBUVEsUUFBVUEsRUFDckRFLEVBQVcsYUFBY1YsSUFBWUEsRUFBUVUsU0FBV0EsR0FFbkRiLEVBQVNDLEVBQU1DLEVBQU0sQ0FDMUIsUUFBV1MsRUFDWCxRQUFXVCxFQUNYLFNBQVlXLE8sK0JDdFRoQixJQUFJMkIsRUFHSkEsRUFBSSxXQUNILE9BQU92RSxLQURKLEdBSUosSUFFQ3VFLEVBQUlBLEdBQUssSUFBSS9DLFNBQVMsY0FBYixHQUNSLE1BQU9nRCxHQUVjLGlCQUFYL0osU0FBcUI4SixFQUFJOUosUUFPckN4RSxFQUFPRCxRQUFVdU8iLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VzQ29udGVudCI6WyIgXHQvLyBUaGUgbW9kdWxlIGNhY2hlXG4gXHR2YXIgaW5zdGFsbGVkTW9kdWxlcyA9IHt9O1xuXG4gXHQvLyBUaGUgcmVxdWlyZSBmdW5jdGlvblxuIFx0ZnVuY3Rpb24gX193ZWJwYWNrX3JlcXVpcmVfXyhtb2R1bGVJZCkge1xuXG4gXHRcdC8vIENoZWNrIGlmIG1vZHVsZSBpcyBpbiBjYWNoZVxuIFx0XHRpZihpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSkge1xuIFx0XHRcdHJldHVybiBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXS5leHBvcnRzO1xuIFx0XHR9XG4gXHRcdC8vIENyZWF0ZSBhIG5ldyBtb2R1bGUgKGFuZCBwdXQgaXQgaW50byB0aGUgY2FjaGUpXG4gXHRcdHZhciBtb2R1bGUgPSBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSA9IHtcbiBcdFx0XHRpOiBtb2R1bGVJZCxcbiBcdFx0XHRsOiBmYWxzZSxcbiBcdFx0XHRleHBvcnRzOiB7fVxuIFx0XHR9O1xuXG4gXHRcdC8vIEV4ZWN1dGUgdGhlIG1vZHVsZSBmdW5jdGlvblxuIFx0XHRtb2R1bGVzW21vZHVsZUlkXS5jYWxsKG1vZHVsZS5leHBvcnRzLCBtb2R1bGUsIG1vZHVsZS5leHBvcnRzLCBfX3dlYnBhY2tfcmVxdWlyZV9fKTtcblxuIFx0XHQvLyBGbGFnIHRoZSBtb2R1bGUgYXMgbG9hZGVkXG4gXHRcdG1vZHVsZS5sID0gdHJ1ZTtcblxuIFx0XHQvLyBSZXR1cm4gdGhlIGV4cG9ydHMgb2YgdGhlIG1vZHVsZVxuIFx0XHRyZXR1cm4gbW9kdWxlLmV4cG9ydHM7XG4gXHR9XG5cblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGVzIG9iamVjdCAoX193ZWJwYWNrX21vZHVsZXNfXylcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubSA9IG1vZHVsZXM7XG5cbiBcdC8vIGV4cG9zZSB0aGUgbW9kdWxlIGNhY2hlXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLmMgPSBpbnN0YWxsZWRNb2R1bGVzO1xuXG4gXHQvLyBkZWZpbmUgZ2V0dGVyIGZ1bmN0aW9uIGZvciBoYXJtb255IGV4cG9ydHNcbiBcdF9fd2VicGFja19yZXF1aXJlX18uZCA9IGZ1bmN0aW9uKGV4cG9ydHMsIG5hbWUsIGdldHRlcikge1xuIFx0XHRpZighX193ZWJwYWNrX3JlcXVpcmVfXy5vKGV4cG9ydHMsIG5hbWUpKSB7XG4gXHRcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIG5hbWUsIHsgZW51bWVyYWJsZTogdHJ1ZSwgZ2V0OiBnZXR0ZXIgfSk7XG4gXHRcdH1cbiBcdH07XG5cbiBcdC8vIGRlZmluZSBfX2VzTW9kdWxlIG9uIGV4cG9ydHNcbiBcdF9fd2VicGFja19yZXF1aXJlX18uciA9IGZ1bmN0aW9uKGV4cG9ydHMpIHtcbiBcdFx0aWYodHlwZW9mIFN5bWJvbCAhPT0gJ3VuZGVmaW5lZCcgJiYgU3ltYm9sLnRvU3RyaW5nVGFnKSB7XG4gXHRcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFN5bWJvbC50b1N0cmluZ1RhZywgeyB2YWx1ZTogJ01vZHVsZScgfSk7XG4gXHRcdH1cbiBcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsICdfX2VzTW9kdWxlJywgeyB2YWx1ZTogdHJ1ZSB9KTtcbiBcdH07XG5cbiBcdC8vIGNyZWF0ZSBhIGZha2UgbmFtZXNwYWNlIG9iamVjdFxuIFx0Ly8gbW9kZSAmIDE6IHZhbHVlIGlzIGEgbW9kdWxlIGlkLCByZXF1aXJlIGl0XG4gXHQvLyBtb2RlICYgMjogbWVyZ2UgYWxsIHByb3BlcnRpZXMgb2YgdmFsdWUgaW50byB0aGUgbnNcbiBcdC8vIG1vZGUgJiA0OiByZXR1cm4gdmFsdWUgd2hlbiBhbHJlYWR5IG5zIG9iamVjdFxuIFx0Ly8gbW9kZSAmIDh8MTogYmVoYXZlIGxpa2UgcmVxdWlyZVxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy50ID0gZnVuY3Rpb24odmFsdWUsIG1vZGUpIHtcbiBcdFx0aWYobW9kZSAmIDEpIHZhbHVlID0gX193ZWJwYWNrX3JlcXVpcmVfXyh2YWx1ZSk7XG4gXHRcdGlmKG1vZGUgJiA4KSByZXR1cm4gdmFsdWU7XG4gXHRcdGlmKChtb2RlICYgNCkgJiYgdHlwZW9mIHZhbHVlID09PSAnb2JqZWN0JyAmJiB2YWx1ZSAmJiB2YWx1ZS5fX2VzTW9kdWxlKSByZXR1cm4gdmFsdWU7XG4gXHRcdHZhciBucyA9IE9iamVjdC5jcmVhdGUobnVsbCk7XG4gXHRcdF9fd2VicGFja19yZXF1aXJlX18ucihucyk7XG4gXHRcdE9iamVjdC5kZWZpbmVQcm9wZXJ0eShucywgJ2RlZmF1bHQnLCB7IGVudW1lcmFibGU6IHRydWUsIHZhbHVlOiB2YWx1ZSB9KTtcbiBcdFx0aWYobW9kZSAmIDIgJiYgdHlwZW9mIHZhbHVlICE9ICdzdHJpbmcnKSBmb3IodmFyIGtleSBpbiB2YWx1ZSkgX193ZWJwYWNrX3JlcXVpcmVfXy5kKG5zLCBrZXksIGZ1bmN0aW9uKGtleSkgeyByZXR1cm4gdmFsdWVba2V5XTsgfS5iaW5kKG51bGwsIGtleSkpO1xuIFx0XHRyZXR1cm4gbnM7XG4gXHR9O1xuXG4gXHQvLyBnZXREZWZhdWx0RXhwb3J0IGZ1bmN0aW9uIGZvciBjb21wYXRpYmlsaXR5IHdpdGggbm9uLWhhcm1vbnkgbW9kdWxlc1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5uID0gZnVuY3Rpb24obW9kdWxlKSB7XG4gXHRcdHZhciBnZXR0ZXIgPSBtb2R1bGUgJiYgbW9kdWxlLl9fZXNNb2R1bGUgP1xuIFx0XHRcdGZ1bmN0aW9uIGdldERlZmF1bHQoKSB7IHJldHVybiBtb2R1bGVbJ2RlZmF1bHQnXTsgfSA6XG4gXHRcdFx0ZnVuY3Rpb24gZ2V0TW9kdWxlRXhwb3J0cygpIHsgcmV0dXJuIG1vZHVsZTsgfTtcbiBcdFx0X193ZWJwYWNrX3JlcXVpcmVfXy5kKGdldHRlciwgJ2EnLCBnZXR0ZXIpO1xuIFx0XHRyZXR1cm4gZ2V0dGVyO1xuIFx0fTtcblxuIFx0Ly8gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLm8gPSBmdW5jdGlvbihvYmplY3QsIHByb3BlcnR5KSB7IHJldHVybiBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwob2JqZWN0LCBwcm9wZXJ0eSk7IH07XG5cbiBcdC8vIF9fd2VicGFja19wdWJsaWNfcGF0aF9fXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLnAgPSBcIlwiO1xuXG5cbiBcdC8vIExvYWQgZW50cnkgbW9kdWxlIGFuZCByZXR1cm4gZXhwb3J0c1xuIFx0cmV0dXJuIF9fd2VicGFja19yZXF1aXJlX18oX193ZWJwYWNrX3JlcXVpcmVfXy5zID0gMik7XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xubGV0IGNhY2hlZFNldHRpbmdzID0gdW5kZWZpbmVkO1xuZnVuY3Rpb24gZ2V0RGF0YShrZXkpIHtcbiAgICBjb25zdCBlbGVtZW50ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3ZzY29kZS1tYXJrZG93bi1wcmV2aWV3LWRhdGEnKTtcbiAgICBpZiAoZWxlbWVudCkge1xuICAgICAgICBjb25zdCBkYXRhID0gZWxlbWVudC5nZXRBdHRyaWJ1dGUoa2V5KTtcbiAgICAgICAgaWYgKGRhdGEpIHtcbiAgICAgICAgICAgIHJldHVybiBKU09OLnBhcnNlKGRhdGEpO1xuICAgICAgICB9XG4gICAgfVxuICAgIHRocm93IG5ldyBFcnJvcihgQ291bGQgbm90IGxvYWQgZGF0YSBmb3IgJHtrZXl9YCk7XG59XG5leHBvcnRzLmdldERhdGEgPSBnZXREYXRhO1xuZnVuY3Rpb24gZ2V0U2V0dGluZ3MoKSB7XG4gICAgaWYgKGNhY2hlZFNldHRpbmdzKSB7XG4gICAgICAgIHJldHVybiBjYWNoZWRTZXR0aW5ncztcbiAgICB9XG4gICAgY2FjaGVkU2V0dGluZ3MgPSBnZXREYXRhKCdkYXRhLXNldHRpbmdzJyk7XG4gICAgaWYgKGNhY2hlZFNldHRpbmdzKSB7XG4gICAgICAgIHJldHVybiBjYWNoZWRTZXR0aW5ncztcbiAgICB9XG4gICAgdGhyb3cgbmV3IEVycm9yKCdDb3VsZCBub3QgbG9hZCBzZXR0aW5ncycpO1xufVxuZXhwb3J0cy5nZXRTZXR0aW5ncyA9IGdldFNldHRpbmdzO1xuIiwiXCJ1c2Ugc3RyaWN0XCI7XG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBcIl9fZXNNb2R1bGVcIiwgeyB2YWx1ZTogdHJ1ZSB9KTtcbmNvbnN0IHNldHRpbmdzXzEgPSByZXF1aXJlKFwiLi9zZXR0aW5nc1wiKTtcbmNvbnN0IGNvZGVMaW5lQ2xhc3MgPSAnY29kZS1saW5lJztcbmZ1bmN0aW9uIGNsYW1wKG1pbiwgbWF4LCB2YWx1ZSkge1xuICAgIHJldHVybiBNYXRoLm1pbihtYXgsIE1hdGgubWF4KG1pbiwgdmFsdWUpKTtcbn1cbmZ1bmN0aW9uIGNsYW1wTGluZShsaW5lKSB7XG4gICAgcmV0dXJuIGNsYW1wKDAsIHNldHRpbmdzXzEuZ2V0U2V0dGluZ3MoKS5saW5lQ291bnQgLSAxLCBsaW5lKTtcbn1cbmNvbnN0IGdldENvZGVMaW5lRWxlbWVudHMgPSAoKCkgPT4ge1xuICAgIGxldCBlbGVtZW50cztcbiAgICByZXR1cm4gKCkgPT4ge1xuICAgICAgICBpZiAoIWVsZW1lbnRzKSB7XG4gICAgICAgICAgICBlbGVtZW50cyA9IFt7IGVsZW1lbnQ6IGRvY3VtZW50LmJvZHksIGxpbmU6IDAgfV07XG4gICAgICAgICAgICBmb3IgKGNvbnN0IGVsZW1lbnQgb2YgZG9jdW1lbnQuZ2V0RWxlbWVudHNCeUNsYXNzTmFtZShjb2RlTGluZUNsYXNzKSkge1xuICAgICAgICAgICAgICAgIGNvbnN0IGxpbmUgPSArZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ2RhdGEtbGluZScpO1xuICAgICAgICAgICAgICAgIGlmIChpc05hTihsaW5lKSkge1xuICAgICAgICAgICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgaWYgKGVsZW1lbnQudGFnTmFtZSA9PT0gJ0NPREUnICYmIGVsZW1lbnQucGFyZW50RWxlbWVudCAmJiBlbGVtZW50LnBhcmVudEVsZW1lbnQudGFnTmFtZSA9PT0gJ1BSRScpIHtcbiAgICAgICAgICAgICAgICAgICAgLy8gRmVuY2hlZCBjb2RlIGJsb2NrcyBhcmUgYSBzcGVjaWFsIGNhc2Ugc2luY2UgdGhlIGBjb2RlLWxpbmVgIGNhbiBvbmx5IGJlIG1hcmtlZCBvblxuICAgICAgICAgICAgICAgICAgICAvLyB0aGUgYDxjb2RlPmAgZWxlbWVudCBhbmQgbm90IHRoZSBwYXJlbnQgYDxwcmU+YCBlbGVtZW50LlxuICAgICAgICAgICAgICAgICAgICBlbGVtZW50cy5wdXNoKHsgZWxlbWVudDogZWxlbWVudC5wYXJlbnRFbGVtZW50LCBsaW5lIH0pO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgZWxlbWVudHMucHVzaCh7IGVsZW1lbnQ6IGVsZW1lbnQsIGxpbmUgfSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIHJldHVybiBlbGVtZW50cztcbiAgICB9O1xufSkoKTtcbi8qKlxuICogRmluZCB0aGUgaHRtbCBlbGVtZW50cyB0aGF0IG1hcCB0byBhIHNwZWNpZmljIHRhcmdldCBsaW5lIGluIHRoZSBlZGl0b3IuXG4gKlxuICogSWYgYW4gZXhhY3QgbWF0Y2gsIHJldHVybnMgYSBzaW5nbGUgZWxlbWVudC4gSWYgdGhlIGxpbmUgaXMgYmV0d2VlbiBlbGVtZW50cyxcbiAqIHJldHVybnMgdGhlIGVsZW1lbnQgcHJpb3IgdG8gYW5kIHRoZSBlbGVtZW50IGFmdGVyIHRoZSBnaXZlbiBsaW5lLlxuICovXG5mdW5jdGlvbiBnZXRFbGVtZW50c0ZvclNvdXJjZUxpbmUodGFyZ2V0TGluZSkge1xuICAgIGNvbnN0IGxpbmVOdW1iZXIgPSBNYXRoLmZsb29yKHRhcmdldExpbmUpO1xuICAgIGNvbnN0IGxpbmVzID0gZ2V0Q29kZUxpbmVFbGVtZW50cygpO1xuICAgIGxldCBwcmV2aW91cyA9IGxpbmVzWzBdIHx8IG51bGw7XG4gICAgZm9yIChjb25zdCBlbnRyeSBvZiBsaW5lcykge1xuICAgICAgICBpZiAoZW50cnkubGluZSA9PT0gbGluZU51bWJlcikge1xuICAgICAgICAgICAgcmV0dXJuIHsgcHJldmlvdXM6IGVudHJ5LCBuZXh0OiB1bmRlZmluZWQgfTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIGlmIChlbnRyeS5saW5lID4gbGluZU51bWJlcikge1xuICAgICAgICAgICAgcmV0dXJuIHsgcHJldmlvdXMsIG5leHQ6IGVudHJ5IH07XG4gICAgICAgIH1cbiAgICAgICAgcHJldmlvdXMgPSBlbnRyeTtcbiAgICB9XG4gICAgcmV0dXJuIHsgcHJldmlvdXMgfTtcbn1cbmV4cG9ydHMuZ2V0RWxlbWVudHNGb3JTb3VyY2VMaW5lID0gZ2V0RWxlbWVudHNGb3JTb3VyY2VMaW5lO1xuLyoqXG4gKiBGaW5kIHRoZSBodG1sIGVsZW1lbnRzIHRoYXQgYXJlIGF0IGEgc3BlY2lmaWMgcGl4ZWwgb2Zmc2V0IG9uIHRoZSBwYWdlLlxuICovXG5mdW5jdGlvbiBnZXRMaW5lRWxlbWVudHNBdFBhZ2VPZmZzZXQob2Zmc2V0KSB7XG4gICAgY29uc3QgbGluZXMgPSBnZXRDb2RlTGluZUVsZW1lbnRzKCk7XG4gICAgY29uc3QgcG9zaXRpb24gPSBvZmZzZXQgLSB3aW5kb3cuc2Nyb2xsWTtcbiAgICBsZXQgbG8gPSAtMTtcbiAgICBsZXQgaGkgPSBsaW5lcy5sZW5ndGggLSAxO1xuICAgIHdoaWxlIChsbyArIDEgPCBoaSkge1xuICAgICAgICBjb25zdCBtaWQgPSBNYXRoLmZsb29yKChsbyArIGhpKSAvIDIpO1xuICAgICAgICBjb25zdCBib3VuZHMgPSBnZXRFbGVtZW50Qm91bmRzKGxpbmVzW21pZF0pO1xuICAgICAgICBpZiAoYm91bmRzLnRvcCArIGJvdW5kcy5oZWlnaHQgPj0gcG9zaXRpb24pIHtcbiAgICAgICAgICAgIGhpID0gbWlkO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgbG8gPSBtaWQ7XG4gICAgICAgIH1cbiAgICB9XG4gICAgY29uc3QgaGlFbGVtZW50ID0gbGluZXNbaGldO1xuICAgIGNvbnN0IGhpQm91bmRzID0gZ2V0RWxlbWVudEJvdW5kcyhoaUVsZW1lbnQpO1xuICAgIGlmIChoaSA+PSAxICYmIGhpQm91bmRzLnRvcCA+IHBvc2l0aW9uKSB7XG4gICAgICAgIGNvbnN0IGxvRWxlbWVudCA9IGxpbmVzW2xvXTtcbiAgICAgICAgcmV0dXJuIHsgcHJldmlvdXM6IGxvRWxlbWVudCwgbmV4dDogaGlFbGVtZW50IH07XG4gICAgfVxuICAgIGlmIChoaSA+IDEgJiYgaGkgPCBsaW5lcy5sZW5ndGggJiYgaGlCb3VuZHMudG9wICsgaGlCb3VuZHMuaGVpZ2h0ID4gcG9zaXRpb24pIHtcbiAgICAgICAgcmV0dXJuIHsgcHJldmlvdXM6IGhpRWxlbWVudCwgbmV4dDogbGluZXNbaGkgKyAxXSB9O1xuICAgIH1cbiAgICByZXR1cm4geyBwcmV2aW91czogaGlFbGVtZW50IH07XG59XG5leHBvcnRzLmdldExpbmVFbGVtZW50c0F0UGFnZU9mZnNldCA9IGdldExpbmVFbGVtZW50c0F0UGFnZU9mZnNldDtcbmZ1bmN0aW9uIGdldEVsZW1lbnRCb3VuZHMoeyBlbGVtZW50IH0pIHtcbiAgICBjb25zdCBteUJvdW5kcyA9IGVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7XG4gICAgLy8gU29tZSBjb2RlIGxpbmUgZWxlbWVudHMgbWF5IGNvbnRhaW4gb3RoZXIgY29kZSBsaW5lIGVsZW1lbnRzLlxuICAgIC8vIEluIHRob3NlIGNhc2VzLCBvbmx5IHRha2UgdGhlIGhlaWdodCB1cCB0byB0aGF0IGNoaWxkLlxuICAgIGNvbnN0IGNvZGVMaW5lQ2hpbGQgPSBlbGVtZW50LnF1ZXJ5U2VsZWN0b3IoYC4ke2NvZGVMaW5lQ2xhc3N9YCk7XG4gICAgaWYgKGNvZGVMaW5lQ2hpbGQpIHtcbiAgICAgICAgY29uc3QgY2hpbGRCb3VuZHMgPSBjb2RlTGluZUNoaWxkLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO1xuICAgICAgICBjb25zdCBoZWlnaHQgPSBNYXRoLm1heCgxLCAoY2hpbGRCb3VuZHMudG9wIC0gbXlCb3VuZHMudG9wKSk7XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICB0b3A6IG15Qm91bmRzLnRvcCxcbiAgICAgICAgICAgIGhlaWdodDogaGVpZ2h0XG4gICAgICAgIH07XG4gICAgfVxuICAgIHJldHVybiBteUJvdW5kcztcbn1cbi8qKlxuICogQXR0ZW1wdCB0byByZXZlYWwgdGhlIGVsZW1lbnQgZm9yIGEgc291cmNlIGxpbmUgaW4gdGhlIGVkaXRvci5cbiAqL1xuZnVuY3Rpb24gc2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lKGxpbmUpIHtcbiAgICBpZiAoIXNldHRpbmdzXzEuZ2V0U2V0dGluZ3MoKS5zY3JvbGxQcmV2aWV3V2l0aEVkaXRvcikge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGlmIChsaW5lIDw9IDApIHtcbiAgICAgICAgd2luZG93LnNjcm9sbCh3aW5kb3cuc2Nyb2xsWCwgMCk7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc3QgeyBwcmV2aW91cywgbmV4dCB9ID0gZ2V0RWxlbWVudHNGb3JTb3VyY2VMaW5lKGxpbmUpO1xuICAgIGlmICghcHJldmlvdXMpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBsZXQgc2Nyb2xsVG8gPSAwO1xuICAgIGNvbnN0IHJlY3QgPSBnZXRFbGVtZW50Qm91bmRzKHByZXZpb3VzKTtcbiAgICBjb25zdCBwcmV2aW91c1RvcCA9IHJlY3QudG9wO1xuICAgIGlmIChuZXh0ICYmIG5leHQubGluZSAhPT0gcHJldmlvdXMubGluZSkge1xuICAgICAgICAvLyBCZXR3ZWVuIHR3byBlbGVtZW50cy4gR28gdG8gcGVyY2VudGFnZSBvZmZzZXQgYmV0d2VlbiB0aGVtLlxuICAgICAgICBjb25zdCBiZXR3ZWVuUHJvZ3Jlc3MgPSAobGluZSAtIHByZXZpb3VzLmxpbmUpIC8gKG5leHQubGluZSAtIHByZXZpb3VzLmxpbmUpO1xuICAgICAgICBjb25zdCBlbGVtZW50T2Zmc2V0ID0gbmV4dC5lbGVtZW50LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLnRvcCAtIHByZXZpb3VzVG9wO1xuICAgICAgICBzY3JvbGxUbyA9IHByZXZpb3VzVG9wICsgYmV0d2VlblByb2dyZXNzICogZWxlbWVudE9mZnNldDtcbiAgICB9XG4gICAgZWxzZSB7XG4gICAgICAgIGNvbnN0IHByb2dyZXNzSW5FbGVtZW50ID0gbGluZSAtIE1hdGguZmxvb3IobGluZSk7XG4gICAgICAgIHNjcm9sbFRvID0gcHJldmlvdXNUb3AgKyAocmVjdC5oZWlnaHQgKiBwcm9ncmVzc0luRWxlbWVudCk7XG4gICAgfVxuICAgIHdpbmRvdy5zY3JvbGwod2luZG93LnNjcm9sbFgsIE1hdGgubWF4KDEsIHdpbmRvdy5zY3JvbGxZICsgc2Nyb2xsVG8pKTtcbn1cbmV4cG9ydHMuc2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lID0gc2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lO1xuZnVuY3Rpb24gZ2V0RWRpdG9yTGluZU51bWJlckZvclBhZ2VPZmZzZXQob2Zmc2V0KSB7XG4gICAgY29uc3QgeyBwcmV2aW91cywgbmV4dCB9ID0gZ2V0TGluZUVsZW1lbnRzQXRQYWdlT2Zmc2V0KG9mZnNldCk7XG4gICAgaWYgKHByZXZpb3VzKSB7XG4gICAgICAgIGNvbnN0IHByZXZpb3VzQm91bmRzID0gZ2V0RWxlbWVudEJvdW5kcyhwcmV2aW91cyk7XG4gICAgICAgIGNvbnN0IG9mZnNldEZyb21QcmV2aW91cyA9IChvZmZzZXQgLSB3aW5kb3cuc2Nyb2xsWSAtIHByZXZpb3VzQm91bmRzLnRvcCk7XG4gICAgICAgIGlmIChuZXh0KSB7XG4gICAgICAgICAgICBjb25zdCBwcm9ncmVzc0JldHdlZW5FbGVtZW50cyA9IG9mZnNldEZyb21QcmV2aW91cyAvIChnZXRFbGVtZW50Qm91bmRzKG5leHQpLnRvcCAtIHByZXZpb3VzQm91bmRzLnRvcCk7XG4gICAgICAgICAgICBjb25zdCBsaW5lID0gcHJldmlvdXMubGluZSArIHByb2dyZXNzQmV0d2VlbkVsZW1lbnRzICogKG5leHQubGluZSAtIHByZXZpb3VzLmxpbmUpO1xuICAgICAgICAgICAgcmV0dXJuIGNsYW1wTGluZShsaW5lKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIGNvbnN0IHByb2dyZXNzV2l0aGluRWxlbWVudCA9IG9mZnNldEZyb21QcmV2aW91cyAvIChwcmV2aW91c0JvdW5kcy5oZWlnaHQpO1xuICAgICAgICAgICAgY29uc3QgbGluZSA9IHByZXZpb3VzLmxpbmUgKyBwcm9ncmVzc1dpdGhpbkVsZW1lbnQ7XG4gICAgICAgICAgICByZXR1cm4gY2xhbXBMaW5lKGxpbmUpO1xuICAgICAgICB9XG4gICAgfVxuICAgIHJldHVybiBudWxsO1xufVxuZXhwb3J0cy5nZXRFZGl0b3JMaW5lTnVtYmVyRm9yUGFnZU9mZnNldCA9IGdldEVkaXRvckxpbmVOdW1iZXJGb3JQYWdlT2Zmc2V0O1xuLyoqXG4gKiBUcnkgdG8gZmluZCB0aGUgaHRtbCBlbGVtZW50IGJ5IHVzaW5nIGEgZnJhZ21lbnQgaWRcbiAqL1xuZnVuY3Rpb24gZ2V0TGluZUVsZW1lbnRGb3JGcmFnbWVudChmcmFnbWVudCkge1xuICAgIHJldHVybiBnZXRDb2RlTGluZUVsZW1lbnRzKCkuZmluZCgoZWxlbWVudCkgPT4ge1xuICAgICAgICByZXR1cm4gZWxlbWVudC5lbGVtZW50LmlkID09PSBmcmFnbWVudDtcbiAgICB9KTtcbn1cbmV4cG9ydHMuZ2V0TGluZUVsZW1lbnRGb3JGcmFnbWVudCA9IGdldExpbmVFbGVtZW50Rm9yRnJhZ21lbnQ7XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuY29uc3QgYWN0aXZlTGluZU1hcmtlcl8xID0gcmVxdWlyZShcIi4vYWN0aXZlTGluZU1hcmtlclwiKTtcbmNvbnN0IGV2ZW50c18xID0gcmVxdWlyZShcIi4vZXZlbnRzXCIpO1xuY29uc3QgbWVzc2FnaW5nXzEgPSByZXF1aXJlKFwiLi9tZXNzYWdpbmdcIik7XG5jb25zdCBzY3JvbGxfc3luY18xID0gcmVxdWlyZShcIi4vc2Nyb2xsLXN5bmNcIik7XG5jb25zdCBzZXR0aW5nc18xID0gcmVxdWlyZShcIi4vc2V0dGluZ3NcIik7XG5jb25zdCB0aHJvdHRsZSA9IHJlcXVpcmUoXCJsb2Rhc2gudGhyb3R0bGVcIik7XG5sZXQgc2Nyb2xsRGlzYWJsZWQgPSB0cnVlO1xuY29uc3QgbWFya2VyID0gbmV3IGFjdGl2ZUxpbmVNYXJrZXJfMS5BY3RpdmVMaW5lTWFya2VyKCk7XG5jb25zdCBzZXR0aW5ncyA9IHNldHRpbmdzXzEuZ2V0U2V0dGluZ3MoKTtcbmNvbnN0IHZzY29kZSA9IGFjcXVpcmVWc0NvZGVBcGkoKTtcbi8vIFNldCBWUyBDb2RlIHN0YXRlXG5sZXQgc3RhdGUgPSBzZXR0aW5nc18xLmdldERhdGEoJ2RhdGEtc3RhdGUnKTtcbnZzY29kZS5zZXRTdGF0ZShzdGF0ZSk7XG5jb25zdCBtZXNzYWdpbmcgPSBtZXNzYWdpbmdfMS5jcmVhdGVQb3N0ZXJGb3JWc0NvZGUodnNjb2RlKTtcbndpbmRvdy5jc3BBbGVydGVyLnNldFBvc3RlcihtZXNzYWdpbmcpO1xud2luZG93LnN0eWxlTG9hZGluZ01vbml0b3Iuc2V0UG9zdGVyKG1lc3NhZ2luZyk7XG53aW5kb3cub25sb2FkID0gKCkgPT4ge1xuICAgIHVwZGF0ZUltYWdlU2l6ZXMoKTtcbn07XG5ldmVudHNfMS5vbmNlRG9jdW1lbnRMb2FkZWQoKCkgPT4ge1xuICAgIGlmIChzZXR0aW5ncy5zY3JvbGxQcmV2aWV3V2l0aEVkaXRvcikge1xuICAgICAgICBzZXRUaW1lb3V0KCgpID0+IHtcbiAgICAgICAgICAgIC8vIFRyeSB0byBzY3JvbGwgdG8gZnJhZ21lbnQgaWYgYXZhaWxhYmxlXG4gICAgICAgICAgICBpZiAoc3RhdGUuZnJhZ21lbnQpIHtcbiAgICAgICAgICAgICAgICBjb25zdCBlbGVtZW50ID0gc2Nyb2xsX3N5bmNfMS5nZXRMaW5lRWxlbWVudEZvckZyYWdtZW50KHN0YXRlLmZyYWdtZW50KTtcbiAgICAgICAgICAgICAgICBpZiAoZWxlbWVudCkge1xuICAgICAgICAgICAgICAgICAgICBzY3JvbGxEaXNhYmxlZCA9IHRydWU7XG4gICAgICAgICAgICAgICAgICAgIHNjcm9sbF9zeW5jXzEuc2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lKGVsZW1lbnQubGluZSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgY29uc3QgaW5pdGlhbExpbmUgPSArc2V0dGluZ3MubGluZTtcbiAgICAgICAgICAgICAgICBpZiAoIWlzTmFOKGluaXRpYWxMaW5lKSkge1xuICAgICAgICAgICAgICAgICAgICBzY3JvbGxEaXNhYmxlZCA9IHRydWU7XG4gICAgICAgICAgICAgICAgICAgIHNjcm9sbF9zeW5jXzEuc2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lKGluaXRpYWxMaW5lKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH0sIDApO1xuICAgIH1cbn0pO1xuY29uc3Qgb25VcGRhdGVWaWV3ID0gKCgpID0+IHtcbiAgICBjb25zdCBkb1Njcm9sbCA9IHRocm90dGxlKChsaW5lKSA9PiB7XG4gICAgICAgIHNjcm9sbERpc2FibGVkID0gdHJ1ZTtcbiAgICAgICAgc2Nyb2xsX3N5bmNfMS5zY3JvbGxUb1JldmVhbFNvdXJjZUxpbmUobGluZSk7XG4gICAgfSwgNTApO1xuICAgIHJldHVybiAobGluZSwgc2V0dGluZ3MpID0+IHtcbiAgICAgICAgaWYgKCFpc05hTihsaW5lKSkge1xuICAgICAgICAgICAgc2V0dGluZ3MubGluZSA9IGxpbmU7XG4gICAgICAgICAgICBkb1Njcm9sbChsaW5lKTtcbiAgICAgICAgfVxuICAgIH07XG59KSgpO1xubGV0IHVwZGF0ZUltYWdlU2l6ZXMgPSB0aHJvdHRsZSgoKSA9PiB7XG4gICAgY29uc3QgaW1hZ2VJbmZvID0gW107XG4gICAgbGV0IGltYWdlcyA9IGRvY3VtZW50LmdldEVsZW1lbnRzQnlUYWdOYW1lKCdpbWcnKTtcbiAgICBpZiAoaW1hZ2VzKSB7XG4gICAgICAgIGxldCBpO1xuICAgICAgICBmb3IgKGkgPSAwOyBpIDwgaW1hZ2VzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICBjb25zdCBpbWcgPSBpbWFnZXNbaV07XG4gICAgICAgICAgICBpZiAoaW1nLmNsYXNzTGlzdC5jb250YWlucygnbG9hZGluZycpKSB7XG4gICAgICAgICAgICAgICAgaW1nLmNsYXNzTGlzdC5yZW1vdmUoJ2xvYWRpbmcnKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGltYWdlSW5mby5wdXNoKHtcbiAgICAgICAgICAgICAgICBpZDogaW1nLmlkLFxuICAgICAgICAgICAgICAgIGhlaWdodDogaW1nLmhlaWdodCxcbiAgICAgICAgICAgICAgICB3aWR0aDogaW1nLndpZHRoXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgICAgICBtZXNzYWdpbmcucG9zdE1lc3NhZ2UoJ2NhY2hlSW1hZ2VTaXplcycsIGltYWdlSW5mbyk7XG4gICAgfVxufSwgNTApO1xud2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ3Jlc2l6ZScsICgpID0+IHtcbiAgICBzY3JvbGxEaXNhYmxlZCA9IHRydWU7XG4gICAgdXBkYXRlSW1hZ2VTaXplcygpO1xufSwgdHJ1ZSk7XG53aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbWVzc2FnZScsIGV2ZW50ID0+IHtcbiAgICBpZiAoZXZlbnQuZGF0YS5zb3VyY2UgIT09IHNldHRpbmdzLnNvdXJjZSkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIHN3aXRjaCAoZXZlbnQuZGF0YS50eXBlKSB7XG4gICAgICAgIGNhc2UgJ29uRGlkQ2hhbmdlVGV4dEVkaXRvclNlbGVjdGlvbic6XG4gICAgICAgICAgICBtYXJrZXIub25EaWRDaGFuZ2VUZXh0RWRpdG9yU2VsZWN0aW9uKGV2ZW50LmRhdGEubGluZSk7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgY2FzZSAndXBkYXRlVmlldyc6XG4gICAgICAgICAgICBvblVwZGF0ZVZpZXcoZXZlbnQuZGF0YS5saW5lLCBzZXR0aW5ncyk7XG4gICAgICAgICAgICBicmVhaztcbiAgICB9XG59LCBmYWxzZSk7XG5kb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdkYmxjbGljaycsIGV2ZW50ID0+IHtcbiAgICBpZiAoIXNldHRpbmdzLmRvdWJsZUNsaWNrVG9Td2l0Y2hUb0VkaXRvcikge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIC8vIElnbm9yZSBjbGlja3Mgb24gbGlua3NcbiAgICBmb3IgKGxldCBub2RlID0gZXZlbnQudGFyZ2V0OyBub2RlOyBub2RlID0gbm9kZS5wYXJlbnROb2RlKSB7XG4gICAgICAgIGlmIChub2RlLnRhZ05hbWUgPT09ICdBJykge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgfVxuICAgIGNvbnN0IG9mZnNldCA9IGV2ZW50LnBhZ2VZO1xuICAgIGNvbnN0IGxpbmUgPSBzY3JvbGxfc3luY18xLmdldEVkaXRvckxpbmVOdW1iZXJGb3JQYWdlT2Zmc2V0KG9mZnNldCk7XG4gICAgaWYgKHR5cGVvZiBsaW5lID09PSAnbnVtYmVyJyAmJiAhaXNOYU4obGluZSkpIHtcbiAgICAgICAgbWVzc2FnaW5nLnBvc3RNZXNzYWdlKCdkaWRDbGljaycsIHsgbGluZTogTWF0aC5mbG9vcihsaW5lKSB9KTtcbiAgICB9XG59KTtcbmNvbnN0IHBhc3NUaHJvdWdoTGlua1NjaGVtZXMgPSBbJ2h0dHA6JywgJ2h0dHBzOicsICdtYWlsdG86JywgJ3ZzY29kZTonLCAndnNjb2RlLWluc2lkZXJzOiddO1xuZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcignY2xpY2snLCBldmVudCA9PiB7XG4gICAgaWYgKCFldmVudCkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGxldCBub2RlID0gZXZlbnQudGFyZ2V0O1xuICAgIHdoaWxlIChub2RlKSB7XG4gICAgICAgIGlmIChub2RlLnRhZ05hbWUgJiYgbm9kZS50YWdOYW1lID09PSAnQScgJiYgbm9kZS5ocmVmKSB7XG4gICAgICAgICAgICBpZiAobm9kZS5nZXRBdHRyaWJ1dGUoJ2hyZWYnKS5zdGFydHNXaXRoKCcjJykpIHtcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvLyBQYXNzIHRocm91Z2gga25vd24gc2NoZW1lc1xuICAgICAgICAgICAgaWYgKHBhc3NUaHJvdWdoTGlua1NjaGVtZXMuc29tZShzY2hlbWUgPT4gbm9kZS5ocmVmLnN0YXJ0c1dpdGgoc2NoZW1lKSkpIHtcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBjb25zdCBocmVmVGV4dCA9IG5vZGUuZ2V0QXR0cmlidXRlKCdkYXRhLWhyZWYnKSB8fCBub2RlLmdldEF0dHJpYnV0ZSgnaHJlZicpO1xuICAgICAgICAgICAgLy8gSWYgb3JpZ2luYWwgbGluayBkb2Vzbid0IGxvb2sgbGlrZSBhIHVybCwgZGVsZWdhdGUgYmFjayB0byBWUyBDb2RlIHRvIHJlc29sdmVcbiAgICAgICAgICAgIGlmICghL15bYS16XFwtXSs6L2kudGVzdChocmVmVGV4dCkpIHtcbiAgICAgICAgICAgICAgICBtZXNzYWdpbmcucG9zdE1lc3NhZ2UoJ29wZW5MaW5rJywgeyBocmVmOiBocmVmVGV4dCB9KTtcbiAgICAgICAgICAgICAgICBldmVudC5wcmV2ZW50RGVmYXVsdCgpO1xuICAgICAgICAgICAgICAgIGV2ZW50LnN0b3BQcm9wYWdhdGlvbigpO1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICBub2RlID0gbm9kZS5wYXJlbnROb2RlO1xuICAgIH1cbn0sIHRydWUpO1xud2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ3Njcm9sbCcsIHRocm90dGxlKCgpID0+IHtcbiAgICBpZiAoc2Nyb2xsRGlzYWJsZWQpIHtcbiAgICAgICAgc2Nyb2xsRGlzYWJsZWQgPSBmYWxzZTtcbiAgICB9XG4gICAgZWxzZSB7XG4gICAgICAgIGNvbnN0IGxpbmUgPSBzY3JvbGxfc3luY18xLmdldEVkaXRvckxpbmVOdW1iZXJGb3JQYWdlT2Zmc2V0KHdpbmRvdy5zY3JvbGxZKTtcbiAgICAgICAgaWYgKHR5cGVvZiBsaW5lID09PSAnbnVtYmVyJyAmJiAhaXNOYU4obGluZSkpIHtcbiAgICAgICAgICAgIG1lc3NhZ2luZy5wb3N0TWVzc2FnZSgncmV2ZWFsTGluZScsIHsgbGluZSB9KTtcbiAgICAgICAgICAgIHN0YXRlLmxpbmUgPSBsaW5lO1xuICAgICAgICAgICAgdnNjb2RlLnNldFN0YXRlKHN0YXRlKTtcbiAgICAgICAgfVxuICAgIH1cbn0sIDUwKSk7XG4iLCJcInVzZSBzdHJpY3RcIjtcbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBcIl9fZXNNb2R1bGVcIiwgeyB2YWx1ZTogdHJ1ZSB9KTtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuY29uc3Qgc2Nyb2xsX3N5bmNfMSA9IHJlcXVpcmUoXCIuL3Njcm9sbC1zeW5jXCIpO1xuY2xhc3MgQWN0aXZlTGluZU1hcmtlciB7XG4gICAgb25EaWRDaGFuZ2VUZXh0RWRpdG9yU2VsZWN0aW9uKGxpbmUpIHtcbiAgICAgICAgY29uc3QgeyBwcmV2aW91cyB9ID0gc2Nyb2xsX3N5bmNfMS5nZXRFbGVtZW50c0ZvclNvdXJjZUxpbmUobGluZSk7XG4gICAgICAgIHRoaXMuX3VwZGF0ZShwcmV2aW91cyAmJiBwcmV2aW91cy5lbGVtZW50KTtcbiAgICB9XG4gICAgX3VwZGF0ZShiZWZvcmUpIHtcbiAgICAgICAgdGhpcy5fdW5tYXJrQWN0aXZlRWxlbWVudCh0aGlzLl9jdXJyZW50KTtcbiAgICAgICAgdGhpcy5fbWFya0FjdGl2ZUVsZW1lbnQoYmVmb3JlKTtcbiAgICAgICAgdGhpcy5fY3VycmVudCA9IGJlZm9yZTtcbiAgICB9XG4gICAgX3VubWFya0FjdGl2ZUVsZW1lbnQoZWxlbWVudCkge1xuICAgICAgICBpZiAoIWVsZW1lbnQpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICBlbGVtZW50LmNsYXNzTmFtZSA9IGVsZW1lbnQuY2xhc3NOYW1lLnJlcGxhY2UoL1xcYmNvZGUtYWN0aXZlLWxpbmVcXGIvZywgJycpO1xuICAgIH1cbiAgICBfbWFya0FjdGl2ZUVsZW1lbnQoZWxlbWVudCkge1xuICAgICAgICBpZiAoIWVsZW1lbnQpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICBlbGVtZW50LmNsYXNzTmFtZSArPSAnIGNvZGUtYWN0aXZlLWxpbmUnO1xuICAgIH1cbn1cbmV4cG9ydHMuQWN0aXZlTGluZU1hcmtlciA9IEFjdGl2ZUxpbmVNYXJrZXI7XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuZnVuY3Rpb24gb25jZURvY3VtZW50TG9hZGVkKGYpIHtcbiAgICBpZiAoZG9jdW1lbnQucmVhZHlTdGF0ZSA9PT0gJ2xvYWRpbmcnIHx8IGRvY3VtZW50LnJlYWR5U3RhdGUgPT09ICd1bmluaXRpYWxpemVkJykge1xuICAgICAgICBkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdET01Db250ZW50TG9hZGVkJywgZik7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICBmKCk7XG4gICAgfVxufVxuZXhwb3J0cy5vbmNlRG9jdW1lbnRMb2FkZWQgPSBvbmNlRG9jdW1lbnRMb2FkZWQ7XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuY29uc3Qgc2V0dGluZ3NfMSA9IHJlcXVpcmUoXCIuL3NldHRpbmdzXCIpO1xuZXhwb3J0cy5jcmVhdGVQb3N0ZXJGb3JWc0NvZGUgPSAodnNjb2RlKSA9PiB7XG4gICAgcmV0dXJuIG5ldyBjbGFzcyB7XG4gICAgICAgIHBvc3RNZXNzYWdlKHR5cGUsIGJvZHkpIHtcbiAgICAgICAgICAgIHZzY29kZS5wb3N0TWVzc2FnZSh7XG4gICAgICAgICAgICAgICAgdHlwZSxcbiAgICAgICAgICAgICAgICBzb3VyY2U6IHNldHRpbmdzXzEuZ2V0U2V0dGluZ3MoKS5zb3VyY2UsXG4gICAgICAgICAgICAgICAgYm9keVxuICAgICAgICAgICAgfSk7XG4gICAgICAgIH1cbiAgICB9O1xufTtcbiIsIi8qKlxuICogbG9kYXNoIChDdXN0b20gQnVpbGQpIDxodHRwczovL2xvZGFzaC5jb20vPlxuICogQnVpbGQ6IGBsb2Rhc2ggbW9kdWxhcml6ZSBleHBvcnRzPVwibnBtXCIgLW8gLi9gXG4gKiBDb3B5cmlnaHQgalF1ZXJ5IEZvdW5kYXRpb24gYW5kIG90aGVyIGNvbnRyaWJ1dG9ycyA8aHR0cHM6Ly9qcXVlcnkub3JnLz5cbiAqIFJlbGVhc2VkIHVuZGVyIE1JVCBsaWNlbnNlIDxodHRwczovL2xvZGFzaC5jb20vbGljZW5zZT5cbiAqIEJhc2VkIG9uIFVuZGVyc2NvcmUuanMgMS44LjMgPGh0dHA6Ly91bmRlcnNjb3JlanMub3JnL0xJQ0VOU0U+XG4gKiBDb3B5cmlnaHQgSmVyZW15IEFzaGtlbmFzLCBEb2N1bWVudENsb3VkIGFuZCBJbnZlc3RpZ2F0aXZlIFJlcG9ydGVycyAmIEVkaXRvcnNcbiAqL1xuXG4vKiogVXNlZCBhcyB0aGUgYFR5cGVFcnJvcmAgbWVzc2FnZSBmb3IgXCJGdW5jdGlvbnNcIiBtZXRob2RzLiAqL1xudmFyIEZVTkNfRVJST1JfVEVYVCA9ICdFeHBlY3RlZCBhIGZ1bmN0aW9uJztcblxuLyoqIFVzZWQgYXMgcmVmZXJlbmNlcyBmb3IgdmFyaW91cyBgTnVtYmVyYCBjb25zdGFudHMuICovXG52YXIgTkFOID0gMCAvIDA7XG5cbi8qKiBgT2JqZWN0I3RvU3RyaW5nYCByZXN1bHQgcmVmZXJlbmNlcy4gKi9cbnZhciBzeW1ib2xUYWcgPSAnW29iamVjdCBTeW1ib2xdJztcblxuLyoqIFVzZWQgdG8gbWF0Y2ggbGVhZGluZyBhbmQgdHJhaWxpbmcgd2hpdGVzcGFjZS4gKi9cbnZhciByZVRyaW0gPSAvXlxccyt8XFxzKyQvZztcblxuLyoqIFVzZWQgdG8gZGV0ZWN0IGJhZCBzaWduZWQgaGV4YWRlY2ltYWwgc3RyaW5nIHZhbHVlcy4gKi9cbnZhciByZUlzQmFkSGV4ID0gL15bLStdMHhbMC05YS1mXSskL2k7XG5cbi8qKiBVc2VkIHRvIGRldGVjdCBiaW5hcnkgc3RyaW5nIHZhbHVlcy4gKi9cbnZhciByZUlzQmluYXJ5ID0gL14wYlswMV0rJC9pO1xuXG4vKiogVXNlZCB0byBkZXRlY3Qgb2N0YWwgc3RyaW5nIHZhbHVlcy4gKi9cbnZhciByZUlzT2N0YWwgPSAvXjBvWzAtN10rJC9pO1xuXG4vKiogQnVpbHQtaW4gbWV0aG9kIHJlZmVyZW5jZXMgd2l0aG91dCBhIGRlcGVuZGVuY3kgb24gYHJvb3RgLiAqL1xudmFyIGZyZWVQYXJzZUludCA9IHBhcnNlSW50O1xuXG4vKiogRGV0ZWN0IGZyZWUgdmFyaWFibGUgYGdsb2JhbGAgZnJvbSBOb2RlLmpzLiAqL1xudmFyIGZyZWVHbG9iYWwgPSB0eXBlb2YgZ2xvYmFsID09ICdvYmplY3QnICYmIGdsb2JhbCAmJiBnbG9iYWwuT2JqZWN0ID09PSBPYmplY3QgJiYgZ2xvYmFsO1xuXG4vKiogRGV0ZWN0IGZyZWUgdmFyaWFibGUgYHNlbGZgLiAqL1xudmFyIGZyZWVTZWxmID0gdHlwZW9mIHNlbGYgPT0gJ29iamVjdCcgJiYgc2VsZiAmJiBzZWxmLk9iamVjdCA9PT0gT2JqZWN0ICYmIHNlbGY7XG5cbi8qKiBVc2VkIGFzIGEgcmVmZXJlbmNlIHRvIHRoZSBnbG9iYWwgb2JqZWN0LiAqL1xudmFyIHJvb3QgPSBmcmVlR2xvYmFsIHx8IGZyZWVTZWxmIHx8IEZ1bmN0aW9uKCdyZXR1cm4gdGhpcycpKCk7XG5cbi8qKiBVc2VkIGZvciBidWlsdC1pbiBtZXRob2QgcmVmZXJlbmNlcy4gKi9cbnZhciBvYmplY3RQcm90byA9IE9iamVjdC5wcm90b3R5cGU7XG5cbi8qKlxuICogVXNlZCB0byByZXNvbHZlIHRoZVxuICogW2B0b1N0cmluZ1RhZ2BdKGh0dHA6Ly9lY21hLWludGVybmF0aW9uYWwub3JnL2VjbWEtMjYyLzcuMC8jc2VjLW9iamVjdC5wcm90b3R5cGUudG9zdHJpbmcpXG4gKiBvZiB2YWx1ZXMuXG4gKi9cbnZhciBvYmplY3RUb1N0cmluZyA9IG9iamVjdFByb3RvLnRvU3RyaW5nO1xuXG4vKiBCdWlsdC1pbiBtZXRob2QgcmVmZXJlbmNlcyBmb3IgdGhvc2Ugd2l0aCB0aGUgc2FtZSBuYW1lIGFzIG90aGVyIGBsb2Rhc2hgIG1ldGhvZHMuICovXG52YXIgbmF0aXZlTWF4ID0gTWF0aC5tYXgsXG4gICAgbmF0aXZlTWluID0gTWF0aC5taW47XG5cbi8qKlxuICogR2V0cyB0aGUgdGltZXN0YW1wIG9mIHRoZSBudW1iZXIgb2YgbWlsbGlzZWNvbmRzIHRoYXQgaGF2ZSBlbGFwc2VkIHNpbmNlXG4gKiB0aGUgVW5peCBlcG9jaCAoMSBKYW51YXJ5IDE5NzAgMDA6MDA6MDAgVVRDKS5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDIuNC4wXG4gKiBAY2F0ZWdvcnkgRGF0ZVxuICogQHJldHVybnMge251bWJlcn0gUmV0dXJucyB0aGUgdGltZXN0YW1wLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLmRlZmVyKGZ1bmN0aW9uKHN0YW1wKSB7XG4gKiAgIGNvbnNvbGUubG9nKF8ubm93KCkgLSBzdGFtcCk7XG4gKiB9LCBfLm5vdygpKTtcbiAqIC8vID0+IExvZ3MgdGhlIG51bWJlciBvZiBtaWxsaXNlY29uZHMgaXQgdG9vayBmb3IgdGhlIGRlZmVycmVkIGludm9jYXRpb24uXG4gKi9cbnZhciBub3cgPSBmdW5jdGlvbigpIHtcbiAgcmV0dXJuIHJvb3QuRGF0ZS5ub3coKTtcbn07XG5cbi8qKlxuICogQ3JlYXRlcyBhIGRlYm91bmNlZCBmdW5jdGlvbiB0aGF0IGRlbGF5cyBpbnZva2luZyBgZnVuY2AgdW50aWwgYWZ0ZXIgYHdhaXRgXG4gKiBtaWxsaXNlY29uZHMgaGF2ZSBlbGFwc2VkIHNpbmNlIHRoZSBsYXN0IHRpbWUgdGhlIGRlYm91bmNlZCBmdW5jdGlvbiB3YXNcbiAqIGludm9rZWQuIFRoZSBkZWJvdW5jZWQgZnVuY3Rpb24gY29tZXMgd2l0aCBhIGBjYW5jZWxgIG1ldGhvZCB0byBjYW5jZWxcbiAqIGRlbGF5ZWQgYGZ1bmNgIGludm9jYXRpb25zIGFuZCBhIGBmbHVzaGAgbWV0aG9kIHRvIGltbWVkaWF0ZWx5IGludm9rZSB0aGVtLlxuICogUHJvdmlkZSBgb3B0aW9uc2AgdG8gaW5kaWNhdGUgd2hldGhlciBgZnVuY2Agc2hvdWxkIGJlIGludm9rZWQgb24gdGhlXG4gKiBsZWFkaW5nIGFuZC9vciB0cmFpbGluZyBlZGdlIG9mIHRoZSBgd2FpdGAgdGltZW91dC4gVGhlIGBmdW5jYCBpcyBpbnZva2VkXG4gKiB3aXRoIHRoZSBsYXN0IGFyZ3VtZW50cyBwcm92aWRlZCB0byB0aGUgZGVib3VuY2VkIGZ1bmN0aW9uLiBTdWJzZXF1ZW50XG4gKiBjYWxscyB0byB0aGUgZGVib3VuY2VkIGZ1bmN0aW9uIHJldHVybiB0aGUgcmVzdWx0IG9mIHRoZSBsYXN0IGBmdW5jYFxuICogaW52b2NhdGlvbi5cbiAqXG4gKiAqKk5vdGU6KiogSWYgYGxlYWRpbmdgIGFuZCBgdHJhaWxpbmdgIG9wdGlvbnMgYXJlIGB0cnVlYCwgYGZ1bmNgIGlzXG4gKiBpbnZva2VkIG9uIHRoZSB0cmFpbGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0IG9ubHkgaWYgdGhlIGRlYm91bmNlZCBmdW5jdGlvblxuICogaXMgaW52b2tlZCBtb3JlIHRoYW4gb25jZSBkdXJpbmcgdGhlIGB3YWl0YCB0aW1lb3V0LlxuICpcbiAqIElmIGB3YWl0YCBpcyBgMGAgYW5kIGBsZWFkaW5nYCBpcyBgZmFsc2VgLCBgZnVuY2AgaW52b2NhdGlvbiBpcyBkZWZlcnJlZFxuICogdW50aWwgdG8gdGhlIG5leHQgdGljaywgc2ltaWxhciB0byBgc2V0VGltZW91dGAgd2l0aCBhIHRpbWVvdXQgb2YgYDBgLlxuICpcbiAqIFNlZSBbRGF2aWQgQ29yYmFjaG8ncyBhcnRpY2xlXShodHRwczovL2Nzcy10cmlja3MuY29tL2RlYm91bmNpbmctdGhyb3R0bGluZy1leHBsYWluZWQtZXhhbXBsZXMvKVxuICogZm9yIGRldGFpbHMgb3ZlciB0aGUgZGlmZmVyZW5jZXMgYmV0d2VlbiBgXy5kZWJvdW5jZWAgYW5kIGBfLnRocm90dGxlYC5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDAuMS4wXG4gKiBAY2F0ZWdvcnkgRnVuY3Rpb25cbiAqIEBwYXJhbSB7RnVuY3Rpb259IGZ1bmMgVGhlIGZ1bmN0aW9uIHRvIGRlYm91bmNlLlxuICogQHBhcmFtIHtudW1iZXJ9IFt3YWl0PTBdIFRoZSBudW1iZXIgb2YgbWlsbGlzZWNvbmRzIHRvIGRlbGF5LlxuICogQHBhcmFtIHtPYmplY3R9IFtvcHRpb25zPXt9XSBUaGUgb3B0aW9ucyBvYmplY3QuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLmxlYWRpbmc9ZmFsc2VdXG4gKiAgU3BlY2lmeSBpbnZva2luZyBvbiB0aGUgbGVhZGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0LlxuICogQHBhcmFtIHtudW1iZXJ9IFtvcHRpb25zLm1heFdhaXRdXG4gKiAgVGhlIG1heGltdW0gdGltZSBgZnVuY2AgaXMgYWxsb3dlZCB0byBiZSBkZWxheWVkIGJlZm9yZSBpdCdzIGludm9rZWQuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLnRyYWlsaW5nPXRydWVdXG4gKiAgU3BlY2lmeSBpbnZva2luZyBvbiB0aGUgdHJhaWxpbmcgZWRnZSBvZiB0aGUgdGltZW91dC5cbiAqIEByZXR1cm5zIHtGdW5jdGlvbn0gUmV0dXJucyB0aGUgbmV3IGRlYm91bmNlZCBmdW5jdGlvbi5cbiAqIEBleGFtcGxlXG4gKlxuICogLy8gQXZvaWQgY29zdGx5IGNhbGN1bGF0aW9ucyB3aGlsZSB0aGUgd2luZG93IHNpemUgaXMgaW4gZmx1eC5cbiAqIGpRdWVyeSh3aW5kb3cpLm9uKCdyZXNpemUnLCBfLmRlYm91bmNlKGNhbGN1bGF0ZUxheW91dCwgMTUwKSk7XG4gKlxuICogLy8gSW52b2tlIGBzZW5kTWFpbGAgd2hlbiBjbGlja2VkLCBkZWJvdW5jaW5nIHN1YnNlcXVlbnQgY2FsbHMuXG4gKiBqUXVlcnkoZWxlbWVudCkub24oJ2NsaWNrJywgXy5kZWJvdW5jZShzZW5kTWFpbCwgMzAwLCB7XG4gKiAgICdsZWFkaW5nJzogdHJ1ZSxcbiAqICAgJ3RyYWlsaW5nJzogZmFsc2VcbiAqIH0pKTtcbiAqXG4gKiAvLyBFbnN1cmUgYGJhdGNoTG9nYCBpcyBpbnZva2VkIG9uY2UgYWZ0ZXIgMSBzZWNvbmQgb2YgZGVib3VuY2VkIGNhbGxzLlxuICogdmFyIGRlYm91bmNlZCA9IF8uZGVib3VuY2UoYmF0Y2hMb2csIDI1MCwgeyAnbWF4V2FpdCc6IDEwMDAgfSk7XG4gKiB2YXIgc291cmNlID0gbmV3IEV2ZW50U291cmNlKCcvc3RyZWFtJyk7XG4gKiBqUXVlcnkoc291cmNlKS5vbignbWVzc2FnZScsIGRlYm91bmNlZCk7XG4gKlxuICogLy8gQ2FuY2VsIHRoZSB0cmFpbGluZyBkZWJvdW5jZWQgaW52b2NhdGlvbi5cbiAqIGpRdWVyeSh3aW5kb3cpLm9uKCdwb3BzdGF0ZScsIGRlYm91bmNlZC5jYW5jZWwpO1xuICovXG5mdW5jdGlvbiBkZWJvdW5jZShmdW5jLCB3YWl0LCBvcHRpb25zKSB7XG4gIHZhciBsYXN0QXJncyxcbiAgICAgIGxhc3RUaGlzLFxuICAgICAgbWF4V2FpdCxcbiAgICAgIHJlc3VsdCxcbiAgICAgIHRpbWVySWQsXG4gICAgICBsYXN0Q2FsbFRpbWUsXG4gICAgICBsYXN0SW52b2tlVGltZSA9IDAsXG4gICAgICBsZWFkaW5nID0gZmFsc2UsXG4gICAgICBtYXhpbmcgPSBmYWxzZSxcbiAgICAgIHRyYWlsaW5nID0gdHJ1ZTtcblxuICBpZiAodHlwZW9mIGZ1bmMgIT0gJ2Z1bmN0aW9uJykge1xuICAgIHRocm93IG5ldyBUeXBlRXJyb3IoRlVOQ19FUlJPUl9URVhUKTtcbiAgfVxuICB3YWl0ID0gdG9OdW1iZXIod2FpdCkgfHwgMDtcbiAgaWYgKGlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgbGVhZGluZyA9ICEhb3B0aW9ucy5sZWFkaW5nO1xuICAgIG1heGluZyA9ICdtYXhXYWl0JyBpbiBvcHRpb25zO1xuICAgIG1heFdhaXQgPSBtYXhpbmcgPyBuYXRpdmVNYXgodG9OdW1iZXIob3B0aW9ucy5tYXhXYWl0KSB8fCAwLCB3YWl0KSA6IG1heFdhaXQ7XG4gICAgdHJhaWxpbmcgPSAndHJhaWxpbmcnIGluIG9wdGlvbnMgPyAhIW9wdGlvbnMudHJhaWxpbmcgOiB0cmFpbGluZztcbiAgfVxuXG4gIGZ1bmN0aW9uIGludm9rZUZ1bmModGltZSkge1xuICAgIHZhciBhcmdzID0gbGFzdEFyZ3MsXG4gICAgICAgIHRoaXNBcmcgPSBsYXN0VGhpcztcblxuICAgIGxhc3RBcmdzID0gbGFzdFRoaXMgPSB1bmRlZmluZWQ7XG4gICAgbGFzdEludm9rZVRpbWUgPSB0aW1lO1xuICAgIHJlc3VsdCA9IGZ1bmMuYXBwbHkodGhpc0FyZywgYXJncyk7XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfVxuXG4gIGZ1bmN0aW9uIGxlYWRpbmdFZGdlKHRpbWUpIHtcbiAgICAvLyBSZXNldCBhbnkgYG1heFdhaXRgIHRpbWVyLlxuICAgIGxhc3RJbnZva2VUaW1lID0gdGltZTtcbiAgICAvLyBTdGFydCB0aGUgdGltZXIgZm9yIHRoZSB0cmFpbGluZyBlZGdlLlxuICAgIHRpbWVySWQgPSBzZXRUaW1lb3V0KHRpbWVyRXhwaXJlZCwgd2FpdCk7XG4gICAgLy8gSW52b2tlIHRoZSBsZWFkaW5nIGVkZ2UuXG4gICAgcmV0dXJuIGxlYWRpbmcgPyBpbnZva2VGdW5jKHRpbWUpIDogcmVzdWx0O1xuICB9XG5cbiAgZnVuY3Rpb24gcmVtYWluaW5nV2FpdCh0aW1lKSB7XG4gICAgdmFyIHRpbWVTaW5jZUxhc3RDYWxsID0gdGltZSAtIGxhc3RDYWxsVGltZSxcbiAgICAgICAgdGltZVNpbmNlTGFzdEludm9rZSA9IHRpbWUgLSBsYXN0SW52b2tlVGltZSxcbiAgICAgICAgcmVzdWx0ID0gd2FpdCAtIHRpbWVTaW5jZUxhc3RDYWxsO1xuXG4gICAgcmV0dXJuIG1heGluZyA/IG5hdGl2ZU1pbihyZXN1bHQsIG1heFdhaXQgLSB0aW1lU2luY2VMYXN0SW52b2tlKSA6IHJlc3VsdDtcbiAgfVxuXG4gIGZ1bmN0aW9uIHNob3VsZEludm9rZSh0aW1lKSB7XG4gICAgdmFyIHRpbWVTaW5jZUxhc3RDYWxsID0gdGltZSAtIGxhc3RDYWxsVGltZSxcbiAgICAgICAgdGltZVNpbmNlTGFzdEludm9rZSA9IHRpbWUgLSBsYXN0SW52b2tlVGltZTtcblxuICAgIC8vIEVpdGhlciB0aGlzIGlzIHRoZSBmaXJzdCBjYWxsLCBhY3Rpdml0eSBoYXMgc3RvcHBlZCBhbmQgd2UncmUgYXQgdGhlXG4gICAgLy8gdHJhaWxpbmcgZWRnZSwgdGhlIHN5c3RlbSB0aW1lIGhhcyBnb25lIGJhY2t3YXJkcyBhbmQgd2UncmUgdHJlYXRpbmdcbiAgICAvLyBpdCBhcyB0aGUgdHJhaWxpbmcgZWRnZSwgb3Igd2UndmUgaGl0IHRoZSBgbWF4V2FpdGAgbGltaXQuXG4gICAgcmV0dXJuIChsYXN0Q2FsbFRpbWUgPT09IHVuZGVmaW5lZCB8fCAodGltZVNpbmNlTGFzdENhbGwgPj0gd2FpdCkgfHxcbiAgICAgICh0aW1lU2luY2VMYXN0Q2FsbCA8IDApIHx8IChtYXhpbmcgJiYgdGltZVNpbmNlTGFzdEludm9rZSA+PSBtYXhXYWl0KSk7XG4gIH1cblxuICBmdW5jdGlvbiB0aW1lckV4cGlyZWQoKSB7XG4gICAgdmFyIHRpbWUgPSBub3coKTtcbiAgICBpZiAoc2hvdWxkSW52b2tlKHRpbWUpKSB7XG4gICAgICByZXR1cm4gdHJhaWxpbmdFZGdlKHRpbWUpO1xuICAgIH1cbiAgICAvLyBSZXN0YXJ0IHRoZSB0aW1lci5cbiAgICB0aW1lcklkID0gc2V0VGltZW91dCh0aW1lckV4cGlyZWQsIHJlbWFpbmluZ1dhaXQodGltZSkpO1xuICB9XG5cbiAgZnVuY3Rpb24gdHJhaWxpbmdFZGdlKHRpbWUpIHtcbiAgICB0aW1lcklkID0gdW5kZWZpbmVkO1xuXG4gICAgLy8gT25seSBpbnZva2UgaWYgd2UgaGF2ZSBgbGFzdEFyZ3NgIHdoaWNoIG1lYW5zIGBmdW5jYCBoYXMgYmVlblxuICAgIC8vIGRlYm91bmNlZCBhdCBsZWFzdCBvbmNlLlxuICAgIGlmICh0cmFpbGluZyAmJiBsYXN0QXJncykge1xuICAgICAgcmV0dXJuIGludm9rZUZ1bmModGltZSk7XG4gICAgfVxuICAgIGxhc3RBcmdzID0gbGFzdFRoaXMgPSB1bmRlZmluZWQ7XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfVxuXG4gIGZ1bmN0aW9uIGNhbmNlbCgpIHtcbiAgICBpZiAodGltZXJJZCAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICBjbGVhclRpbWVvdXQodGltZXJJZCk7XG4gICAgfVxuICAgIGxhc3RJbnZva2VUaW1lID0gMDtcbiAgICBsYXN0QXJncyA9IGxhc3RDYWxsVGltZSA9IGxhc3RUaGlzID0gdGltZXJJZCA9IHVuZGVmaW5lZDtcbiAgfVxuXG4gIGZ1bmN0aW9uIGZsdXNoKCkge1xuICAgIHJldHVybiB0aW1lcklkID09PSB1bmRlZmluZWQgPyByZXN1bHQgOiB0cmFpbGluZ0VkZ2Uobm93KCkpO1xuICB9XG5cbiAgZnVuY3Rpb24gZGVib3VuY2VkKCkge1xuICAgIHZhciB0aW1lID0gbm93KCksXG4gICAgICAgIGlzSW52b2tpbmcgPSBzaG91bGRJbnZva2UodGltZSk7XG5cbiAgICBsYXN0QXJncyA9IGFyZ3VtZW50cztcbiAgICBsYXN0VGhpcyA9IHRoaXM7XG4gICAgbGFzdENhbGxUaW1lID0gdGltZTtcblxuICAgIGlmIChpc0ludm9raW5nKSB7XG4gICAgICBpZiAodGltZXJJZCA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHJldHVybiBsZWFkaW5nRWRnZShsYXN0Q2FsbFRpbWUpO1xuICAgICAgfVxuICAgICAgaWYgKG1heGluZykge1xuICAgICAgICAvLyBIYW5kbGUgaW52b2NhdGlvbnMgaW4gYSB0aWdodCBsb29wLlxuICAgICAgICB0aW1lcklkID0gc2V0VGltZW91dCh0aW1lckV4cGlyZWQsIHdhaXQpO1xuICAgICAgICByZXR1cm4gaW52b2tlRnVuYyhsYXN0Q2FsbFRpbWUpO1xuICAgICAgfVxuICAgIH1cbiAgICBpZiAodGltZXJJZCA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICB0aW1lcklkID0gc2V0VGltZW91dCh0aW1lckV4cGlyZWQsIHdhaXQpO1xuICAgIH1cbiAgICByZXR1cm4gcmVzdWx0O1xuICB9XG4gIGRlYm91bmNlZC5jYW5jZWwgPSBjYW5jZWw7XG4gIGRlYm91bmNlZC5mbHVzaCA9IGZsdXNoO1xuICByZXR1cm4gZGVib3VuY2VkO1xufVxuXG4vKipcbiAqIENyZWF0ZXMgYSB0aHJvdHRsZWQgZnVuY3Rpb24gdGhhdCBvbmx5IGludm9rZXMgYGZ1bmNgIGF0IG1vc3Qgb25jZSBwZXJcbiAqIGV2ZXJ5IGB3YWl0YCBtaWxsaXNlY29uZHMuIFRoZSB0aHJvdHRsZWQgZnVuY3Rpb24gY29tZXMgd2l0aCBhIGBjYW5jZWxgXG4gKiBtZXRob2QgdG8gY2FuY2VsIGRlbGF5ZWQgYGZ1bmNgIGludm9jYXRpb25zIGFuZCBhIGBmbHVzaGAgbWV0aG9kIHRvXG4gKiBpbW1lZGlhdGVseSBpbnZva2UgdGhlbS4gUHJvdmlkZSBgb3B0aW9uc2AgdG8gaW5kaWNhdGUgd2hldGhlciBgZnVuY2BcbiAqIHNob3VsZCBiZSBpbnZva2VkIG9uIHRoZSBsZWFkaW5nIGFuZC9vciB0cmFpbGluZyBlZGdlIG9mIHRoZSBgd2FpdGBcbiAqIHRpbWVvdXQuIFRoZSBgZnVuY2AgaXMgaW52b2tlZCB3aXRoIHRoZSBsYXN0IGFyZ3VtZW50cyBwcm92aWRlZCB0byB0aGVcbiAqIHRocm90dGxlZCBmdW5jdGlvbi4gU3Vic2VxdWVudCBjYWxscyB0byB0aGUgdGhyb3R0bGVkIGZ1bmN0aW9uIHJldHVybiB0aGVcbiAqIHJlc3VsdCBvZiB0aGUgbGFzdCBgZnVuY2AgaW52b2NhdGlvbi5cbiAqXG4gKiAqKk5vdGU6KiogSWYgYGxlYWRpbmdgIGFuZCBgdHJhaWxpbmdgIG9wdGlvbnMgYXJlIGB0cnVlYCwgYGZ1bmNgIGlzXG4gKiBpbnZva2VkIG9uIHRoZSB0cmFpbGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0IG9ubHkgaWYgdGhlIHRocm90dGxlZCBmdW5jdGlvblxuICogaXMgaW52b2tlZCBtb3JlIHRoYW4gb25jZSBkdXJpbmcgdGhlIGB3YWl0YCB0aW1lb3V0LlxuICpcbiAqIElmIGB3YWl0YCBpcyBgMGAgYW5kIGBsZWFkaW5nYCBpcyBgZmFsc2VgLCBgZnVuY2AgaW52b2NhdGlvbiBpcyBkZWZlcnJlZFxuICogdW50aWwgdG8gdGhlIG5leHQgdGljaywgc2ltaWxhciB0byBgc2V0VGltZW91dGAgd2l0aCBhIHRpbWVvdXQgb2YgYDBgLlxuICpcbiAqIFNlZSBbRGF2aWQgQ29yYmFjaG8ncyBhcnRpY2xlXShodHRwczovL2Nzcy10cmlja3MuY29tL2RlYm91bmNpbmctdGhyb3R0bGluZy1leHBsYWluZWQtZXhhbXBsZXMvKVxuICogZm9yIGRldGFpbHMgb3ZlciB0aGUgZGlmZmVyZW5jZXMgYmV0d2VlbiBgXy50aHJvdHRsZWAgYW5kIGBfLmRlYm91bmNlYC5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDAuMS4wXG4gKiBAY2F0ZWdvcnkgRnVuY3Rpb25cbiAqIEBwYXJhbSB7RnVuY3Rpb259IGZ1bmMgVGhlIGZ1bmN0aW9uIHRvIHRocm90dGxlLlxuICogQHBhcmFtIHtudW1iZXJ9IFt3YWl0PTBdIFRoZSBudW1iZXIgb2YgbWlsbGlzZWNvbmRzIHRvIHRocm90dGxlIGludm9jYXRpb25zIHRvLlxuICogQHBhcmFtIHtPYmplY3R9IFtvcHRpb25zPXt9XSBUaGUgb3B0aW9ucyBvYmplY3QuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLmxlYWRpbmc9dHJ1ZV1cbiAqICBTcGVjaWZ5IGludm9raW5nIG9uIHRoZSBsZWFkaW5nIGVkZ2Ugb2YgdGhlIHRpbWVvdXQuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLnRyYWlsaW5nPXRydWVdXG4gKiAgU3BlY2lmeSBpbnZva2luZyBvbiB0aGUgdHJhaWxpbmcgZWRnZSBvZiB0aGUgdGltZW91dC5cbiAqIEByZXR1cm5zIHtGdW5jdGlvbn0gUmV0dXJucyB0aGUgbmV3IHRocm90dGxlZCBmdW5jdGlvbi5cbiAqIEBleGFtcGxlXG4gKlxuICogLy8gQXZvaWQgZXhjZXNzaXZlbHkgdXBkYXRpbmcgdGhlIHBvc2l0aW9uIHdoaWxlIHNjcm9sbGluZy5cbiAqIGpRdWVyeSh3aW5kb3cpLm9uKCdzY3JvbGwnLCBfLnRocm90dGxlKHVwZGF0ZVBvc2l0aW9uLCAxMDApKTtcbiAqXG4gKiAvLyBJbnZva2UgYHJlbmV3VG9rZW5gIHdoZW4gdGhlIGNsaWNrIGV2ZW50IGlzIGZpcmVkLCBidXQgbm90IG1vcmUgdGhhbiBvbmNlIGV2ZXJ5IDUgbWludXRlcy5cbiAqIHZhciB0aHJvdHRsZWQgPSBfLnRocm90dGxlKHJlbmV3VG9rZW4sIDMwMDAwMCwgeyAndHJhaWxpbmcnOiBmYWxzZSB9KTtcbiAqIGpRdWVyeShlbGVtZW50KS5vbignY2xpY2snLCB0aHJvdHRsZWQpO1xuICpcbiAqIC8vIENhbmNlbCB0aGUgdHJhaWxpbmcgdGhyb3R0bGVkIGludm9jYXRpb24uXG4gKiBqUXVlcnkod2luZG93KS5vbigncG9wc3RhdGUnLCB0aHJvdHRsZWQuY2FuY2VsKTtcbiAqL1xuZnVuY3Rpb24gdGhyb3R0bGUoZnVuYywgd2FpdCwgb3B0aW9ucykge1xuICB2YXIgbGVhZGluZyA9IHRydWUsXG4gICAgICB0cmFpbGluZyA9IHRydWU7XG5cbiAgaWYgKHR5cGVvZiBmdW5jICE9ICdmdW5jdGlvbicpIHtcbiAgICB0aHJvdyBuZXcgVHlwZUVycm9yKEZVTkNfRVJST1JfVEVYVCk7XG4gIH1cbiAgaWYgKGlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgbGVhZGluZyA9ICdsZWFkaW5nJyBpbiBvcHRpb25zID8gISFvcHRpb25zLmxlYWRpbmcgOiBsZWFkaW5nO1xuICAgIHRyYWlsaW5nID0gJ3RyYWlsaW5nJyBpbiBvcHRpb25zID8gISFvcHRpb25zLnRyYWlsaW5nIDogdHJhaWxpbmc7XG4gIH1cbiAgcmV0dXJuIGRlYm91bmNlKGZ1bmMsIHdhaXQsIHtcbiAgICAnbGVhZGluZyc6IGxlYWRpbmcsXG4gICAgJ21heFdhaXQnOiB3YWl0LFxuICAgICd0cmFpbGluZyc6IHRyYWlsaW5nXG4gIH0pO1xufVxuXG4vKipcbiAqIENoZWNrcyBpZiBgdmFsdWVgIGlzIHRoZVxuICogW2xhbmd1YWdlIHR5cGVdKGh0dHA6Ly93d3cuZWNtYS1pbnRlcm5hdGlvbmFsLm9yZy9lY21hLTI2Mi83LjAvI3NlYy1lY21hc2NyaXB0LWxhbmd1YWdlLXR5cGVzKVxuICogb2YgYE9iamVjdGAuIChlLmcuIGFycmF5cywgZnVuY3Rpb25zLCBvYmplY3RzLCByZWdleGVzLCBgbmV3IE51bWJlcigwKWAsIGFuZCBgbmV3IFN0cmluZygnJylgKVxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAc2luY2UgMC4xLjBcbiAqIEBjYXRlZ29yeSBMYW5nXG4gKiBAcGFyYW0geyp9IHZhbHVlIFRoZSB2YWx1ZSB0byBjaGVjay5cbiAqIEByZXR1cm5zIHtib29sZWFufSBSZXR1cm5zIGB0cnVlYCBpZiBgdmFsdWVgIGlzIGFuIG9iamVjdCwgZWxzZSBgZmFsc2VgLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLmlzT2JqZWN0KHt9KTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmlzT2JqZWN0KFsxLCAyLCAzXSk7XG4gKiAvLyA9PiB0cnVlXG4gKlxuICogXy5pc09iamVjdChfLm5vb3ApO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNPYmplY3QobnVsbCk7XG4gKiAvLyA9PiBmYWxzZVxuICovXG5mdW5jdGlvbiBpc09iamVjdCh2YWx1ZSkge1xuICB2YXIgdHlwZSA9IHR5cGVvZiB2YWx1ZTtcbiAgcmV0dXJuICEhdmFsdWUgJiYgKHR5cGUgPT0gJ29iamVjdCcgfHwgdHlwZSA9PSAnZnVuY3Rpb24nKTtcbn1cblxuLyoqXG4gKiBDaGVja3MgaWYgYHZhbHVlYCBpcyBvYmplY3QtbGlrZS4gQSB2YWx1ZSBpcyBvYmplY3QtbGlrZSBpZiBpdCdzIG5vdCBgbnVsbGBcbiAqIGFuZCBoYXMgYSBgdHlwZW9mYCByZXN1bHQgb2YgXCJvYmplY3RcIi5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDQuMC4wXG4gKiBAY2F0ZWdvcnkgTGFuZ1xuICogQHBhcmFtIHsqfSB2YWx1ZSBUaGUgdmFsdWUgdG8gY2hlY2suXG4gKiBAcmV0dXJucyB7Ym9vbGVhbn0gUmV0dXJucyBgdHJ1ZWAgaWYgYHZhbHVlYCBpcyBvYmplY3QtbGlrZSwgZWxzZSBgZmFsc2VgLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLmlzT2JqZWN0TGlrZSh7fSk7XG4gKiAvLyA9PiB0cnVlXG4gKlxuICogXy5pc09iamVjdExpa2UoWzEsIDIsIDNdKTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmlzT2JqZWN0TGlrZShfLm5vb3ApO1xuICogLy8gPT4gZmFsc2VcbiAqXG4gKiBfLmlzT2JqZWN0TGlrZShudWxsKTtcbiAqIC8vID0+IGZhbHNlXG4gKi9cbmZ1bmN0aW9uIGlzT2JqZWN0TGlrZSh2YWx1ZSkge1xuICByZXR1cm4gISF2YWx1ZSAmJiB0eXBlb2YgdmFsdWUgPT0gJ29iamVjdCc7XG59XG5cbi8qKlxuICogQ2hlY2tzIGlmIGB2YWx1ZWAgaXMgY2xhc3NpZmllZCBhcyBhIGBTeW1ib2xgIHByaW1pdGl2ZSBvciBvYmplY3QuXG4gKlxuICogQHN0YXRpY1xuICogQG1lbWJlck9mIF9cbiAqIEBzaW5jZSA0LjAuMFxuICogQGNhdGVnb3J5IExhbmdcbiAqIEBwYXJhbSB7Kn0gdmFsdWUgVGhlIHZhbHVlIHRvIGNoZWNrLlxuICogQHJldHVybnMge2Jvb2xlYW59IFJldHVybnMgYHRydWVgIGlmIGB2YWx1ZWAgaXMgYSBzeW1ib2wsIGVsc2UgYGZhbHNlYC5cbiAqIEBleGFtcGxlXG4gKlxuICogXy5pc1N5bWJvbChTeW1ib2wuaXRlcmF0b3IpO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNTeW1ib2woJ2FiYycpO1xuICogLy8gPT4gZmFsc2VcbiAqL1xuZnVuY3Rpb24gaXNTeW1ib2wodmFsdWUpIHtcbiAgcmV0dXJuIHR5cGVvZiB2YWx1ZSA9PSAnc3ltYm9sJyB8fFxuICAgIChpc09iamVjdExpa2UodmFsdWUpICYmIG9iamVjdFRvU3RyaW5nLmNhbGwodmFsdWUpID09IHN5bWJvbFRhZyk7XG59XG5cbi8qKlxuICogQ29udmVydHMgYHZhbHVlYCB0byBhIG51bWJlci5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDQuMC4wXG4gKiBAY2F0ZWdvcnkgTGFuZ1xuICogQHBhcmFtIHsqfSB2YWx1ZSBUaGUgdmFsdWUgdG8gcHJvY2Vzcy5cbiAqIEByZXR1cm5zIHtudW1iZXJ9IFJldHVybnMgdGhlIG51bWJlci5cbiAqIEBleGFtcGxlXG4gKlxuICogXy50b051bWJlcigzLjIpO1xuICogLy8gPT4gMy4yXG4gKlxuICogXy50b051bWJlcihOdW1iZXIuTUlOX1ZBTFVFKTtcbiAqIC8vID0+IDVlLTMyNFxuICpcbiAqIF8udG9OdW1iZXIoSW5maW5pdHkpO1xuICogLy8gPT4gSW5maW5pdHlcbiAqXG4gKiBfLnRvTnVtYmVyKCczLjInKTtcbiAqIC8vID0+IDMuMlxuICovXG5mdW5jdGlvbiB0b051bWJlcih2YWx1ZSkge1xuICBpZiAodHlwZW9mIHZhbHVlID09ICdudW1iZXInKSB7XG4gICAgcmV0dXJuIHZhbHVlO1xuICB9XG4gIGlmIChpc1N5bWJvbCh2YWx1ZSkpIHtcbiAgICByZXR1cm4gTkFOO1xuICB9XG4gIGlmIChpc09iamVjdCh2YWx1ZSkpIHtcbiAgICB2YXIgb3RoZXIgPSB0eXBlb2YgdmFsdWUudmFsdWVPZiA9PSAnZnVuY3Rpb24nID8gdmFsdWUudmFsdWVPZigpIDogdmFsdWU7XG4gICAgdmFsdWUgPSBpc09iamVjdChvdGhlcikgPyAob3RoZXIgKyAnJykgOiBvdGhlcjtcbiAgfVxuICBpZiAodHlwZW9mIHZhbHVlICE9ICdzdHJpbmcnKSB7XG4gICAgcmV0dXJuIHZhbHVlID09PSAwID8gdmFsdWUgOiArdmFsdWU7XG4gIH1cbiAgdmFsdWUgPSB2YWx1ZS5yZXBsYWNlKHJlVHJpbSwgJycpO1xuICB2YXIgaXNCaW5hcnkgPSByZUlzQmluYXJ5LnRlc3QodmFsdWUpO1xuICByZXR1cm4gKGlzQmluYXJ5IHx8IHJlSXNPY3RhbC50ZXN0KHZhbHVlKSlcbiAgICA/IGZyZWVQYXJzZUludCh2YWx1ZS5zbGljZSgyKSwgaXNCaW5hcnkgPyAyIDogOClcbiAgICA6IChyZUlzQmFkSGV4LnRlc3QodmFsdWUpID8gTkFOIDogK3ZhbHVlKTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSB0aHJvdHRsZTtcbiIsInZhciBnO1xuXG4vLyBUaGlzIHdvcmtzIGluIG5vbi1zdHJpY3QgbW9kZVxuZyA9IChmdW5jdGlvbigpIHtcblx0cmV0dXJuIHRoaXM7XG59KSgpO1xuXG50cnkge1xuXHQvLyBUaGlzIHdvcmtzIGlmIGV2YWwgaXMgYWxsb3dlZCAoc2VlIENTUClcblx0ZyA9IGcgfHwgbmV3IEZ1bmN0aW9uKFwicmV0dXJuIHRoaXNcIikoKTtcbn0gY2F0Y2ggKGUpIHtcblx0Ly8gVGhpcyB3b3JrcyBpZiB0aGUgd2luZG93IHJlZmVyZW5jZSBpcyBhdmFpbGFibGVcblx0aWYgKHR5cGVvZiB3aW5kb3cgPT09IFwib2JqZWN0XCIpIGcgPSB3aW5kb3c7XG59XG5cbi8vIGcgY2FuIHN0aWxsIGJlIHVuZGVmaW5lZCwgYnV0IG5vdGhpbmcgdG8gZG8gYWJvdXQgaXQuLi5cbi8vIFdlIHJldHVybiB1bmRlZmluZWQsIGluc3RlYWQgb2Ygbm90aGluZyBoZXJlLCBzbyBpdCdzXG4vLyBlYXNpZXIgdG8gaGFuZGxlIHRoaXMgY2FzZS4gaWYoIWdsb2JhbCkgeyAuLi59XG5cbm1vZHVsZS5leHBvcnRzID0gZztcbiJdLCJzb3VyY2VSb290IjoiIn0= \ No newline at end of file diff --git a/extensions/markdown-language-features/media/markdown.css b/extensions/markdown-language-features/media/markdown.css index ebf3b5c35f..d4efc854d4 100644 --- a/extensions/markdown-language-features/media/markdown.css +++ b/extensions/markdown-language-features/media/markdown.css @@ -129,15 +129,6 @@ h1, h2, h3 { font-weight: normal; } -h1 code, -h2 code, -h3 code, -h4 code, -h5 code, -h6 code { - line-height: auto; -} - table { border-collapse: collapse; } diff --git a/extensions/markdown-language-features/media/pre.js b/extensions/markdown-language-features/media/pre.js index 2d0f917feb..bddc3b86ac 100644 --- a/extensions/markdown-language-features/media/pre.js +++ b/extensions/markdown-language-features/media/pre.js @@ -1,2 +1,2 @@ -!function(e){var t={};function s(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,s),o.l=!0,o.exports}s.m=e,s.c=t,s.d=function(e,t,n){s.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:n})},s.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},s.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return s.d(t,"a",t),t},s.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},s.p="",s(s.s=5)}([function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});let n=void 0;function o(e){const t=document.getElementById("vscode-markdown-preview-data");if(t){const s=t.getAttribute(e);if(s)return JSON.parse(s)}throw new Error(`Could not load data for ${e}`)}t.getData=o,t.getSettings=function(){if(n)return n;if(n=o("data-settings"))return n;throw new Error("Could not load settings")}},,function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});t.StyleLoadingMonitor=class{constructor(){this.unloadedStyles=[],this.finishedLoading=!1;const e=e=>{const t=e.target.dataset.source;this.unloadedStyles.push(t)};window.addEventListener("DOMContentLoaded",()=>{for(const t of document.getElementsByClassName("code-user-style"))t.dataset.source&&(t.onerror=e)}),window.addEventListener("load",()=>{this.unloadedStyles.length&&(this.finishedLoading=!0,this.poster&&this.poster.postMessage("previewStyleLoadError",{unloadedStyles:this.unloadedStyles}))})}setPoster(e){this.poster=e,this.finishedLoading&&e.postMessage("previewStyleLoadError",{unloadedStyles:this.unloadedStyles})}}},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getStrings=function(){const e=document.getElementById("vscode-markdown-preview-data");if(e){const t=e.getAttribute("data-strings");if(t)return JSON.parse(t)}throw new Error("Could not load strings")}},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=s(0),o=s(3);t.CspAlerter=class{constructor(){this.didShow=!1,this.didHaveCspWarning=!1,document.addEventListener("securitypolicyviolation",()=>{this.onCspWarning()}),window.addEventListener("message",e=>{e&&e.data&&"vscode-did-block-svg"===e.data.name&&this.onCspWarning()})}setPoster(e){this.messaging=e,this.didHaveCspWarning&&this.showCspWarning()}onCspWarning(){this.didHaveCspWarning=!0,this.showCspWarning()}showCspWarning(){const e=o.getStrings(),t=n.getSettings();if(this.didShow||t.disableSecurityWarnings||!this.messaging)return;this.didShow=!0;const s=document.createElement("a");s.innerText=e.cspAlertMessageText,s.setAttribute("id","code-csp-warning"),s.setAttribute("title",e.cspAlertMessageTitle),s.setAttribute("role","button"),s.setAttribute("aria-label",e.cspAlertMessageLabel),s.onclick=(()=>{this.messaging.postMessage("showPreviewSecuritySelector",{source:t.source})}),document.body.appendChild(s)}}},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=s(4),o=s(2);window.cspAlerter=new n.CspAlerter,window.styleLoadingMonitor=new o.StyleLoadingMonitor}]); -//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvc2V0dGluZ3MudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvbG9hZGluZy50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9zdHJpbmdzLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL2NzcC50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9wcmUudHMiXSwibmFtZXMiOlsiaW5zdGFsbGVkTW9kdWxlcyIsIl9fd2VicGFja19yZXF1aXJlX18iLCJtb2R1bGVJZCIsImV4cG9ydHMiLCJtb2R1bGUiLCJpIiwibCIsIm1vZHVsZXMiLCJjYWxsIiwibSIsImMiLCJkIiwibmFtZSIsImdldHRlciIsIm8iLCJPYmplY3QiLCJkZWZpbmVQcm9wZXJ0eSIsImNvbmZpZ3VyYWJsZSIsImVudW1lcmFibGUiLCJnZXQiLCJyIiwidmFsdWUiLCJuIiwiX19lc01vZHVsZSIsIm9iamVjdCIsInByb3BlcnR5IiwicHJvdG90eXBlIiwiaGFzT3duUHJvcGVydHkiLCJwIiwicyIsImNhY2hlZFNldHRpbmdzIiwidW5kZWZpbmVkIiwiZ2V0RGF0YSIsImtleSIsImVsZW1lbnQiLCJkb2N1bWVudCIsImdldEVsZW1lbnRCeUlkIiwiZGF0YSIsImdldEF0dHJpYnV0ZSIsIkpTT04iLCJwYXJzZSIsIkVycm9yIiwiZ2V0U2V0dGluZ3MiLCJTdHlsZUxvYWRpbmdNb25pdG9yIiwiW29iamVjdCBPYmplY3RdIiwidGhpcyIsInVubG9hZGVkU3R5bGVzIiwiZmluaXNoZWRMb2FkaW5nIiwib25TdHlsZUxvYWRFcnJvciIsImV2ZW50Iiwic291cmNlIiwidGFyZ2V0IiwiZGF0YXNldCIsInB1c2giLCJ3aW5kb3ciLCJhZGRFdmVudExpc3RlbmVyIiwibGluayIsImdldEVsZW1lbnRzQnlDbGFzc05hbWUiLCJvbmVycm9yIiwibGVuZ3RoIiwicG9zdGVyIiwicG9zdE1lc3NhZ2UiLCJnZXRTdHJpbmdzIiwic3RvcmUiLCJzZXR0aW5nc18xIiwic3RyaW5nc18xIiwiQ3NwQWxlcnRlciIsImRpZFNob3ciLCJkaWRIYXZlQ3NwV2FybmluZyIsIm9uQ3NwV2FybmluZyIsIm1lc3NhZ2luZyIsInNob3dDc3BXYXJuaW5nIiwic3RyaW5ncyIsInNldHRpbmdzIiwiZGlzYWJsZVNlY3VyaXR5V2FybmluZ3MiLCJub3RpZmljYXRpb24iLCJjcmVhdGVFbGVtZW50IiwiaW5uZXJUZXh0IiwiY3NwQWxlcnRNZXNzYWdlVGV4dCIsInNldEF0dHJpYnV0ZSIsImNzcEFsZXJ0TWVzc2FnZVRpdGxlIiwiY3NwQWxlcnRNZXNzYWdlTGFiZWwiLCJvbmNsaWNrIiwiYm9keSIsImFwcGVuZENoaWxkIiwiY3NwXzEiLCJsb2FkaW5nXzEiLCJjc3BBbGVydGVyIiwic3R5bGVMb2FkaW5nTW9uaXRvciJdLCJtYXBwaW5ncyI6ImFBQ0EsSUFBQUEsS0FHQSxTQUFBQyxFQUFBQyxHQUdBLEdBQUFGLEVBQUFFLEdBQ0EsT0FBQUYsRUFBQUUsR0FBQUMsUUFHQSxJQUFBQyxFQUFBSixFQUFBRSxJQUNBRyxFQUFBSCxFQUNBSSxHQUFBLEVBQ0FILFlBVUEsT0FOQUksRUFBQUwsR0FBQU0sS0FBQUosRUFBQUQsUUFBQUMsSUFBQUQsUUFBQUYsR0FHQUcsRUFBQUUsR0FBQSxFQUdBRixFQUFBRCxRQUtBRixFQUFBUSxFQUFBRixFQUdBTixFQUFBUyxFQUFBVixFQUdBQyxFQUFBVSxFQUFBLFNBQUFSLEVBQUFTLEVBQUFDLEdBQ0FaLEVBQUFhLEVBQUFYLEVBQUFTLElBQ0FHLE9BQUFDLGVBQUFiLEVBQUFTLEdBQ0FLLGNBQUEsRUFDQUMsWUFBQSxFQUNBQyxJQUFBTixLQU1BWixFQUFBbUIsRUFBQSxTQUFBakIsR0FDQVksT0FBQUMsZUFBQWIsRUFBQSxjQUFpRGtCLE9BQUEsS0FJakRwQixFQUFBcUIsRUFBQSxTQUFBbEIsR0FDQSxJQUFBUyxFQUFBVCxLQUFBbUIsV0FDQSxXQUEyQixPQUFBbkIsRUFBQSxTQUMzQixXQUFpQyxPQUFBQSxHQUVqQyxPQURBSCxFQUFBVSxFQUFBRSxFQUFBLElBQUFBLEdBQ0FBLEdBSUFaLEVBQUFhLEVBQUEsU0FBQVUsRUFBQUMsR0FBc0QsT0FBQVYsT0FBQVcsVUFBQUMsZUFBQW5CLEtBQUFnQixFQUFBQyxJQUd0RHhCLEVBQUEyQixFQUFBLEdBSUEzQixJQUFBNEIsRUFBQSxrQ0M5REFkLE9BQUFDLGVBQUFiLEVBQUEsY0FBOENrQixPQUFBLElBQzlDLElBQUFTLE9BQUFDLEVBQ0EsU0FBQUMsRUFBQUMsR0FDQSxNQUFBQyxFQUFBQyxTQUFBQyxlQUFBLGdDQUNBLEdBQUFGLEVBQUEsQ0FDQSxNQUFBRyxFQUFBSCxFQUFBSSxhQUFBTCxHQUNBLEdBQUFJLEVBQ0EsT0FBQUUsS0FBQUMsTUFBQUgsR0FHQSxVQUFBSSxpQ0FBK0NSLEtBRS9DOUIsRUFBQTZCLFVBV0E3QixFQUFBdUMsWUFWQSxXQUNBLEdBQUFaLEVBQ0EsT0FBQUEsRUFHQSxHQURBQSxFQUFBRSxFQUFBLGlCQUVBLE9BQUFGLEVBRUEsVUFBQVcsTUFBQSwyREN6QkExQixPQUFBQyxlQUFBYixFQUFBLGNBQThDa0IsT0FBQSxJQWlDOUNsQixFQUFBd0MsMEJBL0JBQyxjQUNBQyxLQUFBQyxrQkFDQUQsS0FBQUUsaUJBQUEsRUFDQSxNQUFBQyxFQUFBQyxJQUNBLE1BQUFDLEVBQUFELEVBQUFFLE9BQUFDLFFBQUFGLE9BQ0FMLEtBQUFDLGVBQUFPLEtBQUFILElBRUFJLE9BQUFDLGlCQUFBLHdCQUNBLFVBQUFDLEtBQUFyQixTQUFBc0IsdUJBQUEsbUJBQ0FELEVBQUFKLFFBQUFGLFNBQ0FNLEVBQUFFLFFBQUFWLEtBSUFNLE9BQUFDLGlCQUFBLFlBQ0FWLEtBQUFDLGVBQUFhLFNBR0FkLEtBQUFFLGlCQUFBLEVBQ0FGLEtBQUFlLFFBQ0FmLEtBQUFlLE9BQUFDLFlBQUEseUJBQWtFZixlQUFBRCxLQUFBQyxvQkFJbEVGLFVBQUFnQixHQUNBZixLQUFBZSxTQUNBZixLQUFBRSxpQkFDQWEsRUFBQUMsWUFBQSx5QkFBeURmLGVBQUFELEtBQUFDLGlEQ3pCekQvQixPQUFBQyxlQUFBYixFQUFBLGNBQThDa0IsT0FBQSxJQVc5Q2xCLEVBQUEyRCxXQVZBLFdBQ0EsTUFBQUMsRUFBQTVCLFNBQUFDLGVBQUEsZ0NBQ0EsR0FBQTJCLEVBQUEsQ0FDQSxNQUFBMUIsRUFBQTBCLEVBQUF6QixhQUFBLGdCQUNBLEdBQUFELEVBQ0EsT0FBQUUsS0FBQUMsTUFBQUgsR0FHQSxVQUFBSSxNQUFBLHlEQ1RBMUIsT0FBQUMsZUFBQWIsRUFBQSxjQUE4Q2tCLE9BQUEsSUFDOUMsTUFBQTJDLEVBQUEvRCxFQUFBLEdBQ0FnRSxFQUFBaEUsRUFBQSxHQThDQUUsRUFBQStELGlCQXpDQXRCLGNBQ0FDLEtBQUFzQixTQUFBLEVBQ0F0QixLQUFBdUIsbUJBQUEsRUFDQWpDLFNBQUFvQixpQkFBQSwrQkFDQVYsS0FBQXdCLGlCQUVBZixPQUFBQyxpQkFBQSxVQUFBTixJQUNBQSxLQUFBWixNQUFBLHlCQUFBWSxFQUFBWixLQUFBekIsTUFDQWlDLEtBQUF3QixpQkFJQXpCLFVBQUFnQixHQUNBZixLQUFBeUIsVUFBQVYsRUFDQWYsS0FBQXVCLG1CQUNBdkIsS0FBQTBCLGlCQUdBM0IsZUFDQUMsS0FBQXVCLG1CQUFBLEVBQ0F2QixLQUFBMEIsaUJBRUEzQixpQkFDQSxNQUFBNEIsRUFBQVAsRUFBQUgsYUFDQVcsRUFBQVQsRUFBQXRCLGNBQ0EsR0FBQUcsS0FBQXNCLFNBQUFNLEVBQUFDLDBCQUFBN0IsS0FBQXlCLFVBQ0EsT0FFQXpCLEtBQUFzQixTQUFBLEVBQ0EsTUFBQVEsRUFBQXhDLFNBQUF5QyxjQUFBLEtBQ0FELEVBQUFFLFVBQUFMLEVBQUFNLG9CQUNBSCxFQUFBSSxhQUFBLHlCQUNBSixFQUFBSSxhQUFBLFFBQUFQLEVBQUFRLHNCQUNBTCxFQUFBSSxhQUFBLGlCQUNBSixFQUFBSSxhQUFBLGFBQUFQLEVBQUFTLHNCQUNBTixFQUFBTyxRQUFBLE1BQ0FyQyxLQUFBeUIsVUFBQVQsWUFBQSwrQkFBdUVYLE9BQUF1QixFQUFBdkIsV0FFdkVmLFNBQUFnRCxLQUFBQyxZQUFBVCxtQ0M3Q0E1RCxPQUFBQyxlQUFBYixFQUFBLGNBQThDa0IsT0FBQSxJQUM5QyxNQUFBZ0UsRUFBQXBGLEVBQUEsR0FDQXFGLEVBQUFyRixFQUFBLEdBQ0FxRCxPQUFBaUMsV0FBQSxJQUFBRixFQUFBbkIsV0FDQVosT0FBQWtDLG9CQUFBLElBQUFGLEVBQUEzQyIsImZpbGUiOiJwcmUuanMiLCJzb3VyY2VzQ29udGVudCI6WyIgXHQvLyBUaGUgbW9kdWxlIGNhY2hlXG4gXHR2YXIgaW5zdGFsbGVkTW9kdWxlcyA9IHt9O1xuXG4gXHQvLyBUaGUgcmVxdWlyZSBmdW5jdGlvblxuIFx0ZnVuY3Rpb24gX193ZWJwYWNrX3JlcXVpcmVfXyhtb2R1bGVJZCkge1xuXG4gXHRcdC8vIENoZWNrIGlmIG1vZHVsZSBpcyBpbiBjYWNoZVxuIFx0XHRpZihpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSkge1xuIFx0XHRcdHJldHVybiBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXS5leHBvcnRzO1xuIFx0XHR9XG4gXHRcdC8vIENyZWF0ZSBhIG5ldyBtb2R1bGUgKGFuZCBwdXQgaXQgaW50byB0aGUgY2FjaGUpXG4gXHRcdHZhciBtb2R1bGUgPSBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSA9IHtcbiBcdFx0XHRpOiBtb2R1bGVJZCxcbiBcdFx0XHRsOiBmYWxzZSxcbiBcdFx0XHRleHBvcnRzOiB7fVxuIFx0XHR9O1xuXG4gXHRcdC8vIEV4ZWN1dGUgdGhlIG1vZHVsZSBmdW5jdGlvblxuIFx0XHRtb2R1bGVzW21vZHVsZUlkXS5jYWxsKG1vZHVsZS5leHBvcnRzLCBtb2R1bGUsIG1vZHVsZS5leHBvcnRzLCBfX3dlYnBhY2tfcmVxdWlyZV9fKTtcblxuIFx0XHQvLyBGbGFnIHRoZSBtb2R1bGUgYXMgbG9hZGVkXG4gXHRcdG1vZHVsZS5sID0gdHJ1ZTtcblxuIFx0XHQvLyBSZXR1cm4gdGhlIGV4cG9ydHMgb2YgdGhlIG1vZHVsZVxuIFx0XHRyZXR1cm4gbW9kdWxlLmV4cG9ydHM7XG4gXHR9XG5cblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGVzIG9iamVjdCAoX193ZWJwYWNrX21vZHVsZXNfXylcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubSA9IG1vZHVsZXM7XG5cbiBcdC8vIGV4cG9zZSB0aGUgbW9kdWxlIGNhY2hlXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLmMgPSBpbnN0YWxsZWRNb2R1bGVzO1xuXG4gXHQvLyBkZWZpbmUgZ2V0dGVyIGZ1bmN0aW9uIGZvciBoYXJtb255IGV4cG9ydHNcbiBcdF9fd2VicGFja19yZXF1aXJlX18uZCA9IGZ1bmN0aW9uKGV4cG9ydHMsIG5hbWUsIGdldHRlcikge1xuIFx0XHRpZighX193ZWJwYWNrX3JlcXVpcmVfXy5vKGV4cG9ydHMsIG5hbWUpKSB7XG4gXHRcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIG5hbWUsIHtcbiBcdFx0XHRcdGNvbmZpZ3VyYWJsZTogZmFsc2UsXG4gXHRcdFx0XHRlbnVtZXJhYmxlOiB0cnVlLFxuIFx0XHRcdFx0Z2V0OiBnZXR0ZXJcbiBcdFx0XHR9KTtcbiBcdFx0fVxuIFx0fTtcblxuIFx0Ly8gZGVmaW5lIF9fZXNNb2R1bGUgb24gZXhwb3J0c1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5yID0gZnVuY3Rpb24oZXhwb3J0cykge1xuIFx0XHRPYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgJ19fZXNNb2R1bGUnLCB7IHZhbHVlOiB0cnVlIH0pO1xuIFx0fTtcblxuIFx0Ly8gZ2V0RGVmYXVsdEV4cG9ydCBmdW5jdGlvbiBmb3IgY29tcGF0aWJpbGl0eSB3aXRoIG5vbi1oYXJtb255IG1vZHVsZXNcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubiA9IGZ1bmN0aW9uKG1vZHVsZSkge1xuIFx0XHR2YXIgZ2V0dGVyID0gbW9kdWxlICYmIG1vZHVsZS5fX2VzTW9kdWxlID9cbiBcdFx0XHRmdW5jdGlvbiBnZXREZWZhdWx0KCkgeyByZXR1cm4gbW9kdWxlWydkZWZhdWx0J107IH0gOlxuIFx0XHRcdGZ1bmN0aW9uIGdldE1vZHVsZUV4cG9ydHMoKSB7IHJldHVybiBtb2R1bGU7IH07XG4gXHRcdF9fd2VicGFja19yZXF1aXJlX18uZChnZXR0ZXIsICdhJywgZ2V0dGVyKTtcbiBcdFx0cmV0dXJuIGdldHRlcjtcbiBcdH07XG5cbiBcdC8vIE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbFxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5vID0gZnVuY3Rpb24ob2JqZWN0LCBwcm9wZXJ0eSkgeyByZXR1cm4gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKG9iamVjdCwgcHJvcGVydHkpOyB9O1xuXG4gXHQvLyBfX3dlYnBhY2tfcHVibGljX3BhdGhfX1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5wID0gXCJcIjtcblxuXG4gXHQvLyBMb2FkIGVudHJ5IG1vZHVsZSBhbmQgcmV0dXJuIGV4cG9ydHNcbiBcdHJldHVybiBfX3dlYnBhY2tfcmVxdWlyZV9fKF9fd2VicGFja19yZXF1aXJlX18ucyA9IDUpO1xuIiwiXCJ1c2Ugc3RyaWN0XCI7XG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBcIl9fZXNNb2R1bGVcIiwgeyB2YWx1ZTogdHJ1ZSB9KTtcbmxldCBjYWNoZWRTZXR0aW5ncyA9IHVuZGVmaW5lZDtcbmZ1bmN0aW9uIGdldERhdGEoa2V5KSB7XG4gICAgY29uc3QgZWxlbWVudCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCd2c2NvZGUtbWFya2Rvd24tcHJldmlldy1kYXRhJyk7XG4gICAgaWYgKGVsZW1lbnQpIHtcbiAgICAgICAgY29uc3QgZGF0YSA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKGtleSk7XG4gICAgICAgIGlmIChkYXRhKSB7XG4gICAgICAgICAgICByZXR1cm4gSlNPTi5wYXJzZShkYXRhKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICB0aHJvdyBuZXcgRXJyb3IoYENvdWxkIG5vdCBsb2FkIGRhdGEgZm9yICR7a2V5fWApO1xufVxuZXhwb3J0cy5nZXREYXRhID0gZ2V0RGF0YTtcbmZ1bmN0aW9uIGdldFNldHRpbmdzKCkge1xuICAgIGlmIChjYWNoZWRTZXR0aW5ncykge1xuICAgICAgICByZXR1cm4gY2FjaGVkU2V0dGluZ3M7XG4gICAgfVxuICAgIGNhY2hlZFNldHRpbmdzID0gZ2V0RGF0YSgnZGF0YS1zZXR0aW5ncycpO1xuICAgIGlmIChjYWNoZWRTZXR0aW5ncykge1xuICAgICAgICByZXR1cm4gY2FjaGVkU2V0dGluZ3M7XG4gICAgfVxuICAgIHRocm93IG5ldyBFcnJvcignQ291bGQgbm90IGxvYWQgc2V0dGluZ3MnKTtcbn1cbmV4cG9ydHMuZ2V0U2V0dGluZ3MgPSBnZXRTZXR0aW5ncztcbiIsIlwidXNlIHN0cmljdFwiO1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuY2xhc3MgU3R5bGVMb2FkaW5nTW9uaXRvciB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXMudW5sb2FkZWRTdHlsZXMgPSBbXTtcbiAgICAgICAgdGhpcy5maW5pc2hlZExvYWRpbmcgPSBmYWxzZTtcbiAgICAgICAgY29uc3Qgb25TdHlsZUxvYWRFcnJvciA9IChldmVudCkgPT4ge1xuICAgICAgICAgICAgY29uc3Qgc291cmNlID0gZXZlbnQudGFyZ2V0LmRhdGFzZXQuc291cmNlO1xuICAgICAgICAgICAgdGhpcy51bmxvYWRlZFN0eWxlcy5wdXNoKHNvdXJjZSk7XG4gICAgICAgIH07XG4gICAgICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdET01Db250ZW50TG9hZGVkJywgKCkgPT4ge1xuICAgICAgICAgICAgZm9yIChjb25zdCBsaW5rIG9mIGRvY3VtZW50LmdldEVsZW1lbnRzQnlDbGFzc05hbWUoJ2NvZGUtdXNlci1zdHlsZScpKSB7XG4gICAgICAgICAgICAgICAgaWYgKGxpbmsuZGF0YXNldC5zb3VyY2UpIHtcbiAgICAgICAgICAgICAgICAgICAgbGluay5vbmVycm9yID0gb25TdHlsZUxvYWRFcnJvcjtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgICAgICB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbG9hZCcsICgpID0+IHtcbiAgICAgICAgICAgIGlmICghdGhpcy51bmxvYWRlZFN0eWxlcy5sZW5ndGgpIHtcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICB0aGlzLmZpbmlzaGVkTG9hZGluZyA9IHRydWU7XG4gICAgICAgICAgICBpZiAodGhpcy5wb3N0ZXIpIHtcbiAgICAgICAgICAgICAgICB0aGlzLnBvc3Rlci5wb3N0TWVzc2FnZSgncHJldmlld1N0eWxlTG9hZEVycm9yJywgeyB1bmxvYWRlZFN0eWxlczogdGhpcy51bmxvYWRlZFN0eWxlcyB9KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgfVxuICAgIHNldFBvc3Rlcihwb3N0ZXIpIHtcbiAgICAgICAgdGhpcy5wb3N0ZXIgPSBwb3N0ZXI7XG4gICAgICAgIGlmICh0aGlzLmZpbmlzaGVkTG9hZGluZykge1xuICAgICAgICAgICAgcG9zdGVyLnBvc3RNZXNzYWdlKCdwcmV2aWV3U3R5bGVMb2FkRXJyb3InLCB7IHVubG9hZGVkU3R5bGVzOiB0aGlzLnVubG9hZGVkU3R5bGVzIH0pO1xuICAgICAgICB9XG4gICAgfVxufVxuZXhwb3J0cy5TdHlsZUxvYWRpbmdNb25pdG9yID0gU3R5bGVMb2FkaW5nTW9uaXRvcjtcbiIsIlwidXNlIHN0cmljdFwiO1xuLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAqICBDb3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbiAqICBMaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIFNlZSBMaWNlbnNlLnR4dCBpbiB0aGUgcHJvamVjdCByb290IGZvciBsaWNlbnNlIGluZm9ybWF0aW9uLlxuICotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5PYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgXCJfX2VzTW9kdWxlXCIsIHsgdmFsdWU6IHRydWUgfSk7XG5mdW5jdGlvbiBnZXRTdHJpbmdzKCkge1xuICAgIGNvbnN0IHN0b3JlID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3ZzY29kZS1tYXJrZG93bi1wcmV2aWV3LWRhdGEnKTtcbiAgICBpZiAoc3RvcmUpIHtcbiAgICAgICAgY29uc3QgZGF0YSA9IHN0b3JlLmdldEF0dHJpYnV0ZSgnZGF0YS1zdHJpbmdzJyk7XG4gICAgICAgIGlmIChkYXRhKSB7XG4gICAgICAgICAgICByZXR1cm4gSlNPTi5wYXJzZShkYXRhKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICB0aHJvdyBuZXcgRXJyb3IoJ0NvdWxkIG5vdCBsb2FkIHN0cmluZ3MnKTtcbn1cbmV4cG9ydHMuZ2V0U3RyaW5ncyA9IGdldFN0cmluZ3M7XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuY29uc3Qgc2V0dGluZ3NfMSA9IHJlcXVpcmUoXCIuL3NldHRpbmdzXCIpO1xuY29uc3Qgc3RyaW5nc18xID0gcmVxdWlyZShcIi4vc3RyaW5nc1wiKTtcbi8qKlxuICogU2hvd3MgYW4gYWxlcnQgd2hlbiB0aGVyZSBpcyBhIGNvbnRlbnQgc2VjdXJpdHkgcG9saWN5IHZpb2xhdGlvbi5cbiAqL1xuY2xhc3MgQ3NwQWxlcnRlciB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXMuZGlkU2hvdyA9IGZhbHNlO1xuICAgICAgICB0aGlzLmRpZEhhdmVDc3BXYXJuaW5nID0gZmFsc2U7XG4gICAgICAgIGRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ3NlY3VyaXR5cG9saWN5dmlvbGF0aW9uJywgKCkgPT4ge1xuICAgICAgICAgICAgdGhpcy5vbkNzcFdhcm5pbmcoKTtcbiAgICAgICAgfSk7XG4gICAgICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdtZXNzYWdlJywgKGV2ZW50KSA9PiB7XG4gICAgICAgICAgICBpZiAoZXZlbnQgJiYgZXZlbnQuZGF0YSAmJiBldmVudC5kYXRhLm5hbWUgPT09ICd2c2NvZGUtZGlkLWJsb2NrLXN2ZycpIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9uQ3NwV2FybmluZygpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICB9XG4gICAgc2V0UG9zdGVyKHBvc3Rlcikge1xuICAgICAgICB0aGlzLm1lc3NhZ2luZyA9IHBvc3RlcjtcbiAgICAgICAgaWYgKHRoaXMuZGlkSGF2ZUNzcFdhcm5pbmcpIHtcbiAgICAgICAgICAgIHRoaXMuc2hvd0NzcFdhcm5pbmcoKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICBvbkNzcFdhcm5pbmcoKSB7XG4gICAgICAgIHRoaXMuZGlkSGF2ZUNzcFdhcm5pbmcgPSB0cnVlO1xuICAgICAgICB0aGlzLnNob3dDc3BXYXJuaW5nKCk7XG4gICAgfVxuICAgIHNob3dDc3BXYXJuaW5nKCkge1xuICAgICAgICBjb25zdCBzdHJpbmdzID0gc3RyaW5nc18xLmdldFN0cmluZ3MoKTtcbiAgICAgICAgY29uc3Qgc2V0dGluZ3MgPSBzZXR0aW5nc18xLmdldFNldHRpbmdzKCk7XG4gICAgICAgIGlmICh0aGlzLmRpZFNob3cgfHwgc2V0dGluZ3MuZGlzYWJsZVNlY3VyaXR5V2FybmluZ3MgfHwgIXRoaXMubWVzc2FnaW5nKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5kaWRTaG93ID0gdHJ1ZTtcbiAgICAgICAgY29uc3Qgbm90aWZpY2F0aW9uID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnYScpO1xuICAgICAgICBub3RpZmljYXRpb24uaW5uZXJUZXh0ID0gc3RyaW5ncy5jc3BBbGVydE1lc3NhZ2VUZXh0O1xuICAgICAgICBub3RpZmljYXRpb24uc2V0QXR0cmlidXRlKCdpZCcsICdjb2RlLWNzcC13YXJuaW5nJyk7XG4gICAgICAgIG5vdGlmaWNhdGlvbi5zZXRBdHRyaWJ1dGUoJ3RpdGxlJywgc3RyaW5ncy5jc3BBbGVydE1lc3NhZ2VUaXRsZSk7XG4gICAgICAgIG5vdGlmaWNhdGlvbi5zZXRBdHRyaWJ1dGUoJ3JvbGUnLCAnYnV0dG9uJyk7XG4gICAgICAgIG5vdGlmaWNhdGlvbi5zZXRBdHRyaWJ1dGUoJ2FyaWEtbGFiZWwnLCBzdHJpbmdzLmNzcEFsZXJ0TWVzc2FnZUxhYmVsKTtcbiAgICAgICAgbm90aWZpY2F0aW9uLm9uY2xpY2sgPSAoKSA9PiB7XG4gICAgICAgICAgICB0aGlzLm1lc3NhZ2luZy5wb3N0TWVzc2FnZSgnc2hvd1ByZXZpZXdTZWN1cml0eVNlbGVjdG9yJywgeyBzb3VyY2U6IHNldHRpbmdzLnNvdXJjZSB9KTtcbiAgICAgICAgfTtcbiAgICAgICAgZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChub3RpZmljYXRpb24pO1xuICAgIH1cbn1cbmV4cG9ydHMuQ3NwQWxlcnRlciA9IENzcEFsZXJ0ZXI7XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuY29uc3QgY3NwXzEgPSByZXF1aXJlKFwiLi9jc3BcIik7XG5jb25zdCBsb2FkaW5nXzEgPSByZXF1aXJlKFwiLi9sb2FkaW5nXCIpO1xud2luZG93LmNzcEFsZXJ0ZXIgPSBuZXcgY3NwXzEuQ3NwQWxlcnRlcigpO1xud2luZG93LnN0eWxlTG9hZGluZ01vbml0b3IgPSBuZXcgbG9hZGluZ18xLlN0eWxlTG9hZGluZ01vbml0b3IoKTtcbiJdLCJzb3VyY2VSb290IjoiIn0= +!function(e){var t={};function n(s){if(t[s])return t[s].exports;var o=t[s]={i:s,l:!1,exports:{}};return e[s].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,s){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:s})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var s=Object.create(null);if(n.r(s),Object.defineProperty(s,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(s,o,function(t){return e[t]}.bind(null,o));return s},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=8)}([function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});let s=void 0;function o(e){const t=document.getElementById("vscode-markdown-preview-data");if(t){const n=t.getAttribute(e);if(n)return JSON.parse(n)}throw new Error(`Could not load data for ${e}`)}t.getData=o,t.getSettings=function(){if(s)return s;if(s=o("data-settings"))return s;throw new Error("Could not load settings")}},,,,,,,,function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const s=n(9),o=n(11);window.cspAlerter=new s.CspAlerter,window.styleLoadingMonitor=new o.StyleLoadingMonitor},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const s=n(0),o=n(10);t.CspAlerter=class{constructor(){this.didShow=!1,this.didHaveCspWarning=!1,document.addEventListener("securitypolicyviolation",()=>{this.onCspWarning()}),window.addEventListener("message",e=>{e&&e.data&&"vscode-did-block-svg"===e.data.name&&this.onCspWarning()})}setPoster(e){this.messaging=e,this.didHaveCspWarning&&this.showCspWarning()}onCspWarning(){this.didHaveCspWarning=!0,this.showCspWarning()}showCspWarning(){const e=o.getStrings(),t=s.getSettings();if(this.didShow||t.disableSecurityWarnings||!this.messaging)return;this.didShow=!0;const n=document.createElement("a");n.innerText=e.cspAlertMessageText,n.setAttribute("id","code-csp-warning"),n.setAttribute("title",e.cspAlertMessageTitle),n.setAttribute("role","button"),n.setAttribute("aria-label",e.cspAlertMessageLabel),n.onclick=()=>{this.messaging.postMessage("showPreviewSecuritySelector",{source:t.source})},document.body.appendChild(n)}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getStrings=function(){const e=document.getElementById("vscode-markdown-preview-data");if(e){const t=e.getAttribute("data-strings");if(t)return JSON.parse(t)}throw new Error("Could not load strings")}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});t.StyleLoadingMonitor=class{constructor(){this.unloadedStyles=[],this.finishedLoading=!1;const e=e=>{const t=e.target.dataset.source;this.unloadedStyles.push(t)};window.addEventListener("DOMContentLoaded",()=>{for(const t of document.getElementsByClassName("code-user-style"))t.dataset.source&&(t.onerror=e)}),window.addEventListener("load",()=>{this.unloadedStyles.length&&(this.finishedLoading=!0,this.poster&&this.poster.postMessage("previewStyleLoadError",{unloadedStyles:this.unloadedStyles}))})}setPoster(e){this.poster=e,this.finishedLoading&&e.postMessage("previewStyleLoadError",{unloadedStyles:this.unloadedStyles})}}}]); +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvc2V0dGluZ3MudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvcHJlLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL2NzcC50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9zdHJpbmdzLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL2xvYWRpbmcudHMiXSwibmFtZXMiOlsiaW5zdGFsbGVkTW9kdWxlcyIsIl9fd2VicGFja19yZXF1aXJlX18iLCJtb2R1bGVJZCIsImV4cG9ydHMiLCJtb2R1bGUiLCJpIiwibCIsIm1vZHVsZXMiLCJjYWxsIiwibSIsImMiLCJkIiwibmFtZSIsImdldHRlciIsIm8iLCJPYmplY3QiLCJkZWZpbmVQcm9wZXJ0eSIsImVudW1lcmFibGUiLCJnZXQiLCJyIiwiU3ltYm9sIiwidG9TdHJpbmdUYWciLCJ2YWx1ZSIsInQiLCJtb2RlIiwiX19lc01vZHVsZSIsIm5zIiwiY3JlYXRlIiwia2V5IiwiYmluZCIsIm4iLCJvYmplY3QiLCJwcm9wZXJ0eSIsInByb3RvdHlwZSIsImhhc093blByb3BlcnR5IiwicCIsInMiLCJjYWNoZWRTZXR0aW5ncyIsInVuZGVmaW5lZCIsImdldERhdGEiLCJlbGVtZW50IiwiZG9jdW1lbnQiLCJnZXRFbGVtZW50QnlJZCIsImRhdGEiLCJnZXRBdHRyaWJ1dGUiLCJKU09OIiwicGFyc2UiLCJFcnJvciIsImdldFNldHRpbmdzIiwiY3NwXzEiLCJsb2FkaW5nXzEiLCJ3aW5kb3ciLCJjc3BBbGVydGVyIiwiQ3NwQWxlcnRlciIsInN0eWxlTG9hZGluZ01vbml0b3IiLCJTdHlsZUxvYWRpbmdNb25pdG9yIiwic2V0dGluZ3NfMSIsInN0cmluZ3NfMSIsInRoaXMiLCJkaWRTaG93IiwiZGlkSGF2ZUNzcFdhcm5pbmciLCJhZGRFdmVudExpc3RlbmVyIiwib25Dc3BXYXJuaW5nIiwiZXZlbnQiLCJwb3N0ZXIiLCJtZXNzYWdpbmciLCJzaG93Q3NwV2FybmluZyIsInN0cmluZ3MiLCJnZXRTdHJpbmdzIiwic2V0dGluZ3MiLCJkaXNhYmxlU2VjdXJpdHlXYXJuaW5ncyIsIm5vdGlmaWNhdGlvbiIsImNyZWF0ZUVsZW1lbnQiLCJpbm5lclRleHQiLCJjc3BBbGVydE1lc3NhZ2VUZXh0Iiwic2V0QXR0cmlidXRlIiwiY3NwQWxlcnRNZXNzYWdlVGl0bGUiLCJjc3BBbGVydE1lc3NhZ2VMYWJlbCIsIm9uY2xpY2siLCJwb3N0TWVzc2FnZSIsInNvdXJjZSIsImJvZHkiLCJhcHBlbmRDaGlsZCIsInN0b3JlIiwidW5sb2FkZWRTdHlsZXMiLCJmaW5pc2hlZExvYWRpbmciLCJvblN0eWxlTG9hZEVycm9yIiwidGFyZ2V0IiwiZGF0YXNldCIsInB1c2giLCJsaW5rIiwiZ2V0RWxlbWVudHNCeUNsYXNzTmFtZSIsIm9uZXJyb3IiLCJsZW5ndGgiXSwibWFwcGluZ3MiOiJhQUNFLElBQUlBLEVBQW1CLEdBR3ZCLFNBQVNDLEVBQW9CQyxHQUc1QixHQUFHRixFQUFpQkUsR0FDbkIsT0FBT0YsRUFBaUJFLEdBQVVDLFFBR25DLElBQUlDLEVBQVNKLEVBQWlCRSxHQUFZLENBQ3pDRyxFQUFHSCxFQUNISSxHQUFHLEVBQ0hILFFBQVMsSUFVVixPQU5BSSxFQUFRTCxHQUFVTSxLQUFLSixFQUFPRCxRQUFTQyxFQUFRQSxFQUFPRCxRQUFTRixHQUcvREcsRUFBT0UsR0FBSSxFQUdKRixFQUFPRCxRQUtmRixFQUFvQlEsRUFBSUYsRUFHeEJOLEVBQW9CUyxFQUFJVixFQUd4QkMsRUFBb0JVLEVBQUksU0FBU1IsRUFBU1MsRUFBTUMsR0FDM0NaLEVBQW9CYSxFQUFFWCxFQUFTUyxJQUNsQ0csT0FBT0MsZUFBZWIsRUFBU1MsRUFBTSxDQUFFSyxZQUFZLEVBQU1DLElBQUtMLEtBS2hFWixFQUFvQmtCLEVBQUksU0FBU2hCLEdBQ1gsb0JBQVhpQixRQUEwQkEsT0FBT0MsYUFDMUNOLE9BQU9DLGVBQWViLEVBQVNpQixPQUFPQyxZQUFhLENBQUVDLE1BQU8sV0FFN0RQLE9BQU9DLGVBQWViLEVBQVMsYUFBYyxDQUFFbUIsT0FBTyxLQVF2RHJCLEVBQW9Cc0IsRUFBSSxTQUFTRCxFQUFPRSxHQUV2QyxHQURVLEVBQVBBLElBQVVGLEVBQVFyQixFQUFvQnFCLElBQy9CLEVBQVBFLEVBQVUsT0FBT0YsRUFDcEIsR0FBVyxFQUFQRSxHQUE4QixpQkFBVkYsR0FBc0JBLEdBQVNBLEVBQU1HLFdBQVksT0FBT0gsRUFDaEYsSUFBSUksRUFBS1gsT0FBT1ksT0FBTyxNQUd2QixHQUZBMUIsRUFBb0JrQixFQUFFTyxHQUN0QlgsT0FBT0MsZUFBZVUsRUFBSSxVQUFXLENBQUVULFlBQVksRUFBTUssTUFBT0EsSUFDdEQsRUFBUEUsR0FBNEIsaUJBQVRGLEVBQW1CLElBQUksSUFBSU0sS0FBT04sRUFBT3JCLEVBQW9CVSxFQUFFZSxFQUFJRSxFQUFLLFNBQVNBLEdBQU8sT0FBT04sRUFBTU0sSUFBUUMsS0FBSyxLQUFNRCxJQUM5SSxPQUFPRixHQUlSekIsRUFBb0I2QixFQUFJLFNBQVMxQixHQUNoQyxJQUFJUyxFQUFTVCxHQUFVQSxFQUFPcUIsV0FDN0IsV0FBd0IsT0FBT3JCLEVBQWdCLFNBQy9DLFdBQThCLE9BQU9BLEdBRXRDLE9BREFILEVBQW9CVSxFQUFFRSxFQUFRLElBQUtBLEdBQzVCQSxHQUlSWixFQUFvQmEsRUFBSSxTQUFTaUIsRUFBUUMsR0FBWSxPQUFPakIsT0FBT2tCLFVBQVVDLGVBQWUxQixLQUFLdUIsRUFBUUMsSUFHekcvQixFQUFvQmtDLEVBQUksR0FJakJsQyxFQUFvQkEsRUFBb0JtQyxFQUFJLEcsK0JDN0VyRHJCLE9BQU9DLGVBQWViLEVBQVMsYUFBYyxDQUFFbUIsT0FBTyxJQUN0RCxJQUFJZSxPQUFpQkMsRUFDckIsU0FBU0MsRUFBUVgsR0FDYixNQUFNWSxFQUFVQyxTQUFTQyxlQUFlLGdDQUN4QyxHQUFJRixFQUFTLENBQ1QsTUFBTUcsRUFBT0gsRUFBUUksYUFBYWhCLEdBQ2xDLEdBQUllLEVBQ0EsT0FBT0UsS0FBS0MsTUFBTUgsR0FHMUIsTUFBTSxJQUFJSSxNQUFNLDJCQUEyQm5CLEtBRS9DekIsRUFBUW9DLFFBQVVBLEVBV2xCcEMsRUFBUTZDLFlBVlIsV0FDSSxHQUFJWCxFQUNBLE9BQU9BLEVBR1gsR0FEQUEsRUFBaUJFLEVBQVEsaUJBRXJCLE9BQU9GLEVBRVgsTUFBTSxJQUFJVSxNQUFNLDZCLG9DQ3JCcEJoQyxPQUFPQyxlQUFlYixFQUFTLGFBQWMsQ0FBRW1CLE9BQU8sSUFDdEQsTUFBTTJCLEVBQVEsRUFBUSxHQUNoQkMsRUFBWSxFQUFRLElBQzFCQyxPQUFPQyxXQUFhLElBQUlILEVBQU1JLFdBQzlCRixPQUFPRyxvQkFBc0IsSUFBSUosRUFBVUsscUIsNkJDSjNDeEMsT0FBT0MsZUFBZWIsRUFBUyxhQUFjLENBQUVtQixPQUFPLElBQ3RELE1BQU1rQyxFQUFhLEVBQVEsR0FDckJDLEVBQVksRUFBUSxJQThDMUJ0RCxFQUFRa0QsV0ExQ1IsTUFDSSxjQUNJSyxLQUFLQyxTQUFVLEVBQ2ZELEtBQUtFLG1CQUFvQixFQUN6Qm5CLFNBQVNvQixpQkFBaUIsMEJBQTJCLEtBQ2pESCxLQUFLSSxpQkFFVFgsT0FBT1UsaUJBQWlCLFVBQVlFLElBQzVCQSxHQUFTQSxFQUFNcEIsTUFBNEIseUJBQXBCb0IsRUFBTXBCLEtBQUsvQixNQUNsQzhDLEtBQUtJLGlCQUlqQixVQUFVRSxHQUNOTixLQUFLTyxVQUFZRCxFQUNiTixLQUFLRSxtQkFDTEYsS0FBS1EsaUJBR2IsZUFDSVIsS0FBS0UsbUJBQW9CLEVBQ3pCRixLQUFLUSxpQkFFVCxpQkFDSSxNQUFNQyxFQUFVVixFQUFVVyxhQUNwQkMsRUFBV2IsRUFBV1IsY0FDNUIsR0FBSVUsS0FBS0MsU0FBV1UsRUFBU0MsMEJBQTRCWixLQUFLTyxVQUMxRCxPQUVKUCxLQUFLQyxTQUFVLEVBQ2YsTUFBTVksRUFBZTlCLFNBQVMrQixjQUFjLEtBQzVDRCxFQUFhRSxVQUFZTixFQUFRTyxvQkFDakNILEVBQWFJLGFBQWEsS0FBTSxvQkFDaENKLEVBQWFJLGFBQWEsUUFBU1IsRUFBUVMsc0JBQzNDTCxFQUFhSSxhQUFhLE9BQVEsVUFDbENKLEVBQWFJLGFBQWEsYUFBY1IsRUFBUVUsc0JBQ2hETixFQUFhTyxRQUFVLEtBQ25CcEIsS0FBS08sVUFBVWMsWUFBWSw4QkFBK0IsQ0FBRUMsT0FBUVgsRUFBU1csVUFFakZ2QyxTQUFTd0MsS0FBS0MsWUFBWVgsTSw2QkM3Q2xDeEQsT0FBT0MsZUFBZWIsRUFBUyxhQUFjLENBQUVtQixPQUFPLElBV3REbkIsRUFBUWlFLFdBVlIsV0FDSSxNQUFNZSxFQUFRMUMsU0FBU0MsZUFBZSxnQ0FDdEMsR0FBSXlDLEVBQU8sQ0FDUCxNQUFNeEMsRUFBT3dDLEVBQU12QyxhQUFhLGdCQUNoQyxHQUFJRCxFQUNBLE9BQU9FLEtBQUtDLE1BQU1ILEdBRzFCLE1BQU0sSUFBSUksTUFBTSw0Qiw2QkNicEJoQyxPQUFPQyxlQUFlYixFQUFTLGFBQWMsQ0FBRW1CLE9BQU8sSUFpQ3REbkIsRUFBUW9ELG9CQWhDUixNQUNJLGNBQ0lHLEtBQUswQixlQUFpQixHQUN0QjFCLEtBQUsyQixpQkFBa0IsRUFDdkIsTUFBTUMsRUFBb0J2QixJQUN0QixNQUFNaUIsRUFBU2pCLEVBQU13QixPQUFPQyxRQUFRUixPQUNwQ3RCLEtBQUswQixlQUFlSyxLQUFLVCxJQUU3QjdCLE9BQU9VLGlCQUFpQixtQkFBb0IsS0FDeEMsSUFBSyxNQUFNNkIsS0FBUWpELFNBQVNrRCx1QkFBdUIsbUJBQzNDRCxFQUFLRixRQUFRUixTQUNiVSxFQUFLRSxRQUFVTixLQUkzQm5DLE9BQU9VLGlCQUFpQixPQUFRLEtBQ3ZCSCxLQUFLMEIsZUFBZVMsU0FHekJuQyxLQUFLMkIsaUJBQWtCLEVBQ25CM0IsS0FBS00sUUFDTE4sS0FBS00sT0FBT2UsWUFBWSx3QkFBeUIsQ0FBRUssZUFBZ0IxQixLQUFLMEIsb0JBSXBGLFVBQVVwQixHQUNOTixLQUFLTSxPQUFTQSxFQUNWTixLQUFLMkIsaUJBQ0xyQixFQUFPZSxZQUFZLHdCQUF5QixDQUFFSyxlQUFnQjFCLEtBQUswQiIsImZpbGUiOiJwcmUuanMiLCJzb3VyY2VzQ29udGVudCI6WyIgXHQvLyBUaGUgbW9kdWxlIGNhY2hlXG4gXHR2YXIgaW5zdGFsbGVkTW9kdWxlcyA9IHt9O1xuXG4gXHQvLyBUaGUgcmVxdWlyZSBmdW5jdGlvblxuIFx0ZnVuY3Rpb24gX193ZWJwYWNrX3JlcXVpcmVfXyhtb2R1bGVJZCkge1xuXG4gXHRcdC8vIENoZWNrIGlmIG1vZHVsZSBpcyBpbiBjYWNoZVxuIFx0XHRpZihpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSkge1xuIFx0XHRcdHJldHVybiBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXS5leHBvcnRzO1xuIFx0XHR9XG4gXHRcdC8vIENyZWF0ZSBhIG5ldyBtb2R1bGUgKGFuZCBwdXQgaXQgaW50byB0aGUgY2FjaGUpXG4gXHRcdHZhciBtb2R1bGUgPSBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSA9IHtcbiBcdFx0XHRpOiBtb2R1bGVJZCxcbiBcdFx0XHRsOiBmYWxzZSxcbiBcdFx0XHRleHBvcnRzOiB7fVxuIFx0XHR9O1xuXG4gXHRcdC8vIEV4ZWN1dGUgdGhlIG1vZHVsZSBmdW5jdGlvblxuIFx0XHRtb2R1bGVzW21vZHVsZUlkXS5jYWxsKG1vZHVsZS5leHBvcnRzLCBtb2R1bGUsIG1vZHVsZS5leHBvcnRzLCBfX3dlYnBhY2tfcmVxdWlyZV9fKTtcblxuIFx0XHQvLyBGbGFnIHRoZSBtb2R1bGUgYXMgbG9hZGVkXG4gXHRcdG1vZHVsZS5sID0gdHJ1ZTtcblxuIFx0XHQvLyBSZXR1cm4gdGhlIGV4cG9ydHMgb2YgdGhlIG1vZHVsZVxuIFx0XHRyZXR1cm4gbW9kdWxlLmV4cG9ydHM7XG4gXHR9XG5cblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGVzIG9iamVjdCAoX193ZWJwYWNrX21vZHVsZXNfXylcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubSA9IG1vZHVsZXM7XG5cbiBcdC8vIGV4cG9zZSB0aGUgbW9kdWxlIGNhY2hlXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLmMgPSBpbnN0YWxsZWRNb2R1bGVzO1xuXG4gXHQvLyBkZWZpbmUgZ2V0dGVyIGZ1bmN0aW9uIGZvciBoYXJtb255IGV4cG9ydHNcbiBcdF9fd2VicGFja19yZXF1aXJlX18uZCA9IGZ1bmN0aW9uKGV4cG9ydHMsIG5hbWUsIGdldHRlcikge1xuIFx0XHRpZighX193ZWJwYWNrX3JlcXVpcmVfXy5vKGV4cG9ydHMsIG5hbWUpKSB7XG4gXHRcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIG5hbWUsIHsgZW51bWVyYWJsZTogdHJ1ZSwgZ2V0OiBnZXR0ZXIgfSk7XG4gXHRcdH1cbiBcdH07XG5cbiBcdC8vIGRlZmluZSBfX2VzTW9kdWxlIG9uIGV4cG9ydHNcbiBcdF9fd2VicGFja19yZXF1aXJlX18uciA9IGZ1bmN0aW9uKGV4cG9ydHMpIHtcbiBcdFx0aWYodHlwZW9mIFN5bWJvbCAhPT0gJ3VuZGVmaW5lZCcgJiYgU3ltYm9sLnRvU3RyaW5nVGFnKSB7XG4gXHRcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFN5bWJvbC50b1N0cmluZ1RhZywgeyB2YWx1ZTogJ01vZHVsZScgfSk7XG4gXHRcdH1cbiBcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsICdfX2VzTW9kdWxlJywgeyB2YWx1ZTogdHJ1ZSB9KTtcbiBcdH07XG5cbiBcdC8vIGNyZWF0ZSBhIGZha2UgbmFtZXNwYWNlIG9iamVjdFxuIFx0Ly8gbW9kZSAmIDE6IHZhbHVlIGlzIGEgbW9kdWxlIGlkLCByZXF1aXJlIGl0XG4gXHQvLyBtb2RlICYgMjogbWVyZ2UgYWxsIHByb3BlcnRpZXMgb2YgdmFsdWUgaW50byB0aGUgbnNcbiBcdC8vIG1vZGUgJiA0OiByZXR1cm4gdmFsdWUgd2hlbiBhbHJlYWR5IG5zIG9iamVjdFxuIFx0Ly8gbW9kZSAmIDh8MTogYmVoYXZlIGxpa2UgcmVxdWlyZVxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy50ID0gZnVuY3Rpb24odmFsdWUsIG1vZGUpIHtcbiBcdFx0aWYobW9kZSAmIDEpIHZhbHVlID0gX193ZWJwYWNrX3JlcXVpcmVfXyh2YWx1ZSk7XG4gXHRcdGlmKG1vZGUgJiA4KSByZXR1cm4gdmFsdWU7XG4gXHRcdGlmKChtb2RlICYgNCkgJiYgdHlwZW9mIHZhbHVlID09PSAnb2JqZWN0JyAmJiB2YWx1ZSAmJiB2YWx1ZS5fX2VzTW9kdWxlKSByZXR1cm4gdmFsdWU7XG4gXHRcdHZhciBucyA9IE9iamVjdC5jcmVhdGUobnVsbCk7XG4gXHRcdF9fd2VicGFja19yZXF1aXJlX18ucihucyk7XG4gXHRcdE9iamVjdC5kZWZpbmVQcm9wZXJ0eShucywgJ2RlZmF1bHQnLCB7IGVudW1lcmFibGU6IHRydWUsIHZhbHVlOiB2YWx1ZSB9KTtcbiBcdFx0aWYobW9kZSAmIDIgJiYgdHlwZW9mIHZhbHVlICE9ICdzdHJpbmcnKSBmb3IodmFyIGtleSBpbiB2YWx1ZSkgX193ZWJwYWNrX3JlcXVpcmVfXy5kKG5zLCBrZXksIGZ1bmN0aW9uKGtleSkgeyByZXR1cm4gdmFsdWVba2V5XTsgfS5iaW5kKG51bGwsIGtleSkpO1xuIFx0XHRyZXR1cm4gbnM7XG4gXHR9O1xuXG4gXHQvLyBnZXREZWZhdWx0RXhwb3J0IGZ1bmN0aW9uIGZvciBjb21wYXRpYmlsaXR5IHdpdGggbm9uLWhhcm1vbnkgbW9kdWxlc1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5uID0gZnVuY3Rpb24obW9kdWxlKSB7XG4gXHRcdHZhciBnZXR0ZXIgPSBtb2R1bGUgJiYgbW9kdWxlLl9fZXNNb2R1bGUgP1xuIFx0XHRcdGZ1bmN0aW9uIGdldERlZmF1bHQoKSB7IHJldHVybiBtb2R1bGVbJ2RlZmF1bHQnXTsgfSA6XG4gXHRcdFx0ZnVuY3Rpb24gZ2V0TW9kdWxlRXhwb3J0cygpIHsgcmV0dXJuIG1vZHVsZTsgfTtcbiBcdFx0X193ZWJwYWNrX3JlcXVpcmVfXy5kKGdldHRlciwgJ2EnLCBnZXR0ZXIpO1xuIFx0XHRyZXR1cm4gZ2V0dGVyO1xuIFx0fTtcblxuIFx0Ly8gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLm8gPSBmdW5jdGlvbihvYmplY3QsIHByb3BlcnR5KSB7IHJldHVybiBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwob2JqZWN0LCBwcm9wZXJ0eSk7IH07XG5cbiBcdC8vIF9fd2VicGFja19wdWJsaWNfcGF0aF9fXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLnAgPSBcIlwiO1xuXG5cbiBcdC8vIExvYWQgZW50cnkgbW9kdWxlIGFuZCByZXR1cm4gZXhwb3J0c1xuIFx0cmV0dXJuIF9fd2VicGFja19yZXF1aXJlX18oX193ZWJwYWNrX3JlcXVpcmVfXy5zID0gOCk7XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xubGV0IGNhY2hlZFNldHRpbmdzID0gdW5kZWZpbmVkO1xuZnVuY3Rpb24gZ2V0RGF0YShrZXkpIHtcbiAgICBjb25zdCBlbGVtZW50ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3ZzY29kZS1tYXJrZG93bi1wcmV2aWV3LWRhdGEnKTtcbiAgICBpZiAoZWxlbWVudCkge1xuICAgICAgICBjb25zdCBkYXRhID0gZWxlbWVudC5nZXRBdHRyaWJ1dGUoa2V5KTtcbiAgICAgICAgaWYgKGRhdGEpIHtcbiAgICAgICAgICAgIHJldHVybiBKU09OLnBhcnNlKGRhdGEpO1xuICAgICAgICB9XG4gICAgfVxuICAgIHRocm93IG5ldyBFcnJvcihgQ291bGQgbm90IGxvYWQgZGF0YSBmb3IgJHtrZXl9YCk7XG59XG5leHBvcnRzLmdldERhdGEgPSBnZXREYXRhO1xuZnVuY3Rpb24gZ2V0U2V0dGluZ3MoKSB7XG4gICAgaWYgKGNhY2hlZFNldHRpbmdzKSB7XG4gICAgICAgIHJldHVybiBjYWNoZWRTZXR0aW5ncztcbiAgICB9XG4gICAgY2FjaGVkU2V0dGluZ3MgPSBnZXREYXRhKCdkYXRhLXNldHRpbmdzJyk7XG4gICAgaWYgKGNhY2hlZFNldHRpbmdzKSB7XG4gICAgICAgIHJldHVybiBjYWNoZWRTZXR0aW5ncztcbiAgICB9XG4gICAgdGhyb3cgbmV3IEVycm9yKCdDb3VsZCBub3QgbG9hZCBzZXR0aW5ncycpO1xufVxuZXhwb3J0cy5nZXRTZXR0aW5ncyA9IGdldFNldHRpbmdzO1xuIiwiXCJ1c2Ugc3RyaWN0XCI7XG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBcIl9fZXNNb2R1bGVcIiwgeyB2YWx1ZTogdHJ1ZSB9KTtcbmNvbnN0IGNzcF8xID0gcmVxdWlyZShcIi4vY3NwXCIpO1xuY29uc3QgbG9hZGluZ18xID0gcmVxdWlyZShcIi4vbG9hZGluZ1wiKTtcbndpbmRvdy5jc3BBbGVydGVyID0gbmV3IGNzcF8xLkNzcEFsZXJ0ZXIoKTtcbndpbmRvdy5zdHlsZUxvYWRpbmdNb25pdG9yID0gbmV3IGxvYWRpbmdfMS5TdHlsZUxvYWRpbmdNb25pdG9yKCk7XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuY29uc3Qgc2V0dGluZ3NfMSA9IHJlcXVpcmUoXCIuL3NldHRpbmdzXCIpO1xuY29uc3Qgc3RyaW5nc18xID0gcmVxdWlyZShcIi4vc3RyaW5nc1wiKTtcbi8qKlxuICogU2hvd3MgYW4gYWxlcnQgd2hlbiB0aGVyZSBpcyBhIGNvbnRlbnQgc2VjdXJpdHkgcG9saWN5IHZpb2xhdGlvbi5cbiAqL1xuY2xhc3MgQ3NwQWxlcnRlciB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXMuZGlkU2hvdyA9IGZhbHNlO1xuICAgICAgICB0aGlzLmRpZEhhdmVDc3BXYXJuaW5nID0gZmFsc2U7XG4gICAgICAgIGRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ3NlY3VyaXR5cG9saWN5dmlvbGF0aW9uJywgKCkgPT4ge1xuICAgICAgICAgICAgdGhpcy5vbkNzcFdhcm5pbmcoKTtcbiAgICAgICAgfSk7XG4gICAgICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdtZXNzYWdlJywgKGV2ZW50KSA9PiB7XG4gICAgICAgICAgICBpZiAoZXZlbnQgJiYgZXZlbnQuZGF0YSAmJiBldmVudC5kYXRhLm5hbWUgPT09ICd2c2NvZGUtZGlkLWJsb2NrLXN2ZycpIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9uQ3NwV2FybmluZygpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICB9XG4gICAgc2V0UG9zdGVyKHBvc3Rlcikge1xuICAgICAgICB0aGlzLm1lc3NhZ2luZyA9IHBvc3RlcjtcbiAgICAgICAgaWYgKHRoaXMuZGlkSGF2ZUNzcFdhcm5pbmcpIHtcbiAgICAgICAgICAgIHRoaXMuc2hvd0NzcFdhcm5pbmcoKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICBvbkNzcFdhcm5pbmcoKSB7XG4gICAgICAgIHRoaXMuZGlkSGF2ZUNzcFdhcm5pbmcgPSB0cnVlO1xuICAgICAgICB0aGlzLnNob3dDc3BXYXJuaW5nKCk7XG4gICAgfVxuICAgIHNob3dDc3BXYXJuaW5nKCkge1xuICAgICAgICBjb25zdCBzdHJpbmdzID0gc3RyaW5nc18xLmdldFN0cmluZ3MoKTtcbiAgICAgICAgY29uc3Qgc2V0dGluZ3MgPSBzZXR0aW5nc18xLmdldFNldHRpbmdzKCk7XG4gICAgICAgIGlmICh0aGlzLmRpZFNob3cgfHwgc2V0dGluZ3MuZGlzYWJsZVNlY3VyaXR5V2FybmluZ3MgfHwgIXRoaXMubWVzc2FnaW5nKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5kaWRTaG93ID0gdHJ1ZTtcbiAgICAgICAgY29uc3Qgbm90aWZpY2F0aW9uID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnYScpO1xuICAgICAgICBub3RpZmljYXRpb24uaW5uZXJUZXh0ID0gc3RyaW5ncy5jc3BBbGVydE1lc3NhZ2VUZXh0O1xuICAgICAgICBub3RpZmljYXRpb24uc2V0QXR0cmlidXRlKCdpZCcsICdjb2RlLWNzcC13YXJuaW5nJyk7XG4gICAgICAgIG5vdGlmaWNhdGlvbi5zZXRBdHRyaWJ1dGUoJ3RpdGxlJywgc3RyaW5ncy5jc3BBbGVydE1lc3NhZ2VUaXRsZSk7XG4gICAgICAgIG5vdGlmaWNhdGlvbi5zZXRBdHRyaWJ1dGUoJ3JvbGUnLCAnYnV0dG9uJyk7XG4gICAgICAgIG5vdGlmaWNhdGlvbi5zZXRBdHRyaWJ1dGUoJ2FyaWEtbGFiZWwnLCBzdHJpbmdzLmNzcEFsZXJ0TWVzc2FnZUxhYmVsKTtcbiAgICAgICAgbm90aWZpY2F0aW9uLm9uY2xpY2sgPSAoKSA9PiB7XG4gICAgICAgICAgICB0aGlzLm1lc3NhZ2luZy5wb3N0TWVzc2FnZSgnc2hvd1ByZXZpZXdTZWN1cml0eVNlbGVjdG9yJywgeyBzb3VyY2U6IHNldHRpbmdzLnNvdXJjZSB9KTtcbiAgICAgICAgfTtcbiAgICAgICAgZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChub3RpZmljYXRpb24pO1xuICAgIH1cbn1cbmV4cG9ydHMuQ3NwQWxlcnRlciA9IENzcEFsZXJ0ZXI7XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuZnVuY3Rpb24gZ2V0U3RyaW5ncygpIHtcbiAgICBjb25zdCBzdG9yZSA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCd2c2NvZGUtbWFya2Rvd24tcHJldmlldy1kYXRhJyk7XG4gICAgaWYgKHN0b3JlKSB7XG4gICAgICAgIGNvbnN0IGRhdGEgPSBzdG9yZS5nZXRBdHRyaWJ1dGUoJ2RhdGEtc3RyaW5ncycpO1xuICAgICAgICBpZiAoZGF0YSkge1xuICAgICAgICAgICAgcmV0dXJuIEpTT04ucGFyc2UoZGF0YSk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgdGhyb3cgbmV3IEVycm9yKCdDb3VsZCBub3QgbG9hZCBzdHJpbmdzJyk7XG59XG5leHBvcnRzLmdldFN0cmluZ3MgPSBnZXRTdHJpbmdzO1xuIiwiXCJ1c2Ugc3RyaWN0XCI7XG5PYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgXCJfX2VzTW9kdWxlXCIsIHsgdmFsdWU6IHRydWUgfSk7XG5jbGFzcyBTdHlsZUxvYWRpbmdNb25pdG9yIHtcbiAgICBjb25zdHJ1Y3RvcigpIHtcbiAgICAgICAgdGhpcy51bmxvYWRlZFN0eWxlcyA9IFtdO1xuICAgICAgICB0aGlzLmZpbmlzaGVkTG9hZGluZyA9IGZhbHNlO1xuICAgICAgICBjb25zdCBvblN0eWxlTG9hZEVycm9yID0gKGV2ZW50KSA9PiB7XG4gICAgICAgICAgICBjb25zdCBzb3VyY2UgPSBldmVudC50YXJnZXQuZGF0YXNldC5zb3VyY2U7XG4gICAgICAgICAgICB0aGlzLnVubG9hZGVkU3R5bGVzLnB1c2goc291cmNlKTtcbiAgICAgICAgfTtcbiAgICAgICAgd2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ0RPTUNvbnRlbnRMb2FkZWQnLCAoKSA9PiB7XG4gICAgICAgICAgICBmb3IgKGNvbnN0IGxpbmsgb2YgZG9jdW1lbnQuZ2V0RWxlbWVudHNCeUNsYXNzTmFtZSgnY29kZS11c2VyLXN0eWxlJykpIHtcbiAgICAgICAgICAgICAgICBpZiAobGluay5kYXRhc2V0LnNvdXJjZSkge1xuICAgICAgICAgICAgICAgICAgICBsaW5rLm9uZXJyb3IgPSBvblN0eWxlTG9hZEVycm9yO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdsb2FkJywgKCkgPT4ge1xuICAgICAgICAgICAgaWYgKCF0aGlzLnVubG9hZGVkU3R5bGVzLmxlbmd0aCkge1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHRoaXMuZmluaXNoZWRMb2FkaW5nID0gdHJ1ZTtcbiAgICAgICAgICAgIGlmICh0aGlzLnBvc3Rlcikge1xuICAgICAgICAgICAgICAgIHRoaXMucG9zdGVyLnBvc3RNZXNzYWdlKCdwcmV2aWV3U3R5bGVMb2FkRXJyb3InLCB7IHVubG9hZGVkU3R5bGVzOiB0aGlzLnVubG9hZGVkU3R5bGVzIH0pO1xuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICB9XG4gICAgc2V0UG9zdGVyKHBvc3Rlcikge1xuICAgICAgICB0aGlzLnBvc3RlciA9IHBvc3RlcjtcbiAgICAgICAgaWYgKHRoaXMuZmluaXNoZWRMb2FkaW5nKSB7XG4gICAgICAgICAgICBwb3N0ZXIucG9zdE1lc3NhZ2UoJ3ByZXZpZXdTdHlsZUxvYWRFcnJvcicsIHsgdW5sb2FkZWRTdHlsZXM6IHRoaXMudW5sb2FkZWRTdHlsZXMgfSk7XG4gICAgICAgIH1cbiAgICB9XG59XG5leHBvcnRzLlN0eWxlTG9hZGluZ01vbml0b3IgPSBTdHlsZUxvYWRpbmdNb25pdG9yO1xuIl0sInNvdXJjZVJvb3QiOiIifQ== \ No newline at end of file diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 29cef108b9..bcc21a57aa 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -25,7 +25,9 @@ "onCommand:markdown.showSource", "onCommand:markdown.showPreviewSecuritySelector", "onCommand:markdown.api.render", - "onWebviewPanel:markdown.preview" + "onCommand:notebook.showPreview", + "onWebviewPanel:markdown.preview", + "onWebviewEditor:vscode.markdown.preview.editor" ], "contributes": { "commands": [ @@ -307,6 +309,18 @@ ], "markdown.previewScripts": [ "./media/index.js" + ], + "webviewEditors": [ + { + "viewType": "vscode.markdown.preview.editor", + "displayName": "(Experimental) VS Code Markdown Preview", + "priority": "option", + "selector": [ + { + "filenamePattern": "*.md" + } + ] + } ] }, "scripts": { @@ -327,14 +341,14 @@ "@types/highlight.js": "9.12.3", "@types/lodash.throttle": "^4.1.3", "@types/markdown-it": "0.0.2", - "@types/node": "^10.14.8", + "@types/node": "^12.11.7", "lodash.throttle": "^4.1.1", "mocha-junit-reporter": "^1.17.0", "mocha-multi-reporters": "^1.1.7", - "ts-loader": "^4.0.1", - "typescript": "^3.3.1", + "ts-loader": "^6.2.1", + "typescript": "^3.7.2", "vscode": "^1.1.10", - "webpack": "^4.1.0", - "webpack-cli": "^2.0.10" + "webpack": "^4.41.2", + "webpack-cli": "^3.3.0" } } diff --git a/extensions/markdown-language-features/preview-src/index.ts b/extensions/markdown-language-features/preview-src/index.ts index ea34ba3b32..ba524893fa 100644 --- a/extensions/markdown-language-features/preview-src/index.ts +++ b/extensions/markdown-language-features/preview-src/index.ts @@ -129,7 +129,7 @@ document.addEventListener('dblclick', event => { } }); -const passThroughLinkSchemes = ['http:', 'https:', 'mailto:', 'vscode:', 'vscode-insiders']; +const passThroughLinkSchemes = ['http:', 'https:', 'mailto:', 'vscode:', 'vscode-insiders:']; document.addEventListener('click', event => { if (!event) { diff --git a/extensions/markdown-language-features/preview-src/scroll-sync.ts b/extensions/markdown-language-features/preview-src/scroll-sync.ts index 91fe31c229..f7a97f6a42 100644 --- a/extensions/markdown-language-features/preview-src/scroll-sync.ts +++ b/extensions/markdown-language-features/preview-src/scroll-sync.ts @@ -5,6 +5,7 @@ import { getSettings } from './settings'; +const codeLineClass = 'code-line'; function clamp(min: number, max: number, value: number) { return Math.min(max, Math.max(min, value)); @@ -25,9 +26,17 @@ const getCodeLineElements = (() => { return () => { if (!elements) { elements = [{ element: document.body, line: 0 }]; - for (const element of document.getElementsByClassName('code-line')) { + for (const element of document.getElementsByClassName(codeLineClass)) { const line = +element.getAttribute('data-line')!; - if (!isNaN(line)) { + if (isNaN(line)) { + continue; + } + + if (element.tagName === 'CODE' && element.parentElement && element.parentElement.tagName === 'PRE') { + // Fenched code blocks are a special case since the `code-line` can only be marked on + // the `` element and not the parent `
` element.
+					elements.push({ element: element.parentElement as HTMLElement, line });
+				} else {
 					elements.push({ element: element as HTMLElement, line });
 				}
 			}
@@ -67,7 +76,7 @@ export function getLineElementsAtPageOffset(offset: number): { previous: CodeLin
 	let hi = lines.length - 1;
 	while (lo + 1 < hi) {
 		const mid = Math.floor((lo + hi) / 2);
-		const bounds = lines[mid].element.getBoundingClientRect();
+		const bounds = getElementBounds(lines[mid]);
 		if (bounds.top + bounds.height >= position) {
 			hi = mid;
 		}
@@ -76,14 +85,35 @@ export function getLineElementsAtPageOffset(offset: number): { previous: CodeLin
 		}
 	}
 	const hiElement = lines[hi];
-	const hiBounds = hiElement.element.getBoundingClientRect();
+	const hiBounds = getElementBounds(hiElement);
 	if (hi >= 1 && hiBounds.top > position) {
 		const loElement = lines[lo];
 		return { previous: loElement, next: hiElement };
 	}
+	if (hi > 1 && hi < lines.length && hiBounds.top + hiBounds.height > position) {
+		return { previous: hiElement, next: lines[hi + 1] };
+	}
 	return { previous: hiElement };
 }
 
+function getElementBounds({ element }: CodeLineElement): { top: number, height: number } {
+	const myBounds = element.getBoundingClientRect();
+
+	// Some code line elements may contain other code line elements.
+	// In those cases, only take the height up to that child.
+	const codeLineChild = element.querySelector(`.${codeLineClass}`);
+	if (codeLineChild) {
+		const childBounds = codeLineChild.getBoundingClientRect();
+		const height = Math.max(1, (childBounds.top - myBounds.top));
+		return {
+			top: myBounds.top,
+			height: height
+		};
+	}
+
+	return myBounds;
+}
+
 /**
  * Attempt to reveal the element for a source line in the editor.
  */
@@ -102,7 +132,7 @@ export function scrollToRevealSourceLine(line: number) {
 		return;
 	}
 	let scrollTo = 0;
-	const rect = previous.element.getBoundingClientRect();
+	const rect = getElementBounds(previous);
 	const previousTop = rect.top;
 	if (next && next.line !== previous.line) {
 		// Between two elements. Go to percentage offset between them.
@@ -119,14 +149,13 @@ export function scrollToRevealSourceLine(line: number) {
 export function getEditorLineNumberForPageOffset(offset: number) {
 	const { previous, next } = getLineElementsAtPageOffset(offset);
 	if (previous) {
-		const previousBounds = previous.element.getBoundingClientRect();
+		const previousBounds = getElementBounds(previous);
 		const offsetFromPrevious = (offset - window.scrollY - previousBounds.top);
 		if (next) {
-			const progressBetweenElements = offsetFromPrevious / (next.element.getBoundingClientRect().top - previousBounds.top);
+			const progressBetweenElements = offsetFromPrevious / (getElementBounds(next).top - previousBounds.top);
 			const line = previous.line + progressBetweenElements * (next.line - previous.line);
 			return clampLine(line);
-		}
-		else {
+		} else {
 			const progressWithinElement = offsetFromPrevious / (previousBounds.height);
 			const line = previous.line + progressWithinElement;
 			return clampLine(line);
diff --git a/extensions/markdown-language-features/src/commands/showPreview.ts b/extensions/markdown-language-features/src/commands/showPreview.ts
index 659696e129..b58571dcb9 100644
--- a/extensions/markdown-language-features/src/commands/showPreview.ts
+++ b/extensions/markdown-language-features/src/commands/showPreview.ts
@@ -6,9 +6,8 @@
 import * as vscode from 'vscode';
 
 import { Command } from '../commandManager';
-import { MarkdownPreviewManager } from '../features/previewManager';
+import { MarkdownPreviewManager, DynamicPreviewSettings } from '../features/previewManager';
 import { TelemetryReporter } from '../telemetryReporter';
-import { PreviewSettings } from '../features/preview';
 
 interface ShowPreviewSettings {
 	readonly sideBySide?: boolean;
@@ -39,7 +38,7 @@ async function showPreview(
 	}
 
 	const resourceColumn = (vscode.window.activeTextEditor && vscode.window.activeTextEditor.viewColumn) || vscode.ViewColumn.One;
-	webviewManager.preview(resource, {
+	webviewManager.openDynamicPreview(resource, {
 		resourceColumn: resourceColumn,
 		previewColumn: previewSettings.sideBySide ? resourceColumn + 1 : resourceColumn,
 		locked: !!previewSettings.locked
@@ -59,7 +58,7 @@ export class ShowPreviewCommand implements Command {
 		private readonly telemetryReporter: TelemetryReporter
 	) { }
 
-	public execute(mainUri?: vscode.Uri, allUris?: vscode.Uri[], previewSettings?: PreviewSettings) {
+	public execute(mainUri?: vscode.Uri, allUris?: vscode.Uri[], previewSettings?: DynamicPreviewSettings) {
 		for (const uri of Array.isArray(allUris) ? allUris : [mainUri]) {
 			showPreview(this.webviewManager, this.telemetryReporter, uri, {
 				sideBySide: false,
@@ -77,7 +76,7 @@ export class ShowPreviewToSideCommand implements Command {
 		private readonly telemetryReporter: TelemetryReporter
 	) { }
 
-	public execute(uri?: vscode.Uri, previewSettings?: PreviewSettings) {
+	public execute(uri?: vscode.Uri, previewSettings?: DynamicPreviewSettings) {
 		showPreview(this.webviewManager, this.telemetryReporter, uri, {
 			sideBySide: true,
 			locked: previewSettings && previewSettings.locked
diff --git a/extensions/markdown-language-features/src/extension.ts b/extensions/markdown-language-features/src/extension.ts
index 567d5291a7..3e030be58f 100644
--- a/extensions/markdown-language-features/src/extension.ts
+++ b/extensions/markdown-language-features/src/extension.ts
@@ -54,9 +54,11 @@ function registerMarkdownLanguageFeatures(
 		{ language: 'markdown', scheme: 'untitled' }
 	];
 
+	const charPattern = '(\\p{Alphabetic}|\\p{Number}|\\p{Nonspacing_Mark})';
+
 	return vscode.Disposable.from(
 		vscode.languages.setLanguageConfiguration('markdown', {
-			wordPattern: new RegExp('(\\p{Alphabetic}|\\p{Number}|\\p{Nonspacing_Mark})+', 'ug'),
+			wordPattern: new RegExp(`${charPattern}((${charPattern}|[_])?${charPattern})*`, 'ug'),
 		}),
 		vscode.languages.registerDocumentSymbolProvider(selector, symbolProvider),
 		vscode.languages.registerDocumentLinkProvider(selector, new LinkProvider()),
diff --git a/extensions/markdown-language-features/src/features/documentLinkProvider.ts b/extensions/markdown-language-features/src/features/documentLinkProvider.ts
index 4554ad03f9..e601c59b84 100644
--- a/extensions/markdown-language-features/src/features/documentLinkProvider.ts
+++ b/extensions/markdown-language-features/src/features/documentLinkProvider.ts
@@ -7,7 +7,7 @@ import * as path from 'path';
 import * as vscode from 'vscode';
 import * as nls from 'vscode-nls';
 import { OpenDocumentLinkCommand } from '../commands/openDocumentLink';
-import { getUriForLinkWithKnownExternalScheme } from '../util/links';
+import { getUriForLinkWithKnownExternalScheme, isOfScheme, Schemes } from '../util/links';
 
 const localize = nls.loadMessageBundle();
 
@@ -18,6 +18,10 @@ function parseLink(
 ): { uri: vscode.Uri, tooltip?: string } {
 	const externalSchemeUri = getUriForLinkWithKnownExternalScheme(link);
 	if (externalSchemeUri) {
+		// Normalize VS Code links to target currently running version
+		if (isOfScheme(Schemes.vscode, link) || isOfScheme(Schemes['vscode-insiders'], link)) {
+			return { uri: vscode.Uri.parse(link).with({ scheme: vscode.env.uriScheme }) };
+		}
 		return { uri: externalSchemeUri };
 	}
 
diff --git a/extensions/markdown-language-features/src/features/preview.ts b/extensions/markdown-language-features/src/features/preview.ts
index 2624bcdadb..aaf8083c3f 100644
--- a/extensions/markdown-language-features/src/features/preview.ts
+++ b/extensions/markdown-language-features/src/features/preview.ts
@@ -11,7 +11,7 @@ import { MarkdownContentProvider } from './previewContentProvider';
 import { Disposable } from '../util/dispose';
 
 import * as nls from 'vscode-nls';
-import { getVisibleLine, MarkdownFileTopmostLineMonitor } from '../util/topmostLineMonitor';
+import { getVisibleLine, TopmostLineMonitor } from '../util/topmostLineMonitor';
 import { MarkdownPreviewConfigurationManager } from './previewConfig';
 import { MarkdownContributionProvider, MarkdownContributions } from '../markdownExtensions';
 import { isMarkdownFile } from '../util/file';
@@ -72,11 +72,22 @@ export class PreviewDocumentVersion {
 	}
 }
 
-export class MarkdownPreview extends Disposable {
+interface DynamicPreviewInput {
+	readonly resource: vscode.Uri;
+	readonly resourceColumn: vscode.ViewColumn;
+	readonly locked: boolean;
+	readonly line?: number;
+}
+
+export class DynamicMarkdownPreview extends Disposable {
 
 	public static readonly viewType = 'markdown.preview';
 
+	private readonly delay = 300;
+
 	private _resource: vscode.Uri;
+	private readonly _resourceColumn: vscode.ViewColumn;
+
 	private _locked: boolean;
 
 	private readonly editor: vscode.WebviewPanel;
@@ -84,92 +95,65 @@ export class MarkdownPreview extends Disposable {
 	private line: number | undefined = undefined;
 	private firstUpdate = true;
 	private currentVersion?: PreviewDocumentVersion;
-	private forceUpdate = false;
 	private isScrolling = false;
 	private _disposed: boolean = false;
 	private imageInfo: { id: string, width: number, height: number; }[] = [];
 	private scrollToFragment: string | undefined;
 
-	public static async revive(
+	public static revive(
+		input: DynamicPreviewInput,
 		webview: vscode.WebviewPanel,
-		state: any,
 		contentProvider: MarkdownContentProvider,
 		previewConfigurations: MarkdownPreviewConfigurationManager,
 		logger: Logger,
-		topmostLineMonitor: MarkdownFileTopmostLineMonitor,
+		topmostLineMonitor: TopmostLineMonitor,
 		contributionProvider: MarkdownContributionProvider,
-	): Promise {
-		const resource = vscode.Uri.parse(state.resource);
-		const locked = state.locked;
-		const line = state.line;
-		const resourceColumn = state.resourceColumn;
+	): DynamicMarkdownPreview {
+		webview.webview.options = DynamicMarkdownPreview.getWebviewOptions(input.resource, contributionProvider.contributions);
+		webview.title = DynamicMarkdownPreview.getPreviewTitle(input.resource, input.locked);
 
-		const preview = new MarkdownPreview(
-			webview,
-			resource,
-			locked,
-			resourceColumn,
-			contentProvider,
-			previewConfigurations,
-			logger,
-			topmostLineMonitor,
-			contributionProvider);
-
-		preview.editor.webview.options = MarkdownPreview.getWebviewOptions(resource, contributionProvider.contributions);
-
-		if (!isNaN(line)) {
-			preview.line = line;
-		}
-		await preview.doUpdate();
-		return preview;
+		return new DynamicMarkdownPreview(webview, input,
+			contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider);
 	}
 
 	public static create(
-		resource: vscode.Uri,
+		input: DynamicPreviewInput,
 		previewColumn: vscode.ViewColumn,
-		resourceColumn: vscode.ViewColumn,
-		locked: boolean,
 		contentProvider: MarkdownContentProvider,
 		previewConfigurations: MarkdownPreviewConfigurationManager,
 		logger: Logger,
-		topmostLineMonitor: MarkdownFileTopmostLineMonitor,
+		topmostLineMonitor: TopmostLineMonitor,
 		contributionProvider: MarkdownContributionProvider
-	): MarkdownPreview {
+	): DynamicMarkdownPreview {
 		const webview = vscode.window.createWebviewPanel(
-			MarkdownPreview.viewType,
-			MarkdownPreview.getPreviewTitle(resource, locked),
+			DynamicMarkdownPreview.viewType,
+			DynamicMarkdownPreview.getPreviewTitle(input.resource, input.locked),
 			previewColumn, {
 			enableFindWidget: true,
-			...MarkdownPreview.getWebviewOptions(resource, contributionProvider.contributions)
+			...DynamicMarkdownPreview.getWebviewOptions(input.resource, contributionProvider.contributions)
 		});
 
-		return new MarkdownPreview(
-			webview,
-			resource,
-			locked,
-			resourceColumn,
-			contentProvider,
-			previewConfigurations,
-			logger,
-			topmostLineMonitor,
-			contributionProvider);
+		return new DynamicMarkdownPreview(webview, input,
+			contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider);
 	}
 
 	private constructor(
 		webview: vscode.WebviewPanel,
-		resource: vscode.Uri,
-		locked: boolean,
-		private readonly _resourceColumn: vscode.ViewColumn,
+		input: DynamicPreviewInput,
 		private readonly _contentProvider: MarkdownContentProvider,
 		private readonly _previewConfigurations: MarkdownPreviewConfigurationManager,
 		private readonly _logger: Logger,
-		topmostLineMonitor: MarkdownFileTopmostLineMonitor,
+		topmostLineMonitor: TopmostLineMonitor,
 		private readonly _contributionProvider: MarkdownContributionProvider,
 	) {
 		super();
-		this._resource = resource;
-		this._locked = locked;
+		this._resource = input.resource;
+		this._resourceColumn = input.resourceColumn;
+		this._locked = input.locked;
 		this.editor = webview;
+		if (!isNaN(input.line!)) {
+			this.line = input.line;
+		}
 
 		this._register(this.editor.onDidDispose(() => {
 			this.dispose();
@@ -190,7 +174,7 @@ export class MarkdownPreview extends Disposable {
 
 			switch (e.type) {
 				case 'cacheImageSizes':
-					this.onCacheImageSizes(e.body);
+					this.imageInfo = e.body;
 					break;
 
 				case 'revealLine':
@@ -221,7 +205,7 @@ export class MarkdownPreview extends Disposable {
 			}
 		}));
 
-		this._register(topmostLineMonitor.onDidChangeTopmostLine(event => {
+		this._register(topmostLineMonitor.onDidChanged(event => {
 			if (this.isPreviewOf(event.resource)) {
 				this.updateForView(event.resource, event.line);
 			}
@@ -239,9 +223,11 @@ export class MarkdownPreview extends Disposable {
 
 		this._register(vscode.window.onDidChangeActiveTextEditor(editor => {
 			if (editor && isMarkdownFile(editor.document) && !this._locked) {
-				this.update(editor.document.uri);
+				this.update(editor.document.uri, false);
 			}
 		}));
+
+		this.doUpdate();
 	}
 
 	private readonly _onDisposeEmitter = this._register(new vscode.EventEmitter());
@@ -278,7 +264,6 @@ export class MarkdownPreview extends Disposable {
 		this._onDisposeEmitter.fire();
 		this._onDisposeEmitter.dispose();
 
-		this._onDidChangeViewStateEmitter.dispose();
 		this.editor.dispose();
 		super.dispose();
 	}
@@ -297,7 +282,6 @@ export class MarkdownPreview extends Disposable {
 			}
 		}
 
-
 		// If we have changed resources, cancel any pending updates
 		const isResourceChange = resource.fsPath !== this._resource.fsPath;
 		if (isResourceChange) {
@@ -310,9 +294,9 @@ export class MarkdownPreview extends Disposable {
 		// Schedule update if none is pending
 		if (!this.throttleTimer) {
 			if (isResourceChange || this.firstUpdate) {
-				this.doUpdate();
+				this.doUpdate(isRefresh);
 			} else {
-				this.throttleTimer = setTimeout(() => this.doUpdate(), 300);
+				this.throttleTimer = setTimeout(() => this.doUpdate(isRefresh), this.delay);
 			}
 		}
 
@@ -320,7 +304,6 @@ export class MarkdownPreview extends Disposable {
 	}
 
 	public refresh() {
-		this.forceUpdate = true;
 		this.update(this._resource, true);
 	}
 
@@ -350,7 +333,7 @@ export class MarkdownPreview extends Disposable {
 		}
 	}
 
-	public matches(otherPreview: MarkdownPreview): boolean {
+	public matches(otherPreview: DynamicMarkdownPreview): boolean {
 		return this.matchesResource(otherPreview._resource, otherPreview.position, otherPreview._locked);
 	}
 
@@ -360,7 +343,7 @@ export class MarkdownPreview extends Disposable {
 
 	public toggleLock() {
 		this._locked = !this._locked;
-		this.editor.title = MarkdownPreview.getPreviewTitle(this._resource, this._locked);
+		this.editor.title = DynamicMarkdownPreview.getPreviewTitle(this._resource, this._locked);
 	}
 
 	private get iconPath() {
@@ -408,7 +391,7 @@ export class MarkdownPreview extends Disposable {
 		}
 	}
 
-	private async doUpdate(): Promise {
+	private async doUpdate(forceUpdate?: boolean): Promise {
 		if (this._disposed) {
 			return;
 		}
@@ -431,13 +414,12 @@ export class MarkdownPreview extends Disposable {
 		}
 
 		const pendingVersion = new PreviewDocumentVersion(markdownResource, document.version);
-		if (!this.forceUpdate && this.currentVersion && this.currentVersion.equals(pendingVersion)) {
+		if (!forceUpdate && this.currentVersion?.equals(pendingVersion)) {
 			if (this.line) {
 				this.updateForView(markdownResource, this.line);
 			}
 			return;
 		}
-		this.forceUpdate = false;
 
 		this.currentVersion = pendingVersion;
 		if (this._resource === markdownResource) {
@@ -463,7 +445,7 @@ export class MarkdownPreview extends Disposable {
 	): vscode.WebviewOptions {
 		return {
 			enableScripts: true,
-			localResourceRoots: MarkdownPreview.getLocalResourceRoots(resource, contributions)
+			localResourceRoots: DynamicMarkdownPreview.getLocalResourceRoots(resource, contributions)
 		};
 	}
 
@@ -508,6 +490,9 @@ export class MarkdownPreview extends Disposable {
 	}
 
 	private async onDidClickPreview(line: number): Promise {
+		// fix #82457, find currently opened but unfocused source tab
+		await vscode.commands.executeCommand('markdown.showSource');
+
 		for (const visibleEditor of vscode.window.visibleTextEditors) {
 			if (this.isPreviewOf(visibleEditor.document.uri)) {
 				const editor = await vscode.window.showTextDocument(visibleEditor.document, visibleEditor.viewColumn);
@@ -529,9 +514,9 @@ export class MarkdownPreview extends Disposable {
 	}
 
 	private setContent(html: string): void {
-		this.editor.title = MarkdownPreview.getPreviewTitle(this._resource, this._locked);
+		this.editor.title = DynamicMarkdownPreview.getPreviewTitle(this._resource, this._locked);
 		this.editor.iconPath = this.iconPath;
-		this.editor.webview.options = MarkdownPreview.getWebviewOptions(this._resource, this._contributionProvider.contributions);
+		this.editor.webview.options = DynamicMarkdownPreview.getWebviewOptions(this._resource, this._contributionProvider.contributions);
 		this.editor.webview.html = html;
 	}
 
@@ -559,14 +544,4 @@ export class MarkdownPreview extends Disposable {
 
 		vscode.commands.executeCommand('_markdown.openDocumentLink', { path: hrefPath, fragment, fromResource: this.resource });
 	}
-
-	private async onCacheImageSizes(imageInfo: { id: string, width: number, height: number; }[]) {
-		this.imageInfo = imageInfo;
-	}
-}
-
-export interface PreviewSettings {
-	readonly resourceColumn: vscode.ViewColumn;
-	readonly previewColumn: vscode.ViewColumn;
-	readonly locked: boolean;
 }
diff --git a/extensions/markdown-language-features/src/features/previewManager.ts b/extensions/markdown-language-features/src/features/previewManager.ts
index 787d65510f..21388327ce 100644
--- a/extensions/markdown-language-features/src/features/previewManager.ts
+++ b/extensions/markdown-language-features/src/features/previewManager.ts
@@ -7,19 +7,61 @@ import * as vscode from 'vscode';
 import { Logger } from '../logger';
 import { MarkdownContributionProvider } from '../markdownExtensions';
 import { disposeAll, Disposable } from '../util/dispose';
-import { MarkdownFileTopmostLineMonitor } from '../util/topmostLineMonitor';
-import { MarkdownPreview, PreviewSettings } from './preview';
+import { TopmostLineMonitor } from '../util/topmostLineMonitor';
+import { DynamicMarkdownPreview } from './preview';
 import { MarkdownPreviewConfigurationManager } from './previewConfig';
 import { MarkdownContentProvider } from './previewContentProvider';
 
+export interface DynamicPreviewSettings {
+	readonly resourceColumn: vscode.ViewColumn;
+	readonly previewColumn: vscode.ViewColumn;
+	readonly locked: boolean;
+}
 
-export class MarkdownPreviewManager extends Disposable implements vscode.WebviewPanelSerializer {
+class PreviewStore extends Disposable {
+
+	private readonly _previews = new Set();
+
+	public dispose(): void {
+		super.dispose();
+		for (const preview of this._previews) {
+			preview.dispose();
+		}
+		this._previews.clear();
+	}
+
+	[Symbol.iterator](): Iterator {
+		return this._previews[Symbol.iterator]();
+	}
+
+	public get(resource: vscode.Uri, previewSettings: DynamicPreviewSettings): DynamicMarkdownPreview | undefined {
+		for (const preview of this._previews) {
+			if (preview.matchesResource(resource, previewSettings.previewColumn, previewSettings.locked)) {
+				return preview;
+			}
+		}
+		return undefined;
+	}
+
+	public add(preview: DynamicMarkdownPreview) {
+		this._previews.add(preview);
+	}
+
+	public delete(preview: DynamicMarkdownPreview) {
+		this._previews.delete(preview);
+	}
+}
+
+export class MarkdownPreviewManager extends Disposable implements vscode.WebviewPanelSerializer, vscode.WebviewEditorProvider {
 	private static readonly markdownPreviewActiveContextKey = 'markdownPreviewFocus';
 
-	private readonly _topmostLineMonitor = new MarkdownFileTopmostLineMonitor();
+	private readonly _topmostLineMonitor = new TopmostLineMonitor();
 	private readonly _previewConfigurations = new MarkdownPreviewConfigurationManager();
-	private readonly _previews: MarkdownPreview[] = [];
-	private _activePreview: MarkdownPreview | undefined = undefined;
+
+	private readonly _dynamicPreviews = this._register(new PreviewStore());
+	private readonly _staticPreviews = this._register(new PreviewStore());
+
+	private _activePreview: DynamicMarkdownPreview | undefined = undefined;
 
 	public constructor(
 		private readonly _contentProvider: MarkdownContentProvider,
@@ -27,46 +69,48 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
 		private readonly _contributions: MarkdownContributionProvider
 	) {
 		super();
-		this._register(vscode.window.registerWebviewPanelSerializer(MarkdownPreview.viewType, this));
-	}
-
-	public dispose(): void {
-		super.dispose();
-		disposeAll(this._previews);
+		this._register(vscode.window.registerWebviewPanelSerializer(DynamicMarkdownPreview.viewType, this));
+		this._register(vscode.window.registerWebviewEditorProvider('vscode.markdown.preview.editor', this));
 	}
 
 	public refresh() {
-		for (const preview of this._previews) {
+		for (const preview of this._dynamicPreviews) {
+			preview.refresh();
+		}
+		for (const preview of this._staticPreviews) {
 			preview.refresh();
 		}
 	}
 
 	public updateConfiguration() {
-		for (const preview of this._previews) {
+		for (const preview of this._dynamicPreviews) {
+			preview.updateConfiguration();
+		}
+		for (const preview of this._staticPreviews) {
 			preview.updateConfiguration();
 		}
 	}
 
-	public preview(
+	public openDynamicPreview(
 		resource: vscode.Uri,
-		previewSettings: PreviewSettings
+		settings: DynamicPreviewSettings
 	): void {
-		let preview = this.getExistingPreview(resource, previewSettings);
+		let preview = this._dynamicPreviews.get(resource, settings);
 		if (preview) {
-			preview.reveal(previewSettings.previewColumn);
+			preview.reveal(settings.previewColumn);
 		} else {
-			preview = this.createNewPreview(resource, previewSettings);
+			preview = this.createNewDynamicPreview(resource, settings);
 		}
 
 		preview.update(resource);
 	}
 
 	public get activePreviewResource() {
-		return this._activePreview && this._activePreview.resource;
+		return this._activePreview?.resource;
 	}
 
 	public get activePreviewResourceColumn() {
-		return this._activePreview && this._activePreview.resourceColumn;
+		return this._activePreview?.resourceColumn;
 	}
 
 	public toggleLock() {
@@ -75,7 +119,7 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
 			preview.toggleLock();
 
 			// Close any previews that are now redundant, such as having two dynamic previews in the same editor group
-			for (const otherPreview of this._previews) {
+			for (const otherPreview of this._dynamicPreviews) {
 				if (otherPreview !== preview && preview.matches(otherPreview)) {
 					otherPreview.dispose();
 				}
@@ -87,35 +131,50 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
 		webview: vscode.WebviewPanel,
 		state: any
 	): Promise {
-		const preview = await MarkdownPreview.revive(
+		const resource = vscode.Uri.parse(state.resource);
+		const locked = state.locked;
+		const line = state.line;
+		const resourceColumn = state.resourceColumn;
+
+		const preview = await DynamicMarkdownPreview.revive(
+			{ resource, locked, line, resourceColumn },
 			webview,
-			state,
 			this._contentProvider,
 			this._previewConfigurations,
 			this._logger,
 			this._topmostLineMonitor,
 			this._contributions);
 
-		this.registerPreview(preview);
+		this.registerDynamicPreview(preview);
 	}
 
-	private getExistingPreview(
-		resource: vscode.Uri,
-		previewSettings: PreviewSettings
-	): MarkdownPreview | undefined {
-		return this._previews.find(preview =>
-			preview.matchesResource(resource, previewSettings.previewColumn, previewSettings.locked));
+	public async resolveWebviewEditor(
+		input: { readonly resource: vscode.Uri; },
+		webview: vscode.WebviewPanel
+	): Promise {
+		const preview = DynamicMarkdownPreview.revive(
+			{ resource: input.resource, locked: false, resourceColumn: vscode.ViewColumn.One },
+			webview,
+			this._contentProvider,
+			this._previewConfigurations,
+			this._logger,
+			this._topmostLineMonitor,
+			this._contributions);
+		this.registerStaticPreview(preview);
+		return {};
 	}
 
-	private createNewPreview(
+	private createNewDynamicPreview(
 		resource: vscode.Uri,
-		previewSettings: PreviewSettings
-	): MarkdownPreview {
-		const preview = MarkdownPreview.create(
-			resource,
+		previewSettings: DynamicPreviewSettings
+	): DynamicMarkdownPreview {
+		const preview = DynamicMarkdownPreview.create(
+			{
+				resource,
+				resourceColumn: previewSettings.resourceColumn,
+				locked: previewSettings.locked,
+			},
 			previewSettings.previewColumn,
-			previewSettings.resourceColumn,
-			previewSettings.locked,
 			this._contentProvider,
 			this._previewConfigurations,
 			this._logger,
@@ -124,34 +183,48 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
 
 		this.setPreviewActiveContext(true);
 		this._activePreview = preview;
-		return this.registerPreview(preview);
+		return this.registerDynamicPreview(preview);
 	}
 
-	private registerPreview(
-		preview: MarkdownPreview
-	): MarkdownPreview {
-		this._previews.push(preview);
+	private registerDynamicPreview(preview: DynamicMarkdownPreview): DynamicMarkdownPreview {
+		this._dynamicPreviews.add(preview);
 
 		preview.onDispose(() => {
-			const existing = this._previews.indexOf(preview);
-			if (existing === -1) {
-				return;
-			}
+			this._dynamicPreviews.delete(preview);
+		});
 
-			this._previews.splice(existing, 1);
+		this.trackActive(preview);
+
+		preview.onDidChangeViewState(() => {
+			// Remove other dynamic previews in our column
+			disposeAll(Array.from(this._dynamicPreviews).filter(otherPreview => preview !== otherPreview && preview.matches(otherPreview)));
+		});
+		return preview;
+	}
+
+	private registerStaticPreview(preview: DynamicMarkdownPreview): DynamicMarkdownPreview {
+		this._staticPreviews.add(preview);
+
+		preview.onDispose(() => {
+			this._staticPreviews.delete(preview);
+		});
+
+		this.trackActive(preview);
+		return preview;
+	}
+
+	private trackActive(preview: DynamicMarkdownPreview): void {
+		preview.onDidChangeViewState(({ webviewPanel }) => {
+			this.setPreviewActiveContext(webviewPanel.active);
+			this._activePreview = webviewPanel.active ? preview : undefined;
+		});
+
+		preview.onDispose(() => {
 			if (this._activePreview === preview) {
 				this.setPreviewActiveContext(false);
 				this._activePreview = undefined;
 			}
 		});
-
-		preview.onDidChangeViewState(({ webviewPanel }) => {
-			disposeAll(this._previews.filter(otherPreview => preview !== otherPreview && preview!.matches(otherPreview)));
-			this.setPreviewActiveContext(webviewPanel.active);
-			this._activePreview = webviewPanel.active ? preview : undefined;
-		});
-
-		return preview;
 	}
 
 	private setPreviewActiveContext(value: boolean) {
diff --git a/extensions/markdown-language-features/src/logger.ts b/extensions/markdown-language-features/src/logger.ts
index 98da9ea4fe..c1bb92023f 100644
--- a/extensions/markdown-language-features/src/logger.ts
+++ b/extensions/markdown-language-features/src/logger.ts
@@ -41,13 +41,21 @@ export class Logger {
 
 	public log(message: string, data?: any): void {
 		if (this.trace === Trace.Verbose) {
-			this.appendLine(`[Log - ${(new Date().toLocaleTimeString())}] ${message}`);
+			this.appendLine(`[Log - ${this.now()}] ${message}`);
 			if (data) {
 				this.appendLine(Logger.data2String(data));
 			}
 		}
 	}
 
+
+	private now(): string {
+		const now = new Date();
+		return padLeft(now.getUTCHours() + '', 2, '0')
+			+ ':' + padLeft(now.getMinutes() + '', 2, '0')
+			+ ':' + padLeft(now.getUTCSeconds() + '', 2, '0') + '.' + now.getMilliseconds();
+	}
+
 	public updateConfiguration() {
 		this.trace = this.readTrace();
 	}
@@ -73,3 +81,7 @@ export class Logger {
 		return JSON.stringify(data, undefined, 2);
 	}
 }
+
+function padLeft(s: string, n: number, pad = ' ') {
+	return pad.repeat(Math.max(0, n - s.length)) + s;
+}
diff --git a/extensions/markdown-language-features/src/markdownEngine.ts b/extensions/markdown-language-features/src/markdownEngine.ts
index 74d7634d8a..079bb4873c 100644
--- a/extensions/markdown-language-features/src/markdownEngine.ts
+++ b/extensions/markdown-language-features/src/markdownEngine.ts
@@ -234,6 +234,11 @@ export class MarkdownEngine {
 		const normalizeLink = md.normalizeLink;
 		md.normalizeLink = (link: string) => {
 			try {
+				// Normalize VS Code schemes to target the current version
+				if (isOfScheme(Schemes.vscode, link) || isOfScheme(Schemes['vscode-insiders'], link)) {
+					return normalizeLink(vscode.Uri.parse(link).with({ scheme: vscode.env.uriScheme }).toString());
+				}
+
 				// If original link doesn't look like a url with a scheme, assume it must be a link to a file in workspace
 				if (!/^[a-z\-]+:/i.test(link)) {
 					// Use a fake scheme for parsing
@@ -268,7 +273,11 @@ export class MarkdownEngine {
 		const validateLink = md.validateLink;
 		md.validateLink = (link: string) => {
 			// support file:// links
-			return validateLink(link) || isOfScheme(Schemes.file, link) || /^data:image\/.*?;/.test(link);
+			return validateLink(link)
+				|| isOfScheme(Schemes.file, link)
+				|| isOfScheme(Schemes.vscode, link)
+				|| isOfScheme(Schemes['vscode-insiders'], link)
+				|| /^data:image\/.*?;/.test(link);
 		};
 	}
 
diff --git a/extensions/markdown-language-features/src/test/index.ts b/extensions/markdown-language-features/src/test/index.ts
index a6ac226afd..c8a387eeec 100644
--- a/extensions/markdown-language-features/src/test/index.ts
+++ b/extensions/markdown-language-features/src/test/index.ts
@@ -10,7 +10,7 @@ const suite = 'Integration Markdown Tests';
 
 const options: any = {
 	ui: 'tdd',
-	useColors: true,
+	useColors: (!process.env.BUILD_ARTIFACTSTAGINGDIRECTORY && process.platform !== 'win32'),
 	timeout: 60000
 };
 
diff --git a/extensions/markdown-language-features/src/util/links.ts b/extensions/markdown-language-features/src/util/links.ts
index b4b0e35331..fd0a7e173c 100644
--- a/extensions/markdown-language-features/src/util/links.ts
+++ b/extensions/markdown-language-features/src/util/links.ts
@@ -13,7 +13,7 @@ export const Schemes = {
 	data: 'data:',
 	vscode: 'vscode:',
 	'vscode-insiders': 'vscode-insiders:',
-	'vscode-resource': 'vscode-resource',
+	'vscode-resource': 'vscode-resource:',
 };
 
 const knownSchemes = [
diff --git a/extensions/markdown-language-features/src/util/topmostLineMonitor.ts b/extensions/markdown-language-features/src/util/topmostLineMonitor.ts
index 389149031e..acf08006cf 100644
--- a/extensions/markdown-language-features/src/util/topmostLineMonitor.ts
+++ b/extensions/markdown-language-features/src/util/topmostLineMonitor.ts
@@ -4,33 +4,28 @@
  *--------------------------------------------------------------------------------------------*/
 
 import * as vscode from 'vscode';
-import { disposeAll } from '../util/dispose';
+import { Disposable } from '../util/dispose';
 import { isMarkdownFile } from './file';
 
-export class MarkdownFileTopmostLineMonitor {
-	private readonly disposables: vscode.Disposable[] = [];
+export class TopmostLineMonitor extends Disposable {
 
 	private readonly pendingUpdates = new Map();
-
 	private readonly throttle = 50;
 
 	constructor() {
-		vscode.window.onDidChangeTextEditorVisibleRanges(event => {
+		super();
+		this._register(vscode.window.onDidChangeTextEditorVisibleRanges(event => {
 			if (isMarkdownFile(event.textEditor.document)) {
 				const line = getVisibleLine(event.textEditor);
 				if (typeof line === 'number') {
 					this.updateLine(event.textEditor.document.uri, line);
 				}
 			}
-		}, null, this.disposables);
+		}));
 	}
 
-	dispose() {
-		disposeAll(this.disposables);
-	}
-
-	private readonly _onDidChangeTopmostLineEmitter = new vscode.EventEmitter<{ resource: vscode.Uri, line: number }>();
-	public readonly onDidChangeTopmostLine = this._onDidChangeTopmostLineEmitter.event;
+	private readonly _onChanged = this._register(new vscode.EventEmitter<{ readonly resource: vscode.Uri, readonly line: number }>());
+	public readonly onDidChanged = this._onChanged.event;
 
 	private updateLine(
 		resource: vscode.Uri,
@@ -41,7 +36,7 @@ export class MarkdownFileTopmostLineMonitor {
 			// schedule update
 			setTimeout(() => {
 				if (this.pendingUpdates.has(key)) {
-					this._onDidChangeTopmostLineEmitter.fire({
+					this._onChanged.fire({
 						resource,
 						line: this.pendingUpdates.get(key) as number
 					});
diff --git a/extensions/markdown-language-features/yarn.lock b/extensions/markdown-language-features/yarn.lock
index ccaebc2cbe..956c457152 100644
--- a/extensions/markdown-language-features/yarn.lock
+++ b/extensions/markdown-language-features/yarn.lock
@@ -2,11 +2,6 @@
 # yarn lockfile v1
 
 
-"@sindresorhus/is@^0.7.0":
-  version "0.7.0"
-  resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"
-  integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==
-
 "@types/highlight.js@9.12.3":
   version "9.12.3"
   resolved "https://registry.yarnpkg.com/@types/highlight.js/-/highlight.js-9.12.3.tgz#b672cfaac25cbbc634a0fd92c515f66faa18dbca"
@@ -29,33 +24,192 @@
   resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.2.tgz#5d9ad19e6e6508cdd2f2596df86fd0aade598660"
   integrity sha1-XZrRnm5lCM3S8llt+G/Qqt5ZhmA=
 
-"@types/node@^10.14.8":
-  version "10.14.8"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9"
-  integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw==
+"@types/node@^12.11.7":
+  version "12.11.7"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a"
+  integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA==
+
+"@webassemblyjs/ast@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359"
+  integrity sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ==
+  dependencies:
+    "@webassemblyjs/helper-module-context" "1.8.5"
+    "@webassemblyjs/helper-wasm-bytecode" "1.8.5"
+    "@webassemblyjs/wast-parser" "1.8.5"
+
+"@webassemblyjs/floating-point-hex-parser@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz#1ba926a2923613edce496fd5b02e8ce8a5f49721"
+  integrity sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ==
+
+"@webassemblyjs/helper-api-error@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz#c49dad22f645227c5edb610bdb9697f1aab721f7"
+  integrity sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA==
+
+"@webassemblyjs/helper-buffer@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz#fea93e429863dd5e4338555f42292385a653f204"
+  integrity sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q==
+
+"@webassemblyjs/helper-code-frame@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz#9a740ff48e3faa3022b1dff54423df9aa293c25e"
+  integrity sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ==
+  dependencies:
+    "@webassemblyjs/wast-printer" "1.8.5"
+
+"@webassemblyjs/helper-fsm@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz#ba0b7d3b3f7e4733da6059c9332275d860702452"
+  integrity sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow==
+
+"@webassemblyjs/helper-module-context@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz#def4b9927b0101dc8cbbd8d1edb5b7b9c82eb245"
+  integrity sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g==
+  dependencies:
+    "@webassemblyjs/ast" "1.8.5"
+    mamacro "^0.0.3"
+
+"@webassemblyjs/helper-wasm-bytecode@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz#537a750eddf5c1e932f3744206551c91c1b93e61"
+  integrity sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ==
+
+"@webassemblyjs/helper-wasm-section@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz#74ca6a6bcbe19e50a3b6b462847e69503e6bfcbf"
+  integrity sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA==
+  dependencies:
+    "@webassemblyjs/ast" "1.8.5"
+    "@webassemblyjs/helper-buffer" "1.8.5"
+    "@webassemblyjs/helper-wasm-bytecode" "1.8.5"
+    "@webassemblyjs/wasm-gen" "1.8.5"
+
+"@webassemblyjs/ieee754@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz#712329dbef240f36bf57bd2f7b8fb9bf4154421e"
+  integrity sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g==
+  dependencies:
+    "@xtuc/ieee754" "^1.2.0"
+
+"@webassemblyjs/leb128@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.8.5.tgz#044edeb34ea679f3e04cd4fd9824d5e35767ae10"
+  integrity sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A==
+  dependencies:
+    "@xtuc/long" "4.2.2"
+
+"@webassemblyjs/utf8@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.8.5.tgz#a8bf3b5d8ffe986c7c1e373ccbdc2a0915f0cedc"
+  integrity sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw==
+
+"@webassemblyjs/wasm-edit@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz#962da12aa5acc1c131c81c4232991c82ce56e01a"
+  integrity sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q==
+  dependencies:
+    "@webassemblyjs/ast" "1.8.5"
+    "@webassemblyjs/helper-buffer" "1.8.5"
+    "@webassemblyjs/helper-wasm-bytecode" "1.8.5"
+    "@webassemblyjs/helper-wasm-section" "1.8.5"
+    "@webassemblyjs/wasm-gen" "1.8.5"
+    "@webassemblyjs/wasm-opt" "1.8.5"
+    "@webassemblyjs/wasm-parser" "1.8.5"
+    "@webassemblyjs/wast-printer" "1.8.5"
+
+"@webassemblyjs/wasm-gen@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz#54840766c2c1002eb64ed1abe720aded714f98bc"
+  integrity sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg==
+  dependencies:
+    "@webassemblyjs/ast" "1.8.5"
+    "@webassemblyjs/helper-wasm-bytecode" "1.8.5"
+    "@webassemblyjs/ieee754" "1.8.5"
+    "@webassemblyjs/leb128" "1.8.5"
+    "@webassemblyjs/utf8" "1.8.5"
+
+"@webassemblyjs/wasm-opt@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz#b24d9f6ba50394af1349f510afa8ffcb8a63d264"
+  integrity sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q==
+  dependencies:
+    "@webassemblyjs/ast" "1.8.5"
+    "@webassemblyjs/helper-buffer" "1.8.5"
+    "@webassemblyjs/wasm-gen" "1.8.5"
+    "@webassemblyjs/wasm-parser" "1.8.5"
+
+"@webassemblyjs/wasm-parser@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz#21576f0ec88b91427357b8536383668ef7c66b8d"
+  integrity sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw==
+  dependencies:
+    "@webassemblyjs/ast" "1.8.5"
+    "@webassemblyjs/helper-api-error" "1.8.5"
+    "@webassemblyjs/helper-wasm-bytecode" "1.8.5"
+    "@webassemblyjs/ieee754" "1.8.5"
+    "@webassemblyjs/leb128" "1.8.5"
+    "@webassemblyjs/utf8" "1.8.5"
+
+"@webassemblyjs/wast-parser@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz#e10eecd542d0e7bd394f6827c49f3df6d4eefb8c"
+  integrity sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg==
+  dependencies:
+    "@webassemblyjs/ast" "1.8.5"
+    "@webassemblyjs/floating-point-hex-parser" "1.8.5"
+    "@webassemblyjs/helper-api-error" "1.8.5"
+    "@webassemblyjs/helper-code-frame" "1.8.5"
+    "@webassemblyjs/helper-fsm" "1.8.5"
+    "@xtuc/long" "4.2.2"
+
+"@webassemblyjs/wast-printer@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz#114bbc481fd10ca0e23b3560fa812748b0bae5bc"
+  integrity sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg==
+  dependencies:
+    "@webassemblyjs/ast" "1.8.5"
+    "@webassemblyjs/wast-parser" "1.8.5"
+    "@xtuc/long" "4.2.2"
+
+"@xtuc/ieee754@^1.2.0":
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
+  integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==
+
+"@xtuc/long@4.2.2":
+  version "4.2.2"
+  resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
+  integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
 
 abbrev@1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
   integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
 
-acorn-dynamic-import@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-3.0.0.tgz#901ceee4c7faaef7e07ad2a47e890675da50a278"
-  integrity sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg==
-  dependencies:
-    acorn "^5.0.0"
+acorn@^6.2.1:
+  version "6.3.0"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.3.0.tgz#0087509119ffa4fc0a0041d1e93a417e68cb856e"
+  integrity sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==
 
-acorn@^5.0.0:
-  version "5.5.0"
-  resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.5.0.tgz#1abb587fbf051f94e3de20e6b26ef910b1828298"
-  integrity sha512-arn53F07VXmls4o4pUhSzBa4fvaagPRe7AVZ8l7NHxFWUie2DsuFSBMMNAkgzRlOhEhzAnxeKyaWVzOH4xqp/g==
+ajv-errors@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d"
+  integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==
 
 ajv-keywords@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.1.0.tgz#ac2b27939c543e95d2c06e7f7f5c27be4aa543be"
   integrity sha1-rCsnk5xUPpXSwG5/f1wnvkqlQ74=
 
+ajv-keywords@^3.4.1:
+  version "3.4.1"
+  resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da"
+  integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==
+
 ajv@^4.9.1:
   version "4.11.8"
   resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536"
@@ -83,6 +237,16 @@ ajv@^6.1.0:
     fast-json-stable-stringify "^2.0.0"
     json-schema-traverse "^0.3.0"
 
+ajv@^6.10.2:
+  version "6.10.2"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52"
+  integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==
+  dependencies:
+    fast-deep-equal "^2.0.1"
+    fast-json-stable-stringify "^2.0.0"
+    json-schema-traverse "^0.4.1"
+    uri-js "^4.2.2"
+
 ansi-cyan@^0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/ansi-cyan/-/ansi-cyan-0.1.1.tgz#538ae528af8982f28ae30d86f2f17456d2609873"
@@ -90,16 +254,6 @@ ansi-cyan@^0.1.1:
   dependencies:
     ansi-wrap "0.1.0"
 
-ansi-escapes@^1.0.0, ansi-escapes@^1.1.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e"
-  integrity sha1-06ioOzGapneTZisT52HHkRQiMG4=
-
-ansi-escapes@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92"
-  integrity sha512-O/klc27mWNUigtv0F8NJWbLF00OcegQalkqKURWdosW08YZKi4m6CnSUSvIZG1otNJbTWhN01Hhz389DW7mvDQ==
-
 ansi-gray@^0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/ansi-gray/-/ansi-gray-0.1.1.tgz#2962cf54ec9792c48510a3deb524436861ef7251"
@@ -124,33 +278,28 @@ ansi-regex@^3.0.0:
   resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
   integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
 
+ansi-regex@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
+  integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
+
 ansi-styles@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
   integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=
 
-ansi-styles@^3.2.1:
+ansi-styles@^3.2.0, ansi-styles@^3.2.1:
   version "3.2.1"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
   integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
   dependencies:
     color-convert "^1.9.0"
 
-ansi-styles@~1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.0.0.tgz#cb102df1c56f5123eab8b67cd7b98027a0279178"
-  integrity sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=
-
 ansi-wrap@0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf"
   integrity sha1-qCJQ3bABXponyoLoLqYDu/pF768=
 
-any-observable@^0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.2.0.tgz#c67870058003579009083f54ac0abafb5c33d242"
-  integrity sha1-xnhwBYADV5AJCD9UrAq6+1wz0kI=
-
 anymatch@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb"
@@ -188,11 +337,6 @@ argparse@^1.0.7:
   dependencies:
     sprintf-js "~1.0.2"
 
-argv@0.0.2:
-  version "0.0.2"
-  resolved "https://registry.yarnpkg.com/argv/-/argv-0.0.2.tgz#ecbd16f8949b157183711b1bda334f37840185ab"
-  integrity sha1-7L0W+JSbFXGDcRsb2jNPN4QBhas=
-
 arr-diff@^1.0.1:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-1.1.0.tgz#687c32758163588fef7de7b36fabe495eb1a399a"
@@ -301,33 +445,11 @@ assign-symbols@^1.0.0:
   resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
   integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=
 
-ast-types@0.10.1:
-  version "0.10.1"
-  resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.10.1.tgz#f52fca9715579a14f841d67d7f8d25432ab6a3dd"
-  integrity sha512-UY7+9DPzlJ9VM8eY0b2TUZcZvF+1pO0hzMtAyjBYKhOmnvRlqYNYnWdtsMj0V16CGaMlpL0G1jnLbLo4AyotuQ==
-
-ast-types@0.11.2:
-  version "0.11.2"
-  resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.11.2.tgz#cc4e1d15a36b39979a1986fe1e91321cbfae7783"
-  integrity sha512-aL+pcOQ+6dpWd0xrUe+Obo2CgdkFvsntkXEmzZKqEN4cR0PStF+1MBuc4V+YZsv4Q36luvyjG7F4lc+wH2bmag==
-
 async-each@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
   integrity sha1-GdOGodntxufByF04iu28xW0zYC0=
 
-async@^1.5.0:
-  version "1.5.2"
-  resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
-  integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=
-
-async@^2.0.0:
-  version "2.6.0"
-  resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4"
-  integrity sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==
-  dependencies:
-    lodash "^4.14.0"
-
 asynckit@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@@ -353,663 +475,6 @@ aws4@^1.2.1, aws4@^1.6.0:
   resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
   integrity sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=
 
-babel-code-frame@^6.26.0:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
-  integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=
-  dependencies:
-    chalk "^1.1.3"
-    esutils "^2.0.2"
-    js-tokens "^3.0.2"
-
-babel-core@^6.26.0:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.0.tgz#af32f78b31a6fcef119c87b0fd8d9753f03a0bb8"
-  integrity sha1-rzL3izGm/O8RnIew/Y2XU/A6C7g=
-  dependencies:
-    babel-code-frame "^6.26.0"
-    babel-generator "^6.26.0"
-    babel-helpers "^6.24.1"
-    babel-messages "^6.23.0"
-    babel-register "^6.26.0"
-    babel-runtime "^6.26.0"
-    babel-template "^6.26.0"
-    babel-traverse "^6.26.0"
-    babel-types "^6.26.0"
-    babylon "^6.18.0"
-    convert-source-map "^1.5.0"
-    debug "^2.6.8"
-    json5 "^0.5.1"
-    lodash "^4.17.4"
-    minimatch "^3.0.4"
-    path-is-absolute "^1.0.1"
-    private "^0.1.7"
-    slash "^1.0.0"
-    source-map "^0.5.6"
-
-babel-generator@^6.26.0:
-  version "6.26.1"
-  resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90"
-  integrity sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==
-  dependencies:
-    babel-messages "^6.23.0"
-    babel-runtime "^6.26.0"
-    babel-types "^6.26.0"
-    detect-indent "^4.0.0"
-    jsesc "^1.3.0"
-    lodash "^4.17.4"
-    source-map "^0.5.7"
-    trim-right "^1.0.1"
-
-babel-helper-bindify-decorators@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz#14c19e5f142d7b47f19a52431e52b1ccbc40a330"
-  integrity sha1-FMGeXxQte0fxmlJDHlKxzLxAozA=
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-helper-builder-binary-assignment-operator-visitor@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664"
-  integrity sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=
-  dependencies:
-    babel-helper-explode-assignable-expression "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-helper-call-delegate@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d"
-  integrity sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=
-  dependencies:
-    babel-helper-hoist-variables "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-helper-define-map@^6.24.1:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz#a5f56dab41a25f97ecb498c7ebaca9819f95be5f"
-  integrity sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=
-  dependencies:
-    babel-helper-function-name "^6.24.1"
-    babel-runtime "^6.26.0"
-    babel-types "^6.26.0"
-    lodash "^4.17.4"
-
-babel-helper-explode-assignable-expression@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa"
-  integrity sha1-8luCz33BBDPFX3BZLVdGQArCLKo=
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-helper-explode-class@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-explode-class/-/babel-helper-explode-class-6.24.1.tgz#7dc2a3910dee007056e1e31d640ced3d54eaa9eb"
-  integrity sha1-fcKjkQ3uAHBW4eMdZAztPVTqqes=
-  dependencies:
-    babel-helper-bindify-decorators "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-helper-function-name@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9"
-  integrity sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=
-  dependencies:
-    babel-helper-get-function-arity "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-helper-get-function-arity@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d"
-  integrity sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-helper-hoist-variables@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76"
-  integrity sha1-HssnaJydJVE+rbyZFKc/VAi+enY=
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-helper-optimise-call-expression@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257"
-  integrity sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-helper-regex@^6.24.1:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz#325c59f902f82f24b74faceed0363954f6495e72"
-  integrity sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=
-  dependencies:
-    babel-runtime "^6.26.0"
-    babel-types "^6.26.0"
-    lodash "^4.17.4"
-
-babel-helper-remap-async-to-generator@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b"
-  integrity sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=
-  dependencies:
-    babel-helper-function-name "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-helper-replace-supers@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a"
-  integrity sha1-v22/5Dk40XNpohPKiov3S2qQqxo=
-  dependencies:
-    babel-helper-optimise-call-expression "^6.24.1"
-    babel-messages "^6.23.0"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-helpers@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2"
-  integrity sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-
-babel-messages@^6.23.0:
-  version "6.23.0"
-  resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e"
-  integrity sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-check-es2015-constants@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a"
-  integrity sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-syntax-async-functions@^6.8.0:
-  version "6.13.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95"
-  integrity sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=
-
-babel-plugin-syntax-async-generators@^6.5.0:
-  version "6.13.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz#6bc963ebb16eccbae6b92b596eb7f35c342a8b9a"
-  integrity sha1-a8lj67FuzLrmuStZbrfzXDQqi5o=
-
-babel-plugin-syntax-class-constructor-call@^6.18.0:
-  version "6.18.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-constructor-call/-/babel-plugin-syntax-class-constructor-call-6.18.0.tgz#9cb9d39fe43c8600bec8146456ddcbd4e1a76416"
-  integrity sha1-nLnTn+Q8hgC+yBRkVt3L1OGnZBY=
-
-babel-plugin-syntax-class-properties@^6.8.0:
-  version "6.13.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz#d7eb23b79a317f8543962c505b827c7d6cac27de"
-  integrity sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=
-
-babel-plugin-syntax-decorators@^6.13.0:
-  version "6.13.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz#312563b4dbde3cc806cee3e416cceeaddd11ac0b"
-  integrity sha1-MSVjtNvePMgGzuPkFszurd0RrAs=
-
-babel-plugin-syntax-dynamic-import@^6.18.0:
-  version "6.18.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz#8d6a26229c83745a9982a441051572caa179b1da"
-  integrity sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo=
-
-babel-plugin-syntax-exponentiation-operator@^6.8.0:
-  version "6.13.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de"
-  integrity sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=
-
-babel-plugin-syntax-export-extensions@^6.8.0:
-  version "6.13.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-export-extensions/-/babel-plugin-syntax-export-extensions-6.13.0.tgz#70a1484f0f9089a4e84ad44bac353c95b9b12721"
-  integrity sha1-cKFITw+QiaToStRLrDU8lbmxJyE=
-
-babel-plugin-syntax-flow@^6.18.0:
-  version "6.18.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d"
-  integrity sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=
-
-babel-plugin-syntax-object-rest-spread@^6.8.0:
-  version "6.13.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
-  integrity sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=
-
-babel-plugin-syntax-trailing-function-commas@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3"
-  integrity sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=
-
-babel-plugin-transform-async-generator-functions@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz#f058900145fd3e9907a6ddf28da59f215258a5db"
-  integrity sha1-8FiQAUX9PpkHpt3yjaWfIVJYpds=
-  dependencies:
-    babel-helper-remap-async-to-generator "^6.24.1"
-    babel-plugin-syntax-async-generators "^6.5.0"
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-async-to-generator@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761"
-  integrity sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=
-  dependencies:
-    babel-helper-remap-async-to-generator "^6.24.1"
-    babel-plugin-syntax-async-functions "^6.8.0"
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-class-constructor-call@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-constructor-call/-/babel-plugin-transform-class-constructor-call-6.24.1.tgz#80dc285505ac067dcb8d6c65e2f6f11ab7765ef9"
-  integrity sha1-gNwoVQWsBn3LjWxl4vbxGrd2Xvk=
-  dependencies:
-    babel-plugin-syntax-class-constructor-call "^6.18.0"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-
-babel-plugin-transform-class-properties@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz#6a79763ea61d33d36f37b611aa9def81a81b46ac"
-  integrity sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=
-  dependencies:
-    babel-helper-function-name "^6.24.1"
-    babel-plugin-syntax-class-properties "^6.8.0"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-
-babel-plugin-transform-decorators@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.24.1.tgz#788013d8f8c6b5222bdf7b344390dfd77569e24d"
-  integrity sha1-eIAT2PjGtSIr33s0Q5Df13Vp4k0=
-  dependencies:
-    babel-helper-explode-class "^6.24.1"
-    babel-plugin-syntax-decorators "^6.13.0"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-plugin-transform-es2015-arrow-functions@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221"
-  integrity sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-block-scoped-functions@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141"
-  integrity sha1-u8UbSflk1wy42OC5ToICRs46YUE=
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-block-scoping@^6.24.1:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f"
-  integrity sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=
-  dependencies:
-    babel-runtime "^6.26.0"
-    babel-template "^6.26.0"
-    babel-traverse "^6.26.0"
-    babel-types "^6.26.0"
-    lodash "^4.17.4"
-
-babel-plugin-transform-es2015-classes@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db"
-  integrity sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=
-  dependencies:
-    babel-helper-define-map "^6.24.1"
-    babel-helper-function-name "^6.24.1"
-    babel-helper-optimise-call-expression "^6.24.1"
-    babel-helper-replace-supers "^6.24.1"
-    babel-messages "^6.23.0"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-plugin-transform-es2015-computed-properties@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3"
-  integrity sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-
-babel-plugin-transform-es2015-destructuring@^6.22.0:
-  version "6.23.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d"
-  integrity sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-duplicate-keys@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e"
-  integrity sha1-c+s9MQypaePvnskcU3QabxV2Qj4=
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-plugin-transform-es2015-for-of@^6.22.0:
-  version "6.23.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691"
-  integrity sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-function-name@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b"
-  integrity sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=
-  dependencies:
-    babel-helper-function-name "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-plugin-transform-es2015-literals@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e"
-  integrity sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-modules-amd@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154"
-  integrity sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=
-  dependencies:
-    babel-plugin-transform-es2015-modules-commonjs "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-
-babel-plugin-transform-es2015-modules-commonjs@^6.24.1:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz#0d8394029b7dc6abe1a97ef181e00758dd2e5d8a"
-  integrity sha1-DYOUApt9xqvhqX7xgeAHWN0uXYo=
-  dependencies:
-    babel-plugin-transform-strict-mode "^6.24.1"
-    babel-runtime "^6.26.0"
-    babel-template "^6.26.0"
-    babel-types "^6.26.0"
-
-babel-plugin-transform-es2015-modules-systemjs@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23"
-  integrity sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=
-  dependencies:
-    babel-helper-hoist-variables "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-
-babel-plugin-transform-es2015-modules-umd@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468"
-  integrity sha1-rJl+YoXNGO1hdq22B9YCNErThGg=
-  dependencies:
-    babel-plugin-transform-es2015-modules-amd "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-
-babel-plugin-transform-es2015-object-super@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d"
-  integrity sha1-JM72muIcuDp/hgPa0CH1cusnj40=
-  dependencies:
-    babel-helper-replace-supers "^6.24.1"
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-parameters@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b"
-  integrity sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=
-  dependencies:
-    babel-helper-call-delegate "^6.24.1"
-    babel-helper-get-function-arity "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-plugin-transform-es2015-shorthand-properties@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0"
-  integrity sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-plugin-transform-es2015-spread@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1"
-  integrity sha1-1taKmfia7cRTbIGlQujdnxdG+NE=
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-sticky-regex@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc"
-  integrity sha1-AMHNsaynERLN8M9hJsLta0V8zbw=
-  dependencies:
-    babel-helper-regex "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-plugin-transform-es2015-template-literals@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d"
-  integrity sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-typeof-symbol@^6.22.0:
-  version "6.23.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372"
-  integrity sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-unicode-regex@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9"
-  integrity sha1-04sS9C6nMj9yk4fxinxa4frrNek=
-  dependencies:
-    babel-helper-regex "^6.24.1"
-    babel-runtime "^6.22.0"
-    regexpu-core "^2.0.0"
-
-babel-plugin-transform-exponentiation-operator@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e"
-  integrity sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=
-  dependencies:
-    babel-helper-builder-binary-assignment-operator-visitor "^6.24.1"
-    babel-plugin-syntax-exponentiation-operator "^6.8.0"
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-export-extensions@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-export-extensions/-/babel-plugin-transform-export-extensions-6.22.0.tgz#53738b47e75e8218589eea946cbbd39109bbe653"
-  integrity sha1-U3OLR+deghhYnuqUbLvTkQm75lM=
-  dependencies:
-    babel-plugin-syntax-export-extensions "^6.8.0"
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-flow-strip-types@^6.8.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz#84cb672935d43714fdc32bce84568d87441cf7cf"
-  integrity sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988=
-  dependencies:
-    babel-plugin-syntax-flow "^6.18.0"
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-object-rest-spread@^6.22.0:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz#0f36692d50fef6b7e2d4b3ac1478137a963b7b06"
-  integrity sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=
-  dependencies:
-    babel-plugin-syntax-object-rest-spread "^6.8.0"
-    babel-runtime "^6.26.0"
-
-babel-plugin-transform-regenerator@^6.24.1:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f"
-  integrity sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=
-  dependencies:
-    regenerator-transform "^0.10.0"
-
-babel-plugin-transform-strict-mode@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758"
-  integrity sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-preset-es2015@^6.9.0:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz#d44050d6bc2c9feea702aaf38d727a0210538939"
-  integrity sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk=
-  dependencies:
-    babel-plugin-check-es2015-constants "^6.22.0"
-    babel-plugin-transform-es2015-arrow-functions "^6.22.0"
-    babel-plugin-transform-es2015-block-scoped-functions "^6.22.0"
-    babel-plugin-transform-es2015-block-scoping "^6.24.1"
-    babel-plugin-transform-es2015-classes "^6.24.1"
-    babel-plugin-transform-es2015-computed-properties "^6.24.1"
-    babel-plugin-transform-es2015-destructuring "^6.22.0"
-    babel-plugin-transform-es2015-duplicate-keys "^6.24.1"
-    babel-plugin-transform-es2015-for-of "^6.22.0"
-    babel-plugin-transform-es2015-function-name "^6.24.1"
-    babel-plugin-transform-es2015-literals "^6.22.0"
-    babel-plugin-transform-es2015-modules-amd "^6.24.1"
-    babel-plugin-transform-es2015-modules-commonjs "^6.24.1"
-    babel-plugin-transform-es2015-modules-systemjs "^6.24.1"
-    babel-plugin-transform-es2015-modules-umd "^6.24.1"
-    babel-plugin-transform-es2015-object-super "^6.24.1"
-    babel-plugin-transform-es2015-parameters "^6.24.1"
-    babel-plugin-transform-es2015-shorthand-properties "^6.24.1"
-    babel-plugin-transform-es2015-spread "^6.22.0"
-    babel-plugin-transform-es2015-sticky-regex "^6.24.1"
-    babel-plugin-transform-es2015-template-literals "^6.22.0"
-    babel-plugin-transform-es2015-typeof-symbol "^6.22.0"
-    babel-plugin-transform-es2015-unicode-regex "^6.24.1"
-    babel-plugin-transform-regenerator "^6.24.1"
-
-babel-preset-stage-1@^6.5.0:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-preset-stage-1/-/babel-preset-stage-1-6.24.1.tgz#7692cd7dcd6849907e6ae4a0a85589cfb9e2bfb0"
-  integrity sha1-dpLNfc1oSZB+auSgqFWJz7niv7A=
-  dependencies:
-    babel-plugin-transform-class-constructor-call "^6.24.1"
-    babel-plugin-transform-export-extensions "^6.22.0"
-    babel-preset-stage-2 "^6.24.1"
-
-babel-preset-stage-2@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-preset-stage-2/-/babel-preset-stage-2-6.24.1.tgz#d9e2960fb3d71187f0e64eec62bc07767219bdc1"
-  integrity sha1-2eKWD7PXEYfw5k7sYrwHdnIZvcE=
-  dependencies:
-    babel-plugin-syntax-dynamic-import "^6.18.0"
-    babel-plugin-transform-class-properties "^6.24.1"
-    babel-plugin-transform-decorators "^6.24.1"
-    babel-preset-stage-3 "^6.24.1"
-
-babel-preset-stage-3@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-preset-stage-3/-/babel-preset-stage-3-6.24.1.tgz#836ada0a9e7a7fa37cb138fb9326f87934a48395"
-  integrity sha1-g2raCp56f6N8sTj7kyb4eTSkg5U=
-  dependencies:
-    babel-plugin-syntax-trailing-function-commas "^6.22.0"
-    babel-plugin-transform-async-generator-functions "^6.24.1"
-    babel-plugin-transform-async-to-generator "^6.24.1"
-    babel-plugin-transform-exponentiation-operator "^6.24.1"
-    babel-plugin-transform-object-rest-spread "^6.22.0"
-
-babel-register@^6.26.0, babel-register@^6.9.0:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071"
-  integrity sha1-btAhFz4vy0htestFxgCahW9kcHE=
-  dependencies:
-    babel-core "^6.26.0"
-    babel-runtime "^6.26.0"
-    core-js "^2.5.0"
-    home-or-tmp "^2.0.0"
-    lodash "^4.17.4"
-    mkdirp "^0.5.1"
-    source-map-support "^0.4.15"
-
-babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
-  integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4=
-  dependencies:
-    core-js "^2.4.0"
-    regenerator-runtime "^0.11.0"
-
-babel-template@^6.24.1, babel-template@^6.26.0:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02"
-  integrity sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=
-  dependencies:
-    babel-runtime "^6.26.0"
-    babel-traverse "^6.26.0"
-    babel-types "^6.26.0"
-    babylon "^6.18.0"
-    lodash "^4.17.4"
-
-babel-traverse@^6.24.1, babel-traverse@^6.26.0:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee"
-  integrity sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=
-  dependencies:
-    babel-code-frame "^6.26.0"
-    babel-messages "^6.23.0"
-    babel-runtime "^6.26.0"
-    babel-types "^6.26.0"
-    babylon "^6.18.0"
-    debug "^2.6.8"
-    globals "^9.18.0"
-    invariant "^2.2.2"
-    lodash "^4.17.4"
-
-babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497"
-  integrity sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=
-  dependencies:
-    babel-runtime "^6.26.0"
-    esutils "^2.0.2"
-    lodash "^4.17.4"
-    to-fast-properties "^1.0.3"
-
-babylon@^6.17.3, babylon@^6.18.0:
-  version "6.18.0"
-  resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
-  integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==
-
 balanced-match@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
@@ -1050,16 +515,16 @@ big.js@^3.1.3:
   resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
   integrity sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==
 
+big.js@^5.2.2:
+  version "5.2.2"
+  resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
+  integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
+
 binary-extensions@^1.0.0:
   version "1.11.0"
   resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205"
   integrity sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=
 
-binaryextensions@2:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-2.1.1.tgz#3209a51ca4a4ad541a3b8d3d6a6d5b83a2485935"
-  integrity sha512-XBaoWE9RW8pPdPQNibZsW2zh8TW6gcarXp1FZPwT8Uop8ScSNldJEWf2k9l3HeTqdrEwsOsFcq74RiJECW34yA==
-
 block-stream@*:
   version "0.0.9"
   resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"
@@ -1067,10 +532,10 @@ block-stream@*:
   dependencies:
     inherits "~2.0.0"
 
-bluebird@^3.5.1:
-  version "3.5.1"
-  resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
-  integrity sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==
+bluebird@^3.5.5:
+  version "3.7.1"
+  resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.1.tgz#df70e302b471d7473489acf26a93d63b53f874de"
+  integrity sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==
 
 bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
   version "4.11.8"
@@ -1133,6 +598,13 @@ braces@^2.3.0, braces@^2.3.1:
     split-string "^3.0.2"
     to-regex "^3.0.1"
 
+braces@^3.0.1:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
+  integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
+  dependencies:
+    fill-range "^7.0.1"
+
 brorand@^1.0.1:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
@@ -1206,6 +678,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-from@^1.0.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
+  integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
+
 buffer-xor@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
@@ -1220,33 +697,30 @@ buffer@^4.3.0:
     ieee754 "^1.1.4"
     isarray "^1.0.0"
 
-builtin-modules@^1.0.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
-  integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=
-
 builtin-status-codes@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
   integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=
 
-cacache@^10.0.1:
-  version "10.0.4"
-  resolved "https://registry.yarnpkg.com/cacache/-/cacache-10.0.4.tgz#6452367999eff9d4188aefd9a14e9d7c6a263460"
-  integrity sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==
+cacache@^12.0.2:
+  version "12.0.3"
+  resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.3.tgz#be99abba4e1bf5df461cd5a2c1071fc432573390"
+  integrity sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==
   dependencies:
-    bluebird "^3.5.1"
-    chownr "^1.0.1"
-    glob "^7.1.2"
-    graceful-fs "^4.1.11"
-    lru-cache "^4.1.1"
-    mississippi "^2.0.0"
+    bluebird "^3.5.5"
+    chownr "^1.1.1"
+    figgy-pudding "^3.5.1"
+    glob "^7.1.4"
+    graceful-fs "^4.1.15"
+    infer-owner "^1.0.3"
+    lru-cache "^5.1.1"
+    mississippi "^3.0.0"
     mkdirp "^0.5.1"
     move-concurrently "^1.0.1"
     promise-inflight "^1.0.1"
-    rimraf "^2.6.2"
-    ssri "^5.2.4"
-    unique-filename "^1.1.0"
+    rimraf "^2.6.3"
+    ssri "^6.0.1"
+    unique-filename "^1.1.1"
     y18n "^4.0.0"
 
 cache-base@^1.0.1:
@@ -1264,23 +738,10 @@ cache-base@^1.0.1:
     union-value "^1.0.0"
     unset-value "^1.0.0"
 
-cacheable-request@^2.1.1:
-  version "2.1.4"
-  resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-2.1.4.tgz#0d808801b6342ad33c91df9d0b44dc09b91e5c3d"
-  integrity sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0=
-  dependencies:
-    clone-response "1.0.2"
-    get-stream "3.0.0"
-    http-cache-semantics "3.8.1"
-    keyv "3.0.0"
-    lowercase-keys "1.0.0"
-    normalize-url "2.0.1"
-    responselike "1.0.2"
-
-camelcase@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
-  integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=
+camelcase@^5.0.0:
+  version "5.3.1"
+  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
+  integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
 
 caseless@~0.11.0:
   version "0.11.0"
@@ -1292,7 +753,16 @@ caseless@~0.12.0:
   resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
   integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
 
-chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
+chalk@2.4.2:
+  version "2.4.2"
+  resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
+  integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
+  dependencies:
+    ansi-styles "^3.2.1"
+    escape-string-regexp "^1.0.5"
+    supports-color "^5.3.0"
+
+chalk@^1.0.0, chalk@^1.1.1:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
   integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=
@@ -1303,7 +773,7 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
     strip-ansi "^3.0.0"
     supports-color "^2.0.0"
 
-chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1:
+chalk@^2.3.0:
   version "2.3.2"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.2.tgz#250dc96b07491bfd601e648d66ddf5f60c7a5c65"
   integrity sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==
@@ -1312,20 +782,6 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1:
     escape-string-regexp "^1.0.5"
     supports-color "^5.3.0"
 
-chalk@~0.4.0:
-  version "0.4.0"
-  resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.4.0.tgz#5199a3ddcd0c1efe23bc08c1b027b06176e0c64f"
-  integrity sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=
-  dependencies:
-    ansi-styles "~1.0.0"
-    has-color "~0.1.0"
-    strip-ansi "~0.1.0"
-
-chardet@^0.4.0:
-  version "0.4.2"
-  resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2"
-  integrity sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=
-
 charenc@~0.0.1:
   version "0.0.2"
   resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
@@ -1350,15 +806,17 @@ chokidar@^2.0.2:
   optionalDependencies:
     fsevents "^1.0.0"
 
-chownr@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181"
-  integrity sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=
+chownr@^1.1.1:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142"
+  integrity sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==
 
-chrome-trace-event@^0.1.1:
-  version "0.1.2"
-  resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-0.1.2.tgz#90f36885d5345a50621332f0717b595883d5d982"
-  integrity sha1-kPNohdU0WlBiEzLwcXtZWIPV2YI=
+chrome-trace-event@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4"
+  integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==
+  dependencies:
+    tslib "^1.9.0"
 
 cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
   version "1.0.4"
@@ -1378,66 +836,20 @@ class-utils@^0.3.5:
     isobject "^3.0.0"
     static-extend "^0.1.1"
 
-cli-cursor@^1.0.1, cli-cursor@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987"
-  integrity sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=
+cliui@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5"
+  integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==
   dependencies:
-    restore-cursor "^1.0.1"
-
-cli-cursor@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
-  integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=
-  dependencies:
-    restore-cursor "^2.0.0"
-
-cli-spinners@^0.1.2:
-  version "0.1.2"
-  resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-0.1.2.tgz#bb764d88e185fb9e1e6a2a1f19772318f605e31c"
-  integrity sha1-u3ZNiOGF+54eaiofGXcjGPYF4xw=
-
-cli-table@^0.3.1:
-  version "0.3.1"
-  resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23"
-  integrity sha1-9TsFJmqLGguTSz0IIebi3FkUriM=
-  dependencies:
-    colors "1.0.3"
-
-cli-truncate@^0.2.1:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574"
-  integrity sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=
-  dependencies:
-    slice-ansi "0.0.4"
-    string-width "^1.0.1"
-
-cli-width@^2.0.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
-  integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
-
-cliui@^3.2.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
-  integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=
-  dependencies:
-    string-width "^1.0.1"
-    strip-ansi "^3.0.1"
-    wrap-ansi "^2.0.0"
+    string-width "^3.1.0"
+    strip-ansi "^5.2.0"
+    wrap-ansi "^5.1.0"
 
 clone-buffer@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58"
   integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg=
 
-clone-response@1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
-  integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=
-  dependencies:
-    mimic-response "^1.0.0"
-
 clone-stats@^0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1"
@@ -1482,15 +894,6 @@ code-point-at@^1.0.0:
   resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
   integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
 
-codecov@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/codecov/-/codecov-3.0.0.tgz#c273b8c4f12945723e8dc9d25803d89343e5f28e"
-  integrity sha1-wnO4xPEpRXI+jcnSWAPYk0Pl8o4=
-  dependencies:
-    argv "0.0.2"
-    request "2.81.0"
-    urlgrey "0.4.4"
-
 collection-visit@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0"
@@ -1516,16 +919,6 @@ color-support@^1.1.3:
   resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2"
   integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==
 
-colors@1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
-  integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=
-
-colors@^1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
-  integrity sha1-FopHAXVran9RoSzgyXv6KMCE7WM=
-
 combined-stream@^1.0.5, combined-stream@~1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009"
@@ -1538,7 +931,12 @@ commander@2.11.0:
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563"
   integrity sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==
 
-commander@^2.9.0, commander@~2.14.1:
+commander@^2.20.0:
+  version "2.20.3"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
+  integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
+
+commander@^2.9.0:
   version "2.14.1"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa"
   integrity sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==
@@ -1558,7 +956,7 @@ concat-map@0.0.1:
   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
   integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
 
-concat-stream@^1.4.7, concat-stream@^1.5.0:
+concat-stream@^1.5.0:
   version "1.6.1"
   resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.1.tgz#261b8f518301f1d834e36342b9fea095d2620a26"
   integrity sha512-gslSSJx03QKa59cIKqeJO9HQ/WZMotvYJCuaUULrLpjj8oG40kV2Z+gz82pVxlTkOADi4PJxQPPfhl1ELYrrXw==
@@ -1584,7 +982,7 @@ constants-browserify@^1.0.0:
   resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
   integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=
 
-convert-source-map@^1.1.1, convert-source-map@^1.5.0:
+convert-source-map@^1.1.1:
   version "1.5.1"
   resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5"
   integrity sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=
@@ -1606,11 +1004,6 @@ copy-descriptor@^0.1.0:
   resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
   integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
 
-core-js@^2.4.0, core-js@^2.4.1, core-js@^2.5.0:
-  version "2.5.3"
-  resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e"
-  integrity sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=
-
 core-util-is@1.0.2, 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"
@@ -1646,16 +1039,7 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
     safe-buffer "^5.0.1"
     sha.js "^2.4.8"
 
-cross-spawn@^5.0.1:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
-  integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=
-  dependencies:
-    lru-cache "^4.0.1"
-    shebang-command "^1.2.0"
-    which "^1.2.9"
-
-cross-spawn@^6.0.4:
+cross-spawn@6.0.5, cross-spawn@^6.0.0:
   version "6.0.5"
   resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
   integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
@@ -1707,11 +1091,6 @@ cyclist@~0.2.2:
   resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
   integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=
 
-dargs@^5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/dargs/-/dargs-5.1.0.tgz#ec7ea50c78564cd36c9d5ec18f66329fade27829"
-  integrity sha1-7H6lDHhWTNNsnV7Bj2Yyn63ieCk=
-
 dashdash@^1.12.0:
   version "1.14.1"
   resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
@@ -1719,11 +1098,6 @@ dashdash@^1.12.0:
   dependencies:
     assert-plus "^1.0.0"
 
-date-fns@^1.27.2:
-  version "1.29.0"
-  resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6"
-  integrity sha512-lbTXWZ6M20cWH8N9S6afb0SBm6tMk+uUg6z3MqHPKE9atmsY3kJkTm8vKe93izJ2B2+q5MV990sM2CHgtAZaOw==
-
 date-now@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
@@ -1741,14 +1115,14 @@ debug@3.1.0, debug@^3.1.0:
   dependencies:
     ms "2.0.0"
 
-debug@^2.0.0, debug@^2.1.0, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8:
+debug@^2.2.0, debug@^2.3.3:
   version "2.6.9"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
   integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
   dependencies:
     ms "2.0.0"
 
-decamelize@^1.1.1:
+decamelize@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
   integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
@@ -1758,13 +1132,6 @@ decode-uri-component@^0.2.0:
   resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
   integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
 
-decompress-response@^3.2.0, decompress-response@^3.3.0:
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3"
-  integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=
-  dependencies:
-    mimic-response "^1.0.0"
-
 deep-assign@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/deep-assign/-/deep-assign-1.0.0.tgz#b092743be8427dc621ea0067cdec7e70dd19f37b"
@@ -1772,7 +1139,7 @@ deep-assign@^1.0.0:
   dependencies:
     is-obj "^1.0.0"
 
-deep-extend@^0.4.0, deep-extend@~0.4.0:
+deep-extend@~0.4.0:
   version "0.4.2"
   resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f"
   integrity sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=
@@ -1817,17 +1184,10 @@ des.js@^1.0.0:
     inherits "^2.0.1"
     minimalistic-assert "^1.0.0"
 
-detect-conflict@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/detect-conflict/-/detect-conflict-1.0.1.tgz#088657a66a961c05019db7c4230883b1c6b4176e"
-  integrity sha1-CIZXpmqWHAUBnbfEIwiDsca0F24=
-
-detect-indent@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208"
-  integrity sha1-920GQ1LN9Docts5hnE7jqUdd4gg=
-  dependencies:
-    repeating "^2.0.0"
+detect-file@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7"
+  integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=
 
 detect-libc@^1.0.2:
   version "1.0.3"
@@ -1851,16 +1211,6 @@ diff@3.3.1:
   resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75"
   integrity sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==
 
-diff@^2.1.2:
-  version "2.2.3"
-  resolved "https://registry.yarnpkg.com/diff/-/diff-2.2.3.tgz#60eafd0d28ee906e4e8ff0a52c1229521033bf99"
-  integrity sha1-YOr9DSjukG5Oj/ClLBIpUhAzv5k=
-
-diff@^3.3.0, diff@^3.3.1:
-  version "3.5.0"
-  resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
-  integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
-
 diffie-hellman@^5.0.0:
   version "5.0.2"
   resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e"
@@ -1870,11 +1220,6 @@ diffie-hellman@^5.0.0:
     miller-rabin "^4.0.0"
     randombytes "^2.0.0"
 
-dom-walk@^0.1.0:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018"
-  integrity sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=
-
 domain-browser@^1.1.1:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
@@ -1887,11 +1232,6 @@ duplexer2@0.0.2:
   dependencies:
     readable-stream "~1.1.9"
 
-duplexer3@^0.1.4:
-  version "0.1.4"
-  resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
-  integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
-
 duplexer@~0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1"
@@ -1924,21 +1264,6 @@ ecc-jsbn@~0.1.1:
   dependencies:
     jsbn "~0.1.0"
 
-editions@^1.3.3:
-  version "1.3.4"
-  resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.4.tgz#3662cb592347c3168eb8e498a0ff73271d67f50b"
-  integrity sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg==
-
-ejs@^2.3.1:
-  version "2.5.7"
-  resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a"
-  integrity sha1-zIcsFoiArjxxiXYv1f/ACJbJUYo=
-
-elegant-spinner@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e"
-  integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=
-
 elliptic@^6.0.0:
   version "6.4.0"
   resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df"
@@ -1952,6 +1277,11 @@ elliptic@^6.0.0:
     minimalistic-assert "^1.0.0"
     minimalistic-crypto-utils "^1.0.0"
 
+emoji-regex@^7.0.1:
+  version "7.0.3"
+  resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
+  integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==
+
 emojis-list@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
@@ -1964,6 +1294,15 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0:
   dependencies:
     once "^1.4.0"
 
+enhanced-resolve@4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f"
+  integrity sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==
+  dependencies:
+    graceful-fs "^4.1.2"
+    memory-fs "^0.4.0"
+    tapable "^1.0.0"
+
 enhanced-resolve@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.0.0.tgz#e34a6eaa790f62fccd71d93959f56b2b432db10a"
@@ -1973,6 +1312,15 @@ enhanced-resolve@^4.0.0:
     memory-fs "^0.4.0"
     tapable "^1.0.0"
 
+enhanced-resolve@^4.1.0:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz#2937e2b8066cd0fe7ce0990a98f0d71a35189f66"
+  integrity sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==
+  dependencies:
+    graceful-fs "^4.1.2"
+    memory-fs "^0.5.0"
+    tapable "^1.0.0"
+
 entities@~2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4"
@@ -1985,39 +1333,19 @@ errno@^0.1.3, errno@~0.1.7:
   dependencies:
     prr "~1.0.1"
 
-error-ex@^1.2.0:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc"
-  integrity sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=
-  dependencies:
-    is-arrayish "^0.2.1"
-
-error@^7.0.2:
-  version "7.0.2"
-  resolved "https://registry.yarnpkg.com/error/-/error-7.0.2.tgz#a5f75fff4d9926126ddac0ea5dc38e689153cb02"
-  integrity sha1-pfdf/02ZJhJt2sDqXcOOaJFTywI=
-  dependencies:
-    string-template "~0.2.1"
-    xtend "~4.0.0"
-
 escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
   integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
 
-eslint-scope@^3.7.1:
-  version "3.7.1"
-  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8"
-  integrity sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=
+eslint-scope@^4.0.3:
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848"
+  integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==
   dependencies:
     esrecurse "^4.1.0"
     estraverse "^4.1.1"
 
-esprima@~4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804"
-  integrity sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==
-
 esrecurse@^4.1.0:
   version "4.2.1"
   resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf"
@@ -2030,11 +1358,6 @@ estraverse@^4.1.0, estraverse@^4.1.1:
   resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
   integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=
 
-esutils@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
-  integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=
-
 event-stream@^3.3.1, event-stream@~3.3.4:
   version "3.3.4"
   resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571"
@@ -2048,10 +1371,10 @@ event-stream@^3.3.1, event-stream@~3.3.4:
     stream-combiner "~0.0.4"
     through "~2.3.1"
 
-events@^1.0.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
-  integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=
+events@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88"
+  integrity sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==
 
 evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
   version "1.0.3"
@@ -2061,24 +1384,19 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
     md5.js "^1.3.4"
     safe-buffer "^5.1.1"
 
-execa@^0.7.0:
-  version "0.7.0"
-  resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
-  integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=
+execa@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8"
+  integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==
   dependencies:
-    cross-spawn "^5.0.1"
-    get-stream "^3.0.0"
+    cross-spawn "^6.0.0"
+    get-stream "^4.0.0"
     is-stream "^1.1.0"
     npm-run-path "^2.0.0"
     p-finally "^1.0.0"
     signal-exit "^3.0.0"
     strip-eof "^1.0.0"
 
-exit-hook@^1.0.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8"
-  integrity sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=
-
 expand-brackets@^0.1.4:
   version "0.1.5"
   resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b"
@@ -2140,24 +1458,6 @@ extend@^3.0.0, extend@~3.0.0, extend@~3.0.1:
   resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444"
   integrity sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=
 
-external-editor@^1.1.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-1.1.1.tgz#12d7b0db850f7ff7e7081baf4005700060c4600b"
-  integrity sha1-Etew24UPf/fnCBuvQAVwAGDEYAs=
-  dependencies:
-    extend "^3.0.0"
-    spawn-sync "^1.0.15"
-    tmp "^0.0.29"
-
-external-editor@^2.0.4, external-editor@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.1.0.tgz#3d026a21b7f95b5726387d4200ac160d372c3b48"
-  integrity sha512-E44iT5QVOUJBKij4IIV3uvxuNlbKS38Tw1HiupxEIHPv9qtC2PrDYohbXV5U+1jnfIXttny8gUhj+oZvflFlzA==
-  dependencies:
-    chardet "^0.4.0"
-    iconv-lite "^0.4.17"
-    tmp "^0.0.33"
-
 extglob@^0.3.1:
   version "0.3.2"
   resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1"
@@ -2203,6 +1503,11 @@ fast-deep-equal@^1.0.0:
   resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
   integrity sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=
 
+fast-deep-equal@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
+  integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=
+
 fast-json-stable-stringify@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
@@ -2215,20 +1520,10 @@ fd-slicer@~1.0.1:
   dependencies:
     pend "~1.2.0"
 
-figures@^1.3.5, figures@^1.7.0:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
-  integrity sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=
-  dependencies:
-    escape-string-regexp "^1.0.5"
-    object-assign "^4.1.0"
-
-figures@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
-  integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=
-  dependencies:
-    escape-string-regexp "^1.0.5"
+figgy-pudding@^3.5.1:
+  version "3.5.1"
+  resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790"
+  integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==
 
 filename-regex@^2.0.0:
   version "2.0.1"
@@ -2256,39 +1551,44 @@ fill-range@^4.0.0:
     repeat-string "^1.6.1"
     to-regex-range "^2.1.0"
 
-find-cache-dir@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f"
-  integrity sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=
+fill-range@^7.0.1:
+  version "7.0.1"
+  resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
+  integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
+  dependencies:
+    to-regex-range "^5.0.1"
+
+find-cache-dir@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7"
+  integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==
   dependencies:
     commondir "^1.0.1"
-    make-dir "^1.0.0"
-    pkg-dir "^2.0.0"
+    make-dir "^2.0.0"
+    pkg-dir "^3.0.0"
 
-find-up@^2.0.0, find-up@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
-  integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c=
+find-up@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
+  integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==
   dependencies:
-    locate-path "^2.0.0"
+    locate-path "^3.0.0"
+
+findup-sync@3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1"
+  integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==
+  dependencies:
+    detect-file "^1.0.0"
+    is-glob "^4.0.0"
+    micromatch "^3.0.4"
+    resolve-dir "^1.0.1"
 
 first-chunk-stream@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz#59bfb50cd905f60d7c394cd3d9acaab4e6ad934e"
   integrity sha1-Wb+1DNkF9g18OUzT2ayqtOatk04=
 
-first-chunk-stream@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz#1bdecdb8e083c0664b91945581577a43a9f31d70"
-  integrity sha1-G97NuOCDwGZLkZRVgVd6Q6nzHXA=
-  dependencies:
-    readable-stream "^2.0.2"
-
-flow-parser@^0.*:
-  version "0.66.0"
-  resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.66.0.tgz#be583fefb01192aa5164415d31a6241b35718983"
-  integrity sha1-vlg/77ARkqpRZEFdMaYkGzVxiYM=
-
 flush-write-stream@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.2.tgz#c81b90d8746766f1a609a46809946c45dd8ae417"
@@ -2339,7 +1639,7 @@ fragment-cache@^0.2.1:
   dependencies:
     map-cache "^0.2.2"
 
-from2@^2.1.0, from2@^2.1.1:
+from2@^2.1.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af"
   integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=
@@ -2420,15 +1720,17 @@ generate-object-property@^1.1.0:
   dependencies:
     is-property "^1.0.0"
 
-get-caller-file@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
-  integrity sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=
+get-caller-file@^2.0.1:
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
+  integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
 
-get-stream@3.0.0, get-stream@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
-  integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=
+get-stream@^4.0.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
+  integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==
+  dependencies:
+    pump "^3.0.0"
 
 get-value@^2.0.3, get-value@^2.0.6:
   version "2.0.6"
@@ -2442,29 +1744,6 @@ getpass@^0.1.1:
   dependencies:
     assert-plus "^1.0.0"
 
-gh-got@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/gh-got/-/gh-got-6.0.0.tgz#d74353004c6ec466647520a10bd46f7299d268d0"
-  integrity sha512-F/mS+fsWQMo1zfgG9MD8KWvTWPPzzhuVwY++fhQ5Ggd+0P+CAMHtzMZhNxG+TqGfHDChJKsbh6otfMGqO2AKBw==
-  dependencies:
-    got "^7.0.0"
-    is-plain-obj "^1.1.0"
-
-github-username@^4.0.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/github-username/-/github-username-4.1.0.tgz#cbe280041883206da4212ae9e4b5f169c30bf417"
-  integrity sha1-y+KABBiDIG2kISrp5LXxacML9Bc=
-  dependencies:
-    gh-got "^6.0.0"
-
-glob-all@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/glob-all/-/glob-all-3.1.0.tgz#8913ddfb5ee1ac7812656241b03d5217c64b02ab"
-  integrity sha1-iRPd+17hrHgSZWJBsD1SF8ZLAqs=
-  dependencies:
-    glob "^7.0.5"
-    yargs "~1.2.6"
-
 glob-base@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
@@ -2502,7 +1781,7 @@ glob-stream@^5.3.2:
     to-absolute-glob "^0.1.1"
     unique-stream "^2.0.2"
 
-glob@7.1.2, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.2:
+glob@7.1.2, glob@^7.0.5, glob@^7.1.2:
   version "7.1.2"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
   integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==
@@ -2525,17 +1804,25 @@ glob@^5.0.3:
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
-glob@^6.0.1:
-  version "6.0.4"
-  resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22"
-  integrity sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=
+glob@^7.1.3, glob@^7.1.4:
+  version "7.1.5"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.5.tgz#6714c69bee20f3c3e64c4dd905553e532b40cdc0"
+  integrity sha512-J9dlskqUXK1OeTOYBEn5s8aMukWMwWfs+rPTn/jn50Ux4MNXVhubL1wu/j2t+H4NVI+cXEcCaYellqaPVGXNqQ==
   dependencies:
+    fs.realpath "^1.0.0"
     inflight "^1.0.4"
     inherits "2"
-    minimatch "2 || 3"
+    minimatch "^3.0.4"
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
+global-modules@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780"
+  integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==
+  dependencies:
+    global-prefix "^3.0.0"
+
 global-modules@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea"
@@ -2556,41 +1843,14 @@ global-prefix@^1.0.1:
     is-windows "^1.0.1"
     which "^1.2.14"
 
-global@^4.3.2:
-  version "4.3.2"
-  resolved "https://registry.yarnpkg.com/global/-/global-4.3.2.tgz#e76989268a6c74c38908b1305b10fc0e394e9d0f"
-  integrity sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=
+global-prefix@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97"
+  integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==
   dependencies:
-    min-document "^2.19.0"
-    process "~0.5.1"
-
-globals@^9.18.0:
-  version "9.18.0"
-  resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
-  integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==
-
-globby@^4.0.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/globby/-/globby-4.1.0.tgz#080f54549ec1b82a6c60e631fc82e1211dbe95f8"
-  integrity sha1-CA9UVJ7BuCpsYOYx/ILhIR2+lfg=
-  dependencies:
-    array-union "^1.0.1"
-    arrify "^1.0.0"
-    glob "^6.0.1"
-    object-assign "^4.0.1"
-    pify "^2.0.0"
-    pinkie-promise "^2.0.0"
-
-globby@^6.1.0:
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c"
-  integrity sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=
-  dependencies:
-    array-union "^1.0.1"
-    glob "^7.0.3"
-    object-assign "^4.0.1"
-    pify "^2.0.0"
-    pinkie-promise "^2.0.0"
+    ini "^1.3.5"
+    kind-of "^6.0.2"
+    which "^1.3.1"
 
 glogg@^1.0.0:
   version "1.0.1"
@@ -2599,60 +1859,15 @@ glogg@^1.0.0:
   dependencies:
     sparkles "^1.0.0"
 
-got@^7.0.0:
-  version "7.1.0"
-  resolved "https://registry.yarnpkg.com/got/-/got-7.1.0.tgz#05450fd84094e6bbea56f451a43a9c289166385a"
-  integrity sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==
-  dependencies:
-    decompress-response "^3.2.0"
-    duplexer3 "^0.1.4"
-    get-stream "^3.0.0"
-    is-plain-obj "^1.1.0"
-    is-retry-allowed "^1.0.0"
-    is-stream "^1.0.0"
-    isurl "^1.0.0-alpha5"
-    lowercase-keys "^1.0.0"
-    p-cancelable "^0.3.0"
-    p-timeout "^1.1.1"
-    safe-buffer "^5.0.1"
-    timed-out "^4.0.0"
-    url-parse-lax "^1.0.0"
-    url-to-options "^1.0.1"
-
-got@^8.2.0:
-  version "8.2.0"
-  resolved "https://registry.yarnpkg.com/got/-/got-8.2.0.tgz#0d11a071d05046348a2f5c0a5fa047fb687fdfc6"
-  integrity sha512-giadqJpXIwjY+ZsuWys8p2yjZGhOHiU4hiJHjS/oeCxw1u8vANQz3zPlrxW2Zw/siCXsSMI3hvzWGcnFyujyAg==
-  dependencies:
-    "@sindresorhus/is" "^0.7.0"
-    cacheable-request "^2.1.1"
-    decompress-response "^3.3.0"
-    duplexer3 "^0.1.4"
-    get-stream "^3.0.0"
-    into-stream "^3.1.0"
-    is-retry-allowed "^1.1.0"
-    isurl "^1.0.0-alpha5"
-    lowercase-keys "^1.0.0"
-    mimic-response "^1.0.0"
-    p-cancelable "^0.3.0"
-    p-timeout "^2.0.1"
-    pify "^3.0.0"
-    safe-buffer "^5.1.1"
-    timed-out "^4.0.1"
-    url-parse-lax "^3.0.0"
-    url-to-options "^1.0.1"
-
-graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2:
+graceful-fs@^4.0.0, graceful-fs@^4.1.2:
   version "4.1.11"
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
   integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=
 
-grouped-queue@^0.3.0, grouped-queue@^0.3.3:
-  version "0.3.3"
-  resolved "https://registry.yarnpkg.com/grouped-queue/-/grouped-queue-0.3.3.tgz#c167d2a5319c5a0e0964ef6a25b7c2df8996c85c"
-  integrity sha1-wWfSpTGcWg4JZO9qJbfC34mWyFw=
-  dependencies:
-    lodash "^4.17.2"
+graceful-fs@^4.1.15:
+  version "4.2.3"
+  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"
+  integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==
 
 growl@1.10.3:
   version "1.10.3"
@@ -2815,11 +2030,6 @@ has-ansi@^2.0.0:
   dependencies:
     ansi-regex "^2.0.0"
 
-has-color@~0.1.0:
-  version "0.1.7"
-  resolved "https://registry.yarnpkg.com/has-color/-/has-color-0.1.7.tgz#67144a5260c34fc3cca677d041daf52fe7b78b2f"
-  integrity sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=
-
 has-flag@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51"
@@ -2837,18 +2047,6 @@ has-gulplog@^0.1.0:
   dependencies:
     sparkles "^1.0.0"
 
-has-symbol-support-x@^1.4.1:
-  version "1.4.2"
-  resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455"
-  integrity sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==
-
-has-to-string-tag-x@^1.2.0:
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d"
-  integrity sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==
-  dependencies:
-    has-symbol-support-x "^1.4.1"
-
 has-unicode@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
@@ -2957,14 +2155,6 @@ hoek@4.x.x:
   resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d"
   integrity sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==
 
-home-or-tmp@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
-  integrity sha1-42w/LSyufXRqhX440Y1fMqeILbg=
-  dependencies:
-    os-homedir "^1.0.0"
-    os-tmpdir "^1.0.1"
-
 homedir-polyfill@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc"
@@ -2972,16 +2162,6 @@ homedir-polyfill@^1.0.1:
   dependencies:
     parse-passwd "^1.0.0"
 
-hosted-git-info@^2.1.4:
-  version "2.5.0"
-  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c"
-  integrity sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==
-
-http-cache-semantics@3.8.1:
-  version "3.8.1"
-  resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2"
-  integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==
-
 http-signature@~1.1.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf"
@@ -3005,11 +2185,6 @@ https-browserify@^1.0.0:
   resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
   integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
 
-iconv-lite@^0.4.17:
-  version "0.4.19"
-  resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
-  integrity sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==
-
 ieee754@^1.1.4:
   version "1.1.8"
   resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
@@ -3020,27 +2195,23 @@ iferr@^0.1.5:
   resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501"
   integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE=
 
+import-local@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d"
+  integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==
+  dependencies:
+    pkg-dir "^3.0.0"
+    resolve-cwd "^2.0.0"
+
 imurmurhash@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
   integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
 
-indent-string@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80"
-  integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=
-  dependencies:
-    repeating "^2.0.0"
-
-indent-string@^3.0.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289"
-  integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=
-
-indexof@0.0.1:
-  version "0.0.1"
-  resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
-  integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=
+infer-owner@^1.0.3:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467"
+  integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==
 
 inflight@^1.0.4:
   version "1.0.6"
@@ -3050,7 +2221,7 @@ inflight@^1.0.4:
     once "^1.3.0"
     wrappy "1"
 
-inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
+inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
   integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
@@ -3060,94 +2231,20 @@ inherits@2.0.1:
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
   integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=
 
-ini@^1.3.4, ini@~1.3.0:
+ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
   version "1.3.5"
   resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
   integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
 
-inquirer@^1.0.2:
-  version "1.2.3"
-  resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-1.2.3.tgz#4dec6f32f37ef7bb0b2ed3f1d1a5c3f545074918"
-  integrity sha1-TexvMvN+97sLLtPx0aXD9UUHSRg=
-  dependencies:
-    ansi-escapes "^1.1.0"
-    chalk "^1.0.0"
-    cli-cursor "^1.0.1"
-    cli-width "^2.0.0"
-    external-editor "^1.1.0"
-    figures "^1.3.5"
-    lodash "^4.3.0"
-    mute-stream "0.0.6"
-    pinkie-promise "^2.0.0"
-    run-async "^2.2.0"
-    rx "^4.1.0"
-    string-width "^1.0.1"
-    strip-ansi "^3.0.0"
-    through "^2.3.6"
+interpret@1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296"
+  integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==
 
-inquirer@^3.3.0:
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9"
-  integrity sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==
-  dependencies:
-    ansi-escapes "^3.0.0"
-    chalk "^2.0.0"
-    cli-cursor "^2.1.0"
-    cli-width "^2.0.0"
-    external-editor "^2.0.4"
-    figures "^2.0.0"
-    lodash "^4.3.0"
-    mute-stream "0.0.7"
-    run-async "^2.2.0"
-    rx-lite "^4.0.8"
-    rx-lite-aggregates "^4.0.8"
-    string-width "^2.1.0"
-    strip-ansi "^4.0.0"
-    through "^2.3.6"
-
-inquirer@^5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-5.1.0.tgz#19da508931892328abbbdd4c477f1efc65abfd67"
-  integrity sha512-kn7N70US1MSZHZHSGJLiZ7iCwwncc7b0gc68YtlX29OjI3Mp0tSVV+snVXpZ1G+ONS3Ac9zd1m6hve2ibLDYfA==
-  dependencies:
-    ansi-escapes "^3.0.0"
-    chalk "^2.0.0"
-    cli-cursor "^2.1.0"
-    cli-width "^2.0.0"
-    external-editor "^2.1.0"
-    figures "^2.0.0"
-    lodash "^4.3.0"
-    mute-stream "0.0.7"
-    run-async "^2.2.0"
-    rxjs "^5.5.2"
-    string-width "^2.1.0"
-    strip-ansi "^4.0.0"
-    through "^2.3.6"
-
-interpret@^1.0.0, interpret@^1.0.4:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
-  integrity sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=
-
-into-stream@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-3.1.0.tgz#96fb0a936c12babd6ff1752a17d05616abd094c6"
-  integrity sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY=
-  dependencies:
-    from2 "^2.1.1"
-    p-is-promise "^1.1.0"
-
-invariant@^2.2.2:
-  version "2.2.3"
-  resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.3.tgz#1a827dfde7dcbd7c323f0ca826be8fa7c5e9d688"
-  integrity sha512-7Z5PPegwDTyjbaeCnV0efcyS6vdKAU51kpEmS7QFib3P4822l8ICYyMn7qvJnc+WzLoDsuI9gPMKbJ8pCu8XtA==
-  dependencies:
-    loose-envify "^1.0.0"
-
-invert-kv@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
-  integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY=
+invert-kv@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02"
+  integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==
 
 is-accessor-descriptor@^0.1.6:
   version "0.1.6"
@@ -3163,11 +2260,6 @@ is-accessor-descriptor@^1.0.0:
   dependencies:
     kind-of "^6.0.0"
 
-is-arrayish@^0.2.1:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
-  integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=
-
 is-binary-path@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898"
@@ -3180,13 +2272,6 @@ is-buffer@^1.1.5, is-buffer@~1.1.1:
   resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
   integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
 
-is-builtin-module@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe"
-  integrity sha1-VAVy0096wxGfj3bDDLwbHgN6/74=
-  dependencies:
-    builtin-modules "^1.0.0"
-
 is-data-descriptor@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
@@ -3253,13 +2338,6 @@ is-extglob@^2.1.0, is-extglob@^2.1.1:
   resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
   integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
 
-is-finite@^1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa"
-  integrity sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=
-  dependencies:
-    number-is-nan "^1.0.0"
-
 is-fullwidth-code-point@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
@@ -3322,23 +2400,16 @@ is-number@^4.0.0:
   resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff"
   integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==
 
+is-number@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
+  integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
+
 is-obj@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
   integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8=
 
-is-object@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470"
-  integrity sha1-iVJojF7C/9awPsyF52ngKQMINHA=
-
-is-observable@^0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/is-observable/-/is-observable-0.2.0.tgz#b361311d83c6e5d726cabf5e250b0237106f5ae2"
-  integrity sha1-s2ExHYPG5dcmyr9eJQsCNxBvWuI=
-  dependencies:
-    symbol-observable "^0.2.2"
-
 is-odd@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-2.0.0.tgz#7646624671fd7ea558ccd9a2795182f2958f1b24"
@@ -3346,11 +2417,6 @@ is-odd@^2.0.0:
   dependencies:
     is-number "^4.0.0"
 
-is-plain-obj@^1.0.0, is-plain-obj@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
-  integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4=
-
 is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
@@ -3368,29 +2434,12 @@ is-primitive@^2.0.0:
   resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575"
   integrity sha1-IHurkWOEmcB7Kt8kCkGochADRXU=
 
-is-promise@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
-  integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=
-
 is-property@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
   integrity sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=
 
-is-retry-allowed@^1.0.0, is-retry-allowed@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34"
-  integrity sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=
-
-is-scoped@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-scoped/-/is-scoped-1.0.0.tgz#449ca98299e713038256289ecb2b540dc437cb30"
-  integrity sha1-RJypgpnnEwOCViieyytUDcQ3yzA=
-  dependencies:
-    scoped-regex "^1.0.0"
-
-is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0:
+is-stream@^1.0.1, is-stream@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
   integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
@@ -3415,6 +2464,11 @@ is-windows@^1.0.1, is-windows@^1.0.2:
   resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
   integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
 
+is-wsl@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
+  integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=
+
 is@^3.1.0:
   version "3.2.1"
   resolved "https://registry.yarnpkg.com/is/-/is-3.2.1.tgz#d0ac2ad55eb7b0bec926a5266f6c662aaa83dca5"
@@ -3452,74 +2506,26 @@ isstream@~0.1.2:
   resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
   integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
 
-istextorbinary@^2.1.0:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/istextorbinary/-/istextorbinary-2.2.1.tgz#a5231a08ef6dd22b268d0895084cf8d58b5bec53"
-  integrity sha512-TS+hoFl8Z5FAFMK38nhBkdLt44CclNRgDHWeMgsV8ko3nDlr/9UI2Sf839sW7enijf8oKsZYXRvM8g0it9Zmcw==
-  dependencies:
-    binaryextensions "2"
-    editions "^1.3.3"
-    textextensions "2"
-
-isurl@^1.0.0-alpha5:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67"
-  integrity sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==
-  dependencies:
-    has-to-string-tag-x "^1.2.0"
-    is-object "^1.0.1"
-
-js-tokens@^3.0.0, js-tokens@^3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
-  integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls=
-
 jsbn@~0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
   integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
 
-jscodeshift@^0.4.0, jscodeshift@^0.4.1:
-  version "0.4.1"
-  resolved "https://registry.yarnpkg.com/jscodeshift/-/jscodeshift-0.4.1.tgz#da91a1c2eccfa03a3387a21d39948e251ced444a"
-  integrity sha512-iOX6If+hsw0q99V3n31t4f5VlD1TQZddH08xbT65ZqA7T4Vkx68emrDZMUOLVvCEAJ6NpAk7DECe3fjC/t52AQ==
-  dependencies:
-    async "^1.5.0"
-    babel-plugin-transform-flow-strip-types "^6.8.0"
-    babel-preset-es2015 "^6.9.0"
-    babel-preset-stage-1 "^6.5.0"
-    babel-register "^6.9.0"
-    babylon "^6.17.3"
-    colors "^1.1.2"
-    flow-parser "^0.*"
-    lodash "^4.13.1"
-    micromatch "^2.3.7"
-    node-dir "0.1.8"
-    nomnom "^1.8.1"
-    recast "^0.12.5"
-    temp "^0.8.1"
-    write-file-atomic "^1.2.0"
-
-jsesc@^1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b"
-  integrity sha1-RsP+yMGJKxKwgz25vHYiF226s0s=
-
-jsesc@~0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
-  integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=
-
-json-buffer@3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
-  integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=
+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"
+  integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
 
 json-schema-traverse@^0.3.0:
   version "0.3.1"
   resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340"
   integrity sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=
 
+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"
@@ -3537,11 +2543,18 @@ json-stringify-safe@~5.0.1:
   resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
   integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
 
-json5@^0.5.0, json5@^0.5.1:
+json5@^0.5.0:
   version "0.5.1"
   resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
   integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=
 
+json5@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
+  integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==
+  dependencies:
+    minimist "^1.2.0"
+
 jsonify@~0.0.0:
   version "0.0.0"
   resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
@@ -3562,13 +2575,6 @@ jsprim@^1.2.2:
     json-schema "0.2.3"
     verror "1.10.0"
 
-keyv@3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.0.0.tgz#44923ba39e68b12a7cec7df6c3268c031f2ef373"
-  integrity sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==
-  dependencies:
-    json-buffer "3.0.0"
-
 kind-of@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-1.1.0.tgz#140a3d2d41a36d2efcfa9377b62c24f8495a5c44"
@@ -3612,12 +2618,12 @@ lazystream@^1.0.0:
   dependencies:
     readable-stream "^2.0.5"
 
-lcid@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
-  integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=
+lcid@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf"
+  integrity sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==
   dependencies:
-    invert-kv "^1.0.0"
+    invert-kv "^2.0.0"
 
 linkify-it@^2.0.0:
   version "2.0.3"
@@ -3626,74 +2632,21 @@ linkify-it@^2.0.0:
   dependencies:
     uc.micro "^1.0.1"
 
-listr-silent-renderer@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e"
-  integrity sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=
+loader-runner@^2.4.0:
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357"
+  integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==
 
-listr-update-renderer@^0.4.0:
-  version "0.4.0"
-  resolved "https://registry.yarnpkg.com/listr-update-renderer/-/listr-update-renderer-0.4.0.tgz#344d980da2ca2e8b145ba305908f32ae3f4cc8a7"
-  integrity sha1-NE2YDaLKLosUW6MFkI8yrj9MyKc=
+loader-utils@1.2.3, loader-utils@^1.2.3:
+  version "1.2.3"
+  resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
+  integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==
   dependencies:
-    chalk "^1.1.3"
-    cli-truncate "^0.2.1"
-    elegant-spinner "^1.0.1"
-    figures "^1.7.0"
-    indent-string "^3.0.0"
-    log-symbols "^1.0.2"
-    log-update "^1.0.2"
-    strip-ansi "^3.0.1"
+    big.js "^5.2.2"
+    emojis-list "^2.0.0"
+    json5 "^1.0.1"
 
-listr-verbose-renderer@^0.4.0:
-  version "0.4.1"
-  resolved "https://registry.yarnpkg.com/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#8206f4cf6d52ddc5827e5fd14989e0e965933a35"
-  integrity sha1-ggb0z21S3cWCfl/RSYng6WWTOjU=
-  dependencies:
-    chalk "^1.1.3"
-    cli-cursor "^1.0.2"
-    date-fns "^1.27.2"
-    figures "^1.7.0"
-
-listr@^0.13.0:
-  version "0.13.0"
-  resolved "https://registry.yarnpkg.com/listr/-/listr-0.13.0.tgz#20bb0ba30bae660ee84cc0503df4be3d5623887d"
-  integrity sha1-ILsLowuuZg7oTMBQPfS+PVYjiH0=
-  dependencies:
-    chalk "^1.1.3"
-    cli-truncate "^0.2.1"
-    figures "^1.7.0"
-    indent-string "^2.1.0"
-    is-observable "^0.2.0"
-    is-promise "^2.1.0"
-    is-stream "^1.1.0"
-    listr-silent-renderer "^1.1.1"
-    listr-update-renderer "^0.4.0"
-    listr-verbose-renderer "^0.4.0"
-    log-symbols "^1.0.2"
-    log-update "^1.0.2"
-    ora "^0.2.3"
-    p-map "^1.1.1"
-    rxjs "^5.4.2"
-    stream-to-observable "^0.2.0"
-    strip-ansi "^3.0.1"
-
-load-json-file@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8"
-  integrity sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=
-  dependencies:
-    graceful-fs "^4.1.2"
-    parse-json "^2.2.0"
-    pify "^2.0.0"
-    strip-bom "^3.0.0"
-
-loader-runner@^2.3.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2"
-  integrity sha1-9IKuqC1UPgeSFwDVpG7yb9rGuKI=
-
-loader-utils@^1.0.2, loader-utils@^1.1.0:
+loader-utils@^1.0.2:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd"
   integrity sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=
@@ -3702,12 +2655,12 @@ loader-utils@^1.0.2, loader-utils@^1.1.0:
     emojis-list "^2.0.0"
     json5 "^0.5.0"
 
-locate-path@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
-  integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=
+locate-path@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
+  integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==
   dependencies:
-    p-locate "^2.0.0"
+    p-locate "^3.0.0"
     path-exists "^3.0.0"
 
 lodash._basecopy@^3.0.0:
@@ -3819,64 +2772,37 @@ lodash.throttle@^4.1.1:
   resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
   integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=
 
-lodash@^4.11.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0:
-  version "4.17.5"
-  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"
-  integrity sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==
-
 lodash@^4.16.4:
   version "4.17.10"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
   integrity sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==
 
-log-symbols@2.2.0, log-symbols@^2.1.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
-  integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==
+lru-cache@^5.1.1:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
+  integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
   dependencies:
-    chalk "^2.0.1"
+    yallist "^3.0.2"
 
-log-symbols@^1.0.1, log-symbols@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18"
-  integrity sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=
+make-dir@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
+  integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==
   dependencies:
-    chalk "^1.0.0"
+    pify "^4.0.1"
+    semver "^5.6.0"
 
-log-update@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/log-update/-/log-update-1.0.2.tgz#19929f64c4093d2d2e7075a1dad8af59c296b8d1"
-  integrity sha1-GZKfZMQJPS0ucHWh2tivWcKWuNE=
+mamacro@^0.0.3:
+  version "0.0.3"
+  resolved "https://registry.yarnpkg.com/mamacro/-/mamacro-0.0.3.tgz#ad2c9576197c9f1abf308d0787865bd975a3f3e4"
+  integrity sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==
+
+map-age-cleaner@^0.1.1:
+  version "0.1.3"
+  resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a"
+  integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==
   dependencies:
-    ansi-escapes "^1.0.0"
-    cli-cursor "^1.0.2"
-
-loose-envify@^1.0.0:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
-  integrity sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=
-  dependencies:
-    js-tokens "^3.0.0"
-
-lowercase-keys@1.0.0, lowercase-keys@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306"
-  integrity sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=
-
-lru-cache@^4.0.1, lru-cache@^4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55"
-  integrity sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==
-  dependencies:
-    pseudomap "^1.0.2"
-    yallist "^2.1.2"
-
-make-dir@^1.0.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.2.0.tgz#6d6a49eead4aae296c53bbf3a1a008bd6c89469b"
-  integrity sha512-aNUAa4UMg/UougV25bbrU4ZaaKNjJ/3/xnvg/twpmKROPdKZPZ9wGgI0opdZzO8q/zUFawoUuixuOv33eZ61Iw==
-  dependencies:
-    pify "^3.0.0"
+    p-defer "^1.0.0"
 
 map-cache@^0.2.2:
   version "0.2.2"
@@ -3933,39 +2859,16 @@ mdurl@^1.0.1:
   resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
   integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=
 
-mem-fs-editor@^3.0.0:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/mem-fs-editor/-/mem-fs-editor-3.0.2.tgz#dd0a6eaf2bb8a6b37740067aa549eb530105af9f"
-  integrity sha1-3Qpuryu4prN3QAZ6pUnrUwEFr58=
+mem@^4.0.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178"
+  integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==
   dependencies:
-    commondir "^1.0.1"
-    deep-extend "^0.4.0"
-    ejs "^2.3.1"
-    glob "^7.0.3"
-    globby "^6.1.0"
-    mkdirp "^0.5.0"
-    multimatch "^2.0.0"
-    rimraf "^2.2.8"
-    through2 "^2.0.0"
-    vinyl "^2.0.1"
+    map-age-cleaner "^0.1.1"
+    mimic-fn "^2.0.0"
+    p-is-promise "^2.0.0"
 
-mem-fs@^1.1.0:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/mem-fs/-/mem-fs-1.1.3.tgz#b8ae8d2e3fcb6f5d3f9165c12d4551a065d989cc"
-  integrity sha1-uK6NLj/Lb10/kWXBLUVRoGXZicw=
-  dependencies:
-    through2 "^2.0.0"
-    vinyl "^1.1.0"
-    vinyl-file "^2.0.0"
-
-mem@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76"
-  integrity sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=
-  dependencies:
-    mimic-fn "^1.0.0"
-
-memory-fs@^0.4.0, memory-fs@~0.4.1:
+memory-fs@^0.4.0, memory-fs@^0.4.1:
   version "0.4.1"
   resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"
   integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=
@@ -3973,6 +2876,14 @@ memory-fs@^0.4.0, memory-fs@~0.4.1:
     errno "^0.1.3"
     readable-stream "^2.0.1"
 
+memory-fs@^0.5.0:
+  version "0.5.0"
+  resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c"
+  integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==
+  dependencies:
+    errno "^0.1.3"
+    readable-stream "^2.0.1"
+
 merge-stream@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-1.0.1.tgz#4041202d508a342ba00174008df0c251b8c135e1"
@@ -3999,7 +2910,26 @@ micromatch@^2.3.7:
     parse-glob "^3.0.4"
     regex-cache "^0.4.2"
 
-micromatch@^3.1.4, micromatch@^3.1.8:
+micromatch@^3.0.4, micromatch@^3.1.10:
+  version "3.1.10"
+  resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
+  integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==
+  dependencies:
+    arr-diff "^4.0.0"
+    array-unique "^0.3.2"
+    braces "^2.3.1"
+    define-property "^2.0.2"
+    extend-shallow "^3.0.2"
+    extglob "^2.0.4"
+    fragment-cache "^0.2.1"
+    kind-of "^6.0.2"
+    nanomatch "^1.2.9"
+    object.pick "^1.3.0"
+    regex-not "^1.0.0"
+    snapdragon "^0.8.1"
+    to-regex "^3.0.2"
+
+micromatch@^3.1.4:
   version "3.1.9"
   resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.9.tgz#15dc93175ae39e52e93087847096effc73efcf89"
   integrity sha512-SlIz6sv5UPaAVVFRKodKjCg48EbNoIhgetzfK/Cy0v5U52Z6zB136M8tp0UC9jM53LYbmIRihJszvvqpKkfm9g==
@@ -4018,6 +2948,14 @@ micromatch@^3.1.4, micromatch@^3.1.8:
     snapdragon "^0.8.1"
     to-regex "^3.0.1"
 
+micromatch@^4.0.0:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259"
+  integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==
+  dependencies:
+    braces "^3.0.1"
+    picomatch "^2.0.5"
+
 miller-rabin@^4.0.0:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
@@ -4038,22 +2976,10 @@ mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.7:
   dependencies:
     mime-db "~1.30.0"
 
-mimic-fn@^1.0.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
-  integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==
-
-mimic-response@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.0.tgz#df3d3652a73fded6b9b0b24146e6fd052353458e"
-  integrity sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4=
-
-min-document@^2.19.0:
-  version "2.19.0"
-  resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685"
-  integrity sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=
-  dependencies:
-    dom-walk "^0.1.0"
+mimic-fn@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
+  integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
 
 minimalistic-assert@^1.0.0:
   version "1.0.0"
@@ -4077,20 +3003,15 @@ minimist@0.0.8:
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
   integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
 
-minimist@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.1.0.tgz#99df657a52574c21c9057497df742790b2b4c0de"
-  integrity sha1-md9lelJXTCHJBXSX33QnkLK0wN4=
-
 minimist@^1.1.0, minimist@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
   integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
 
-mississippi@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-2.0.0.tgz#3442a508fafc28500486feea99409676e4ee5a6f"
-  integrity sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==
+mississippi@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022"
+  integrity sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==
   dependencies:
     concat-stream "^1.5.0"
     duplexify "^3.4.2"
@@ -4098,7 +3019,7 @@ mississippi@^2.0.0:
     flush-write-stream "^1.0.0"
     from2 "^2.1.0"
     parallel-transform "^1.1.0"
-    pump "^2.0.1"
+    pump "^3.0.0"
     pumpify "^1.3.3"
     stream-each "^1.1.0"
     through2 "^2.0.0"
@@ -4111,7 +3032,7 @@ mixin-deep@^1.2.0:
     for-in "^1.0.2"
     is-extendable "^1.0.1"
 
-mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
+mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1:
   version "0.5.1"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
   integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
@@ -4187,16 +3108,6 @@ multipipe@^0.1.2:
   dependencies:
     duplexer2 "0.0.2"
 
-mute-stream@0.0.6:
-  version "0.0.6"
-  resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.6.tgz#48962b19e169fd1dfc240b3f1e7317627bbc47db"
-  integrity sha1-SJYrGeFp/R38JAs/HnMXYnu8R9s=
-
-mute-stream@0.0.7:
-  version "0.0.7"
-  resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
-  integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=
-
 nan@^2.3.0:
   version "2.9.2"
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.9.2.tgz#f564d75f5f8f36a6d9456cca7a6c4fe488ab7866"
@@ -4225,20 +3136,20 @@ neo-async@^2.5.0:
   resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.0.tgz#76b1c823130cca26acfbaccc8fbaf0a2fa33b18f"
   integrity sha512-nJmSswG4As/MkRq7QZFuH/sf/yuv8ODdMZrY4Bedjp77a5MK4A6s7YbBB64c9u79EBUOfXUXBvArmvzTD0X+6g==
 
+neo-async@^2.6.1:
+  version "2.6.1"
+  resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
+  integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
+
 nice-try@^1.0.4:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.4.tgz#d93962f6c52f2c1558c0fbda6d512819f1efe1c4"
   integrity sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==
 
-node-dir@0.1.8:
-  version "0.1.8"
-  resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.8.tgz#55fb8deb699070707fb67f91a460f0448294c77d"
-  integrity sha1-VfuN62mQcHB/tn+RpGDwRIKUx30=
-
-node-libs-browser@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.1.0.tgz#5f94263d404f6e44767d726901fff05478d600df"
-  integrity sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==
+node-libs-browser@^2.2.1:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425"
+  integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==
   dependencies:
     assert "^1.1.1"
     browserify-zlib "^0.2.0"
@@ -4247,10 +3158,10 @@ node-libs-browser@^2.0.0:
     constants-browserify "^1.0.0"
     crypto-browserify "^3.11.0"
     domain-browser "^1.1.1"
-    events "^1.0.0"
+    events "^3.0.0"
     https-browserify "^1.0.0"
     os-browserify "^0.3.0"
-    path-browserify "0.0.0"
+    path-browserify "0.0.1"
     process "^0.11.10"
     punycode "^1.2.4"
     querystring-es3 "^0.2.0"
@@ -4261,8 +3172,8 @@ node-libs-browser@^2.0.0:
     timers-browserify "^2.0.4"
     tty-browserify "0.0.0"
     url "^0.11.0"
-    util "^0.10.3"
-    vm-browserify "0.0.4"
+    util "^0.11.0"
+    vm-browserify "^1.0.1"
 
 node-pre-gyp@^0.6.39:
   version "0.6.39"
@@ -4288,14 +3199,6 @@ node.extend@~1.1.2:
   dependencies:
     is "^3.1.0"
 
-nomnom@^1.8.1:
-  version "1.8.1"
-  resolved "https://registry.yarnpkg.com/nomnom/-/nomnom-1.8.1.tgz#2151f722472ba79e50a76fc125bb8c8f2e4dc2a7"
-  integrity sha1-IVH3Ikcrp55Qp2/BJbuMjy5Nwqc=
-  dependencies:
-    chalk "~0.4.0"
-    underscore "~1.6.0"
-
 nopt@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
@@ -4304,16 +3207,6 @@ nopt@^4.0.1:
     abbrev "1"
     osenv "^0.1.4"
 
-normalize-package-data@^2.3.2:
-  version "2.4.0"
-  resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f"
-  integrity sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==
-  dependencies:
-    hosted-git-info "^2.1.4"
-    is-builtin-module "^1.0.0"
-    semver "2 || 3 || 4 || 5"
-    validate-npm-package-license "^3.0.1"
-
 normalize-path@^2.0.1, normalize-path@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
@@ -4321,15 +3214,6 @@ normalize-path@^2.0.1, normalize-path@^2.1.1:
   dependencies:
     remove-trailing-separator "^1.0.1"
 
-normalize-url@2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6"
-  integrity sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==
-  dependencies:
-    prepend-http "^2.0.0"
-    query-string "^5.0.1"
-    sort-keys "^2.0.0"
-
 npm-run-path@^2.0.0:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
@@ -4362,7 +3246,7 @@ object-assign@^3.0.0:
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2"
   integrity sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=
 
-object-assign@^4.0.0, object-assign@^4.0.1, object-assign@^4.1.0:
+object-assign@^4.0.0, object-assign@^4.1.0:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
   integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
@@ -4405,28 +3289,6 @@ once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0:
   dependencies:
     wrappy "1"
 
-onetime@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789"
-  integrity sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=
-
-onetime@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4"
-  integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=
-  dependencies:
-    mimic-fn "^1.0.0"
-
-ora@^0.2.3:
-  version "0.2.3"
-  resolved "https://registry.yarnpkg.com/ora/-/ora-0.2.3.tgz#37527d220adcd53c39b73571d754156d5db657a4"
-  integrity sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q=
-  dependencies:
-    chalk "^1.1.1"
-    cli-cursor "^1.0.2"
-    cli-spinners "^0.1.2"
-    object-assign "^4.0.1"
-
 ordered-read-streams@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz#7137e69b3298bb342247a1bbee3881c80e2fd78b"
@@ -4445,21 +3307,16 @@ os-homedir@^1.0.0:
   resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
   integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M=
 
-os-locale@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2"
-  integrity sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==
+os-locale@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a"
+  integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==
   dependencies:
-    execa "^0.7.0"
-    lcid "^1.0.0"
-    mem "^1.1.0"
+    execa "^1.0.0"
+    lcid "^2.0.0"
+    mem "^4.0.0"
 
-os-shim@^0.1.2:
-  version "0.1.3"
-  resolved "https://registry.yarnpkg.com/os-shim/-/os-shim-0.1.3.tgz#6b62c3791cf7909ea35ed46e17658bb417cb3917"
-  integrity sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=
-
-os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2:
+os-tmpdir@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
   integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
@@ -4472,75 +3329,39 @@ osenv@^0.1.4:
     os-homedir "^1.0.0"
     os-tmpdir "^1.0.0"
 
-p-cancelable@^0.3.0:
-  version "0.3.0"
-  resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa"
-  integrity sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==
-
-p-each-series@^1.0.0:
+p-defer@^1.0.0:
   version "1.0.0"
-  resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-1.0.0.tgz#930f3d12dd1f50e7434457a22cd6f04ac6ad7f71"
-  integrity sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=
-  dependencies:
-    p-reduce "^1.0.0"
+  resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c"
+  integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=
 
 p-finally@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
   integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
 
-p-is-promise@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e"
-  integrity sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=
+p-is-promise@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e"
+  integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==
 
-p-lazy@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/p-lazy/-/p-lazy-1.0.0.tgz#ec53c802f2ee3ac28f166cc82d0b2b02de27a835"
-  integrity sha1-7FPIAvLuOsKPFmzILQsrAt4nqDU=
-
-p-limit@^1.1.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.2.0.tgz#0e92b6bedcb59f022c13d0f1949dc82d15909f1c"
-  integrity sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==
+p-limit@^2.0.0:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.1.tgz#aa07a788cc3151c939b5131f63570f0dd2009537"
+  integrity sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==
   dependencies:
-    p-try "^1.0.0"
+    p-try "^2.0.0"
 
-p-locate@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
-  integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=
+p-locate@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
+  integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==
   dependencies:
-    p-limit "^1.1.0"
+    p-limit "^2.0.0"
 
-p-map@^1.1.1:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b"
-  integrity sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==
-
-p-reduce@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa"
-  integrity sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=
-
-p-timeout@^1.1.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-1.2.1.tgz#5eb3b353b7fce99f101a1038880bb054ebbea386"
-  integrity sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=
-  dependencies:
-    p-finally "^1.0.0"
-
-p-timeout@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038"
-  integrity sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==
-  dependencies:
-    p-finally "^1.0.0"
-
-p-try@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
-  integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=
+p-try@^2.0.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
+  integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
 
 pako@~1.0.5:
   version "1.0.6"
@@ -4577,13 +3398,6 @@ parse-glob@^3.0.4:
     is-extglob "^1.0.0"
     is-glob "^2.0.0"
 
-parse-json@^2.2.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9"
-  integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=
-  dependencies:
-    error-ex "^1.2.0"
-
 parse-passwd@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
@@ -4594,10 +3408,10 @@ pascalcase@^0.1.1:
   resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
   integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=
 
-path-browserify@0.0.0:
-  version "0.0.0"
-  resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a"
-  integrity sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=
+path-browserify@0.0.1:
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a"
+  integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==
 
 path-dirname@^1.0.0:
   version "1.0.2"
@@ -4609,7 +3423,7 @@ path-exists@^3.0.0:
   resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
   integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=
 
-path-is-absolute@^1.0.0, path-is-absolute@^1.0.1:
+path-is-absolute@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
   integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
@@ -4619,18 +3433,6 @@ path-key@^2.0.0, path-key@^2.0.1:
   resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
   integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
 
-path-parse@^1.0.5:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
-  integrity sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=
-
-path-type@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73"
-  integrity sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=
-  dependencies:
-    pify "^2.0.0"
-
 pause-stream@0.0.11:
   version "0.0.11"
   resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445"
@@ -4664,15 +3466,15 @@ performance-now@^2.1.0:
   resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
   integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
 
-pify@^2.0.0, pify@^2.3.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
-  integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
+picomatch@^2.0.5:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.1.0.tgz#0fd042f568d08b1ad9ff2d3ec0f0bfb3cb80e177"
+  integrity sha512-uhnEDzAbrcJ8R3g2fANnSuXZMBtkpSjxTTgn2LeSiQlfmq72enQJWdQllXW24MBLYnA1SBD2vfvx2o0Zw3Ielw==
 
-pify@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
-  integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
+pify@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
+  integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==
 
 pinkie-promise@^2.0.0:
   version "2.0.1"
@@ -4686,12 +3488,12 @@ pinkie@^2.0.0:
   resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
   integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
 
-pkg-dir@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b"
-  integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=
+pkg-dir@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3"
+  integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==
   dependencies:
-    find-up "^2.1.0"
+    find-up "^3.0.0"
 
 plugin-error@^0.1.2:
   version "0.1.2"
@@ -4709,36 +3511,11 @@ posix-character-classes@^0.1.0:
   resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
   integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
 
-prepend-http@^1.0.1:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
-  integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
-
-prepend-http@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
-  integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
-
 preserve@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
   integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=
 
-prettier@^1.5.3:
-  version "1.11.1"
-  resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.11.1.tgz#61e43fc4cd44e68f2b0dfc2c38cd4bb0fccdcc75"
-  integrity sha512-T/KD65Ot0PB97xTrG8afQ46x3oiVhnfGjGESSI9NWYcG92+OUPZKkwHqGWXH2t9jK1crnQjubECW0FuOth+hxw==
-
-pretty-bytes@^4.0.2:
-  version "4.0.2"
-  resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-4.0.2.tgz#b2bf82e7350d65c6c33aa95aaa5a4f6327f61cd9"
-  integrity sha1-sr+C5zUNZcbDOqlaqlpPYyf2HNk=
-
-private@^0.1.6, private@^0.1.7, private@~0.1.5:
-  version "0.1.8"
-  resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
-  integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==
-
 process-nextick-args@^1.0.6, 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"
@@ -4754,11 +3531,6 @@ process@^0.11.10:
   resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
   integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI=
 
-process@~0.5.1:
-  version "0.5.2"
-  resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf"
-  integrity sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=
-
 promise-inflight@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
@@ -4769,11 +3541,6 @@ prr@~1.0.1:
   resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
   integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY=
 
-pseudomap@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
-  integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
-
 public-encrypt@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.0.tgz#39f699f3a46560dd5ebacbca693caf7c65c18cc6"
@@ -4785,7 +3552,7 @@ public-encrypt@^4.0.0:
     parse-asn1 "^5.0.0"
     randombytes "^2.0.1"
 
-pump@^2.0.0, pump@^2.0.1:
+pump@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909"
   integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==
@@ -4793,6 +3560,14 @@ pump@^2.0.0, pump@^2.0.1:
     end-of-stream "^1.1.0"
     once "^1.3.1"
 
+pump@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
+  integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==
+  dependencies:
+    end-of-stream "^1.1.0"
+    once "^1.3.1"
+
 pumpify@^1.3.3:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.4.0.tgz#80b7c5df7e24153d03f0e7ac8a05a5d068bd07fb"
@@ -4812,6 +3587,11 @@ punycode@^1.2.4, punycode@^1.4.1:
   resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
   integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
 
+punycode@^2.1.0:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
+  integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
+
 qs@~6.3.0:
   version "6.3.2"
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c"
@@ -4827,15 +3607,6 @@ qs@~6.5.1:
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
   integrity sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==
 
-query-string@^5.0.1:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.0.tgz#9583b15fd1307f899e973ed418886426a9976469"
-  integrity sha512-F3DkxxlY0AqD/rwe4YAwjRE2HjOkKW7TxsuteyrS/Jbwrxw887PqYBL4sWUJ9D/V1hmFns0SCD6FDyvlwo9RCQ==
-  dependencies:
-    decode-uri-component "^0.2.0"
-    object-assign "^4.1.0"
-    strict-uri-encode "^1.0.0"
-
 querystring-es3@^0.2.0:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
@@ -4898,31 +3669,6 @@ rc@^1.1.7:
     minimist "^1.2.0"
     strip-json-comments "~2.0.1"
 
-read-chunk@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/read-chunk/-/read-chunk-2.1.0.tgz#6a04c0928005ed9d42e1a6ac5600e19cbc7ff655"
-  integrity sha1-agTAkoAF7Z1C4aasVgDhnLx/9lU=
-  dependencies:
-    pify "^3.0.0"
-    safe-buffer "^5.1.1"
-
-read-pkg-up@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be"
-  integrity sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=
-  dependencies:
-    find-up "^2.0.0"
-    read-pkg "^2.0.0"
-
-read-pkg@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8"
-  integrity sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=
-  dependencies:
-    load-json-file "^2.0.0"
-    normalize-package-data "^2.3.2"
-    path-type "^2.0.0"
-
 "readable-stream@1 || 2", readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.2, readable-stream@^2.3.3:
   version "2.3.5"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.5.tgz#b4f85003a938cbb6ecbce2a124fb1012bd1a838d"
@@ -4979,53 +3725,6 @@ readdirp@^2.0.0:
     readable-stream "^2.0.2"
     set-immediate-shim "^1.0.1"
 
-recast@^0.12.5:
-  version "0.12.9"
-  resolved "https://registry.yarnpkg.com/recast/-/recast-0.12.9.tgz#e8e52bdb9691af462ccbd7c15d5a5113647a15f1"
-  integrity sha512-y7ANxCWmMW8xLOaiopiRDlyjQ9ajKRENBH+2wjntIbk3A6ZR1+BLQttkmSHMY7Arl+AAZFwJ10grg2T6f1WI8A==
-  dependencies:
-    ast-types "0.10.1"
-    core-js "^2.4.1"
-    esprima "~4.0.0"
-    private "~0.1.5"
-    source-map "~0.6.1"
-
-recast@^0.14.4:
-  version "0.14.4"
-  resolved "https://registry.yarnpkg.com/recast/-/recast-0.14.4.tgz#383dd606eac924c1157b0293b53191c34bd3c641"
-  integrity sha512-b6fRXujYf8mTIyljymL3rglje1LfuGKdD44CuKs6o1B18MmZ+mEEpD5gsaxGVABZHyPvYwPLcyBTA/SvxtiyFg==
-  dependencies:
-    ast-types "0.11.2"
-    esprima "~4.0.0"
-    private "~0.1.5"
-    source-map "~0.6.1"
-
-rechoir@^0.6.2:
-  version "0.6.2"
-  resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
-  integrity sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=
-  dependencies:
-    resolve "^1.1.6"
-
-regenerate@^1.2.1:
-  version "1.3.3"
-  resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f"
-  integrity sha512-jVpo1GadrDAK59t/0jRx5VxYWQEDkkEKi6+HjE3joFVLfDOh9Xrdh0dF1eSq+BI/SwvTQ44gSscJ8N5zYL61sg==
-
-regenerator-runtime@^0.11.0:
-  version "0.11.1"
-  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
-  integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
-
-regenerator-transform@^0.10.0:
-  version "0.10.1"
-  resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd"
-  integrity sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==
-  dependencies:
-    babel-runtime "^6.18.0"
-    babel-types "^6.19.0"
-    private "^0.1.6"
-
 regex-cache@^0.4.2:
   version "0.4.4"
   resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd"
@@ -5041,27 +3740,6 @@ regex-not@^1.0.0, regex-not@^1.0.2:
     extend-shallow "^3.0.2"
     safe-regex "^1.1.0"
 
-regexpu-core@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240"
-  integrity sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=
-  dependencies:
-    regenerate "^1.2.1"
-    regjsgen "^0.2.0"
-    regjsparser "^0.1.4"
-
-regjsgen@^0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7"
-  integrity sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=
-
-regjsparser@^0.1.4:
-  version "0.1.5"
-  resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c"
-  integrity sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=
-  dependencies:
-    jsesc "~0.5.0"
-
 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"
@@ -5077,13 +3755,6 @@ repeat-string@^1.5.2, repeat-string@^1.6.1:
   resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
   integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
 
-repeating@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda"
-  integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=
-  dependencies:
-    is-finite "^1.0.0"
-
 replace-ext@0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924"
@@ -5181,10 +3852,10 @@ require-directory@^2.1.1:
   resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
   integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
 
-require-main-filename@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
-  integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=
+require-main-filename@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
+  integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
 
 requires-port@~1.0.0:
   version "1.0.0"
@@ -5198,7 +3869,7 @@ resolve-cwd@^2.0.0:
   dependencies:
     resolve-from "^3.0.0"
 
-resolve-dir@^1.0.0:
+resolve-dir@^1.0.0, resolve-dir@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43"
   integrity sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=
@@ -5216,52 +3887,24 @@ resolve-url@^0.2.1:
   resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
   integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
 
-resolve@^1.1.6:
-  version "1.5.0"
-  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36"
-  integrity sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==
-  dependencies:
-    path-parse "^1.0.5"
-
-responselike@1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7"
-  integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=
-  dependencies:
-    lowercase-keys "^1.0.0"
-
-restore-cursor@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541"
-  integrity sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=
-  dependencies:
-    exit-hook "^1.0.0"
-    onetime "^1.0.0"
-
-restore-cursor@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
-  integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368=
-  dependencies:
-    onetime "^2.0.0"
-    signal-exit "^3.0.2"
-
 ret@~0.1.10:
   version "0.1.15"
   resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
   integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
 
-rimraf@2, rimraf@^2.2.0, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2:
+rimraf@2, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1:
   version "2.6.2"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
   integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==
   dependencies:
     glob "^7.0.5"
 
-rimraf@~2.2.6:
-  version "2.2.8"
-  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582"
-  integrity sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=
+rimraf@^2.6.3:
+  version "2.7.1"
+  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
+  integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
+  dependencies:
+    glob "^7.1.3"
 
 ripemd160@^2.0.0, ripemd160@^2.0.1:
   version "2.0.1"
@@ -5271,13 +3914,6 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
     hash-base "^2.0.0"
     inherits "^2.0.1"
 
-run-async@^2.0.0, run-async@^2.2.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"
-  integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA=
-  dependencies:
-    is-promise "^2.1.0"
-
 run-queue@^1.0.0, run-queue@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47"
@@ -5285,30 +3921,6 @@ run-queue@^1.0.0, run-queue@^1.0.3:
   dependencies:
     aproba "^1.1.1"
 
-rx-lite-aggregates@^4.0.8:
-  version "4.0.8"
-  resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be"
-  integrity sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=
-  dependencies:
-    rx-lite "*"
-
-rx-lite@*, rx-lite@^4.0.8:
-  version "4.0.8"
-  resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444"
-  integrity sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=
-
-rx@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782"
-  integrity sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=
-
-rxjs@^5.4.2, rxjs@^5.5.2:
-  version "5.5.6"
-  resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.6.tgz#e31fb96d6fd2ff1fd84bcea8ae9c02d007179c02"
-  integrity sha512-v4Q5HDC0FHAQ7zcBX7T2IL6O5ltl1a2GX4ENjPXg6SjDY69Cmx9v4113C99a4wGF16ClPv5Z8mghuYorVkg/kg==
-  dependencies:
-    symbol-observable "1.0.1"
-
 safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
   version "5.1.1"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
@@ -5321,28 +3933,34 @@ safe-regex@^1.1.0:
   dependencies:
     ret "~0.1.10"
 
-schema-utils@^0.4.2:
-  version "0.4.5"
-  resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.5.tgz#21836f0608aac17b78f9e3e24daff14a5ca13a3e"
-  integrity sha512-yYrjb9TX2k/J1Y5UNy3KYdZq10xhYcF8nMpAW6o3hy6Q8WSIEf9lJHG/ePnOBfziPM3fvQwfOwa13U/Fh8qTfA==
+schema-utils@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770"
+  integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==
   dependencies:
     ajv "^6.1.0"
+    ajv-errors "^1.0.0"
     ajv-keywords "^3.1.0"
 
-scoped-regex@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/scoped-regex/-/scoped-regex-1.0.0.tgz#a346bb1acd4207ae70bd7c0c7ca9e566b6baddb8"
-  integrity sha1-o0a7Gs1CB65wvXwMfKnlZra63bg=
-
-"semver@2 || 3 || 4 || 5", semver@^5.0.1, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0:
+semver@^5.3.0, semver@^5.4.1, semver@^5.5.0:
   version "5.5.0"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
   integrity sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==
 
-serialize-javascript@^1.4.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.4.0.tgz#7c958514db6ac2443a8abc062dc9f7886a7f6005"
-  integrity sha1-fJWFFNtqwkQ6irwGLcn3iGp/YAU=
+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.0.0:
+  version "6.3.0"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
+  integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
+
+serialize-javascript@^1.7.0:
+  version "1.9.1"
+  resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.9.1.tgz#cfc200aef77b600c47da9bb8149c943e798c2fdb"
+  integrity sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==
 
 set-blocking@^2.0.0, set-blocking@~2.0.0:
   version "2.0.0"
@@ -5406,35 +4024,11 @@ shebang-regex@^1.0.0:
   resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
   integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=
 
-shelljs@^0.7.0:
-  version "0.7.8"
-  resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.8.tgz#decbcf874b0d1e5fb72e14b164a9683048e9acb3"
-  integrity sha1-3svPh0sNHl+3LhSxZKloMEjprLM=
-  dependencies:
-    glob "^7.0.0"
-    interpret "^1.0.0"
-    rechoir "^0.6.2"
-
-signal-exit@^3.0.0, signal-exit@^3.0.2:
+signal-exit@^3.0.0:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
   integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
 
-slash@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
-  integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=
-
-slice-ansi@0.0.4:
-  version "0.0.4"
-  resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
-  integrity sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=
-
-slide@^1.1.5:
-  version "1.1.6"
-  resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707"
-  integrity sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=
-
 snapdragon-node@^2.0.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"
@@ -5479,13 +4073,6 @@ sntp@2.x.x:
   dependencies:
     hoek "4.x.x"
 
-sort-keys@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128"
-  integrity sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=
-  dependencies:
-    is-plain-obj "^1.0.0"
-
 source-list-map@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085"
@@ -5502,13 +4089,6 @@ source-map-resolve@^0.5.0:
     source-map-url "^0.4.0"
     urix "^0.1.0"
 
-source-map-support@^0.4.15:
-  version "0.4.18"
-  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f"
-  integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==
-  dependencies:
-    source-map "^0.5.6"
-
 source-map-support@^0.5.0:
   version "0.5.3"
   resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.3.tgz#2b3d5fff298cfa4d1afd7d4352d569e9a0158e76"
@@ -5516,12 +4096,20 @@ source-map-support@^0.5.0:
   dependencies:
     source-map "^0.6.0"
 
+source-map-support@~0.5.12:
+  version "0.5.16"
+  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042"
+  integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==
+  dependencies:
+    buffer-from "^1.0.0"
+    source-map "^0.6.0"
+
 source-map-url@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
   integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=
 
-source-map@^0.5.6, source-map@^0.5.7:
+source-map@^0.5.6:
   version "0.5.7"
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
   integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
@@ -5536,40 +4124,6 @@ sparkles@^1.0.0:
   resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3"
   integrity sha1-Gsu/tZJDbRC76PeFt8xvgoFQEsM=
 
-spawn-sync@^1.0.15:
-  version "1.0.15"
-  resolved "https://registry.yarnpkg.com/spawn-sync/-/spawn-sync-1.0.15.tgz#b00799557eb7fb0c8376c29d44e8a1ea67e57476"
-  integrity sha1-sAeZVX63+wyDdsKdROih6mfldHY=
-  dependencies:
-    concat-stream "^1.4.7"
-    os-shim "^0.1.2"
-
-spdx-correct@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.0.tgz#05a5b4d7153a195bc92c3c425b69f3b2a9524c82"
-  integrity sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==
-  dependencies:
-    spdx-expression-parse "^3.0.0"
-    spdx-license-ids "^3.0.0"
-
-spdx-exceptions@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz#2c7ae61056c714a5b9b9b2b2af7d311ef5c78fe9"
-  integrity sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==
-
-spdx-expression-parse@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0"
-  integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==
-  dependencies:
-    spdx-exceptions "^2.1.0"
-    spdx-license-ids "^3.0.0"
-
-spdx-license-ids@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz#7a7cd28470cc6d3a1cfe6d66886f6bc430d3ac87"
-  integrity sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==
-
 split-string@^3.0.1, split-string@^3.0.2:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2"
@@ -5604,12 +4158,12 @@ sshpk@^1.7.0:
     jsbn "~0.1.0"
     tweetnacl "~0.14.0"
 
-ssri@^5.2.4:
-  version "5.2.4"
-  resolved "https://registry.yarnpkg.com/ssri/-/ssri-5.2.4.tgz#9985e14041e65fc397af96542be35724ac11da52"
-  integrity sha512-UnEAgMZa15973iH7cUi0AHjJn1ACDIkaMyZILoqwN6yzt+4P81I8tBc5Hl+qwi5auMplZtPQsHrPBR5vJLcQtQ==
+ssri@^6.0.1:
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8"
+  integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==
   dependencies:
-    safe-buffer "^5.1.1"
+    figgy-pudding "^3.5.1"
 
 stat-mode@^0.2.0:
   version "0.2.2"
@@ -5663,13 +4217,6 @@ stream-shift@^1.0.0:
   resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
   integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=
 
-stream-to-observable@^0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/stream-to-observable/-/stream-to-observable-0.2.0.tgz#59d6ea393d87c2c0ddac10aa0d561bc6ba6f0e10"
-  integrity sha1-WdbqOT2HwsDdrBCqDVYbxrpvDhA=
-  dependencies:
-    any-observable "^0.2.0"
-
 streamfilter@^1.0.5:
   version "1.0.7"
   resolved "https://registry.yarnpkg.com/streamfilter/-/streamfilter-1.0.7.tgz#ae3e64522aa5a35c061fd17f67620c7653c643c9"
@@ -5682,16 +4229,6 @@ streamifier@~0.1.1:
   resolved "https://registry.yarnpkg.com/streamifier/-/streamifier-0.1.1.tgz#97e98d8fa4d105d62a2691d1dc07e820db8dfc4f"
   integrity sha1-l+mNj6TRBdYqJpHR3AfoINuN/E8=
 
-strict-uri-encode@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
-  integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=
-
-string-template@~0.2.1:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add"
-  integrity sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=
-
 string-width@^1.0.1, string-width@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
@@ -5701,13 +4238,14 @@ string-width@^1.0.1, string-width@^1.0.2:
     is-fullwidth-code-point "^1.0.0"
     strip-ansi "^3.0.0"
 
-string-width@^2.0.0, string-width@^2.1.0:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
-  integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
+string-width@^3.0.0, string-width@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
+  integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==
   dependencies:
+    emoji-regex "^7.0.1"
     is-fullwidth-code-point "^2.0.0"
-    strip-ansi "^4.0.0"
+    strip-ansi "^5.1.0"
 
 string_decoder@^1.0.0, string_decoder@~1.0.3:
   version "1.0.3"
@@ -5740,10 +4278,12 @@ strip-ansi@^4.0.0:
   dependencies:
     ansi-regex "^3.0.0"
 
-strip-ansi@~0.1.0:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.1.1.tgz#39e8a98d044d150660abe4a6808acf70bb7bc991"
-  integrity sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=
+strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
+  integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
+  dependencies:
+    ansi-regex "^4.1.0"
 
 strip-bom-stream@^1.0.0:
   version "1.0.0"
@@ -5753,14 +4293,6 @@ strip-bom-stream@^1.0.0:
     first-chunk-stream "^1.0.0"
     strip-bom "^2.0.0"
 
-strip-bom-stream@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/strip-bom-stream/-/strip-bom-stream-2.0.0.tgz#f87db5ef2613f6968aa545abfe1ec728b6a829ca"
-  integrity sha1-+H217yYT9paKpUWr/h7HKLaoKco=
-  dependencies:
-    first-chunk-stream "^2.0.0"
-    strip-bom "^2.0.0"
-
 strip-bom@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
@@ -5768,11 +4300,6 @@ strip-bom@^2.0.0:
   dependencies:
     is-utf8 "^0.2.0"
 
-strip-bom@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
-  integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=
-
 strip-eof@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
@@ -5790,33 +4317,35 @@ supports-color@4.4.0:
   dependencies:
     has-flag "^2.0.0"
 
+supports-color@6.1.0:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
+  integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
+  dependencies:
+    has-flag "^3.0.0"
+
 supports-color@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
   integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=
 
-supports-color@^5.2.0, supports-color@^5.3.0:
+supports-color@^5.3.0:
   version "5.3.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.3.0.tgz#5b24ac15db80fa927cf5227a4a33fd3c4c7676c0"
   integrity sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==
   dependencies:
     has-flag "^3.0.0"
 
-symbol-observable@1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4"
-  integrity sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=
-
-symbol-observable@^0.2.2:
-  version "0.2.4"
-  resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-0.2.4.tgz#95a83db26186d6af7e7a18dbd9760a2f86d08f40"
-  integrity sha1-lag9smGG1q9+ehjb2XYKL4bQj0A=
-
 tapable@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.0.0.tgz#cbb639d9002eed9c6b5975eb20598d7936f1f9f2"
   integrity sha512-dQRhbNQkRnaqauC7WqSJ21EEksgT0fYZX2lqXzGkpo8JNig9zGZTYoMGvyI2nWmXlE2VSVXVDu7wLVGu/mQEsg==
 
+tapable@^1.1.3:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
+  integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
+
 tar-pack@^3.4.0:
   version "3.4.1"
   resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f"
@@ -5840,23 +4369,29 @@ tar@^2.2.1:
     fstream "^1.0.2"
     inherits "2"
 
-temp@^0.8.1:
-  version "0.8.3"
-  resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.3.tgz#e0c6bc4d26b903124410e4fed81103014dfc1f59"
-  integrity sha1-4Ma8TSa5AxJEEOT+2BEDAU38H1k=
+terser-webpack-plugin@^1.4.1:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz#61b18e40eaee5be97e771cdbb10ed1280888c2b4"
+  integrity sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg==
   dependencies:
-    os-tmpdir "^1.0.0"
-    rimraf "~2.2.6"
+    cacache "^12.0.2"
+    find-cache-dir "^2.1.0"
+    is-wsl "^1.1.0"
+    schema-utils "^1.0.0"
+    serialize-javascript "^1.7.0"
+    source-map "^0.6.1"
+    terser "^4.1.2"
+    webpack-sources "^1.4.0"
+    worker-farm "^1.7.0"
 
-text-table@^0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
-  integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
-
-textextensions@2:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/textextensions/-/textextensions-2.2.0.tgz#38ac676151285b658654581987a0ce1a4490d286"
-  integrity sha512-j5EMxnryTvKxwH2Cq+Pb43tsf6sdEgw6Pdwxk83mPaq0ToeFJt6WE4J3s5BqY7vmjlLgkgXvhtXUxo80FyBhCA==
+terser@^4.1.2:
+  version "4.3.9"
+  resolved "https://registry.yarnpkg.com/terser/-/terser-4.3.9.tgz#e4be37f80553d02645668727777687dad26bbca8"
+  integrity sha512-NFGMpHjlzmyOtPL+fDw3G7+6Ueh/sz4mkaUYa4lJCxOPTNzd0Uj0aZJOmsDYoSQyfuVoWDMSWTPU3huyOm2zdA==
+  dependencies:
+    commander "^2.20.0"
+    source-map "~0.6.1"
+    source-map-support "~0.5.12"
 
 through2-filter@^2.0.0:
   version "2.0.0"
@@ -5882,7 +4417,7 @@ through2@^2.0.0, through2@^2.0.1, through2@^2.0.3, through2@~2.0.0, through2@~2.
     readable-stream "^2.1.5"
     xtend "~4.0.1"
 
-through@2, through@^2.3.6, through@~2.3, through@~2.3.1:
+through@2, through@~2.3, through@~2.3.1:
   version "2.3.8"
   resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
   integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
@@ -5892,11 +4427,6 @@ time-stamp@^1.0.0:
   resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3"
   integrity sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=
 
-timed-out@^4.0.0, timed-out@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
-  integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=
-
 timers-browserify@^2.0.4:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.6.tgz#241e76927d9ca05f4d959819022f5b3664b64bae"
@@ -5904,20 +4434,6 @@ timers-browserify@^2.0.4:
   dependencies:
     setimmediate "^1.0.4"
 
-tmp@^0.0.29:
-  version "0.0.29"
-  resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.29.tgz#f25125ff0dd9da3ccb0c2dd371ee1288bb9128c0"
-  integrity sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA=
-  dependencies:
-    os-tmpdir "~1.0.1"
-
-tmp@^0.0.33:
-  version "0.0.33"
-  resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
-  integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==
-  dependencies:
-    os-tmpdir "~1.0.2"
-
 to-absolute-glob@^0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-0.1.1.tgz#1cdfa472a9ef50c239ee66999b662ca0eb39937f"
@@ -5930,11 +4446,6 @@ to-arraybuffer@^1.0.0:
   resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
   integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=
 
-to-fast-properties@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47"
-  integrity sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=
-
 to-object-path@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af"
@@ -5950,7 +4461,14 @@ to-regex-range@^2.1.0:
     is-number "^3.0.0"
     repeat-string "^1.6.1"
 
-to-regex@^3.0.1:
+to-regex-range@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
+  integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
+  dependencies:
+    is-number "^7.0.0"
+
+to-regex@^3.0.1, to-regex@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce"
   integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==
@@ -5967,21 +4485,21 @@ tough-cookie@~2.3.0, tough-cookie@~2.3.3:
   dependencies:
     punycode "^1.4.1"
 
-trim-right@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
-  integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=
-
-ts-loader@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-4.0.1.tgz#3d920b059966efec9637133ab0ca9b04d625d59a"
-  integrity sha512-dzgQnkAGY4sLqVw6t4LJH8FGR8j0zsPmC1Ff2ChzKhYO+hgWJkSEyrHTlSSZqn5oWr0Po7q/IRoeq8O4SNKQ9A==
+ts-loader@^6.2.1:
+  version "6.2.1"
+  resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-6.2.1.tgz#67939d5772e8a8c6bdaf6277ca023a4812da02ef"
+  integrity sha512-Dd9FekWuABGgjE1g0TlQJ+4dFUfYGbYcs52/HQObE0ZmUNjQlmLAS7xXsSzy23AMaMwipsx5sNHvoEpT2CZq1g==
   dependencies:
     chalk "^2.3.0"
     enhanced-resolve "^4.0.0"
     loader-utils "^1.0.2"
-    micromatch "^3.1.4"
-    semver "^5.0.1"
+    micromatch "^4.0.0"
+    semver "^6.0.0"
+
+tslib@^1.9.0:
+  version "1.10.0"
+  resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
+  integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
 
 tty-browserify@0.0.0:
   version "0.0.0"
@@ -6010,10 +4528,10 @@ typedarray@^0.0.6:
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
   integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
 
-typescript@^3.3.1:
-  version "3.3.1"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.1.tgz#6de14e1db4b8a006ac535e482c8ba018c55f750b"
-  integrity sha512-cTmIDFW7O0IHbn1DPYjkiebHxwtCMU+eTy30ZtJNBPF9j2O1ITu5XH2YnBeVRKWHqF+3JQwWJv0Q0aUgX8W7IA==
+typescript@^3.7.2:
+  version "3.7.2"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb"
+  integrity sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==
 
 uc.micro@^1.0.1:
   version "1.0.3"
@@ -6025,38 +4543,11 @@ uc.micro@^1.0.5:
   resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.5.tgz#0c65f15f815aa08b560a61ce8b4db7ffc3f45376"
   integrity sha512-JoLI4g5zv5qNyT09f4YAvEZIIV1oOjqnewYg5D38dkQljIzpPT296dbIGvKro3digYI1bkb7W6EP1y4uDlmzLg==
 
-uglify-es@^3.3.4:
-  version "3.3.10"
-  resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.10.tgz#8b0b7992cebe20edc26de1bf325cef797b8f3fa5"
-  integrity sha512-rPzPisCzW68Okj1zNrfa2dR9uEm43SevDmpR6FChoZABFk9dANGnzzBMgHYUXI3609//63fnVkyQ1SQmAMyjww==
-  dependencies:
-    commander "~2.14.1"
-    source-map "~0.6.1"
-
-uglifyjs-webpack-plugin@^1.1.1, uglifyjs-webpack-plugin@^1.2.2:
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.2.2.tgz#e7516d4367afdb715c3847841eb46f94c45ca2b9"
-  integrity sha512-CG/NvzXfemUAm5Y4Guh5eEaJYHtkG7kKNpXEJHp9QpxsFVB5/qKvYWoMaq4sa99ccZ0hM3MK8vQV9XPZB4357A==
-  dependencies:
-    cacache "^10.0.1"
-    find-cache-dir "^1.0.0"
-    schema-utils "^0.4.2"
-    serialize-javascript "^1.4.0"
-    source-map "^0.6.1"
-    uglify-es "^3.3.4"
-    webpack-sources "^1.1.0"
-    worker-farm "^1.5.2"
-
 uid-number@^0.0.6:
   version "0.0.6"
   resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
   integrity sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=
 
-underscore@~1.6.0:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8"
-  integrity sha1-izixDKze9jM3uLJOT/htRa6lKag=
-
 union-value@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4"
@@ -6067,10 +4558,10 @@ union-value@^1.0.0:
     is-extendable "^0.1.1"
     set-value "^0.4.3"
 
-unique-filename@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.0.tgz#d05f2fe4032560871f30e93cbe735eea201514f3"
-  integrity sha1-0F8v5AMlYIcfMOk8vnNe6iAVFPM=
+unique-filename@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230"
+  integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==
   dependencies:
     unique-slug "^2.0.0"
 
@@ -6097,42 +4588,23 @@ unset-value@^1.0.0:
     has-value "^0.3.1"
     isobject "^3.0.0"
 
-untildify@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/untildify/-/untildify-2.1.0.tgz#17eb2807987f76952e9c0485fc311d06a826a2e0"
-  integrity sha1-F+soB5h/dpUunASF/DEdBqgmouA=
-  dependencies:
-    os-homedir "^1.0.0"
-
-untildify@^3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/untildify/-/untildify-3.0.2.tgz#7f1f302055b3fea0f3e81dc78eb36766cb65e3f1"
-  integrity sha1-fx8wIFWz/qDz6B3HjrNnZstl4/E=
-
 upath@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd"
   integrity sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==
 
+uri-js@^4.2.2:
+  version "4.2.2"
+  resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
+  integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==
+  dependencies:
+    punycode "^2.1.0"
+
 urix@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
   integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
 
-url-parse-lax@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73"
-  integrity sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=
-  dependencies:
-    prepend-http "^1.0.1"
-
-url-parse-lax@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c"
-  integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=
-  dependencies:
-    prepend-http "^2.0.0"
-
 url-parse@^1.1.9:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.2.0.tgz#3a19e8aaa6d023ddd27dcc44cb4fc8f7fec23986"
@@ -6141,11 +4613,6 @@ url-parse@^1.1.9:
     querystringify "~1.0.0"
     requires-port "~1.0.0"
 
-url-to-options@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9"
-  integrity sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=
-
 url@^0.11.0:
   version "0.11.0"
   resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
@@ -6154,11 +4621,6 @@ url@^0.11.0:
     punycode "1.3.2"
     querystring "0.2.0"
 
-urlgrey@0.4.4:
-  version "0.4.4"
-  resolved "https://registry.yarnpkg.com/urlgrey/-/urlgrey-0.4.4.tgz#892fe95960805e85519f1cd4389f2cb4cbb7652f"
-  integrity sha1-iS/pWWCAXoVRnxzUOJ8stMu3ZS8=
-
 use@^2.0.0:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/use/-/use-2.0.2.tgz#ae28a0d72f93bf22422a18a2e379993112dec8e8"
@@ -6173,36 +4635,35 @@ util-deprecate@~1.0.1:
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
   integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
 
-util@0.10.3, util@^0.10.3:
+util@0.10.3:
   version "0.10.3"
   resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9"
   integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk=
   dependencies:
     inherits "2.0.1"
 
+util@^0.11.0:
+  version "0.11.1"
+  resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61"
+  integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==
+  dependencies:
+    inherits "2.0.3"
+
 uuid@^3.0.0, uuid@^3.1.0:
   version "3.2.1"
   resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14"
   integrity sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==
 
-v8-compile-cache@^1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-1.1.2.tgz#8d32e4f16974654657e676e0e467a348e89b0dc4"
-  integrity sha512-ejdrifsIydN1XDH7EuR2hn8ZrkRKUYF7tUcBjBy/lhrCvs2K+zRlbW9UHc0IQ9RsYFZJFqJrieoIHfkCa0DBRA==
+v8-compile-cache@2.0.3:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe"
+  integrity sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==
 
 vali-date@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/vali-date/-/vali-date-1.0.0.tgz#1b904a59609fb328ef078138420934f6b86709a6"
   integrity sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY=
 
-validate-npm-package-license@^3.0.1:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz#81643bcbef1bdfecd4623793dc4648948ba98338"
-  integrity sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==
-  dependencies:
-    spdx-correct "^3.0.0"
-    spdx-expression-parse "^3.0.0"
-
 verror@1.10.0:
   version "1.10.0"
   resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
@@ -6212,18 +4673,6 @@ verror@1.10.0:
     core-util-is "1.0.2"
     extsprintf "^1.2.0"
 
-vinyl-file@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/vinyl-file/-/vinyl-file-2.0.0.tgz#a7ebf5ffbefda1b7d18d140fcb07b223efb6751a"
-  integrity sha1-p+v1/779obfRjRQPyweyI++2dRo=
-  dependencies:
-    graceful-fs "^4.1.2"
-    pify "^2.3.0"
-    pinkie-promise "^2.0.0"
-    strip-bom "^2.0.0"
-    strip-bom-stream "^2.0.0"
-    vinyl "^1.1.0"
-
 vinyl-fs@^2.0.0, vinyl-fs@^2.4.3:
   version "2.4.4"
   resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-2.4.4.tgz#be6ff3270cb55dfd7d3063640de81f25d7532239"
@@ -6272,7 +4721,7 @@ vinyl@^0.5.0:
     clone-stats "^0.0.1"
     replace-ext "0.0.1"
 
-vinyl@^1.0.0, vinyl@^1.1.0:
+vinyl@^1.0.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-1.2.0.tgz#5c88036cf565e5df05558bfc911f8656df218884"
   integrity sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=
@@ -6281,7 +4730,7 @@ vinyl@^1.0.0, vinyl@^1.1.0:
     clone-stats "^0.0.1"
     replace-ext "0.0.1"
 
-vinyl@^2.0.1, vinyl@^2.0.2:
+vinyl@^2.0.2:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.1.0.tgz#021f9c2cf951d6b939943c89eb5ee5add4fd924c"
   integrity sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=
@@ -6306,12 +4755,10 @@ vinyl@~2.0.1:
     remove-trailing-separator "^1.0.1"
     replace-ext "^1.0.0"
 
-vm-browserify@0.0.4:
-  version "0.0.4"
-  resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73"
-  integrity sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=
-  dependencies:
-    indexof "0.0.1"
+vm-browserify@^1.0.1:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.0.tgz#bd76d6a23323e2ca8ffa12028dc04559c75f9019"
+  integrity sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==
 
 vscode-extension-telemetry@0.1.1:
   version "0.1.1"
@@ -6345,89 +4792,68 @@ vscode@^1.1.10:
     url-parse "^1.1.9"
     vinyl-source-stream "^1.1.0"
 
-watchpack@^1.5.0:
-  version "1.5.0"
-  resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.5.0.tgz#231e783af830a22f8966f65c4c4bacc814072eed"
-  integrity sha512-RSlipNQB1u48cq0wH/BNfCu1tD/cJ8ydFIkNYhp9o+3d+8unClkIovpW5qpFPgmL9OE48wfAnlZydXByWP82AA==
+watchpack@^1.6.0:
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00"
+  integrity sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==
   dependencies:
     chokidar "^2.0.2"
     graceful-fs "^4.1.2"
     neo-async "^2.5.0"
 
-webpack-addons@^1.1.5:
-  version "1.1.5"
-  resolved "https://registry.yarnpkg.com/webpack-addons/-/webpack-addons-1.1.5.tgz#2b178dfe873fb6e75e40a819fa5c26e4a9bc837a"
-  integrity sha512-MGO0nVniCLFAQz1qv22zM02QPjcpAoJdy7ED0i3Zy7SY1IecgXCm460ib7H/Wq7e9oL5VL6S2BxaObxwIcag0g==
+webpack-cli@^3.3.0:
+  version "3.3.10"
+  resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.10.tgz#17b279267e9b4fb549023fae170da8e6e766da13"
+  integrity sha512-u1dgND9+MXaEt74sJR4PR7qkPxXUSQ0RXYq8x1L6Jg1MYVEmGPrH6Ah6C4arD4r0J1P5HKjRqpab36k0eIzPqg==
   dependencies:
-    jscodeshift "^0.4.0"
+    chalk "2.4.2"
+    cross-spawn "6.0.5"
+    enhanced-resolve "4.1.0"
+    findup-sync "3.0.0"
+    global-modules "2.0.0"
+    import-local "2.0.0"
+    interpret "1.2.0"
+    loader-utils "1.2.3"
+    supports-color "6.1.0"
+    v8-compile-cache "2.0.3"
+    yargs "13.2.4"
 
-webpack-cli@^2.0.10:
-  version "2.0.10"
-  resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-2.0.10.tgz#09b888fbaa0b4288ba4b94c4462b6f559dfcf51e"
-  integrity sha512-PQWEOoXkhjBV4svPuESghZRc80VvDoSSRPaLiInWifDlRJgoPWpiLCFXyMLQTTaug7ApLrSEW7BcuwwY6DEv5w==
-  dependencies:
-    chalk "^2.3.1"
-    codecov "^3.0.0"
-    cross-spawn "^6.0.4"
-    diff "^3.3.0"
-    enhanced-resolve "^4.0.0"
-    glob-all "^3.1.0"
-    global "^4.3.2"
-    global-modules "^1.0.0"
-    got "^8.2.0"
-    inquirer "^5.1.0"
-    interpret "^1.0.4"
-    jscodeshift "^0.4.1"
-    listr "^0.13.0"
-    loader-utils "^1.1.0"
-    lodash "^4.17.5"
-    log-symbols "2.2.0"
-    mkdirp "^0.5.1"
-    p-each-series "^1.0.0"
-    p-lazy "^1.0.0"
-    prettier "^1.5.3"
-    recast "^0.14.4"
-    resolve-cwd "^2.0.0"
-    supports-color "^5.2.0"
-    uglifyjs-webpack-plugin "^1.2.2"
-    v8-compile-cache "^1.1.2"
-    webpack-addons "^1.1.5"
-    yargs "9.0.1"
-    yeoman-environment "^2.0.0"
-    yeoman-generator "github:ev1stensberg/generator#Feature-getArgument"
-
-webpack-sources@^1.0.1, webpack-sources@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.1.0.tgz#a101ebae59d6507354d71d8013950a3a8b7a5a54"
-  integrity sha512-aqYp18kPphgoO5c/+NaUvEeACtZjMESmDChuD3NBciVpah3XpMEU9VAAtIaB1BsfJWWTSdv8Vv1m3T0aRk2dUw==
+webpack-sources@^1.4.0, webpack-sources@^1.4.1:
+  version "1.4.3"
+  resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933"
+  integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==
   dependencies:
     source-list-map "^2.0.0"
     source-map "~0.6.1"
 
-webpack@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.1.0.tgz#91b6862e56eb3b18b79bb10b51866987ff10d2d6"
-  integrity sha512-ZFYcAZ44kOT+xsS5MS2H1fQr0PJkwQdYem/d17wacDkkupzsAkBJ3hDShWHdPVvWluFs6pfhHWw/dVso1m0rsA==
+webpack@^4.41.2:
+  version "4.41.2"
+  resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.2.tgz#c34ec76daa3a8468c9b61a50336d8e3303dce74e"
+  integrity sha512-Zhw69edTGfbz9/8JJoyRQ/pq8FYUoY0diOXqW0T6yhgdhCv6wr0hra5DwwWexNRns2Z2+gsnrNcbe9hbGBgk/A==
   dependencies:
-    acorn "^5.0.0"
-    acorn-dynamic-import "^3.0.0"
-    ajv "^6.1.0"
-    ajv-keywords "^3.1.0"
-    chrome-trace-event "^0.1.1"
-    enhanced-resolve "^4.0.0"
-    eslint-scope "^3.7.1"
-    loader-runner "^2.3.0"
-    loader-utils "^1.1.0"
-    memory-fs "~0.4.1"
-    micromatch "^3.1.8"
-    mkdirp "~0.5.0"
-    neo-async "^2.5.0"
-    node-libs-browser "^2.0.0"
-    schema-utils "^0.4.2"
-    tapable "^1.0.0"
-    uglifyjs-webpack-plugin "^1.1.1"
-    watchpack "^1.5.0"
-    webpack-sources "^1.0.1"
+    "@webassemblyjs/ast" "1.8.5"
+    "@webassemblyjs/helper-module-context" "1.8.5"
+    "@webassemblyjs/wasm-edit" "1.8.5"
+    "@webassemblyjs/wasm-parser" "1.8.5"
+    acorn "^6.2.1"
+    ajv "^6.10.2"
+    ajv-keywords "^3.4.1"
+    chrome-trace-event "^1.0.2"
+    enhanced-resolve "^4.1.0"
+    eslint-scope "^4.0.3"
+    json-parse-better-errors "^1.0.2"
+    loader-runner "^2.4.0"
+    loader-utils "^1.2.3"
+    memory-fs "^0.4.1"
+    micromatch "^3.1.10"
+    mkdirp "^0.5.1"
+    neo-async "^2.6.1"
+    node-libs-browser "^2.2.1"
+    schema-utils "^1.0.0"
+    tapable "^1.1.3"
+    terser-webpack-plugin "^1.4.1"
+    watchpack "^1.6.0"
+    webpack-sources "^1.4.1"
 
 which-module@^2.0.0:
   version "2.0.0"
@@ -6441,6 +4867,13 @@ which@^1.2.14, which@^1.2.9:
   dependencies:
     isexe "^2.0.0"
 
+which@^1.3.1:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
+  integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
+  dependencies:
+    isexe "^2.0.0"
+
 wide-align@^1.1.0:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710"
@@ -6448,36 +4881,27 @@ wide-align@^1.1.0:
   dependencies:
     string-width "^1.0.2"
 
-worker-farm@^1.5.2:
-  version "1.5.4"
-  resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.5.4.tgz#4debbe46b40edefcc717ebde74a90b1ae1e909a1"
-  integrity sha512-ITyClEvcfv0ozqJl1vmWFWhvI+OIrkbInYqkEPE50wFPXj8J9Gd3FYf8+CkZJXJJsQBYe+2DvmoK9Zhx5w8W+w==
+worker-farm@^1.7.0:
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8"
+  integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==
   dependencies:
     errno "~0.1.7"
-    xtend "~4.0.1"
 
-wrap-ansi@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
-  integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=
+wrap-ansi@^5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09"
+  integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==
   dependencies:
-    string-width "^1.0.1"
-    strip-ansi "^3.0.1"
+    ansi-styles "^3.2.0"
+    string-width "^3.0.0"
+    strip-ansi "^5.0.0"
 
 wrappy@1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
   integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
 
-write-file-atomic@^1.2.0:
-  version "1.3.4"
-  resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-1.3.4.tgz#f807a4f0b1d9e913ae7a48112e6cc3af1991b45f"
-  integrity sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8=
-  dependencies:
-    graceful-fs "^4.1.11"
-    imurmurhash "^0.1.4"
-    slide "^1.1.5"
-
 xml@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5"
@@ -6488,53 +4912,40 @@ xml@^1.0.0:
   resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
   integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68=
 
-y18n@^3.2.1:
-  version "3.2.1"
-  resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
-  integrity sha1-bRX7qITAhnnA136I53WegR4H+kE=
-
 y18n@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
   integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
 
-yallist@^2.1.2:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
-  integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=
+yallist@^3.0.2:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
+  integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
 
-yargs-parser@^7.0.0:
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9"
-  integrity sha1-jQrELxbqVd69MyyvTEA4s+P139k=
+yargs-parser@^13.1.0:
+  version "13.1.1"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0"
+  integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==
   dependencies:
-    camelcase "^4.1.0"
+    camelcase "^5.0.0"
+    decamelize "^1.2.0"
 
-yargs@9.0.1:
-  version "9.0.1"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-9.0.1.tgz#52acc23feecac34042078ee78c0c007f5085db4c"
-  integrity sha1-UqzCP+7Kw0BCB47njAwAf1CF20w=
+yargs@13.2.4:
+  version "13.2.4"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.4.tgz#0b562b794016eb9651b98bd37acf364aa5d6dc83"
+  integrity sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==
   dependencies:
-    camelcase "^4.1.0"
-    cliui "^3.2.0"
-    decamelize "^1.1.1"
-    get-caller-file "^1.0.1"
-    os-locale "^2.0.0"
-    read-pkg-up "^2.0.0"
+    cliui "^5.0.0"
+    find-up "^3.0.0"
+    get-caller-file "^2.0.1"
+    os-locale "^3.1.0"
     require-directory "^2.1.1"
-    require-main-filename "^1.0.1"
+    require-main-filename "^2.0.0"
     set-blocking "^2.0.0"
-    string-width "^2.0.0"
+    string-width "^3.0.0"
     which-module "^2.0.0"
-    y18n "^3.2.1"
-    yargs-parser "^7.0.0"
-
-yargs@~1.2.6:
-  version "1.2.6"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-1.2.6.tgz#9c7b4a82fd5d595b2bf17ab6dcc43135432fe34b"
-  integrity sha1-nHtKgv1dWVsr8Xq23MQxNUMv40s=
-  dependencies:
-    minimist "^0.1.0"
+    y18n "^4.0.0"
+    yargs-parser "^13.1.0"
 
 yauzl@^2.2.1:
   version "2.9.1"
@@ -6551,73 +4962,6 @@ yazl@^2.2.1:
   dependencies:
     buffer-crc32 "~0.2.3"
 
-yeoman-environment@^1.1.0:
-  version "1.6.6"
-  resolved "https://registry.yarnpkg.com/yeoman-environment/-/yeoman-environment-1.6.6.tgz#cd85fa67d156060e440d7807d7ef7cf0d2d1d671"
-  integrity sha1-zYX6Z9FWBg5EDXgH1+988NLR1nE=
-  dependencies:
-    chalk "^1.0.0"
-    debug "^2.0.0"
-    diff "^2.1.2"
-    escape-string-regexp "^1.0.2"
-    globby "^4.0.0"
-    grouped-queue "^0.3.0"
-    inquirer "^1.0.2"
-    lodash "^4.11.1"
-    log-symbols "^1.0.1"
-    mem-fs "^1.1.0"
-    text-table "^0.2.0"
-    untildify "^2.0.0"
-
-yeoman-environment@^2.0.0:
-  version "2.0.5"
-  resolved "https://registry.yarnpkg.com/yeoman-environment/-/yeoman-environment-2.0.5.tgz#84f22bafa84088971fe99ea85f654a3a3dd2b693"
-  integrity sha512-6/W7/B54OPHJXob0n0+pmkwFsirC8cokuQkPSmT/D0lCcSxkKtg/BA6ZnjUBIwjuGqmw3DTrT4en++htaUju5g==
-  dependencies:
-    chalk "^2.1.0"
-    debug "^3.1.0"
-    diff "^3.3.1"
-    escape-string-regexp "^1.0.2"
-    globby "^6.1.0"
-    grouped-queue "^0.3.3"
-    inquirer "^3.3.0"
-    is-scoped "^1.0.0"
-    lodash "^4.17.4"
-    log-symbols "^2.1.0"
-    mem-fs "^1.1.0"
-    text-table "^0.2.0"
-    untildify "^3.0.2"
-
-"yeoman-generator@github:ev1stensberg/generator#Feature-getArgument":
-  version "1.1.1"
-  resolved "https://codeload.github.com/ev1stensberg/generator/tar.gz/9e24fa31c85302ca1145ae34fc68b4f133251ca0"
-  dependencies:
-    async "^2.0.0"
-    chalk "^1.0.0"
-    cli-table "^0.3.1"
-    cross-spawn "^5.0.1"
-    dargs "^5.1.0"
-    dateformat "^2.0.0"
-    debug "^2.1.0"
-    detect-conflict "^1.0.0"
-    error "^7.0.2"
-    find-up "^2.1.0"
-    github-username "^4.0.0"
-    istextorbinary "^2.1.0"
-    lodash "^4.11.1"
-    mem-fs-editor "^3.0.0"
-    minimist "^1.2.0"
-    mkdirp "^0.5.0"
-    pretty-bytes "^4.0.2"
-    read-chunk "^2.0.0"
-    read-pkg-up "^2.0.0"
-    rimraf "^2.2.0"
-    run-async "^2.0.0"
-    shelljs "^0.7.0"
-    text-table "^0.2.0"
-    through2 "^2.0.0"
-    yeoman-environment "^1.1.0"
-
 zone.js@0.7.6:
   version "0.7.6"
   resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009"
diff --git a/extensions/merge-conflict/package.json b/extensions/merge-conflict/package.json
index 2828669b04..815a412492 100644
--- a/extensions/merge-conflict/package.json
+++ b/extensions/merge-conflict/package.json
@@ -138,6 +138,6 @@
     "vscode-nls": "^4.0.0"
   },
   "devDependencies": {
-    "@types/node": "^10.14.8"
+    "@types/node": "^12.11.7"
   }
 }
diff --git a/extensions/merge-conflict/src/documentMergeConflict.ts b/extensions/merge-conflict/src/documentMergeConflict.ts
index 006260d86b..eca2e3d1b5 100644
--- a/extensions/merge-conflict/src/documentMergeConflict.ts
+++ b/extensions/merge-conflict/src/documentMergeConflict.ts
@@ -35,7 +35,7 @@ export class DocumentMergeConflict implements interfaces.IDocumentMergeConflict
 	public applyEdit(type: interfaces.CommitType, document: vscode.TextDocument, edit: { replace(range: vscode.Range, newText: string): void; }): void {
 
 		// Each conflict is a set of ranges as follows, note placements or newlines
-		// which may not in in spans
+		// which may not in spans
 		// [ Conflict Range             -- (Entire content below)
 		//   [ Current Header ]\n       -- >>>>> Header
 		//   [ Current Content ]        -- (content)
@@ -75,4 +75,4 @@ export class DocumentMergeConflict implements interfaces.IDocumentMergeConflict
 	private isNewlineOnly(text: string) {
 		return text === '\n' || text === '\r\n';
 	}
-}
\ No newline at end of file
+}
diff --git a/extensions/merge-conflict/yarn.lock b/extensions/merge-conflict/yarn.lock
index e6247e2925..7af62a72ce 100644
--- a/extensions/merge-conflict/yarn.lock
+++ b/extensions/merge-conflict/yarn.lock
@@ -2,10 +2,10 @@
 # yarn lockfile v1
 
 
-"@types/node@^10.14.8":
-  version "10.14.8"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9"
-  integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw==
+"@types/node@^12.11.7":
+  version "12.11.7"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a"
+  integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA==
 
 vscode-nls@^4.0.0:
   version "4.0.0"
diff --git a/extensions/notebook/package.json b/extensions/notebook/package.json
index 5a8120142d..707a1bb215 100644
--- a/extensions/notebook/package.json
+++ b/extensions/notebook/package.json
@@ -457,7 +457,7 @@
     "@types/glob": "^7.1.1",
     "@types/js-yaml": "^3.12.1",
     "@types/mocha": "^5.2.5",
-    "@types/node": "^11.9.3",
+    "@types/node": "^12.11.7",
     "@types/request": "^2.48.1",
     "@types/rimraf": "^2.0.2",
     "@types/temp-write": "^3.3.0",
diff --git a/extensions/notebook/yarn.lock b/extensions/notebook/yarn.lock
index b02097a258..ae3493c2ed 100644
--- a/extensions/notebook/yarn.lock
+++ b/extensions/notebook/yarn.lock
@@ -161,10 +161,10 @@
   resolved "https://registry.yarnpkg.com/@types/node/-/node-11.13.4.tgz#f83ec3c3e05b174b7241fadeb6688267fe5b22ca"
   integrity sha512-+rabAZZ3Yn7tF/XPGHupKIL5EcAbrLxnTr/hgQICxbeuAfWtT0UZSfULE+ndusckBItcv4o6ZeOJplQikVcLvQ==
 
-"@types/node@^11.9.3":
-  version "11.9.3"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-11.9.3.tgz#14adbb5ab8cd563f549fbae8dbe92e0b7d6e76cc"
-  integrity sha512-DMiqG51GwES/c4ScBY0u5bDlH44+oY8AeYHjY1SGCWidD7h08o1dfHue/TGK7REmif2KiJzaUskO+Q0eaeZ2fQ==
+"@types/node@^12.11.7":
+  version "12.12.7"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.7.tgz#01e4ea724d9e3bd50d90c11fd5980ba317d8fa11"
+  integrity sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w==
 
 "@types/request@^2.48.1":
   version "2.48.1"
diff --git a/extensions/package.json b/extensions/package.json
index 688a5c0506..d1030bbe6d 100644
--- a/extensions/package.json
+++ b/extensions/package.json
@@ -3,7 +3,7 @@
   "version": "0.0.1",
   "description": "Dependencies shared by all extensions",
   "dependencies": {
-    "typescript": "3.7.2"
+    "typescript": "3.7.3-insiders.20191123"
   },
   "scripts": {
     "postinstall": "node ./postinstall"
diff --git a/extensions/python/cgmanifest.json b/extensions/python/cgmanifest.json
index 139ae46369..3ee82895a8 100644
--- a/extensions/python/cgmanifest.json
+++ b/extensions/python/cgmanifest.json
@@ -6,7 +6,7 @@
 				"git": {
 					"name": "MagicStack/MagicPython",
 					"repositoryUrl": "https://github.com/MagicStack/MagicPython",
-					"commitHash": "38422d302fe0b3e7716d26ce8cd7d0b9685f3a38"
+					"commitHash": "c0f8d514bbe6e9d3899f2b002bcd6971aef5e34b"
 				}
 			},
 			"license": "MIT",
diff --git a/extensions/python/syntaxes/MagicPython.tmLanguage.json b/extensions/python/syntaxes/MagicPython.tmLanguage.json
index d89e7ed110..f59618f75f 100644
--- a/extensions/python/syntaxes/MagicPython.tmLanguage.json
+++ b/extensions/python/syntaxes/MagicPython.tmLanguage.json
@@ -4,7 +4,7 @@
 		"If you want to provide a fix or improvement, please create a pull request against the original repository.",
 		"Once accepted there, we are happy to receive an update request."
 	],
-	"version": "https://github.com/MagicStack/MagicPython/commit/38422d302fe0b3e7716d26ce8cd7d0b9685f3a38",
+	"version": "https://github.com/MagicStack/MagicPython/commit/c0f8d514bbe6e9d3899f2b002bcd6971aef5e34b",
 	"name": "MagicPython",
 	"scopeName": "source.python",
 	"patterns": [
@@ -374,6 +374,7 @@
 			]
 		},
 		"member-access": {
+			"name": "meta.member.access.python",
 			"begin": "(\\.)\\s*(?!\\.)",
 			"end": "(?x)\n  # stop when you've just read non-whitespace followed by non-word\n  # i.e. when finished reading an identifier or function call\n  (?<=\\S)(?=\\W) |\n  # stop when seeing the start of something that's not a word,\n  # i.e. when seeing a non-identifier\n  (^|(?<=\\s))(?=[^\\\\\\w\\s]) |\n  $\n",
 			"beginCaptures": {
@@ -1067,6 +1068,7 @@
 			}
 		},
 		"member-access-class": {
+			"name": "meta.member.access.python",
 			"begin": "(\\.)\\s*(?!\\.)",
 			"end": "(?<=\\S)(?=\\W)|$",
 			"beginCaptures": {
diff --git a/extensions/python/test/colorize-results/test-freeze-56377_py.json b/extensions/python/test/colorize-results/test-freeze-56377_py.json
index 364269c855..63889e26fe 100644
--- a/extensions/python/test/colorize-results/test-freeze-56377_py.json
+++ b/extensions/python/test/colorize-results/test-freeze-56377_py.json
@@ -287,7 +287,7 @@
 	},
 	{
 		"c": ".",
-		"t": "source.python punctuation.separator.period.python",
+		"t": "source.python meta.member.access.python punctuation.separator.period.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -298,7 +298,7 @@
 	},
 	{
 		"c": "request",
-		"t": "source.python",
+		"t": "source.python meta.member.access.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -309,7 +309,7 @@
 	},
 	{
 		"c": ".",
-		"t": "source.python punctuation.separator.period.python",
+		"t": "source.python meta.member.access.python punctuation.separator.period.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -320,7 +320,7 @@
 	},
 	{
 		"c": "META",
-		"t": "source.python constant.other.caps.python",
+		"t": "source.python meta.member.access.python constant.other.caps.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -331,7 +331,7 @@
 	},
 	{
 		"c": ".",
-		"t": "source.python punctuation.separator.period.python",
+		"t": "source.python meta.member.access.python punctuation.separator.period.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -342,7 +342,7 @@
 	},
 	{
 		"c": "items",
-		"t": "source.python meta.function-call.python meta.function-call.generic.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.generic.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -353,7 +353,7 @@
 	},
 	{
 		"c": "(",
-		"t": "source.python meta.function-call.python punctuation.definition.arguments.begin.python",
+		"t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.begin.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -364,7 +364,7 @@
 	},
 	{
 		"c": ")",
-		"t": "source.python meta.function-call.python punctuation.definition.arguments.end.python",
+		"t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.end.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -408,7 +408,7 @@
 	},
 	{
 		"c": ".",
-		"t": "source.python punctuation.separator.period.python",
+		"t": "source.python meta.member.access.python punctuation.separator.period.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -419,7 +419,7 @@
 	},
 	{
 		"c": "startswith",
-		"t": "source.python meta.function-call.python meta.function-call.generic.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.generic.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -430,7 +430,7 @@
 	},
 	{
 		"c": "(",
-		"t": "source.python meta.function-call.python punctuation.definition.arguments.begin.python",
+		"t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.begin.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -441,7 +441,7 @@
 	},
 	{
 		"c": "'",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.quoted.single.python punctuation.definition.string.begin.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.quoted.single.python punctuation.definition.string.begin.python",
 		"r": {
 			"dark_plus": "string: #CE9178",
 			"light_plus": "string: #A31515",
@@ -452,7 +452,7 @@
 	},
 	{
 		"c": "HTTP_",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.quoted.single.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.quoted.single.python",
 		"r": {
 			"dark_plus": "string: #CE9178",
 			"light_plus": "string: #A31515",
@@ -463,7 +463,7 @@
 	},
 	{
 		"c": "'",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.quoted.single.python punctuation.definition.string.end.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.quoted.single.python punctuation.definition.string.end.python",
 		"r": {
 			"dark_plus": "string: #CE9178",
 			"light_plus": "string: #A31515",
@@ -474,7 +474,7 @@
 	},
 	{
 		"c": ")",
-		"t": "source.python meta.function-call.python punctuation.definition.arguments.end.python",
+		"t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.end.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
diff --git a/extensions/python/test/colorize-results/test_py.json b/extensions/python/test/colorize-results/test_py.json
index 8586d7c9b9..2c31265862 100644
--- a/extensions/python/test/colorize-results/test_py.json
+++ b/extensions/python/test/colorize-results/test_py.json
@@ -419,7 +419,7 @@
 	},
 	{
 		"c": ".",
-		"t": "source.python punctuation.separator.period.python",
+		"t": "source.python meta.member.access.python punctuation.separator.period.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -430,7 +430,7 @@
 	},
 	{
 		"c": "size",
-		"t": "source.python",
+		"t": "source.python meta.member.access.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -2223,7 +2223,7 @@
 	},
 	{
 		"c": ".",
-		"t": "source.python punctuation.separator.period.python",
+		"t": "source.python meta.member.access.python punctuation.separator.period.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -2234,7 +2234,7 @@
 	},
 	{
 		"c": "next",
-		"t": "source.python meta.function-call.python meta.function-call.generic.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.generic.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -2245,7 +2245,7 @@
 	},
 	{
 		"c": "(",
-		"t": "source.python meta.function-call.python punctuation.definition.arguments.begin.python",
+		"t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.begin.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -2256,7 +2256,7 @@
 	},
 	{
 		"c": ")",
-		"t": "source.python meta.function-call.python punctuation.definition.arguments.end.python",
+		"t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.end.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -4533,7 +4533,7 @@
 	},
 	{
 		"c": ".",
-		"t": "source.python punctuation.separator.period.python",
+		"t": "source.python meta.member.access.python punctuation.separator.period.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -4543,7 +4543,18 @@
 		}
 	},
 	{
-		"c": "fn ",
+		"c": "fn",
+		"t": "source.python meta.member.access.python",
+		"r": {
+			"dark_plus": "default: #D4D4D4",
+			"light_plus": "default: #000000",
+			"dark_vs": "default: #D4D4D4",
+			"light_vs": "default: #000000",
+			"hc_black": "default: #FFFFFF"
+		}
+	},
+	{
+		"c": " ",
 		"t": "source.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
@@ -4599,7 +4610,7 @@
 	},
 	{
 		"c": ".",
-		"t": "source.python punctuation.separator.period.python",
+		"t": "source.python meta.member.access.python punctuation.separator.period.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -4609,7 +4620,18 @@
 		}
 	},
 	{
-		"c": "memo ",
+		"c": "memo",
+		"t": "source.python meta.member.access.python",
+		"r": {
+			"dark_plus": "default: #D4D4D4",
+			"light_plus": "default: #000000",
+			"dark_vs": "default: #D4D4D4",
+			"light_vs": "default: #000000",
+			"hc_black": "default: #FFFFFF"
+		}
+	},
+	{
+		"c": " ",
 		"t": "source.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
@@ -4885,7 +4907,7 @@
 	},
 	{
 		"c": ".",
-		"t": "source.python punctuation.separator.period.python",
+		"t": "source.python meta.member.access.python punctuation.separator.period.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -4896,7 +4918,7 @@
 	},
 	{
 		"c": "memo",
-		"t": "source.python",
+		"t": "source.python meta.member.access.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -4940,7 +4962,7 @@
 	},
 	{
 		"c": ".",
-		"t": "source.python punctuation.separator.period.python",
+		"t": "source.python meta.member.access.python punctuation.separator.period.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -4951,7 +4973,7 @@
 	},
 	{
 		"c": "memo",
-		"t": "source.python meta.item-access.python",
+		"t": "source.python meta.member.access.python meta.item-access.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -4962,7 +4984,7 @@
 	},
 	{
 		"c": "[",
-		"t": "source.python meta.item-access.python punctuation.definition.arguments.begin.python",
+		"t": "source.python meta.member.access.python meta.item-access.python punctuation.definition.arguments.begin.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -4973,7 +4995,7 @@
 	},
 	{
 		"c": "args",
-		"t": "source.python meta.item-access.python meta.item-access.arguments.python",
+		"t": "source.python meta.member.access.python meta.item-access.python meta.item-access.arguments.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -4984,7 +5006,7 @@
 	},
 	{
 		"c": "]",
-		"t": "source.python meta.item-access.python punctuation.definition.arguments.end.python",
+		"t": "source.python meta.member.access.python meta.item-access.python punctuation.definition.arguments.end.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5039,7 +5061,7 @@
 	},
 	{
 		"c": ".",
-		"t": "source.python punctuation.separator.period.python",
+		"t": "source.python meta.member.access.python punctuation.separator.period.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5050,7 +5072,7 @@
 	},
 	{
 		"c": "fn",
-		"t": "source.python meta.function-call.python meta.function-call.generic.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.generic.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5061,7 +5083,7 @@
 	},
 	{
 		"c": "(",
-		"t": "source.python meta.function-call.python punctuation.definition.arguments.begin.python",
+		"t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.begin.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5072,7 +5094,7 @@
 	},
 	{
 		"c": "*",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python keyword.operator.unpacking.arguments.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python keyword.operator.unpacking.arguments.python",
 		"r": {
 			"dark_plus": "keyword.operator: #D4D4D4",
 			"light_plus": "keyword.operator: #000000",
@@ -5083,7 +5105,7 @@
 	},
 	{
 		"c": "args",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5094,7 +5116,7 @@
 	},
 	{
 		"c": ")",
-		"t": "source.python meta.function-call.python punctuation.definition.arguments.end.python",
+		"t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.end.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5149,7 +5171,7 @@
 	},
 	{
 		"c": ".",
-		"t": "source.python punctuation.separator.period.python",
+		"t": "source.python meta.member.access.python punctuation.separator.period.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5160,7 +5182,7 @@
 	},
 	{
 		"c": "memo",
-		"t": "source.python meta.item-access.python",
+		"t": "source.python meta.member.access.python meta.item-access.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5171,7 +5193,7 @@
 	},
 	{
 		"c": "[",
-		"t": "source.python meta.item-access.python punctuation.definition.arguments.begin.python",
+		"t": "source.python meta.member.access.python meta.item-access.python punctuation.definition.arguments.begin.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5182,7 +5204,7 @@
 	},
 	{
 		"c": "args",
-		"t": "source.python meta.item-access.python meta.item-access.arguments.python",
+		"t": "source.python meta.member.access.python meta.item-access.python meta.item-access.arguments.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5193,7 +5215,7 @@
 	},
 	{
 		"c": "]",
-		"t": "source.python meta.item-access.python punctuation.definition.arguments.end.python",
+		"t": "source.python meta.member.access.python meta.item-access.python punctuation.definition.arguments.end.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5237,7 +5259,7 @@
 	},
 	{
 		"c": ".",
-		"t": "source.python punctuation.separator.period.python",
+		"t": "source.python meta.member.access.python punctuation.separator.period.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5248,7 +5270,7 @@
 	},
 	{
 		"c": "search",
-		"t": "source.python meta.function-call.python meta.function-call.generic.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.generic.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5259,7 +5281,7 @@
 	},
 	{
 		"c": "(",
-		"t": "source.python meta.function-call.python punctuation.definition.arguments.begin.python",
+		"t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.begin.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5270,7 +5292,7 @@
 	},
 	{
 		"c": "r",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python storage.type.string.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python storage.type.string.python",
 		"r": {
 			"dark_plus": "storage.type: #569CD6",
 			"light_plus": "storage.type: #0000FF",
@@ -5281,7 +5303,7 @@
 	},
 	{
 		"c": "\"",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python punctuation.definition.string.begin.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python punctuation.definition.string.begin.python",
 		"r": {
 			"dark_plus": "string.regexp: #D16969",
 			"light_plus": "string.regexp: #811F3F",
@@ -5292,7 +5314,7 @@
 	},
 	{
 		"c": "(",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.begin.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.begin.regexp",
 		"r": {
 			"dark_plus": "support.other.parenthesis.regexp: #CE9178",
 			"light_plus": "support.other.parenthesis.regexp: #D16969",
@@ -5303,7 +5325,7 @@
 	},
 	{
 		"c": "[",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp punctuation.character.set.begin.regexp constant.other.set.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp punctuation.character.set.begin.regexp constant.other.set.regexp",
 		"r": {
 			"dark_plus": "punctuation.character.set.begin.regexp: #CE9178",
 			"light_plus": "punctuation.character.set.begin.regexp: #D16969",
@@ -5314,7 +5336,7 @@
 	},
 	{
 		"c": "0-9-",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp constant.character.set.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp constant.character.set.regexp",
 		"r": {
 			"dark_plus": "constant.character.set.regexp: #D16969",
 			"light_plus": "constant.character.set.regexp: #811F3F",
@@ -5325,7 +5347,7 @@
 	},
 	{
 		"c": "]",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp punctuation.character.set.end.regexp constant.other.set.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp punctuation.character.set.end.regexp constant.other.set.regexp",
 		"r": {
 			"dark_plus": "punctuation.character.set.end.regexp: #CE9178",
 			"light_plus": "punctuation.character.set.end.regexp: #D16969",
@@ -5336,7 +5358,7 @@
 	},
 	{
 		"c": "*",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp",
 		"r": {
 			"dark_plus": "keyword.operator.quantifier.regexp: #D7BA7D",
 			"light_plus": "keyword.operator.quantifier.regexp: #000000",
@@ -5347,7 +5369,7 @@
 	},
 	{
 		"c": ")",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.end.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.end.regexp",
 		"r": {
 			"dark_plus": "support.other.parenthesis.regexp: #CE9178",
 			"light_plus": "support.other.parenthesis.regexp: #D16969",
@@ -5358,7 +5380,7 @@
 	},
 	{
 		"c": "\\s",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.escape.special.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.escape.special.regexp",
 		"r": {
 			"dark_plus": "string.regexp: #D16969",
 			"light_plus": "string.regexp: #811F3F",
@@ -5369,7 +5391,7 @@
 	},
 	{
 		"c": "*",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp",
 		"r": {
 			"dark_plus": "keyword.operator.quantifier.regexp: #D7BA7D",
 			"light_plus": "keyword.operator.quantifier.regexp: #000000",
@@ -5380,7 +5402,7 @@
 	},
 	{
 		"c": "(",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.begin.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.begin.regexp",
 		"r": {
 			"dark_plus": "support.other.parenthesis.regexp: #CE9178",
 			"light_plus": "support.other.parenthesis.regexp: #D16969",
@@ -5391,7 +5413,7 @@
 	},
 	{
 		"c": "[",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp punctuation.character.set.begin.regexp constant.other.set.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp punctuation.character.set.begin.regexp constant.other.set.regexp",
 		"r": {
 			"dark_plus": "punctuation.character.set.begin.regexp: #CE9178",
 			"light_plus": "punctuation.character.set.begin.regexp: #D16969",
@@ -5402,7 +5424,7 @@
 	},
 	{
 		"c": "A-Za-z",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp constant.character.set.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp constant.character.set.regexp",
 		"r": {
 			"dark_plus": "constant.character.set.regexp: #D16969",
 			"light_plus": "constant.character.set.regexp: #811F3F",
@@ -5413,7 +5435,7 @@
 	},
 	{
 		"c": "]",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp punctuation.character.set.end.regexp constant.other.set.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp punctuation.character.set.end.regexp constant.other.set.regexp",
 		"r": {
 			"dark_plus": "punctuation.character.set.end.regexp: #CE9178",
 			"light_plus": "punctuation.character.set.end.regexp: #D16969",
@@ -5424,7 +5446,7 @@
 	},
 	{
 		"c": "+",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp",
 		"r": {
 			"dark_plus": "keyword.operator.quantifier.regexp: #D7BA7D",
 			"light_plus": "keyword.operator.quantifier.regexp: #000000",
@@ -5435,7 +5457,7 @@
 	},
 	{
 		"c": ")",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.end.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.end.regexp",
 		"r": {
 			"dark_plus": "support.other.parenthesis.regexp: #CE9178",
 			"light_plus": "support.other.parenthesis.regexp: #D16969",
@@ -5446,7 +5468,7 @@
 	},
 	{
 		"c": ",",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python",
 		"r": {
 			"dark_plus": "string.regexp: #D16969",
 			"light_plus": "string.regexp: #811F3F",
@@ -5457,7 +5479,7 @@
 	},
 	{
 		"c": "\\s",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.escape.special.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.escape.special.regexp",
 		"r": {
 			"dark_plus": "string.regexp: #D16969",
 			"light_plus": "string.regexp: #811F3F",
@@ -5468,7 +5490,7 @@
 	},
 	{
 		"c": "+",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp",
 		"r": {
 			"dark_plus": "keyword.operator.quantifier.regexp: #D7BA7D",
 			"light_plus": "keyword.operator.quantifier.regexp: #000000",
@@ -5479,7 +5501,7 @@
 	},
 	{
 		"c": "(",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.begin.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.begin.regexp",
 		"r": {
 			"dark_plus": "support.other.parenthesis.regexp: #CE9178",
 			"light_plus": "support.other.parenthesis.regexp: #D16969",
@@ -5490,7 +5512,7 @@
 	},
 	{
 		"c": ".",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.match.any.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.match.any.regexp",
 		"r": {
 			"dark_plus": "string.regexp: #D16969",
 			"light_plus": "string.regexp: #811F3F",
@@ -5501,7 +5523,7 @@
 	},
 	{
 		"c": "*",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp",
 		"r": {
 			"dark_plus": "keyword.operator.quantifier.regexp: #D7BA7D",
 			"light_plus": "keyword.operator.quantifier.regexp: #000000",
@@ -5512,7 +5534,7 @@
 	},
 	{
 		"c": ")",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.end.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.end.regexp",
 		"r": {
 			"dark_plus": "support.other.parenthesis.regexp: #CE9178",
 			"light_plus": "support.other.parenthesis.regexp: #D16969",
@@ -5523,7 +5545,7 @@
 	},
 	{
 		"c": "\"",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python punctuation.definition.string.end.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python punctuation.definition.string.end.python",
 		"r": {
 			"dark_plus": "string.regexp: #D16969",
 			"light_plus": "string.regexp: #811F3F",
@@ -5534,7 +5556,7 @@
 	},
 	{
 		"c": ",",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python punctuation.separator.arguments.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python punctuation.separator.arguments.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5545,7 +5567,7 @@
 	},
 	{
 		"c": " i",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5556,7 +5578,7 @@
 	},
 	{
 		"c": ")",
-		"t": "source.python meta.function-call.python punctuation.definition.arguments.end.python",
+		"t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.end.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
diff --git a/extensions/resource-deployment/package.json b/extensions/resource-deployment/package.json
index 19c5c915d3..18fb2bdcc1 100644
--- a/extensions/resource-deployment/package.json
+++ b/extensions/resource-deployment/package.json
@@ -356,7 +356,7 @@
 	"dependencies": {
 		"linux-release-info": "^2.0.0",
 		"promisify-child-process": "^3.1.1",
-		"sudo-prompt": "^9.0.0",
+    "sudo-prompt": "9.1.1",
 		"vscode-nls": "^4.0.0",
 		"yamljs": "^0.3.0"
 	},
diff --git a/extensions/resource-deployment/src/services/platformService.ts b/extensions/resource-deployment/src/services/platformService.ts
index 8d42dfb955..4522050a7e 100644
--- a/extensions/resource-deployment/src/services/platformService.ts
+++ b/extensions/resource-deployment/src/services/platformService.ts
@@ -182,7 +182,7 @@ export class PlatformService implements IPlatformService {
 		}
 	}
 
-	private sudoExec(command: string, options: sudo.SudoOptions): Promise {
+	private sudoExec(command: string, options: { [key: string]: any }): Promise {
 		return new Promise((resolve, reject) => {
 			sudo.exec(command, options, (error, stdout, stderr) => {
 				if (error) {
diff --git a/extensions/resource-deployment/src/typings/ref.d.ts b/extensions/resource-deployment/src/typings/ref.d.ts
index 31c2c56a19..cfdf5dd135 100644
--- a/extensions/resource-deployment/src/typings/ref.d.ts
+++ b/extensions/resource-deployment/src/typings/ref.d.ts
@@ -6,5 +6,4 @@
 /// 
 /// 
 /// 
-/// 
 /// 
diff --git a/extensions/resource-deployment/yarn.lock b/extensions/resource-deployment/yarn.lock
index b00b46b394..73d20a1741 100644
--- a/extensions/resource-deployment/yarn.lock
+++ b/extensions/resource-deployment/yarn.lock
@@ -643,10 +643,10 @@ strip-ansi@^4.0.0:
   dependencies:
     ansi-regex "^3.0.0"
 
-sudo-prompt@^9.0.0:
-  version "9.0.0"
-  resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.0.0.tgz#eebedeee9fcd6f661324e6bb46335e3288e8dc8a"
-  integrity sha512-kUn5fiOk0nhY2oKD9onIkcNCE4Zt85WTsvOfSmqCplmlEvXCcPOmp1npH5YWuf8Bmyy9wLWkIxx+D+8cThBORQ==
+sudo-prompt@9.1.1:
+  version "9.1.1"
+  resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.1.1.tgz#73853d729770392caec029e2470db9c221754db0"
+  integrity sha512-es33J1g2HjMpyAhz8lOR+ICmXXAqTuKbuXuUWLhOLew20oN9oUCgCJx615U/v7aioZg7IX5lIh9x34vwneu4pA==
 
 supports-color@4.4.0:
   version "4.4.0"
diff --git a/extensions/schema-compare/package.json b/extensions/schema-compare/package.json
index 5e234a9375..360c16ab6b 100644
--- a/extensions/schema-compare/package.json
+++ b/extensions/schema-compare/package.json
@@ -64,7 +64,7 @@
   },
   "devDependencies": {
     "@types/mocha": "^5.2.5",
-    "@types/node": "^10.14.8",
+    "@types/node": "^12.11.7",
     "mocha": "^5.2.0",
     "mocha-junit-reporter": "^1.17.0",
     "mocha-multi-reporters": "^1.1.7",
diff --git a/extensions/schema-compare/yarn.lock b/extensions/schema-compare/yarn.lock
index 7ee2b32bd6..f624163030 100644
--- a/extensions/schema-compare/yarn.lock
+++ b/extensions/schema-compare/yarn.lock
@@ -7,10 +7,10 @@
   resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.6.tgz#b8622d50557dd155e9f2f634b7d68fd38de5e94b"
   integrity sha512-1axi39YdtBI7z957vdqXI4Ac25e7YihYQtJa+Clnxg1zTJEaIRbndt71O3sP4GAMgiAm0pY26/b9BrY4MR/PMw==
 
-"@types/node@^10.14.8":
-  version "10.14.17"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.17.tgz#b96d4dd3e427382482848948041d3754d40fd5ce"
-  integrity sha512-p/sGgiPaathCfOtqu2fx5Mu1bcjuP8ALFg4xpGgNkcin7LwRyzUKniEHBKdcE1RPsenq5JVPIpMTJSygLboygQ==
+"@types/node@^12.11.7":
+  version "12.12.7"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.7.tgz#01e4ea724d9e3bd50d90c11fd5980ba317d8fa11"
+  integrity sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w==
 
 ajv@^6.5.5:
   version "6.10.0"
diff --git a/extensions/search-result/README.md b/extensions/search-result/README.md
new file mode 100644
index 0000000000..a28a54db1b
--- /dev/null
+++ b/extensions/search-result/README.md
@@ -0,0 +1,3 @@
+# Language Features for Search Result files
+
+**Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled.
diff --git a/src/typings/yazl.d.ts b/extensions/search-result/extension.webpack.config.js
similarity index 60%
rename from src/typings/yazl.d.ts
rename to extensions/search-result/extension.webpack.config.js
index 172f8d66ad..f35561d9f2 100644
--- a/src/typings/yazl.d.ts
+++ b/extensions/search-result/extension.webpack.config.js
@@ -3,13 +3,18 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
-declare module 'yazl' {
-	import * as stream from 'stream';
+//@ts-check
 
-	class ZipFile {
-		outputStream: stream.Stream;
-		addBuffer(buffer: Buffer, path: string): void;
-		addFile(localPath: string, path: string): void;
-		end(): void;
+'use strict';
+
+const withDefaults = require('../shared.webpack.config');
+
+module.exports = withDefaults({
+	context: __dirname,
+	resolve: {
+		mainFields: ['module', 'main']
+	},
+	entry: {
+		extension: './src/extension.ts',
 	}
-}
\ No newline at end of file
+});
diff --git a/extensions/search-result/package.json b/extensions/search-result/package.json
new file mode 100644
index 0000000000..bc7c6cf951
--- /dev/null
+++ b/extensions/search-result/package.json
@@ -0,0 +1,64 @@
+{
+  "name": "search-result",
+  "displayName": "%displayName%",
+  "description": "%description%",
+  "version": "1.0.0",
+  "publisher": "vscode",
+  "license": "MIT",
+  "engines": {
+    "vscode": "^1.39.0"
+  },
+  "categories": [
+    "Programming Languages"
+  ],
+  "main": "./out/extension.js",
+  "activationEvents": [
+    "*"
+  ],
+  "scripts": {
+    "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:search-result ./tsconfig.json"
+  },
+  "contributes": {
+    "commands": [
+      {
+        "command": "searchResult.rerunSearch",
+        "title": "%searchResult.rerunSearch.title%",
+        "category": "Search Result",
+        "icon": {
+          "light": "./src/media/refresh-light.svg",
+          "dark": "./src/media/refresh-dark.svg"
+        }
+      }
+    ],
+    "menus": {
+      "editor/title": [
+        {
+          "command": "searchResult.rerunSearch",
+          "when": "editorLangId == search-result",
+          "group": "navigation"
+        }
+      ]
+    },
+    "languages": [
+      {
+        "id": "search-result",
+        "extensions": [
+          ".code-search"
+        ],
+        "aliases": [
+          "Search Result"
+        ]
+      }
+    ],
+    "grammars": [
+      {
+        "language": "search-result",
+        "scopeName": "text.searchResult",
+        "path": "./syntaxes/searchResult.tmLanguage.json"
+      }
+    ]
+  },
+  "devDependencies": {
+    "vscode": "^1.1.36"
+  }
+}
diff --git a/extensions/search-result/package.nls.json b/extensions/search-result/package.nls.json
new file mode 100644
index 0000000000..694f6b61d8
--- /dev/null
+++ b/extensions/search-result/package.nls.json
@@ -0,0 +1,5 @@
+{
+	"displayName": "Search Result",
+	"description": "Provides syntax highlighting and language features for tabbed search results.",
+	"searchResult.rerunSearch.title": "Search Again"
+}
diff --git a/extensions/search-result/src/extension.ts b/extensions/search-result/src/extension.ts
new file mode 100644
index 0000000000..56cbecd087
--- /dev/null
+++ b/extensions/search-result/src/extension.ts
@@ -0,0 +1,186 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as vscode from 'vscode';
+import * as pathUtils from 'path';
+
+const FILE_LINE_REGEX = /^(\S.*):$/;
+const RESULT_LINE_REGEX = /^(\s+)(\d+):(\s+)(.*)$/;
+const SEARCH_RESULT_SELECTOR = { language: 'search-result' };
+
+let cachedLastParse: { version: number, parse: ParsedSearchResults } | undefined;
+
+export function activate() {
+
+	vscode.commands.registerCommand('searchResult.rerunSearch', () => vscode.commands.executeCommand('search.action.rerunEditorSearch'));
+
+	vscode.languages.registerDocumentSymbolProvider(SEARCH_RESULT_SELECTOR, {
+		provideDocumentSymbols(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.DocumentSymbol[] {
+			const results = parseSearchResults(document, token)
+				.filter(isFileLine)
+				.map(line => new vscode.DocumentSymbol(
+					line.path,
+					'',
+					vscode.SymbolKind.File,
+					line.allLocations.map(({ originSelectionRange }) => originSelectionRange!).reduce((p, c) => p.union(c), line.location.originSelectionRange!),
+					line.location.originSelectionRange!,
+				));
+
+			return results;
+		}
+	});
+
+	vscode.languages.registerCompletionItemProvider(SEARCH_RESULT_SELECTOR, {
+		provideCompletionItems(document: vscode.TextDocument, position: vscode.Position): vscode.CompletionItem[] {
+
+			const line = document.lineAt(position.line);
+			if (position.line > 3) { return []; }
+			if (position.character === 0 || (position.character === 1 && line.text === '#')) {
+				const header = Array.from({ length: 4 }).map((_, i) => document.lineAt(i).text);
+
+				return ['# Query:', '# Flags:', '# Including:', '# Excluding:']
+					.filter(suggestion => header.every(line => line.indexOf(suggestion) === -1))
+					.map(flag => ({ label: flag, insertText: (flag.slice(position.character)) + ' ' }));
+			}
+
+			if (line.text.indexOf('# Flags:') === -1) { return []; }
+
+			return ['RegExp', 'CaseSensitive', 'IgnoreExcludeSettings', 'WordMatch']
+				.filter(flag => line.text.indexOf(flag) === -1)
+				.map(flag => ({ label: flag, insertText: flag + ' ' }));
+		}
+	}, '#');
+
+	vscode.languages.registerDefinitionProvider(SEARCH_RESULT_SELECTOR, {
+		provideDefinition(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): vscode.DefinitionLink[] {
+			const lineResult = parseSearchResults(document, token)[position.line];
+			if (!lineResult) { return []; }
+			if (lineResult.type === 'file') {
+				// TODO: The multi-match peek UX isnt very smooth.
+				// return lineResult.allLocations.length > 1 ? lineResult.allLocations : [lineResult.location];
+				return [];
+			}
+
+			return [lineResult.location];
+		}
+	});
+
+	vscode.languages.registerDocumentLinkProvider(SEARCH_RESULT_SELECTOR, {
+		async provideDocumentLinks(document: vscode.TextDocument, token: vscode.CancellationToken): Promise {
+			return parseSearchResults(document, token)
+				.filter(({ type }) => type === 'file')
+				.map(({ location }) => ({ range: location.originSelectionRange!, target: location.targetUri }));
+		}
+	});
+
+	vscode.window.onDidChangeActiveTextEditor(e => {
+		if (e?.document.languageId === 'search-result') {
+			// Clear the parse whenever we open a new editor.
+			// Conservative because things like the URI might remain constant even if the contents change, and re-parsing even large files is relatively fast.
+			cachedLastParse = undefined;
+		}
+	});
+}
+
+
+function relativePathToUri(path: string, resultsUri: vscode.Uri): vscode.Uri | undefined {
+	if (pathUtils.isAbsolute(path)) { return vscode.Uri.file(path); }
+	if (path.indexOf('~/') === 0) {
+		return vscode.Uri.file(pathUtils.join(process.env.HOME!, path.slice(2)));
+	}
+
+
+	if (vscode.workspace.workspaceFolders) {
+		const multiRootFormattedPath = /^(.*) • (.*)$/.exec(path);
+		if (multiRootFormattedPath) {
+			const [, workspaceName, workspacePath] = multiRootFormattedPath;
+			const folder = vscode.workspace.workspaceFolders.filter(wf => wf.name === workspaceName)[0];
+			if (folder) {
+				return vscode.Uri.file(pathUtils.join(folder.uri.fsPath, workspacePath));
+			}
+		}
+
+		else if (vscode.workspace.workspaceFolders.length === 1) {
+			return vscode.Uri.file(pathUtils.join(vscode.workspace.workspaceFolders[0].uri.fsPath, path));
+		} else if (resultsUri.scheme !== 'untitled') {
+			// We're in a multi-root workspace, but the path is not multi-root formatted
+			// Possibly a saved search from a single root session. Try checking if the search result document's URI is in a current workspace folder.
+			const prefixMatch = vscode.workspace.workspaceFolders.filter(wf => resultsUri.toString().startsWith(wf.uri.toString()))[0];
+			if (prefixMatch) { return vscode.Uri.file(pathUtils.join(prefixMatch.uri.fsPath, path)); }
+		}
+	}
+
+	console.error(`Unable to resolve path ${path}`);
+	return undefined;
+}
+
+type ParsedSearchFileLine = { type: 'file', location: vscode.LocationLink, allLocations: vscode.LocationLink[], path: string };
+type ParsedSearchResultLine = { type: 'result', location: vscode.LocationLink };
+type ParsedSearchResults = Array;
+const isFileLine = (line: ParsedSearchResultLine | ParsedSearchFileLine): line is ParsedSearchFileLine => line.type === 'file';
+
+
+function parseSearchResults(document: vscode.TextDocument, token: vscode.CancellationToken): ParsedSearchResults {
+
+	if (cachedLastParse && cachedLastParse.version === document.version) {
+		return cachedLastParse.parse;
+	}
+
+	const lines = document.getText().split(/\r?\n/);
+	const links: ParsedSearchResults = [];
+
+	let currentTarget: vscode.Uri | undefined = undefined;
+	let currentTargetLocations: vscode.LocationLink[] | undefined = undefined;
+
+	for (let i = 0; i < lines.length; i++) {
+		if (token.isCancellationRequested) { return []; }
+		const line = lines[i];
+
+		const fileLine = FILE_LINE_REGEX.exec(line);
+		if (fileLine) {
+			const [, path] = fileLine;
+
+			currentTarget = relativePathToUri(path, document.uri);
+			if (!currentTarget) { continue; }
+			currentTargetLocations = [];
+
+			const location: vscode.LocationLink = {
+				targetRange: new vscode.Range(0, 0, 0, 1),
+				targetUri: currentTarget,
+				originSelectionRange: new vscode.Range(i, 0, i, line.length),
+			};
+
+
+			links[i] = { type: 'file', location, allLocations: currentTargetLocations, path };
+		}
+
+		if (!currentTarget) { continue; }
+
+		const resultLine = RESULT_LINE_REGEX.exec(line);
+		if (resultLine) {
+			const [, indentation, _lineNumber, resultIndentation] = resultLine;
+			const lineNumber = +_lineNumber - 1;
+			const resultStart = (indentation + _lineNumber + ':' + resultIndentation).length;
+
+			const location: vscode.LocationLink = {
+				targetRange: new vscode.Range(Math.max(lineNumber - 3, 0), 0, lineNumber + 3, line.length),
+				targetSelectionRange: new vscode.Range(lineNumber, 0, lineNumber, line.length),
+				targetUri: currentTarget,
+				originSelectionRange: new vscode.Range(i, resultStart, i, line.length),
+			};
+
+			currentTargetLocations?.push(location);
+
+			links[i] = { type: 'result', location };
+		}
+	}
+
+	cachedLastParse = {
+		version: document.version,
+		parse: links
+	};
+
+	return links;
+}
diff --git a/extensions/search-result/src/media/refresh-dark.svg b/extensions/search-result/src/media/refresh-dark.svg
new file mode 100644
index 0000000000..e1f05aadee
--- /dev/null
+++ b/extensions/search-result/src/media/refresh-dark.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/extensions/search-result/src/media/refresh-light.svg b/extensions/search-result/src/media/refresh-light.svg
new file mode 100644
index 0000000000..9b1d910840
--- /dev/null
+++ b/extensions/search-result/src/media/refresh-light.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/src/typings/graceful-fs.d.ts b/extensions/search-result/src/typings/refs.d.ts
similarity index 74%
rename from src/typings/graceful-fs.d.ts
rename to extensions/search-result/src/typings/refs.d.ts
index 8ef505afa4..c82a621bfa 100644
--- a/src/typings/graceful-fs.d.ts
+++ b/extensions/search-result/src/typings/refs.d.ts
@@ -3,6 +3,5 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
-declare module 'graceful-fs' {
-	export function gracefulify(fsModule: any): void;
-}
\ No newline at end of file
+/// 
+/// 
diff --git a/extensions/search-result/syntaxes/searchResult.tmLanguage.json b/extensions/search-result/syntaxes/searchResult.tmLanguage.json
new file mode 100644
index 0000000000..4de2a40ba4
--- /dev/null
+++ b/extensions/search-result/syntaxes/searchResult.tmLanguage.json
@@ -0,0 +1,18 @@
+{
+	"name": "Search Results",
+	"scopeName": "text.searchResult",
+	"patterns": [
+		{
+			"match": "^# (Query|Flags|Including|Excluding): .*$",
+			"name": "comment"
+		},
+		{
+			"match": "^\\S.*:$",
+			"name": "string path.searchResult"
+		},
+		{
+			"match": "^  \\d+",
+			"name": "constant.numeric lineNumber.searchResult"
+		}
+	]
+}
diff --git a/extensions/search-result/tsconfig.json b/extensions/search-result/tsconfig.json
new file mode 100644
index 0000000000..16ed233f6e
--- /dev/null
+++ b/extensions/search-result/tsconfig.json
@@ -0,0 +1,9 @@
+{
+	"extends": "../shared.tsconfig.json",
+	"compilerOptions": {
+		"outDir": "./out",
+	},
+	"include": [
+		"src/**/*"
+	]
+}
diff --git a/extensions/search-result/yarn.lock b/extensions/search-result/yarn.lock
new file mode 100644
index 0000000000..4ff83ab98b
--- /dev/null
+++ b/extensions/search-result/yarn.lock
@@ -0,0 +1,602 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+agent-base@4, agent-base@^4.3.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
+  integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==
+  dependencies:
+    es6-promisify "^5.0.0"
+
+ajv@^6.5.5:
+  version "6.10.2"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52"
+  integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==
+  dependencies:
+    fast-deep-equal "^2.0.1"
+    fast-json-stable-stringify "^2.0.0"
+    json-schema-traverse "^0.4.1"
+    uri-js "^4.2.2"
+
+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=
+
+asynckit@^0.4.0:
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
+  integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
+
+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.8.0"
+  resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
+  integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
+
+balanced-match@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
+  integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
+
+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"
+
+brace-expansion@^1.1.7:
+  version "1.1.11"
+  resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
+  integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
+  dependencies:
+    balanced-match "^1.0.0"
+    concat-map "0.0.1"
+
+browser-stdout@1.3.1:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
+  integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==
+
+buffer-from@^1.0.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
+  integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
+
+caseless@~0.12.0:
+  version "0.12.0"
+  resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
+  integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
+
+combined-stream@^1.0.6, 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==
+  dependencies:
+    delayed-stream "~1.0.0"
+
+commander@2.15.1:
+  version "2.15.1"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
+  integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==
+
+concat-map@0.0.1:
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+  integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
+
+core-util-is@1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
+  integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
+
+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=
+  dependencies:
+    assert-plus "^1.0.0"
+
+debug@3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
+  integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
+  dependencies:
+    ms "2.0.0"
+
+debug@^3.1.0:
+  version "3.2.6"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
+  integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
+  dependencies:
+    ms "^2.1.1"
+
+delayed-stream@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
+  integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
+
+diff@3.5.0:
+  version "3.5.0"
+  resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
+  integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
+
+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=
+  dependencies:
+    jsbn "~0.1.0"
+    safer-buffer "^2.1.0"
+
+es6-promise@^4.0.3:
+  version "4.2.8"
+  resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
+  integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
+
+es6-promisify@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
+  integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=
+  dependencies:
+    es6-promise "^4.0.3"
+
+escape-string-regexp@1.0.5:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
+  integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
+
+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==
+
+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@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
+  integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=
+
+fast-json-stable-stringify@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
+  integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I=
+
+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=
+
+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==
+  dependencies:
+    asynckit "^0.4.0"
+    combined-stream "^1.0.6"
+    mime-types "^2.1.12"
+
+fs.realpath@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+  integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
+
+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.1.2:
+  version "7.1.2"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
+  integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==
+  dependencies:
+    fs.realpath "^1.0.0"
+    inflight "^1.0.4"
+    inherits "2"
+    minimatch "^3.0.4"
+    once "^1.3.0"
+    path-is-absolute "^1.0.0"
+
+glob@^7.1.2:
+  version "7.1.6"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
+  integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
+  dependencies:
+    fs.realpath "^1.0.0"
+    inflight "^1.0.4"
+    inherits "2"
+    minimatch "^3.0.4"
+    once "^1.3.0"
+    path-is-absolute "^1.0.0"
+
+growl@1.10.5:
+  version "1.10.5"
+  resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e"
+  integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==
+
+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.0:
+  version "5.1.3"
+  resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080"
+  integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==
+  dependencies:
+    ajv "^6.5.5"
+    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"
+  integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
+
+he@1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
+  integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0=
+
+http-proxy-agent@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
+  integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==
+  dependencies:
+    agent-base "4"
+    debug "3.1.0"
+
+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=
+  dependencies:
+    assert-plus "^1.0.0"
+    jsprim "^1.2.2"
+    sshpk "^1.7.0"
+
+https-proxy-agent@^2.2.1:
+  version "2.2.4"
+  resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b"
+  integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==
+  dependencies:
+    agent-base "^4.3.0"
+    debug "^3.1.0"
+
+inflight@^1.0.4:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+  integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
+  dependencies:
+    once "^1.3.0"
+    wrappy "1"
+
+inherits@2:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+  integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+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=
+
+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=
+
+jsbn@~0.1.0:
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
+  integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
+
+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:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
+  integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
+
+jsprim@^1.2.2:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
+  integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=
+  dependencies:
+    assert-plus "1.0.0"
+    extsprintf "1.3.0"
+    json-schema "0.2.3"
+    verror "1.10.0"
+
+mime-db@1.40.0:
+  version "1.40.0"
+  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32"
+  integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==
+
+mime-types@^2.1.12, mime-types@~2.1.19:
+  version "2.1.24"
+  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81"
+  integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==
+  dependencies:
+    mime-db "1.40.0"
+
+minimatch@3.0.4, minimatch@^3.0.4:
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
+  integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
+  dependencies:
+    brace-expansion "^1.1.7"
+
+minimist@0.0.8:
+  version "0.0.8"
+  resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
+  integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
+
+mkdirp@0.5.1:
+  version "0.5.1"
+  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
+  integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
+  dependencies:
+    minimist "0.0.8"
+
+mocha@^5.2.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.2.0.tgz#6d8ae508f59167f940f2b5b3c4a612ae50c90ae6"
+  integrity sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==
+  dependencies:
+    browser-stdout "1.3.1"
+    commander "2.15.1"
+    debug "3.1.0"
+    diff "3.5.0"
+    escape-string-regexp "1.0.5"
+    glob "7.1.2"
+    growl "1.10.5"
+    he "1.1.1"
+    minimatch "3.0.4"
+    mkdirp "0.5.1"
+    supports-color "5.4.0"
+
+ms@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+  integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
+
+ms@^2.1.1:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
+  integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
+
+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==
+
+once@^1.3.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+  integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
+  dependencies:
+    wrappy "1"
+
+path-is-absolute@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+  integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
+
+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=
+
+psl@^1.1.24:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/psl/-/psl-1.4.0.tgz#5dd26156cdb69fa1fdb8ab1991667d3f80ced7c2"
+  integrity sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==
+
+punycode@^1.4.1:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
+  integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
+
+punycode@^2.1.0:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
+  integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
+
+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==
+
+querystringify@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e"
+  integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==
+
+request@^2.88.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"
+
+requires-port@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
+  integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
+
+safe-buffer@^5.0.1, safe-buffer@^5.1.2:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
+  integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
+
+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==
+
+semver@^5.4.1:
+  version "5.7.1"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
+  integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
+
+source-map-support@^0.5.0:
+  version "0.5.16"
+  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042"
+  integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==
+  dependencies:
+    buffer-from "^1.0.0"
+    source-map "^0.6.0"
+
+source-map@^0.6.0:
+  version "0.6.1"
+  resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
+  integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
+
+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"
+
+supports-color@5.4.0:
+  version "5.4.0"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54"
+  integrity sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==
+  dependencies:
+    has-flag "^3.0.0"
+
+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==
+  dependencies:
+    psl "^1.1.24"
+    punycode "^1.4.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"
+
+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=
+
+uri-js@^4.2.2:
+  version "4.2.2"
+  resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
+  integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==
+  dependencies:
+    punycode "^2.1.0"
+
+url-parse@^1.4.4:
+  version "1.4.7"
+  resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278"
+  integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==
+  dependencies:
+    querystringify "^2.1.1"
+    requires-port "^1.0.0"
+
+uuid@^3.3.2:
+  version "3.3.3"
+  resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866"
+  integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==
+
+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"
+
+vscode-test@^0.4.1:
+  version "0.4.3"
+  resolved "https://registry.yarnpkg.com/vscode-test/-/vscode-test-0.4.3.tgz#461ebf25fc4bc93d77d982aed556658a2e2b90b8"
+  integrity sha512-EkMGqBSefZH2MgW65nY05rdRSko15uvzq4VAPM5jVmwYuFQKE7eikKXNJDRxL+OITXHB6pI+a3XqqD32Y3KC5w==
+  dependencies:
+    http-proxy-agent "^2.1.0"
+    https-proxy-agent "^2.2.1"
+
+vscode@^1.1.36:
+  version "1.1.36"
+  resolved "https://registry.yarnpkg.com/vscode/-/vscode-1.1.36.tgz#5e1a0d1bf4977d0c7bc5159a9a13d5b104d4b1b6"
+  integrity sha512-cGFh9jmGLcTapCpPCKvn8aG/j9zVQ+0x5hzYJq5h5YyUXVGa1iamOaB2M2PZXoumQPES4qeAP1FwkI0b6tL4bQ==
+  dependencies:
+    glob "^7.1.2"
+    mocha "^5.2.0"
+    request "^2.88.0"
+    semver "^5.4.1"
+    source-map-support "^0.5.0"
+    url-parse "^1.4.4"
+    vscode-test "^0.4.1"
+
+wrappy@1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+  integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
diff --git a/extensions/theme-abyss/themes/abyss-color-theme.json b/extensions/theme-abyss/themes/abyss-color-theme.json
index f0a326bb34..1df39d31c9 100644
--- a/extensions/theme-abyss/themes/abyss-color-theme.json
+++ b/extensions/theme-abyss/themes/abyss-color-theme.json
@@ -252,6 +252,9 @@
 	],
 	"colors": {
 
+		"editor.background": "#000c18",
+		"editor.foreground": "#6688cc",
+
 		// Base
 		// "foreground": "",
 		"focusBorder": "#596F99",
@@ -295,8 +298,6 @@
 		"scrollbarSlider.hoverBackground": "#3B3F5188",
 
 		// Editor
-		"editor.background": "#000c18",
-		// "editor.foreground": "#6688cc",
 		"editorWidget.background": "#262641",
 		"editorCursor.foreground": "#ddbb88",
 		"editorWhitespace.foreground": "#103050",
diff --git a/extensions/theme-defaults/themes/dark_plus.json b/extensions/theme-defaults/themes/dark_plus.json
index f8a0ff5b7c..03e62612d6 100644
--- a/extensions/theme-defaults/themes/dark_plus.json
+++ b/extensions/theme-defaults/themes/dark_plus.json
@@ -9,7 +9,8 @@
 				"entity.name.function",
 				"support.function",
 				"support.constant.handlebars",
-				"source.powershell variable.other.member"
+				"source.powershell variable.other.member",
+				"entity.name.operator.custom-literal" // See https://en.cppreference.com/w/cpp/language/user_literal
 			],
 			"settings": {
 				"foreground": "#DCDCAA"
diff --git a/extensions/theme-defaults/themes/dark_vs.json b/extensions/theme-defaults/themes/dark_vs.json
index fe0f8fc889..6e5f4c25f8 100644
--- a/extensions/theme-defaults/themes/dark_vs.json
+++ b/extensions/theme-defaults/themes/dark_vs.json
@@ -171,7 +171,8 @@
 		},
 		{
 			"scope": [
-				"meta.preprocessor"
+				"meta.preprocessor",
+				"entity.name.function.preprocessor"
 			],
 			"settings": {
 				"foreground": "#569cd6"
@@ -226,6 +227,7 @@
 			"scope": [
 				"string",
 				"entity.name.operator.custom-literal.string",
+				"meta.embedded.assembly"
 			],
 			"settings": {
 				"foreground": "#ce9178"
@@ -306,6 +308,7 @@
 				"keyword.operator.expression",
 				"keyword.operator.cast",
 				"keyword.operator.sizeof",
+				"keyword.operator.alignof",
 				"keyword.operator.typeid",
 				"keyword.operator.alignas",
 				"keyword.operator.instanceof",
diff --git a/extensions/theme-defaults/themes/light_plus.json b/extensions/theme-defaults/themes/light_plus.json
index c7599d60d5..faa2b836c2 100644
--- a/extensions/theme-defaults/themes/light_plus.json
+++ b/extensions/theme-defaults/themes/light_plus.json
@@ -9,7 +9,8 @@
 				"entity.name.function",
 				"support.function",
 				"support.constant.handlebars",
-				"source.powershell variable.other.member"
+				"source.powershell variable.other.member",
+				"entity.name.operator.custom-literal" // See https://en.cppreference.com/w/cpp/language/user_literal
 			],
 			"settings": {
 				"foreground": "#795E26"
diff --git a/extensions/theme-defaults/themes/light_vs.json b/extensions/theme-defaults/themes/light_vs.json
index 74bd28d245..5453787c4e 100644
--- a/extensions/theme-defaults/themes/light_vs.json
+++ b/extensions/theme-defaults/themes/light_vs.json
@@ -169,7 +169,8 @@
 		},
 		{
 			"scope": [
-				"meta.preprocessor"
+				"meta.preprocessor",
+				"entity.name.function.preprocessor"
 			],
 			"settings": {
 				"foreground": "#0000ff"
@@ -218,6 +219,7 @@
 			"scope": [
 				"string",
 				"entity.name.operator.custom-literal.string",
+				"meta.embedded.assembly"
 			],
 			"settings": {
 				"foreground": "#a31515"
@@ -330,6 +332,7 @@
 				"keyword.operator.expression",
 				"keyword.operator.cast",
 				"keyword.operator.sizeof",
+				"keyword.operator.alignof",
 				"keyword.operator.typeid",
 				"keyword.operator.alignas",
 				"keyword.operator.instanceof",
diff --git a/extensions/theme-monokai/themes/monokai-color-theme.json b/extensions/theme-monokai/themes/monokai-color-theme.json
index 6bc2e84ce9..c695640f29 100644
--- a/extensions/theme-monokai/themes/monokai-color-theme.json
+++ b/extensions/theme-monokai/themes/monokai-color-theme.json
@@ -382,7 +382,66 @@
 			"name": "Markup Setext Header",
 			"scope": "markup.heading.setext",
 			"settings": {
-				"fontStyle": "",
+				"foreground": "#A6E22E",
+				"fontStyle": "bold"
+			}
+		},
+		{
+			"name": "Markup Headings",
+			"scope": "markup.heading.markdown",
+			"settings": {
+				"fontStyle": "bold"
+			}
+		},
+		{
+			"name": "Markdown Quote",
+			"scope": "markup.quote.markdown",
+			"settings": {
+				"fontStyle": "italic",
+				"foreground": "#75715E"
+			}
+		},
+		{
+			"name": "Markdown Bold",
+			"scope": "markup.bold.markdown",
+			"settings": {
+				"fontStyle": "bold"
+			}
+		},
+		{
+			"name": "Markdown Link Title/Description",
+			"scope": "string.other.link.title.markdown,string.other.link.description.markdown",
+			"settings": {
+				"foreground": "#AE81FF"
+			}
+		},
+		{
+			"name": "Markdown Underline Link/Image",
+			"scope": "markup.underline.link.markdown,markup.underline.link.image.markdown",
+			"settings": {
+				"foreground": "#E6DB74"
+			}
+		},
+		{
+			"name": "Markdown Emphasis",
+			"scope": "markup.italic.markdown",
+			"settings": {
+				"fontStyle": "italic"
+			}
+		},
+		{
+			"name": "Markdown Punctuation Definition Link",
+			"scope": "markup.list.unnumbered.markdown, markup.list.numbered.markdown",
+			"settings": {
+				"foreground": "#f8f8f2"
+			}
+		},
+		{
+			"name": "Markdown List Punctuation",
+			"scope": [
+				"punctuation.definition.list.begin.markdown"
+			],
+			"settings": {
 				"foreground": "#A6E22E"
 			}
 		},
diff --git a/extensions/vscode-colorize-tests/package.json b/extensions/vscode-colorize-tests/package.json
index 702e36e66c..c9834b0554 100644
--- a/extensions/vscode-colorize-tests/package.json
+++ b/extensions/vscode-colorize-tests/package.json
@@ -5,14 +5,22 @@
 	"publisher": "vscode",
 	"license": "MIT",
 	"private": true,
+	"activationEvents": [
+		"onLanguage:json"
+	],
+	"main": "./out/colorizerTestMain",
+	"enableProposedApi": true,
 	"engines": {
 		"vscode": "*"
 	},
 	"scripts": {
 		"vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-colorize-tests ./tsconfig.json"
 	},
+	"dependencies": {
+		"jsonc-parser": "2.2.0"
+	},
 	"devDependencies": {
-		"@types/node": "^10.14.8",
+		"@types/node": "^12.11.7",
 		"mocha-junit-reporter": "^1.17.0",
 		"mocha-multi-reporters": "^1.1.7",
 		"vscode": "1.1.5"
diff --git a/extensions/vscode-colorize-tests/src/colorizerTestMain.ts b/extensions/vscode-colorize-tests/src/colorizerTestMain.ts
new file mode 100644
index 0000000000..ebd24ee350
--- /dev/null
+++ b/extensions/vscode-colorize-tests/src/colorizerTestMain.ts
@@ -0,0 +1,47 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as vscode from 'vscode';
+import * as jsoncParser from 'jsonc-parser';
+
+export function activate(context: vscode.ExtensionContext): any {
+
+	const tokenModifiers = ['static', 'abstract', 'deprecated'];
+	const tokenTypes = ['strings', 'types', 'structs', 'classes', 'functions', 'variables'];
+	const legend = new vscode.SemanticTokensLegend(tokenTypes, tokenModifiers);
+
+	const semanticHighlightProvider: vscode.SemanticTokensProvider = {
+		provideSemanticTokens(document: vscode.TextDocument): vscode.ProviderResult {
+			const builder = new vscode.SemanticTokensBuilder();
+
+			const visitor: jsoncParser.JSONVisitor = {
+				onObjectProperty: (property: string, _offset: number, length: number, startLine: number, startCharacter: number) => {
+					const [type, ...modifiers] = property.split('.');
+					let tokenType = legend.tokenTypes.indexOf(type);
+					if (tokenType === -1) {
+						tokenType = 0;
+					}
+
+					let tokenModifiers = 0;
+					for (let i = 0; i < modifiers.length; i++) {
+						const index = legend.tokenModifiers.indexOf(modifiers[i]);
+						if (index !== -1) {
+							tokenModifiers = tokenModifiers | 1 << index;
+						}
+					}
+
+					builder.push(startLine, startCharacter, length, tokenType, tokenModifiers);
+				}
+			};
+			jsoncParser.visit(document.getText(), visitor);
+
+			return new vscode.SemanticTokens(builder.build());
+		}
+	};
+
+
+	context.subscriptions.push(vscode.languages.registerSemanticTokensProvider({ pattern: '**/color-test.json' }, semanticHighlightProvider, legend));
+
+}
diff --git a/extensions/vscode-colorize-tests/src/index.ts b/extensions/vscode-colorize-tests/src/index.ts
index c622506514..94656bf0ba 100644
--- a/extensions/vscode-colorize-tests/src/index.ts
+++ b/extensions/vscode-colorize-tests/src/index.ts
@@ -10,7 +10,7 @@ const suite = 'Integration Colorize Tests';
 
 const options: any = {
 	ui: 'tdd',
-	useColors: true,
+	useColors: (!process.env.BUILD_ARTIFACTSTAGINGDIRECTORY && process.platform !== 'win32'),
 	timeout: 60000
 };
 
diff --git a/extensions/vscode-colorize-tests/src/typings/ref.d.ts b/extensions/vscode-colorize-tests/src/typings/ref.d.ts
index 3a5401b477..b6afd2b313 100644
--- a/extensions/vscode-colorize-tests/src/typings/ref.d.ts
+++ b/extensions/vscode-colorize-tests/src/typings/ref.d.ts
@@ -3,5 +3,7 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
+/// 
+/// 
 /// 
-/// 
+
diff --git a/extensions/vscode-colorize-tests/yarn.lock b/extensions/vscode-colorize-tests/yarn.lock
index 368b81f2f9..c6b3fdb431 100644
--- a/extensions/vscode-colorize-tests/yarn.lock
+++ b/extensions/vscode-colorize-tests/yarn.lock
@@ -2,10 +2,10 @@
 # yarn lockfile v1
 
 
-"@types/node@^10.14.8":
-  version "10.14.8"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9"
-  integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw==
+"@types/node@^12.11.7":
+  version "12.11.7"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a"
+  integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA==
 
 ajv@^5.1.0:
   version "5.3.0"
@@ -1042,6 +1042,11 @@ json3@3.3.2:
   resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1"
   integrity sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=
 
+jsonc-parser@2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.0.tgz#f206f87f9d49d644b7502052c04e82dd6392e9ef"
+  integrity sha512-4fLQxW1j/5fWj6p78vAlAafoCKtuBm6ghv+Ij5W2DrDx0qE+ZdEl2c6Ko1mgJNF5ftX1iEWQQ4Ap7+3GlhjkOA==
+
 jsonify@~0.0.0:
   version "0.0.0"
   resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
diff --git a/extensions/vscode-test-resolver/package.json b/extensions/vscode-test-resolver/package.json
index 0d93c4ea6d..90e3e0e87e 100644
--- a/extensions/vscode-test-resolver/package.json
+++ b/extensions/vscode-test-resolver/package.json
@@ -8,7 +8,7 @@
 	"engines": {
 		"vscode": "^1.25.0"
 	},
-	"extensionKind": "ui",
+	"extensionKind": [ "ui" ],
 	"scripts": {
 		"compile": "node ./node_modules/vscode/bin/compile -watch -p ./",
 		"vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-test-resolver ./tsconfig.json"
@@ -21,7 +21,7 @@
 	],
 	"main": "./out/extension",
 	"devDependencies": {
-		"@types/node": "^10.14.8",
+		"@types/node": "^12.11.7",
 		"vscode": "1.1.5"
 	},
 	"contributes": {
@@ -90,4 +90,4 @@
 	}
 
 
-}
\ No newline at end of file
+}
diff --git a/extensions/vscode-test-resolver/src/extension.ts b/extensions/vscode-test-resolver/src/extension.ts
index 0910e5e1f7..90ac1ff669 100644
--- a/extensions/vscode-test-resolver/src/extension.ts
+++ b/extensions/vscode-test-resolver/src/extension.ts
@@ -102,8 +102,8 @@ export function activate(context: vscode.ExtensionContext) {
 
 				extHostProcess = cp.spawn(path.join(serverLocation, serverCommand), commandArgs, { env, cwd: serverLocation });
 			}
-			extHostProcess.stdout.on('data', (data: Buffer) => processOutput(data.toString()));
-			extHostProcess.stderr.on('data', (data: Buffer) => processOutput(data.toString()));
+			extHostProcess.stdout!.on('data', (data: Buffer) => processOutput(data.toString()));
+			extHostProcess.stderr!.on('data', (data: Buffer) => processOutput(data.toString()));
 			extHostProcess.on('error', (error: Error) => {
 				processError(`server failed with error:\n${error.message}`);
 				extHostProcess = undefined;
@@ -210,7 +210,7 @@ export function activate(context: vscode.ExtensionContext) {
 		resolve(_authority: string): Thenable {
 			return vscode.window.withProgress({
 				location: vscode.ProgressLocation.Notification,
-				title: 'Open TestResolver Remote ([details](command:remote-testresolver.showLog))',
+				title: 'Open TestResolver Remote ([details](command:vscode-testresolver.showLog))',
 				cancellable: false
 			}, (progress) => doResolve(_authority, progress));
 		}
diff --git a/extensions/vscode-test-resolver/yarn.lock b/extensions/vscode-test-resolver/yarn.lock
index 925cef2c14..4bc1451096 100644
--- a/extensions/vscode-test-resolver/yarn.lock
+++ b/extensions/vscode-test-resolver/yarn.lock
@@ -2,10 +2,10 @@
 # yarn lockfile v1
 
 
-"@types/node@^10.14.8":
-  version "10.14.8"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9"
-  integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw==
+"@types/node@^12.11.7":
+  version "12.11.7"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a"
+  integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA==
 
 ajv@^6.5.5:
   version "6.10.0"
diff --git a/extensions/yaml/package.json b/extensions/yaml/package.json
index 5b12e0fbc5..c7dec1b1b8 100644
--- a/extensions/yaml/package.json
+++ b/extensions/yaml/package.json
@@ -40,8 +40,8 @@
 			"[yaml]": {
 				"editor.insertSpaces": true,
 				"editor.tabSize": 2,
-				"editor.autoIndent": false
+				"editor.autoIndent": "advanced"
 			}
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/extensions/yarn.lock b/extensions/yarn.lock
index 6cae6ac12d..f2e7ae9507 100644
--- a/extensions/yarn.lock
+++ b/extensions/yarn.lock
@@ -2,7 +2,7 @@
 # yarn lockfile v1
 
 
-typescript@3.7.2:
-  version "3.7.2"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb"
-  integrity sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==
+typescript@3.7.3-insiders.20191123:
+  version "3.7.3-insiders.20191123"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.3-insiders.20191123.tgz#f3bef33a2a3f6e02f11bcc0c20b6f0de526f17fd"
+  integrity sha512-b+tLx4D0a6SeuaCa7iehdgkRKHsS67FkioQWw+0REjVNOYZ+AqJ0NjlnomK1hEUvSzSNrH9Du+m+Yiv7JlVpSg==
diff --git a/package.json b/package.json
index 23a44dce63..387e3d5b76 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "azuredatastudio",
   "version": "1.14.0",
-  "distro": "75ae90e1c1dc7eec7de3d6a49f488eb5adbe3182",
+  "distro": "58e879c1d775f402e72ab7bcd800fb84b5334a51",
   "author": {
     "name": "Microsoft Corporation"
   },
@@ -28,6 +28,7 @@
     "strict-null-check": "node node_modules/typescript/bin/tsc -p src/tsconfig.strictNullChecks.json",
     "strict-null-check-watch": "node node_modules/typescript/bin/tsc -p src/tsconfig.strictNullChecks.json --watch",
     "strict-initialization-watch": "tsc --watch -p src/tsconfig.json --noEmit --strictPropertyInitialization",
+    "strict-function-types-watch": "tsc --watch -p src/tsconfig.json --noEmit --strictFunctionTypes",
     "update-distro": "node build/npm/update-distro.js",
     "web": "node scripts/code-web.js"
   },
@@ -45,49 +46,54 @@
     "ansi_up": "^3.0.0",
     "applicationinsights": "1.0.8",
     "chart.js": "^2.6.0",
-    "chokidar": "3.2.2",
+    "chokidar": "3.2.3",
     "graceful-fs": "4.1.11",
     "html-query-plan": "git://github.com/anthonydresser/html-query-plan.git#2.6",
     "http-proxy-agent": "^2.1.0",
     "https-proxy-agent": "^2.2.3",
     "iconv-lite": "0.5.0",
     "jquery": "3.4.0",
-    "jschardet": "1.6.0",
+    "jschardet": "2.1.1",
     "keytar": "^4.11.0",
-    "native-is-elevated": "0.3.0",
-    "native-keymap": "2.0.0",
-    "native-watchdog": "1.2.0",
+    "native-is-elevated": "0.4.1",
+    "native-keymap": "2.1.0",
+    "native-watchdog": "1.3.0",
     "ng2-charts": "^1.6.0",
-    "node-pty": "0.9.0",
-    "nsfw": "1.2.5",
-    "onigasm-umd": "^2.2.2",
+    "node-pty": "^0.10.0-beta2",
+    "onigasm-umd": "2.2.5",
     "plotly.js-dist": "^1.48.3",
     "reflect-metadata": "^0.1.8",
     "rxjs": "5.4.0",
     "sanitize-html": "^1.19.1",
     "semver-umd": "^5.5.3",
     "slickgrid": "github:anthonydresser/SlickGrid#2.3.32",
-    "spdlog": "^0.9.0",
-    "sudo-prompt": "9.0.0",
+    "spdlog": "^0.11.1",
+    "sudo-prompt": "9.1.1",
     "underscore": "^1.8.2",
     "v8-inspect-profiler": "^0.0.20",
-    "vscode-minimist": "^1.2.1",
-    "vscode-proxy-agent": "^0.5.1",
+    "vscode-minimist": "^1.2.2",
+    "vscode-nsfw": "1.2.8",
+    "vscode-proxy-agent": "^0.5.2",
     "vscode-ripgrep": "^1.5.7",
-    "vscode-sqlite3": "4.0.8",
-    "vscode-textmate": "^4.2.2",
-    "xterm": "^4.2.0-beta20",
-    "xterm-addon-search": "0.3.0-beta5",
+    "vscode-sqlite3": "4.0.9",
+    "vscode-textmate": "4.4.0",
+    "xterm": "4.3.0-beta.28",
+    "xterm-addon-search": "0.4.0-beta4",
     "xterm-addon-web-links": "0.2.1",
+    "xterm-addon-webgl": "0.4.0-beta.11",
     "yauzl": "^2.9.2",
     "yazl": "^2.4.3",
     "zone.js": "^0.8.4"
   },
   "devDependencies": {
     "7zip": "0.0.6",
+    "@types/applicationinsights": "0.20.0",
     "@types/chart.js": "^2.7.31",
+    "@types/chokidar": "2.1.3",
     "@types/cookie": "^0.3.3",
-    "@types/htmlparser2": "^3.7.31",
+    "@types/graceful-fs": "4.1.2",
+    "@types/http-proxy-agent": "^2.0.1",
+    "@types/iconv-lite": "0.0.1",
     "@types/keytar": "^4.4.0",
     "@types/mocha": "2.2.39",
     "@types/node": "^10.12.12",
@@ -96,7 +102,11 @@
     "@types/semver": "^5.5.0",
     "@types/sinon": "^1.16.36",
     "@types/webpack": "^4.4.10",
+    "@types/windows-foreground-love": "^0.3.0",
+    "@types/windows-process-tree": "^0.2.0",
     "@types/winreg": "^1.2.30",
+    "@types/yauzl": "^2.9.1",
+    "@types/yazl": "^2.4.2",
     "ansi-colors": "^3.2.3",
     "asar": "^0.14.0",
     "chromium-pickle-js": "^0.2.0",
@@ -104,7 +114,6 @@
     "coveralls": "^2.11.11",
     "cson-parser": "^1.3.3",
     "debounce": "^1.0.0",
-    "documentdb": "^1.5.1",
     "event-stream": "3.3.4",
     "express": "^4.13.1",
     "fancy-log": "^1.3.3",
@@ -160,7 +169,7 @@
     "tslint": "^5.16.0",
     "tslint-microsoft-contrib": "^6.0.0",
     "typemoq": "^0.3.2",
-    "typescript": "3.7.0-dev.20191017",
+    "typescript": "3.7.2",
     "typescript-formatter": "7.1.0",
     "vinyl": "^2.0.0",
     "vinyl-fs": "^3.0.0",
@@ -179,10 +188,10 @@
     "url": "https://github.com/Microsoft/azuredatastudio/issues"
   },
   "optionalDependencies": {
-    "vscode-windows-ca-certs": "0.1.0",
+    "vscode-windows-ca-certs": "0.2.0",
     "vscode-windows-registry": "1.0.2",
     "windows-foreground-love": "0.2.0",
     "windows-mutex": "0.3.0",
     "windows-process-tree": "0.2.4"
   }
-}
\ No newline at end of file
+}
diff --git a/remote/package.json b/remote/package.json
index 6a14e5399d..216a7c3701 100644
--- a/remote/package.json
+++ b/remote/package.json
@@ -3,31 +3,32 @@
   "version": "0.0.0",
   "dependencies": {
     "applicationinsights": "1.0.8",
-    "chokidar": "3.2.2",
+    "chokidar": "3.2.3",
     "cookie": "^0.4.0",
     "graceful-fs": "4.1.11",
     "http-proxy-agent": "^2.1.0",
     "https-proxy-agent": "^2.2.3",
     "iconv-lite": "0.5.0",
-    "jschardet": "1.6.0",
-    "native-watchdog": "1.2.0",
-    "node-pty": "0.9.0",
-    "nsfw": "1.2.5",
-    "onigasm-umd": "^2.2.2",
+    "jschardet": "2.1.1",
+    "native-watchdog": "1.3.0",
+    "node-pty": "^0.10.0-beta2",
+    "onigasm-umd": "2.2.5",
     "semver-umd": "^5.5.3",
-    "spdlog": "^0.9.0",
-    "vscode-minimist": "^1.2.1",
-    "vscode-proxy-agent": "^0.5.1",
+    "spdlog": "^0.11.1",
+    "vscode-minimist": "^1.2.2",
+    "vscode-nsfw": "1.2.8",
+    "vscode-proxy-agent": "^0.5.2",
     "vscode-ripgrep": "^1.5.7",
-    "vscode-textmate": "^4.2.2",
-    "xterm": "^4.2.0-beta20",
-    "xterm-addon-search": "0.3.0-beta5",
+    "vscode-textmate": "4.4.0",
+    "xterm": "4.3.0-beta.28",
+    "xterm-addon-search": "0.4.0-beta4",
     "xterm-addon-web-links": "0.2.1",
+    "xterm-addon-webgl": "0.4.0-beta.11",
     "yauzl": "^2.9.2",
     "yazl": "^2.4.3"
   },
   "optionalDependencies": {
-    "vscode-windows-ca-certs": "0.1.0",
+    "vscode-windows-ca-certs": "0.2.0",
     "vscode-windows-registry": "1.0.2"
   }
 }
diff --git a/remote/web/package.json b/remote/web/package.json
index cf40cc22b1..2a0519d282 100644
--- a/remote/web/package.json
+++ b/remote/web/package.json
@@ -2,11 +2,12 @@
 	"name": "vscode-web",
 	"version": "0.0.0",
 	"dependencies": {
-		"onigasm-umd": "^2.2.2",
+		"onigasm-umd": "2.2.5",
 		"semver-umd": "^5.5.3",
-		"vscode-textmate": "^4.2.2",
-		"xterm": "^4.2.0-beta20",
-		"xterm-addon-search": "0.3.0-beta5",
-		"xterm-addon-web-links": "0.2.1"
+		"vscode-textmate": "4.4.0",
+		"xterm": "4.3.0-beta.28",
+		"xterm-addon-search": "0.4.0-beta4",
+		"xterm-addon-web-links": "0.2.1",
+		"xterm-addon-webgl": "0.4.0-beta.11"
 	}
 }
diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock
index f3ca259409..1e2d3f0de2 100644
--- a/remote/web/yarn.lock
+++ b/remote/web/yarn.lock
@@ -7,10 +7,10 @@ nan@^2.14.0:
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
   integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
 
-onigasm-umd@^2.2.2:
-  version "2.2.2"
-  resolved "https://registry.yarnpkg.com/onigasm-umd/-/onigasm-umd-2.2.2.tgz#b989d762df61f899a3052ac794a50bd93fe20257"
-  integrity sha512-v2eMOJu7iE444L2iJN+U6s6s5S0y7oj/N0DAkrd6wokRtTVoq/v/yaDI1lIqFrTeJbNtqNzYvguDF5yNzW3Rvw==
+onigasm-umd@2.2.5:
+  version "2.2.5"
+  resolved "https://registry.yarnpkg.com/onigasm-umd/-/onigasm-umd-2.2.5.tgz#f104247334a543accd3f8d641a4d99b3d908d6a1"
+  integrity sha512-R3qD7hq6i2bBklF+QyjqZl/G4fe7GwtukI28YLH2vuiatqx52tb9vpg2sxwemKc3nF76SgkeyOKJLchBmTm0Aw==
 
 oniguruma@^7.2.0:
   version "7.2.0"
@@ -24,24 +24,29 @@ semver-umd@^5.5.3:
   resolved "https://registry.yarnpkg.com/semver-umd/-/semver-umd-5.5.3.tgz#b64d7a2d4f5a717b369d56e31940a38e47e34d1e"
   integrity sha512-HOnQrn2iKnVe/xlqCTzMXQdvSz3rPbD0DmQXYuQ+oK1dpptGFfPghonQrx5JHl2O7EJwDqtQnjhE7ME23q6ngw==
 
-vscode-textmate@^4.2.2:
-  version "4.2.2"
-  resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-4.2.2.tgz#0b4dabc69a6fba79a065cb6b615f66eac07c8f4c"
-  integrity sha512-1U4ih0E/KP1zNK/EbpUqyYtI7PY+Ccd2nDGTtiMR/UalLFnmaYkwoWhN1oI7B91ptBN8NdVwWuvyUnvJAulCUw==
+vscode-textmate@4.4.0:
+  version "4.4.0"
+  resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-4.4.0.tgz#14032afeb50152e8f53258c95643e555f2948305"
+  integrity sha512-dFpm2eK0HwEjeFSD1DDh3j0q47bDSVuZt20RiJWxGqjtm73Wu2jip3C2KaZI3dQx/fSeeXCr/uEN4LNaNj7Ytw==
   dependencies:
     oniguruma "^7.2.0"
 
-xterm-addon-search@0.3.0-beta5:
-  version "0.3.0-beta5"
-  resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.3.0-beta5.tgz#fd53d33a77a0235018479c712be8c12f7c0d083a"
-  integrity sha512-3GkGc4hST35/4hzgnQPLLvQ29WH7MkZ0mUrBE/Vm1IQum7TnMvWPTkGemwM+wAl4tdBmynNccHJlFeQzaQtVUg==
+xterm-addon-search@0.4.0-beta4:
+  version "0.4.0-beta4"
+  resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.4.0-beta4.tgz#7762ea342c6b4f5e824d83466bd93793c9d7d779"
+  integrity sha512-TIbEBVhydGIxcyu/CfKJbD+BKHisMGbkAfaWlCPaWis2Xmw8yE7CKrCPn+lhZYl1MdjDVEmb8lQI6WetbC2OZA==
 
 xterm-addon-web-links@0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.2.1.tgz#6d1f2ce613e09870badf17615e7a1170a31542b2"
   integrity sha512-2KnHtiq0IG7hfwv3jw2/jQeH1RBk2d5CH4zvgwQe00rLofSJqSfgnJ7gwowxxpGHrpbPr6Lv4AmH/joaNw2+HQ==
 
-xterm@^4.2.0-beta20:
-  version "4.2.0-beta20"
-  resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.2.0-beta20.tgz#74a759414511b4577159293397914ac33a2375d8"
-  integrity sha512-lH52ksaNQqWmLVV4OdLKWvhGkSRUXgJNvb2YYmVkBAm1PdVVS36ir8Qr4zYygnc2tBw689Wj65t4cNckmfpU1Q==
+xterm-addon-webgl@0.4.0-beta.11:
+  version "0.4.0-beta.11"
+  resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.4.0-beta.11.tgz#0e4a7242e2353cf74aba55e5a5bdc0b4ec87ad10"
+  integrity sha512-AteDxm1RFy1WnjY9r5iJSETozLebvUkR+jextdZk/ASsK21vsYK0DuVWwRI8afgiN2hUVhxcxuHEJUOV+CJDQA==
+
+xterm@4.3.0-beta.28:
+  version "4.3.0-beta.28"
+  resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.3.0-beta.28.tgz#80f7c4ba8f6ee3c953e6f33f8ce5aef08d5a8354"
+  integrity sha512-WWZ4XCvce5h+klL6ObwtMauJff/n2KGGOwJJkDbJhrAjVy2a77GKgAedJTDDFGgKJ6ix1d7puHtVSSKflIVaDQ==
diff --git a/remote/yarn.lock b/remote/yarn.lock
index 5671b877e7..6b661b17e4 100644
--- a/remote/yarn.lock
+++ b/remote/yarn.lock
@@ -64,10 +64,10 @@ buffer-crc32@~0.2.3:
   resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
   integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
 
-chokidar@3.2.2:
-  version "3.2.2"
-  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.2.2.tgz#a433973350021e09f2b853a2287781022c0dc935"
-  integrity sha512-bw3pm7kZ2Wa6+jQWYP/c7bAZy3i4GwiIiMO2EeRjrE48l8vBqC/WvFhSF0xyM8fQiPEGvwMY/5bqDG7sSEOuhg==
+chokidar@3.2.3:
+  version "3.2.3"
+  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.2.3.tgz#b9270a565d14f02f6bfdd537a6a2bbf5549b8c8c"
+  integrity sha512-GtrxGuRf6bzHQmXWRepvsGnXpkQkVU+D2/9a7dAe4a7v1NhrfZOZ2oKf76M3nOs46fFYL8D+Q8JYA4GYeJ8Cjw==
   dependencies:
     anymatch "~3.1.1"
     braces "~3.0.2"
@@ -155,15 +155,15 @@ glob-parent@~5.1.0:
   dependencies:
     is-glob "^4.0.1"
 
-graceful-fs@4.1.11, graceful-fs@^4.1.2:
+graceful-fs@4.1.11:
   version "4.1.11"
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
   integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=
 
-graceful-fs@^4.1.6:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.0.tgz#8d8fdc73977cb04104721cb53666c1ca64cd328b"
-  integrity sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==
+graceful-fs@^4.1.2, graceful-fs@^4.1.6:
+  version "4.2.3"
+  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"
+  integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==
 
 http-proxy-agent@^2.1.0:
   version "2.1.0"
@@ -217,10 +217,10 @@ is-number@^7.0.0:
   resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
   integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
 
-jschardet@1.6.0:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-1.6.0.tgz#c7d1a71edcff2839db2f9ec30fc5d5ebd3c1a678"
-  integrity sha512-xYuhvQ7I9PDJIGBWev9xm0+SMSed3ZDBAmvVjbFR1ZRLAF+vlXcQu6cRI9uAlj81rzikElRVteehwV7DuX2ZmQ==
+jschardet@2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-2.1.1.tgz#af6f8fd0b3b0f5d46a8fd9614a4fce490575c184"
+  integrity sha512-pA5qG9Zwm8CBpGlK/lo2GE9jPxwqRgMV7Lzc/1iaPccw6v4Rhj8Zg2BTyrdmHmxlJojnbLupLeRnaPLsq03x6Q==
 
 jsonfile@^4.0.0:
   version "4.0.0"
@@ -256,25 +256,25 @@ ms@2.0.0:
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
   integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
 
-nan@^2.0.0, nan@^2.14.0:
+nan@^2.10.0, nan@^2.14.0:
   version "2.14.0"
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
   integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
 
-native-watchdog@1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/native-watchdog/-/native-watchdog-1.2.0.tgz#9c710093ac6e9e60b19517cb1ef4ac9d7c997395"
-  integrity sha512-jOOoA3PLSxt1adaeuEW7ymV9cApZiDxn4y4iFs7b4sP73EG+5Lsz+OgUNFcGMyrtznTw6ZvlLcilIN4jeAIgaQ==
+native-watchdog@1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/native-watchdog/-/native-watchdog-1.3.0.tgz#88cee94c9dc766b85c8506eda14c8bd8c9618e27"
+  integrity sha512-WOjGRNGkYZ5MXsntcvCYrKtSYMaewlbCFplbcUVo9bE80LPVt8TAVFHYWB8+a6fWCGYheq21+Wtt6CJrUaCJhw==
 
 node-addon-api@1.6.2:
   version "1.6.2"
   resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.6.2.tgz#d8aad9781a5cfc4132cc2fecdbdd982534265217"
   integrity sha512-479Bjw9nTE5DdBSZZWprFryHGjUaQC31y1wHo19We/k0BZlrmhqQitWoUL0cD8+scljCbIUL+E58oRDEakdGGA==
 
-node-pty@0.9.0:
-  version "0.9.0"
-  resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.9.0.tgz#8f9bcc0d1c5b970a3184ffd533d862c7eb6590a6"
-  integrity sha512-MBnCQl83FTYOu7B4xWw10AW77AAh7ThCE1VXEv+JeWj8mSpGo+0bwgsV+b23ljBFwEM9OmsOv3kM27iUPPm84g==
+node-pty@^0.10.0-beta2:
+  version "0.10.0-beta2"
+  resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.10.0-beta2.tgz#6fd0d2fbbe881869e4e19795a05c557ac958da81"
+  integrity sha512-IU2lzlPUZ+gKG7pHJjzBHpnuwPTxWGgT3iyQicZfdL7dwLvP5cm00QxavAXCInBmRkOMhvM4aBSKvfzqQnCDBA==
   dependencies:
     nan "^2.14.0"
 
@@ -283,20 +283,10 @@ normalize-path@^3.0.0, normalize-path@~3.0.0:
   resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
   integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
 
-nsfw@1.2.5:
-  version "1.2.5"
-  resolved "https://registry.yarnpkg.com/nsfw/-/nsfw-1.2.5.tgz#febe581af616f7b042f89df133abe62416c4c803"
-  integrity sha512-m3mwZUKXiCR69PDMLfAmKmiNzy0Oe9LhFE0DYZC5cc1htNj5Hyb1sAgglXhuaDkibFy22AVvPC5cCFB3A6mYIw==
-  dependencies:
-    fs-extra "^7.0.0"
-    lodash.isinteger "^4.0.4"
-    lodash.isundefined "^3.0.1"
-    nan "^2.0.0"
-
-onigasm-umd@^2.2.2:
-  version "2.2.2"
-  resolved "https://registry.yarnpkg.com/onigasm-umd/-/onigasm-umd-2.2.2.tgz#b989d762df61f899a3052ac794a50bd93fe20257"
-  integrity sha512-v2eMOJu7iE444L2iJN+U6s6s5S0y7oj/N0DAkrd6wokRtTVoq/v/yaDI1lIqFrTeJbNtqNzYvguDF5yNzW3Rvw==
+onigasm-umd@2.2.5:
+  version "2.2.5"
+  resolved "https://registry.yarnpkg.com/onigasm-umd/-/onigasm-umd-2.2.5.tgz#f104247334a543accd3f8d641a4d99b3d908d6a1"
+  integrity sha512-R3qD7hq6i2bBklF+QyjqZl/G4fe7GwtukI28YLH2vuiatqx52tb9vpg2sxwemKc3nF76SgkeyOKJLchBmTm0Aw==
 
 oniguruma@^7.2.0:
   version "7.2.0"
@@ -358,10 +348,10 @@ socks@~2.3.2:
     ip "^1.1.5"
     smart-buffer "4.0.2"
 
-spdlog@^0.9.0:
-  version "0.9.0"
-  resolved "https://registry.yarnpkg.com/spdlog/-/spdlog-0.9.0.tgz#c85dd9d0b9cd385f6f3f5b92dc9d2e1691092b5c"
-  integrity sha512-AeLWPCYjGi4w5DfpXFKb9pCdgMe4gFBMroGfgwXiNfzwmcNYGoFQkIuD1MChZBR1Iwrx0nGhsTSHFslt/qfTAQ==
+spdlog@^0.11.1:
+  version "0.11.1"
+  resolved "https://registry.yarnpkg.com/spdlog/-/spdlog-0.11.1.tgz#29721b31018a5fe6a3ce2531f9d8d43e0bd6b825"
+  integrity sha512-M+sg9/Tnr0lrfnW2/hqgpoc4Z8Jzq7W8NUn35iiSslj+1uj1pgutI60MCpulDP2QyFzOpC8VsJmYD6Fub7wHoA==
   dependencies:
     bindings "^1.5.0"
     mkdirp "^0.5.1"
@@ -379,15 +369,25 @@ universalify@^0.1.0:
   resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
   integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
 
-vscode-minimist@^1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/vscode-minimist/-/vscode-minimist-1.2.1.tgz#e63d3f4a9bf3680dcb8f9304eed612323fd6926a"
-  integrity sha512-cmB72+qDoiCFJ1UKnGUBdGYfXzdpJ3bQM/D/+XhkVk5v7uZgLbYiCz5JcwVyk7NC7hSi5VGtQ4wihzmi12NeXw==
+vscode-minimist@^1.2.2:
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/vscode-minimist/-/vscode-minimist-1.2.2.tgz#65403f44f0c6010d259b2271d36eb5c6f4ad8aab"
+  integrity sha512-DXMNG2QgrXn1jOP12LzjVfvxVkzxv/0Qa27JrMBj/XP2esj+fJ/wP2T4YUH5derj73Lc96dC8F25WyfDUbTpxQ==
 
-vscode-proxy-agent@^0.5.1:
-  version "0.5.1"
-  resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.5.1.tgz#7fd15e157c02176a0dca9f87840ad0991a62ca57"
-  integrity sha512-Nnkc7gBk9iAbbZURYZm3p/wvDvRVwDvdzEvDqF1Jh40p6przwQU/JTlV1XLrmd4cCwh6TOAWD81Z3cq+K7Xdmw==
+vscode-nsfw@1.2.8:
+  version "1.2.8"
+  resolved "https://registry.yarnpkg.com/vscode-nsfw/-/vscode-nsfw-1.2.8.tgz#1bf452e72ff1304934de63692870d039a2d972af"
+  integrity sha512-yRLFDk2nwV0Fp+NWJkIbeXkHYIWoTuWC2siz6JfHc21FkRUjDmuI/1rVL6B+MXW15AsSbXnH5dw4Fo9kUyYclw==
+  dependencies:
+    fs-extra "^7.0.0"
+    lodash.isinteger "^4.0.4"
+    lodash.isundefined "^3.0.1"
+    nan "^2.10.0"
+
+vscode-proxy-agent@^0.5.2:
+  version "0.5.2"
+  resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.5.2.tgz#0c90d24d353957b841d741da7b2701e3f0a044c4"
+  integrity sha512-1cCNPxrWIrmUwS+1XGaXxkh3G1y7z2fpXl1sT74OZvELaryQWYb3NMxMLJJ4Q/CpPLEyuhp/bAN7nzHxxFcQ5Q==
   dependencies:
     debug "^3.1.0"
     http-proxy-agent "^2.1.0"
@@ -399,17 +399,17 @@ vscode-ripgrep@^1.5.7:
   resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.5.7.tgz#acb6b548af488a4bca5d0f1bb5faf761343289ce"
   integrity sha512-/Vsz/+k8kTvui0q3O74pif9FK0nKopgFTiGNVvxicZANxtSA8J8gUE9GQ/4dpi7D/2yI/YVORszwVskFbz46hQ==
 
-vscode-textmate@^4.2.2:
-  version "4.2.2"
-  resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-4.2.2.tgz#0b4dabc69a6fba79a065cb6b615f66eac07c8f4c"
-  integrity sha512-1U4ih0E/KP1zNK/EbpUqyYtI7PY+Ccd2nDGTtiMR/UalLFnmaYkwoWhN1oI7B91ptBN8NdVwWuvyUnvJAulCUw==
+vscode-textmate@4.4.0:
+  version "4.4.0"
+  resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-4.4.0.tgz#14032afeb50152e8f53258c95643e555f2948305"
+  integrity sha512-dFpm2eK0HwEjeFSD1DDh3j0q47bDSVuZt20RiJWxGqjtm73Wu2jip3C2KaZI3dQx/fSeeXCr/uEN4LNaNj7Ytw==
   dependencies:
     oniguruma "^7.2.0"
 
-vscode-windows-ca-certs@0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/vscode-windows-ca-certs/-/vscode-windows-ca-certs-0.1.0.tgz#d58eeb40b536130918cfde2b01e6dc7e5c1bd757"
-  integrity sha512-ZfZbfJIE09Q0dwGqmqTj7kuAq4g6lul9WPJvo0DkKjln8/FL+dY3wUKIKbYwWQp4x56SBTLBq3tJkD72xQ9Gqw==
+vscode-windows-ca-certs@0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/vscode-windows-ca-certs/-/vscode-windows-ca-certs-0.2.0.tgz#086f0f4de57e2760a35ac6920831bff246237115"
+  integrity sha512-YBrJRT0zos+Yb1Qdn73GD8QZr7pa2IE96b5Y1hmmp6XeR8aYB7Iiq5gDAF/+/AxL+caSR9KPZQ6jiYWh5biD7w==
   dependencies:
     node-addon-api "1.6.2"
 
@@ -418,20 +418,25 @@ vscode-windows-registry@1.0.2:
   resolved "https://registry.yarnpkg.com/vscode-windows-registry/-/vscode-windows-registry-1.0.2.tgz#b863e704a6a69c50b3098a55fbddbe595b0c124a"
   integrity sha512-/CLLvuOSM2Vme2z6aNyB+4Omd7hDxpf4Thrt8ImxnXeQtxzel2bClJpFQvQqK/s4oaXlkBKS7LqVLeZM+uSVIA==
 
-xterm-addon-search@0.3.0-beta5:
-  version "0.3.0-beta5"
-  resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.3.0-beta5.tgz#fd53d33a77a0235018479c712be8c12f7c0d083a"
-  integrity sha512-3GkGc4hST35/4hzgnQPLLvQ29WH7MkZ0mUrBE/Vm1IQum7TnMvWPTkGemwM+wAl4tdBmynNccHJlFeQzaQtVUg==
+xterm-addon-search@0.4.0-beta4:
+  version "0.4.0-beta4"
+  resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.4.0-beta4.tgz#7762ea342c6b4f5e824d83466bd93793c9d7d779"
+  integrity sha512-TIbEBVhydGIxcyu/CfKJbD+BKHisMGbkAfaWlCPaWis2Xmw8yE7CKrCPn+lhZYl1MdjDVEmb8lQI6WetbC2OZA==
 
 xterm-addon-web-links@0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.2.1.tgz#6d1f2ce613e09870badf17615e7a1170a31542b2"
   integrity sha512-2KnHtiq0IG7hfwv3jw2/jQeH1RBk2d5CH4zvgwQe00rLofSJqSfgnJ7gwowxxpGHrpbPr6Lv4AmH/joaNw2+HQ==
 
-xterm@^4.2.0-beta20:
-  version "4.2.0-beta20"
-  resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.2.0-beta20.tgz#74a759414511b4577159293397914ac33a2375d8"
-  integrity sha512-lH52ksaNQqWmLVV4OdLKWvhGkSRUXgJNvb2YYmVkBAm1PdVVS36ir8Qr4zYygnc2tBw689Wj65t4cNckmfpU1Q==
+xterm-addon-webgl@0.4.0-beta.11:
+  version "0.4.0-beta.11"
+  resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.4.0-beta.11.tgz#0e4a7242e2353cf74aba55e5a5bdc0b4ec87ad10"
+  integrity sha512-AteDxm1RFy1WnjY9r5iJSETozLebvUkR+jextdZk/ASsK21vsYK0DuVWwRI8afgiN2hUVhxcxuHEJUOV+CJDQA==
+
+xterm@4.3.0-beta.28:
+  version "4.3.0-beta.28"
+  resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.3.0-beta.28.tgz#80f7c4ba8f6ee3c953e6f33f8ce5aef08d5a8354"
+  integrity sha512-WWZ4XCvce5h+klL6ObwtMauJff/n2KGGOwJJkDbJhrAjVy2a77GKgAedJTDDFGgKJ6ix1d7puHtVSSKflIVaDQ==
 
 yauzl@^2.9.2:
   version "2.10.0"
diff --git a/scripts/code-web.js b/scripts/code-web.js
index 33e586d958..a7370520c9 100755
--- a/scripts/code-web.js
+++ b/scripts/code-web.js
@@ -16,15 +16,43 @@ const opn = require('opn');
 const minimist = require('vscode-minimist');
 
 const APP_ROOT = path.dirname(__dirname);
+const EXTENSIONS_ROOT = path.join(APP_ROOT, 'extensions');
 const WEB_MAIN = path.join(APP_ROOT, 'src', 'vs', 'code', 'browser', 'workbench', 'workbench-dev.html');
-const PORT = 8080;
 
 const args = minimist(process.argv, {
+	boolean: [
+		'no-launch',
+		'help'
+	],
 	string: [
-		'no-launch'
-	]
+		'scheme',
+		'host',
+		'port',
+		'local_port'
+	],
 });
 
+if (args.help) {
+	console.log(
+		'yarn web [options]\n' +
+		' --no-launch   Do not open VSCode web in the browser\n' +
+		' --scheme      Protocol (https or http)\n' +
+		' --host        Remote host\n' +
+		' --port        Remote/Local port\n' +
+		' --local_port  Local port override\n' +
+		' --help\n' +
+		'[Example]\n' +
+		' yarn web --scheme https --host example.com --port 8080 --local_port 30000'
+	);
+	process.exit(0);
+}
+
+const PORT = args.port || process.env.PORT || 8080;
+const LOCAL_PORT = args.local_port || process.env.LOCAL_PORT || PORT;
+const SCHEME = args.scheme || process.env.VSCODE_SCHEME || 'http';
+const HOST = args.host || 'localhost';
+const AUTHORITY = process.env.VSCODE_AUTHORITY || `${HOST}:${PORT}`;
+
 const server = http.createServer((req, res) => {
 	const parsedUrl = url.parse(req.url, true);
 	const pathname = parsedUrl.pathname;
@@ -55,8 +83,11 @@ const server = http.createServer((req, res) => {
 	}
 });
 
-server.listen(PORT, () => {
-	console.log(`Web UI available at http://localhost:${PORT}`);
+server.listen(LOCAL_PORT, () => {
+	if (LOCAL_PORT !== PORT) {
+		console.log(`Operating location at http://0.0.0.0:${LOCAL_PORT}`);
+	}
+	console.log(`Web UI available at   ${SCHEME}://${AUTHORITY}`);
 });
 
 server.on('error', err => {
@@ -87,7 +118,7 @@ function handleStaticExtension(req, res, parsedUrl) {
 	// Strip `/static-extension/` from the path
 	const relativeFilePath = path.normalize(decodeURIComponent(parsedUrl.pathname.substr('/static-extension/'.length)));
 
-	const filePath = path.join(APP_ROOT, 'extensions', relativeFilePath);
+	const filePath = path.join(EXTENSIONS_ROOT, relativeFilePath);
 
 	return serveFile(req, res, filePath);
 }
@@ -97,12 +128,12 @@ function handleStaticExtension(req, res, parsedUrl) {
  * @param {import('http').ServerResponse} res
  */
 async function handleRoot(req, res) {
-	const extensionFolders = await util.promisify(fs.readdir)(path.join(APP_ROOT, 'extensions'));
+	const extensionFolders = await util.promisify(fs.readdir)(EXTENSIONS_ROOT);
 	const mapExtensionFolderToExtensionPackageJSON = new Map();
 
 	await Promise.all(extensionFolders.map(async extensionFolder => {
 		try {
-			const packageJSON = JSON.parse((await util.promisify(fs.readFile)(path.join(APP_ROOT, 'extensions', extensionFolder, 'package.json'))).toString());
+			const packageJSON = JSON.parse((await util.promisify(fs.readFile)(path.join(EXTENSIONS_ROOT, extensionFolder, 'package.json'))).toString());
 			if (packageJSON.main && packageJSON.name !== 'vscode-api-tests') {
 				return; // unsupported
 			}
@@ -111,7 +142,7 @@ async function handleRoot(req, res) {
 				return; // seems to fail to JSON.parse()?!
 			}
 
-			packageJSON.extensionKind = 'web'; // enable for Web
+			packageJSON.extensionKind = ['web']; // enable for Web
 
 			mapExtensionFolderToExtensionPackageJSON.set(extensionFolder, packageJSON);
 		} catch (error) {
@@ -125,14 +156,14 @@ async function handleRoot(req, res) {
 	mapExtensionFolderToExtensionPackageJSON.forEach((packageJSON, extensionFolder) => {
 		staticExtensions.push({
 			packageJSON,
-			extensionLocation: { scheme: 'http', authority: `localhost:${PORT}`, path: `/static-extension/${extensionFolder}` }
+			extensionLocation: { scheme: SCHEME, authority: AUTHORITY, path: `/static-extension/${extensionFolder}` }
 		});
 	});
 
 	const data = (await util.promisify(fs.readFile)(WEB_MAIN)).toString()
 		.replace('{{WORKBENCH_WEB_CONFIGURATION}}', escapeAttribute(JSON.stringify({
 			staticExtensions,
-			folderUri: { scheme: 'memfs', path: `/` }
+			folderUri: { scheme: 'memfs', path: `/sample-folder` }
 		})))
 		.replace('{{WEBVIEW_ENDPOINT}}', '')
 		.replace('{{REMOTE_USER_DATA_URI}}', '');
@@ -196,6 +227,14 @@ function getMediaMime(forPath) {
  */
 async function serveFile(req, res, filePath, responseHeaders = Object.create(null)) {
 	try {
+
+		// Sanity checks
+		filePath = path.normalize(filePath); // ensure no "." and ".."
+		if (filePath.indexOf(`${APP_ROOT}${path.sep}`) !== 0) {
+			// invalid location outside of APP_ROOT
+			return serveError(req, res, 400, `Bad request.`);
+		}
+
 		const stat = await util.promisify(fs.stat)(filePath);
 
 		// Check if file modified since
@@ -221,5 +260,5 @@ async function serveFile(req, res, filePath, responseHeaders = Object.create(nul
 }
 
 if (args.launch !== false) {
-	opn(`http://localhost:${PORT}`);
+	opn(`${SCHEME}://${HOST}:${PORT}`);
 }
diff --git a/scripts/code.bat b/scripts/code.bat
index 0eb0eb0b34..770d37b7ae 100644
--- a/scripts/code.bat
+++ b/scripts/code.bat
@@ -13,9 +13,8 @@ set NAMESHORT=%NAMESHORT: "=%
 set NAMESHORT=%NAMESHORT:"=%.exe
 set CODE=".build\electron\%NAMESHORT%"
 
-:: Download Electron if needed
-node build\lib\electron.js
-if %errorlevel% neq 0 node .\node_modules\gulp\bin\gulp.js electron
+:: Get electron
+call yarn electron
 
 :: Manage built-in extensions
 if "%1"=="--builtin" goto builtin
diff --git a/scripts/test-integration.bat b/scripts/test-integration.bat
index a9732aec35..57cc0e0f9c 100755
--- a/scripts/test-integration.bat
+++ b/scripts/test-integration.bat
@@ -8,7 +8,9 @@ set VSCODEUSERDATADIR=%TMP%\vscodeuserfolder-%RANDOM%-%TIME:~6,5%
 :: Figure out which Electron to use for running tests
 if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" (
 	:: Run out of sources: no need to compile as code.sh takes care of it
+	chcp 65001
 	set INTEGRATION_TEST_ELECTRON_PATH=.\scripts\code.bat
+	set VSCODE_BUILD_BUILTIN_EXTENSIONS_SILENCE_PLEASE=1
 
 	echo "Running integration tests out of sources."
 ) else (
@@ -35,7 +37,6 @@ call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\tes
 if %errorlevel% neq 0 exit /b %errorlevel%
 
 call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\testworkspace.code-workspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\workspace-tests --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR%
-
 if %errorlevel% neq 0 exit /b %errorlevel%
 
 call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-colorize-tests\test --extensionDevelopmentPath=%~dp0\..\extensions\vscode-colorize-tests --extensionTestsPath=%~dp0\..\extensions\vscode-colorize-tests\out --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR%
diff --git a/src/bootstrap-fork.js b/src/bootstrap-fork.js
index 41c57a40a0..531e1be448 100644
--- a/src/bootstrap-fork.js
+++ b/src/bootstrap-fork.js
@@ -20,11 +20,6 @@ if (!!process.send && process.env.PIPE_LOGGING === 'true') {
 	pipeLoggingToParent();
 }
 
-// Disable IO if configured
-if (!process.env['VSCODE_ALLOW_IO']) {
-	disableSTDIO();
-}
-
 // Handle Exceptions
 if (!process.env['VSCODE_HANDLES_UNCAUGHT_ERRORS']) {
 	handleExceptions();
@@ -141,20 +136,6 @@ function pipeLoggingToParent() {
 	console.error = function () { safeSend({ type: '__$console', severity: 'error', arguments: safeToArray(arguments) }); };
 }
 
-function disableSTDIO() {
-
-	// const stdout, stderr and stdin be no-op streams. This prevents an issue where we would get an EBADF
-	// error when we are inside a forked process and this process tries to access those channels.
-	const stream = require('stream');
-	const writable = new stream.Writable({
-		write: function () { /* No OP */ }
-	});
-
-	process['__defineGetter__']('stdout', function () { return writable; });
-	process['__defineGetter__']('stderr', function () { return writable; });
-	process['__defineGetter__']('stdin', function () { return writable; });
-}
-
 function handleExceptions() {
 
 	// Handle uncaught exceptions
@@ -198,4 +179,4 @@ function configureCrashReporter() {
 	}
 }
 
-//#endregion
\ No newline at end of file
+//#endregion
diff --git a/src/bootstrap-window.js b/src/bootstrap-window.js
index f7b16145ea..7b2c31b81a 100644
--- a/src/bootstrap-window.js
+++ b/src/bootstrap-window.js
@@ -161,7 +161,7 @@ exports.load = function (modulePaths, resultCallback, options) {
 		} catch (error) {
 			onUnexpectedError(error, enableDeveloperTools);
 		}
-	});
+	}, onUnexpectedError);
 };
 
 /**
diff --git a/src/bootstrap.js b/src/bootstrap.js
index 8a6a02ab5a..58e1408811 100644
--- a/src/bootstrap.js
+++ b/src/bootstrap.js
@@ -39,10 +39,12 @@ exports.injectNodeModuleLookupPath = function (injectPath) {
 	// @ts-ignore
 	Module._resolveLookupPaths = function (moduleName, parent) {
 		const paths = originalResolveLookupPaths(moduleName, parent);
-		for (let i = 0, len = paths.length; i < len; i++) {
-			if (paths[i] === nodeModulesPath) {
-				paths.splice(i, 0, injectPath);
-				break;
+		if (Array.isArray(paths)) {
+			for (let i = 0, len = paths.length; i < len; i++) {
+				if (paths[i] === nodeModulesPath) {
+					paths.splice(i, 0, injectPath);
+					break;
+				}
 			}
 		}
 
@@ -74,10 +76,12 @@ exports.enableASARSupport = function (nodeModulesPath) {
 	// @ts-ignore
 	Module._resolveLookupPaths = function (request, parent) {
 		const paths = originalResolveLookupPaths(request, parent);
-		for (let i = 0, len = paths.length; i < len; i++) {
-			if (paths[i] === NODE_MODULES_PATH) {
-				paths.splice(i, 0, NODE_MODULES_ASAR_PATH);
-				break;
+		if (Array.isArray(paths)) {
+			for (let i = 0, len = paths.length; i < len; i++) {
+				if (paths[i] === NODE_MODULES_PATH) {
+					paths.splice(i, 0, NODE_MODULES_ASAR_PATH);
+					break;
+				}
 			}
 		}
 
@@ -153,30 +157,10 @@ exports.writeFile = function (file, content) {
  * @param {string} dir
  * @returns {Promise}
  */
-function mkdir(dir) {
+exports.mkdirp = function mkdirp(dir) {
 	const fs = require('fs');
 
-	return new Promise((c, e) => fs.mkdir(dir, err => (err && err.code !== 'EEXIST') ? e(err) : c(dir)));
-}
-
-/**
- * @param {string} dir
- * @returns {Promise}
- */
-exports.mkdirp = function mkdirp(dir) {
-	const path = require('path');
-
-	return mkdir(dir).then(null, err => {
-		if (err && err.code === 'ENOENT') {
-			const parent = path.dirname(dir);
-
-			if (parent !== dir) { // if not arrived at root
-				return mkdirp(parent).then(() => mkdir(dir));
-			}
-		}
-
-		throw err;
-	});
+	return new Promise((c, e) => fs.mkdir(dir, { recursive: true }, err => (err && err.code !== 'EEXIST') ? e(err) : c(dir)));
 };
 //#endregion
 
@@ -279,7 +263,12 @@ exports.configurePortable = function () {
 	}
 
 	if (isTempPortable) {
-		process.env[process.platform === 'win32' ? 'TEMP' : 'TMPDIR'] = portableTempPath;
+		if (process.platform === 'win32') {
+			process.env['TMP'] = portableTempPath;
+			process.env['TEMP'] = portableTempPath;
+		} else {
+			process.env['TMPDIR'] = portableTempPath;
+		}
 	}
 
 	return {
diff --git a/src/main.js b/src/main.js
index d08f5de5cb..d7dafd836c 100644
--- a/src/main.js
+++ b/src/main.js
@@ -155,10 +155,8 @@ function configureCommandlineSwitchesSync(cliArgs) {
 			if (argvKey === 'disable-hardware-acceleration') {
 				app.disableHardwareAcceleration(); // needs to be called explicitly
 			} else {
-				app.commandLine.appendArgument(argvKey);
+				app.commandLine.appendSwitch(argvKey);
 			}
-		} else {
-			app.commandLine.appendSwitch(argvKey, argvValue);
 		}
 	});
 
@@ -221,24 +219,19 @@ function createDefaultArgvConfigSync(argvConfigPath) {
 
 		// Default argv content
 		const defaultArgvConfigContent = [
-			'// This configuration file allows to pass permanent command line arguments to VSCode.',
+			'// This configuration file allows you to pass permanent command line arguments to VS Code.',
 			'// Only a subset of arguments is currently supported to reduce the likelyhood of breaking',
 			'// the installation.',
 			'//',
 			'// PLEASE DO NOT CHANGE WITHOUT UNDERSTANDING THE IMPACT',
 			'//',
-			'// If the command line argument does not have any values, simply assign',
-			'// it in the JSON below with a value of \'true\'. Otherwise, put the value',
-			'// directly.',
-			'//',
-			'// If you see rendering issues in VSCode and have a better experience',
-			'// with software rendering, you can configure this by adding:',
-			'//',
-			'// \'disable-hardware-acceleration\': true',
-			'//',
-			'// NOTE: Changing this file requires a restart of VSCode.',
+			'// NOTE: Changing this file requires a restart of VS Code.',
 			'{',
-			'	// Enabled by default by VSCode to resolve color issues in the renderer',
+			'	// Use software rendering instead of hardware accelerated rendering.',
+			'	// This can help in cases where you see rendering issues in VS Code.',
+			'	// "disable-hardware-acceleration": true,',
+			'',
+			'	// Enabled by default by VS Code to resolve color issues in the renderer',
 			'	// See https://github.com/Microsoft/vscode/issues/51791 for details',
 			'	"disable-color-correct-rendering": true'
 		];
@@ -247,7 +240,7 @@ function createDefaultArgvConfigSync(argvConfigPath) {
 			defaultArgvConfigContent[defaultArgvConfigContent.length - 1] = `${defaultArgvConfigContent[defaultArgvConfigContent.length - 1]},`; // append trailing ","
 
 			defaultArgvConfigContent.push('');
-			defaultArgvConfigContent.push('	// Display language of VSCode');
+			defaultArgvConfigContent.push('	// Display language of VS Code');
 			defaultArgvConfigContent.push(`	"locale": "${legacyLocale}"`);
 		}
 
diff --git a/src/sql/base/browser/ui/selectBox/media/selectBox.css b/src/sql/base/browser/ui/selectBox/media/selectBox.css
index 3c2a8bfbc1..0996d599cb 100644
--- a/src/sql/base/browser/ui/selectBox/media/selectBox.css
+++ b/src/sql/base/browser/ui/selectBox/media/selectBox.css
@@ -13,3 +13,8 @@
 	flex-direction: row;
 	margin-right: 5px;
 }
+
+.monaco-select-box.monaco-select-box-dropdown-padding {
+	padding: 2px 8px;
+	padding: 0 22px 0 6px !important; /* I don't like this but for now its fine */
+}
diff --git a/src/sql/media/actionBarLabel.css b/src/sql/media/actionBarLabel.css
index caf4409f50..2e886846d6 100644
--- a/src/sql/media/actionBarLabel.css
+++ b/src/sql/media/actionBarLabel.css
@@ -26,9 +26,3 @@
 	-webkit-mask: url('icons/search_inverse.svg') no-repeat 50% 50%;
 	-webkit-mask-size: 25px 25px;
 }
-
-/* Activity Bar - Data Explorer */
-.monaco-workbench > .activitybar .monaco-action-bar .action-label.dataExplorer {
-	-webkit-mask: url('icons/server_page_inverse.svg') no-repeat 50% 50%;
-	-webkit-mask-size: 25px 25px;
-}
diff --git a/src/sql/platform/tasks/browser/tasksRegistry.ts b/src/sql/platform/tasks/browser/tasksRegistry.ts
index 92aed2d119..c36b997dac 100644
--- a/src/sql/platform/tasks/browser/tasksRegistry.ts
+++ b/src/sql/platform/tasks/browser/tasksRegistry.ts
@@ -14,6 +14,7 @@ import { createCSSRule, asCSSUrl } from 'vs/base/browser/dom';
 import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
 import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
 import { IdGenerator } from 'vs/base/common/idGenerator';
+import { ThemeIcon } from 'vs/platform/theme/common/themeService';
 
 const ids = new IdGenerator('task-icon-');
 
@@ -56,10 +57,12 @@ export const TaskRegistry: ITaskRegistry = new class implements ITaskRegistry {
 		let iconClass: string | undefined;
 		if (this.taskIdToIconClassNameMap.has(item.id)) {
 			iconClass = this.taskIdToIconClassNameMap.get(item.id);
-		} else if (item.iconLocation) {
+		} else if (ThemeIcon.isThemeIcon(item.icon)) {
+			// TODO
+		} else if (item.icon?.dark) { // at the very least we need a dark icon
 			iconClass = ids.nextId();
-			createCSSRule(`.codicon.${iconClass}`, `background-image: ${asCSSUrl(item.iconLocation.light || item.iconLocation.dark)}`);
-			createCSSRule(`.vs-dark .codicon.${iconClass}, .hc-black .codicon.${iconClass}`, `background-image: ${asCSSUrl(item.iconLocation.dark)}`);
+			createCSSRule(`.codicon.${iconClass}`, `background-image: ${asCSSUrl(item.icon.light || item.icon.dark)}`);
+			createCSSRule(`.vs-dark .codicon.${iconClass}, .hc-black .codicon.${iconClass}`, `background-image: ${asCSSUrl(item.icon.dark)}`);
 			this.taskIdToIconClassNameMap.set(item.id, iconClass);
 		}
 		return iconClass;
@@ -102,7 +105,7 @@ export abstract class Task {
 
 	private toCommandAction(): ICommandAction {
 		return {
-			iconLocation: this.iconPath,
+			icon: this.iconPath,
 			id: this.id,
 			title: this.title
 		};
diff --git a/src/sql/platform/theme/common/styler.ts b/src/sql/platform/theme/common/styler.ts
index a14374b96c..b86a5137e8 100644
--- a/src/sql/platform/theme/common/styler.ts
+++ b/src/sql/platform/theme/common/styler.ts
@@ -7,9 +7,10 @@ import * as sqlcolors from './colors';
 
 import { IThemeService } from 'vs/platform/theme/common/themeService';
 import * as cr from 'vs/platform/theme/common/colorRegistry';
-import { IThemable, attachStyler } from 'vs/platform/theme/common/styler';
+import { attachStyler } from 'vs/platform/theme/common/styler';
 import { IDisposable } from 'vs/base/common/lifecycle';
 import { SIDE_BAR_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_DRAG_AND_DROP_BACKGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND } from 'vs/workbench/common/theme';
+import { IThemable } from 'vs/base/common/styler';
 
 export function attachModalDialogStyler(widget: IThemable, themeService: IThemeService, style?:
 	{
diff --git a/src/sql/workbench/api/browser/mainThreadNotebook.ts b/src/sql/workbench/api/browser/mainThreadNotebook.ts
index ead712bf1c..520aad6891 100644
--- a/src/sql/workbench/api/browser/mainThreadNotebook.ts
+++ b/src/sql/workbench/api/browser/mainThreadNotebook.ts
@@ -91,7 +91,7 @@ class NotebookProviderWrapper extends Disposable implements INotebookProvider {
 
 	constructor(
 		private _proxy: Proxies,
-		public readonly providerId,
+		public readonly providerId: string,
 		public readonly providerHandle: number,
 		@IInstantiationService private readonly instantiationService: IInstantiationService
 	) {
@@ -127,7 +127,7 @@ class NotebookManagerWrapper implements INotebookManager {
 	private managerDetails: INotebookManagerDetails;
 
 	constructor(private _proxy: Proxies,
-		public readonly providerId,
+		public readonly providerId: string,
 		private notebookUri: URI,
 		@IInstantiationService private readonly instantiationService: IInstantiationService
 	) { }
diff --git a/src/sql/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts b/src/sql/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts
index 44c37fec44..f6dd0ca4e4 100644
--- a/src/sql/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts
+++ b/src/sql/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts
@@ -27,7 +27,6 @@ import { disposed } from 'vs/base/common/errors';
 import { ICellModel, NotebookContentChange, INotebookModel } from 'sql/workbench/contrib/notebook/browser/models/modelInterfaces';
 import { NotebookChangeType, CellTypes } from 'sql/workbench/contrib/notebook/common/models/contracts';
 import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
-import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
 import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
 import { viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor';
 import { localize } from 'vs/nls';
@@ -35,6 +34,9 @@ import { IFileService } from 'vs/platform/files/common/files';
 import { UntitledNotebookInput } from 'sql/workbench/contrib/notebook/common/models/untitledNotebookInput';
 import { FileNotebookInput } from 'sql/workbench/contrib/notebook/common/models/fileNotebookInput';
 import { find } from 'vs/base/common/arrays';
+import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
+import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
 
 class MainThreadNotebookEditor extends Disposable {
 	private _contentChangedEmitter = new Emitter();
@@ -325,7 +327,7 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
 	private _modelToDisposeMap = new Map();
 	constructor(
 		extHostContext: IExtHostContext,
-		@IUntitledEditorService private _untitledEditorService: IUntitledEditorService,
+		@IUntitledTextEditorService private _untitledEditorService: IUntitledTextEditorService,
 		@IInstantiationService private _instantiationService: IInstantiationService,
 		@IEditorService private _editorService: IEditorService,
 		@IEditorGroupsService private _editorGroupService: IEditorGroupsService,
@@ -458,9 +460,9 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
 			this._editorService.createInput({ resource: uri, mode: 'notebook' });
 		let input: NotebookInput;
 		if (isUntitled) {
-			input = this._instantiationService.createInstance(UntitledNotebookInput, path.basename(uri.fsPath), uri, fileInput);
+			input = this._instantiationService.createInstance(UntitledNotebookInput, path.basename(uri.fsPath), uri, fileInput as UntitledTextEditorInput);
 		} else {
-			input = this._instantiationService.createInstance(FileNotebookInput, path.basename(uri.fsPath), uri, fileInput);
+			input = this._instantiationService.createInstance(FileNotebookInput, path.basename(uri.fsPath), uri, fileInput as FileEditorInput);
 		}
 		input.defaultKernel = options.defaultKernel;
 		input.connectionProfile = new ConnectionProfile(this._capabilitiesService, options.connectionProfile);
diff --git a/src/sql/workbench/browser/modal/modal.ts b/src/sql/workbench/browser/modal/modal.ts
index ccfb3c2ae1..8c56eaa802 100644
--- a/src/sql/workbench/browser/modal/modal.ts
+++ b/src/sql/workbench/browser/modal/modal.ts
@@ -3,7 +3,7 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 import 'vs/css!./media/modal';
-import { IThemable, attachButtonStyler } from 'vs/platform/theme/common/styler';
+import { attachButtonStyler } from 'vs/platform/theme/common/styler';
 import { Color } from 'vs/base/common/color';
 import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
 import { mixin } from 'vs/base/common/objects';
@@ -26,6 +26,7 @@ import { ITextResourcePropertiesService } from 'vs/editor/common/services/resour
 import { URI } from 'vs/base/common/uri';
 import { Schemas } from 'vs/base/common/network';
 import { find, firstIndex } from 'vs/base/common/arrays';
+import { IThemable } from 'vs/base/common/styler';
 import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
 
 export const MODAL_SHOWING_KEY = 'modalShowing';
diff --git a/src/sql/workbench/browser/modal/optionsDialog.ts b/src/sql/workbench/browser/modal/optionsDialog.ts
index f6faee774c..78744439d0 100644
--- a/src/sql/workbench/browser/modal/optionsDialog.ts
+++ b/src/sql/workbench/browser/modal/optionsDialog.ts
@@ -24,7 +24,6 @@ import * as styler from 'vs/platform/theme/common/styler';
 import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
 import { Widget } from 'vs/base/browser/ui/widget';
 import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
-import { IViewletPanelOptions, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet';
 import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
 import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -34,13 +33,14 @@ import { ILogService } from 'vs/platform/log/common/log';
 import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
 import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
 import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
+import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet';
 
-export class CategoryView extends ViewletPanel {
+export class CategoryView extends ViewletPane {
 
 	constructor(
 		private contentElement: HTMLElement,
 		private size: number,
-		options: IViewletPanelOptions,
+		options: IViewletPaneOptions,
 		@IKeybindingService keybindingService: IKeybindingService,
 		@IContextMenuService contextMenuService: IContextMenuService,
 		@IConfigurationService configurationService: IConfigurationService,
diff --git a/src/sql/workbench/browser/modelComponents/diffeditor.component.ts b/src/sql/workbench/browser/modelComponents/diffeditor.component.ts
index 3f52af539a..465e52ab4d 100644
--- a/src/sql/workbench/browser/modelComponents/diffeditor.component.ts
+++ b/src/sql/workbench/browser/modelComponents/diffeditor.component.ts
@@ -90,7 +90,7 @@ export default class DiffEditorComponent extends ComponentBase implements ICompo
 
 		let editorinput1 = this._instantiationService.createInstance(ResourceEditorInput, 'source', undefined, uri1, undefined);
 		let editorinput2 = this._instantiationService.createInstance(ResourceEditorInput, 'target', undefined, uri2, undefined);
-		this._editorInput = this._instantiationService.createInstance(DiffEditorInput, 'DiffEditor', undefined, editorinput1, editorinput2, true);
+		this._editorInput = new DiffEditorInput('DiffEditor', undefined, editorinput1, editorinput2, true);
 		this._editor.setInput(this._editorInput, undefined, cancellationTokenSource.token);
 
 
diff --git a/src/sql/workbench/browser/modelComponents/editor.component.ts b/src/sql/workbench/browser/modelComponents/editor.component.ts
index 64a789f37e..c483f19f3e 100644
--- a/src/sql/workbench/browser/modelComponents/editor.component.ts
+++ b/src/sql/workbench/browser/modelComponents/editor.component.ts
@@ -11,7 +11,6 @@ import * as azdata from 'azdata';
 import * as DOM from 'vs/base/browser/dom';
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
 import { ITextModel } from 'vs/editor/common/model';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { URI } from 'vs/base/common/uri';
 import { Schemas } from 'vs/base/common/network';
 import { IModeService } from 'vs/editor/common/services/modeService';
@@ -24,6 +23,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle
 import { SimpleEditorProgressService } from 'vs/editor/standalone/browser/simpleServices';
 import { IProgressService } from 'vs/platform/progress/common/progress';
 import { ILogService } from 'vs/platform/log/common/log';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 
 @Component({
 	template: '',
@@ -33,7 +33,7 @@ export default class EditorComponent extends ComponentBase implements IComponent
 	@Input() descriptor: IComponentDescriptor;
 	@Input() modelStore: IModelStore;
 	private _editor: QueryTextEditor;
-	private _editorInput: UntitledEditorInput;
+	private _editorInput: UntitledTextEditorInput;
 	private _editorModel: ITextModel;
 	private _renderedContent: string;
 	private _languageMode: string;
@@ -66,7 +66,7 @@ export default class EditorComponent extends ComponentBase implements IComponent
 		this._editor.create(this._el.nativeElement);
 		this._editor.setVisible(true);
 		let uri = this.createUri();
-		this._editorInput = instantiationService.createInstance(UntitledEditorInput, uri, false, 'plaintext', '', '');
+		this._editorInput = instantiationService.createInstance(UntitledTextEditorInput, uri, false, 'plaintext', '', '');
 		await this._editor.setInput(this._editorInput, undefined);
 		const model = await this._editorInput.resolve();
 		this._editorModel = model.textEditorModel;
diff --git a/src/sql/workbench/browser/modelComponents/modelViewInput.ts b/src/sql/workbench/browser/modelComponents/modelViewInput.ts
index 002249b48c..f4fb921f5c 100644
--- a/src/sql/workbench/browser/modelComponents/modelViewInput.ts
+++ b/src/sql/workbench/browser/modelComponents/modelViewInput.ts
@@ -6,7 +6,7 @@
 import * as azdata from 'azdata';
 
 import { IEditorModel } from 'vs/platform/editor/common/editor';
-import { EditorInput, EditorModel, ConfirmResult } from 'vs/workbench/common/editor';
+import { EditorInput, EditorModel } from 'vs/workbench/common/editor';
 import * as DOM from 'vs/base/browser/dom';
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
 
@@ -136,17 +136,6 @@ export class ModelViewInput extends EditorInput {
 		return this._model.isDirty;
 	}
 
-	/**
-	 * Subclasses should bring up a proper dialog for the user if the editor is dirty and return the result.
-	 */
-	confirmSave(): Promise {
-		// TODO #2530 support save on close / confirm save. This is significantly more work
-		// as we need to either integrate with textFileService (seems like this isn't viable)
-		// or register our own complimentary service that handles the lifecycle operations such
-		// as close all, auto save etc.
-		return Promise.resolve(ConfirmResult.DONT_SAVE);
-	}
-
 	/**
 	 * Saves the editor if it is dirty. Subclasses return a promise with a boolean indicating the success of the operation.
 	 */
diff --git a/src/sql/workbench/browser/modelComponents/queryTextEditor.ts b/src/sql/workbench/browser/modelComponents/queryTextEditor.ts
index dc1a3cd85a..d1924f4c76 100644
--- a/src/sql/workbench/browser/modelComponents/queryTextEditor.ts
+++ b/src/sql/workbench/browser/modelComponents/queryTextEditor.ts
@@ -6,7 +6,6 @@
 import { IEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions';
 import * as nls from 'vs/nls';
 import * as DOM from 'vs/base/browser/dom';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel';
 import * as editorCommon from 'vs/editor/common/editorCommon';
 
@@ -16,7 +15,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
 import { IThemeService } from 'vs/platform/theme/common/themeService';
 import { IStorageService } from 'vs/platform/storage/common/storage';
 import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
-import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
 import { EditorOptions } from 'vs/workbench/common/editor';
 import { StandaloneCodeEditor } from 'vs/editor/standalone/browser/standaloneCodeEditor';
 import { CancellationToken } from 'vs/base/common/cancellation';
@@ -24,7 +22,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
 import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
 import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
 import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
-import { IHostService } from 'vs/workbench/services/host/browser/host';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 
 /**
  * Extension of TextResourceEditor that is always readonly rather than only with non UntitledInputs
@@ -47,16 +45,14 @@ export class QueryTextEditor extends BaseTextEditor {
 		@IStorageService storageService: IStorageService,
 		@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
 		@IThemeService themeService: IThemeService,
-		@ITextFileService textFileService: ITextFileService,
 		@IEditorGroupsService editorGroupService: IEditorGroupsService,
 		@IEditorService protected editorService: IEditorService,
-		@IHostService hostService: IHostService,
-		@IConfigurationService private workspaceConfigurationService: IConfigurationService,
+		@IConfigurationService private workspaceConfigurationService: IConfigurationService
 
 	) {
 		super(
 			QueryTextEditor.ID, telemetryService, instantiationService, storageService,
-			configurationService, themeService, textFileService, editorService, editorGroupService, hostService);
+			configurationService, themeService, editorService, editorGroupService);
 	}
 
 	public createEditorControl(parent: HTMLElement, configuration: IEditorOptions): editorCommon.IEditor {
@@ -90,7 +86,7 @@ export class QueryTextEditor extends BaseTextEditor {
 		return options;
 	}
 
-	setInput(input: UntitledEditorInput, options: EditorOptions): Promise {
+	setInput(input: UntitledTextEditorInput, options: EditorOptions): Promise {
 		return super.setInput(input, options, CancellationToken.None)
 			.then(() => this.input.resolve()
 				.then(editorModel => editorModel.load())
diff --git a/src/sql/workbench/browser/modelComponents/tree.component.ts b/src/sql/workbench/browser/modelComponents/tree.component.ts
index 305cc5613e..1aa3acae86 100644
--- a/src/sql/workbench/browser/modelComponents/tree.component.ts
+++ b/src/sql/workbench/browser/modelComponents/tree.component.ts
@@ -16,7 +16,6 @@ import { IComponent, IComponentDescriptor, IModelStore } from 'sql/workbench/bro
 import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
 import { TreeComponentRenderer } from 'sql/workbench/browser/modelComponents/treeComponentRenderer';
 import { TreeComponentDataSource } from 'sql/workbench/browser/modelComponents/treeDataSource';
-import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
 import { attachListStyler } from 'vs/platform/theme/common/styler';
 import { DefaultFilter, DefaultAccessibilityProvider, DefaultController } from 'vs/base/parts/tree/browser/treeDefaults';
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -26,6 +25,7 @@ import * as DOM from 'vs/base/browser/dom';
 import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
 import { KeyCode } from 'vs/base/common/keyCodes';
 import { values } from 'vs/base/common/collections';
+import { IThemeService } from 'vs/platform/theme/common/themeService';
 
 class Root implements ITreeComponentItem {
 	label = {
@@ -54,7 +54,7 @@ export default class TreeComponent extends ComponentBase implements IComponent,
 	@ViewChild('input', { read: ElementRef }) private _inputContainer: ElementRef;
 	constructor(
 		@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
-		@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
+		@Inject(IThemeService) private themeService: IThemeService,
 		@Inject(IInstantiationService) private _instantiationService: IInstantiationService,
 		@Inject(forwardRef(() => ElementRef)) el: ElementRef
 	) {
@@ -96,7 +96,7 @@ export default class TreeComponent extends ComponentBase implements IComponent,
 	private createTreeControl(): void {
 		if (!this._tree && this._dataProvider) {
 			const dataSource = this._instantiationService.createInstance(TreeComponentDataSource, this._dataProvider);
-			const renderer = this._instantiationService.createInstance(TreeComponentRenderer, this._dataProvider, this.themeService, { withCheckbox: this.withCheckbox });
+			const renderer = new TreeComponentRenderer(this._dataProvider, this.themeService, { withCheckbox: this.withCheckbox });
 			this._treeRenderer = renderer;
 			const controller = new DefaultController();
 			const filter = new DefaultFilter();
diff --git a/src/sql/workbench/browser/modelComponents/treeComponentRenderer.ts b/src/sql/workbench/browser/modelComponents/treeComponentRenderer.ts
index d0d8378f69..b9ba8d3bb2 100644
--- a/src/sql/workbench/browser/modelComponents/treeComponentRenderer.ts
+++ b/src/sql/workbench/browser/modelComponents/treeComponentRenderer.ts
@@ -5,11 +5,10 @@
 
 import * as dom from 'vs/base/browser/dom';
 import { ITree, IRenderer } from 'vs/base/parts/tree/browser/tree';
-import { LIGHT } from 'vs/platform/theme/common/themeService';
+import { LIGHT, IThemeService } from 'vs/platform/theme/common/themeService';
 import { Disposable } from 'vs/base/common/lifecycle';
 import { Event, Emitter } from 'vs/base/common/event';
 import { ITreeComponentItem } from 'sql/workbench/common/views';
-import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
 import { TreeViewDataProvider } from './treeViewDataProvider';
 import { URI } from 'vs/base/common/uri';
 
@@ -95,7 +94,7 @@ export class TreeComponentRenderer extends Disposable implements IRenderer {
 
 	constructor(
 		private _dataProvider: TreeViewDataProvider,
-		private themeService: IWorkbenchThemeService,
+		private themeService: IThemeService,
 		public options?: { withCheckbox: boolean }
 	) {
 		super();
diff --git a/src/sql/workbench/browser/modelComponents/webview.component.ts b/src/sql/workbench/browser/modelComponents/webview.component.ts
index 984cf54b00..8e78fdf2fe 100644
--- a/src/sql/workbench/browser/modelComponents/webview.component.ts
+++ b/src/sql/workbench/browser/modelComponents/webview.component.ts
@@ -85,7 +85,7 @@ export default class WebViewComponent extends ComponentBase implements IComponen
 		});
 
 		this._ready.then(() => {
-			this._register(this._webview.onDidClickLink(link => this.onDidClickLink(link)));
+			this._register(this._webview.onDidClickLink(link => this.onDidClickLink(URI.parse(link))));
 
 			this._register(this._webview.onMessage(e => {
 				this.fireEvent({
diff --git a/src/sql/workbench/browser/parts/views/customView.ts b/src/sql/workbench/browser/parts/views/customView.ts
index d576d4599c..44b09f2f3c 100644
--- a/src/sql/workbench/browser/parts/views/customView.ts
+++ b/src/sql/workbench/browser/parts/views/customView.ts
@@ -28,7 +28,6 @@ import { dirname, basename } from 'vs/base/common/resources';
 import { LIGHT, FileThemeIcon, FolderThemeIcon, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
 import { FileKind } from 'vs/platform/files/common/files';
 import { WorkbenchAsyncDataTree, TreeResourceNavigator2 } from 'vs/platform/list/browser/listService';
-import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
 import { localize } from 'vs/nls';
 import { timeout } from 'vs/base/common/async';
 import { editorFindMatchHighlight, editorFindMatchHighlightBorder, textLinkForeground, textCodeBlockBackground, focusBorder } from 'vs/platform/theme/common/colorRegistry';
@@ -50,8 +49,9 @@ import { IOEShimService } from 'sql/workbench/contrib/objectExplorer/browser/obj
 import { NodeContextKey } from 'sql/workbench/contrib/dataExplorer/browser/nodeContext';
 import { UserCancelledConnectionError } from 'sql/base/common/errors';
 import { firstIndex } from 'vs/base/common/arrays';
+import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet';
 
-export class CustomTreeViewPanel extends ViewletPanel {
+export class CustomTreeViewPanel extends ViewletPane {
 
 	private treeView: ITreeView;
 
@@ -63,7 +63,7 @@ export class CustomTreeViewPanel extends ViewletPanel {
 		@IConfigurationService configurationService: IConfigurationService,
 		@IContextKeyService contextKeyService: IContextKeyService,
 	) {
-		super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService);
+		super({ ...(options as IViewletPaneOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService);
 		const { treeView } = (Registry.as(Extensions.ViewsRegistry).getView(options.id));
 		this.treeView = treeView as ITreeView;
 		this._register(this.treeView.onDidChangeActions(() => this.updateActions(), this));
diff --git a/src/sql/workbench/common/editorReplacerContribution.ts b/src/sql/workbench/common/editorReplacerContribution.ts
index 612c4c6666..319825e7b1 100644
--- a/src/sql/workbench/common/editorReplacerContribution.ts
+++ b/src/sql/workbench/common/editorReplacerContribution.ts
@@ -9,7 +9,6 @@ import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/edito
 import { IEditorInput } from 'vs/workbench/common/editor';
 import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor';
 import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { IModeService } from 'vs/editor/common/services/modeService';
 import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -17,6 +16,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
 import * as path from 'vs/base/common/path';
 
 import { ILanguageAssociationRegistry, Extensions as LanguageAssociationExtensions } from 'sql/workbench/common/languageAssociation';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 
 const languageAssociationRegistry = Registry.as(LanguageAssociationExtensions.LanguageAssociations);
 
@@ -39,14 +39,14 @@ export class EditorReplacementContribution implements IWorkbenchContribution {
 		// 	return undefined;
 		// }
 
-		if (!(editor instanceof FileEditorInput) && !(editor instanceof UntitledEditorInput)) {
+		if (!(editor instanceof FileEditorInput) && !(editor instanceof UntitledTextEditorInput)) {
 			return undefined;
 		}
 
 		let language: string;
 		if (editor instanceof FileEditorInput) {
 			language = editor.getPreferredMode();
-		} else if (editor instanceof UntitledEditorInput) {
+		} else if (editor instanceof UntitledTextEditorInput) {
 			language = editor.getMode();
 		} else {
 			return undefined;
diff --git a/src/sql/workbench/common/languageAssociation.ts b/src/sql/workbench/common/languageAssociation.ts
index 38fa140942..99a382c388 100644
--- a/src/sql/workbench/common/languageAssociation.ts
+++ b/src/sql/workbench/common/languageAssociation.ts
@@ -7,7 +7,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
 import { IEditorInput, EditorInput } from 'vs/workbench/common/editor';
 import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
 import { find } from 'vs/base/common/arrays';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
 import { getCodeEditor } from 'vs/editor/browser/editorBrowser';
 
@@ -38,7 +38,7 @@ export const Extensions = {
 Registry.add(Extensions.LanguageAssociations, languageAssociationRegistery);
 
 export function doHandleUpgrade(accessor: ServicesAccessor, editor: EditorInput): EditorInput {
-	if (editor instanceof UntitledEditorInput || editor instanceof FileEditorInput) {
+	if (editor instanceof UntitledTextEditorInput || editor instanceof FileEditorInput) {
 		const instantiationService = accessor.get(IInstantiationService);
 		const activeWidget = getCodeEditor(editor);
 		const textModel = activeWidget.getModel();
diff --git a/src/sql/workbench/contrib/accounts/browser/accountDialog.ts b/src/sql/workbench/contrib/accounts/browser/accountDialog.ts
index 6f89f75a41..ef991f0b48 100644
--- a/src/sql/workbench/contrib/accounts/browser/accountDialog.ts
+++ b/src/sql/workbench/contrib/accounts/browser/accountDialog.ts
@@ -17,7 +17,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
 import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
 import { SplitView, Sizing } from 'vs/base/browser/ui/splitview/splitview';
 import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
-import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
 import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
 import { values } from 'vs/base/common/map';
 
@@ -36,13 +35,14 @@ import { ILogService } from 'vs/platform/log/common/log';
 import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
 import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
 import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
+import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet';
 
-class AccountPanel extends ViewletPanel {
+class AccountPanel extends ViewletPane {
 	public index: number;
 	private accountList: List;
 
 	constructor(
-		private options: IViewletPanelOptions,
+		private options: IViewletPaneOptions,
 		@IKeybindingService keybindingService: IKeybindingService,
 		@IContextMenuService contextMenuService: IContextMenuService,
 		@IConfigurationService configurationService: IConfigurationService,
@@ -174,7 +174,7 @@ export class AccountDialog extends Modal {
 
 	protected renderBody(container: HTMLElement) {
 		this._container = container;
-		this._splitViewContainer = DOM.$('div.account-view.monaco-panel-view');
+		this._splitViewContainer = DOM.$('div.account-view.monaco-pane-view');
 		DOM.append(container, this._splitViewContainer);
 		this._splitView = new SplitView(this._splitViewContainer);
 
diff --git a/src/sql/workbench/contrib/charts/browser/actions.ts b/src/sql/workbench/contrib/charts/browser/actions.ts
index 4b5f8cd8b8..b2fda3a0c0 100644
--- a/src/sql/workbench/contrib/charts/browser/actions.ts
+++ b/src/sql/workbench/contrib/charts/browser/actions.ts
@@ -11,7 +11,6 @@ import { localize } from 'vs/nls';
 import { Action } from 'vs/base/common/actions';
 import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
 import { URI } from 'vs/base/common/uri';
-import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
 import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
 import { IInsightsConfig } from 'sql/platform/dashboard/browser/insightRegistry';
 import { IInsightOptions } from 'sql/workbench/contrib/charts/common/interfaces';
@@ -21,6 +20,7 @@ import { IFileDialogService, FileFilter } from 'vs/platform/dialogs/common/dialo
 import { VSBuffer } from 'vs/base/common/buffer';
 import { IOpenerService } from 'vs/platform/opener/common/opener';
 import { assign } from 'vs/base/common/objects';
+import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
 
 export interface IChartActionContext {
 	options: IInsightOptions;
@@ -35,7 +35,7 @@ export class CreateInsightAction extends Action {
 	constructor(
 		@IEditorService private editorService: IEditorService,
 		@INotificationService private notificationService: INotificationService,
-		@IUntitledEditorService private untitledEditorService: IUntitledEditorService
+		@IUntitledTextEditorService private untitledEditorService: IUntitledTextEditorService
 	) {
 		super(CreateInsightAction.ID, CreateInsightAction.LABEL, CreateInsightAction.ICON);
 	}
diff --git a/src/sql/workbench/contrib/commandLine/test/electron-browser/commandLine.test.ts b/src/sql/workbench/contrib/commandLine/test/electron-browser/commandLine.test.ts
index ec07fbefab..35da2347fb 100644
--- a/src/sql/workbench/contrib/commandLine/test/electron-browser/commandLine.test.ts
+++ b/src/sql/workbench/contrib/commandLine/test/electron-browser/commandLine.test.ts
@@ -29,12 +29,13 @@ import { UntitledQueryEditorInput } from 'sql/workbench/contrib/query/common/unt
 import { TestQueryModelService } from 'sql/platform/query/test/common/testQueryModelService';
 import { Event } from 'vs/base/common/event';
 import { IQueryModelService } from 'sql/platform/query/common/queryModel';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
 import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
 import { INotificationService } from 'vs/platform/notification/common/notification';
 import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
 import { isUndefinedOrNull } from 'vs/base/common/types';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
+import { SimpleUriLabelService } from 'vs/editor/standalone/browser/simpleServices';
 
 class TestParsedArgs implements ParsedArgs {
 	[arg: string]: any;
@@ -391,7 +392,7 @@ suite('commandLineService tests', () => {
 		querymodelService.setup(c => c.onRunQueryComplete).returns(() => Event.None);
 		const instantiationService = new TestInstantiationService();
 		let uri = URI.file(args._[0]);
-		const untitledEditorInput = new UntitledEditorInput(uri, false, '', '', '', instantiationService, undefined, undefined);
+		const untitledEditorInput = new UntitledTextEditorInput(uri, false, '', '', '', instantiationService, undefined, new SimpleUriLabelService(), undefined, undefined, undefined);
 		const queryInput = new UntitledQueryEditorInput(undefined, untitledEditorInput, undefined, connectionManagementService.object, querymodelService.object, configurationService.object, undefined);
 		queryInput.state.connected = true;
 		const editorService: TypeMoq.Mock = TypeMoq.Mock.ofType(TestEditorService, TypeMoq.MockBehavior.Strict);
diff --git a/src/sql/workbench/contrib/connection/browser/connection.contribution.ts b/src/sql/workbench/contrib/connection/browser/connection.contribution.ts
index e019ad6100..3c383623d4 100644
--- a/src/sql/workbench/contrib/connection/browser/connection.contribution.ts
+++ b/src/sql/workbench/contrib/connection/browser/connection.contribution.ts
@@ -31,7 +31,7 @@ const actionRegistry = Registry.as(Extensions.Workbenc
 
 // Connection Actions
 actionRegistry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		ClearRecentConnectionsAction,
 		ClearRecentConnectionsAction.ID,
 		ClearRecentConnectionsAction.LABEL
@@ -40,7 +40,7 @@ actionRegistry.registerWorkbenchAction(
 );
 
 actionRegistry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		AddServerGroupAction,
 		AddServerGroupAction.ID,
 		AddServerGroupAction.LABEL
@@ -49,7 +49,7 @@ actionRegistry.registerWorkbenchAction(
 );
 
 actionRegistry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		AddServerAction,
 		AddServerAction.ID,
 		AddServerAction.LABEL
@@ -102,7 +102,7 @@ CommandsRegistry.registerCommand('azdata.connect',
 	});
 
 actionRegistry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		GetCurrentConnectionStringAction,
 		GetCurrentConnectionStringAction.ID,
 		GetCurrentConnectionStringAction.LABEL
diff --git a/src/sql/workbench/contrib/connection/common/connectionProviderExtension.ts b/src/sql/workbench/contrib/connection/common/connectionProviderExtension.ts
index 4f16704917..1645610359 100644
--- a/src/sql/workbench/contrib/connection/common/connectionProviderExtension.ts
+++ b/src/sql/workbench/contrib/connection/common/connectionProviderExtension.ts
@@ -130,7 +130,7 @@ const ConnectionProviderContrib: IJSONSchema = {
 						type: 'string'
 					},
 					defaultValue: {
-						type: 'any'
+						type: ['string', 'number', 'boolean', 'object', 'integer', 'null', 'array']
 					},
 					defaultValueOsOverrides: {
 						type: 'array',
@@ -142,22 +142,22 @@ const ConnectionProviderContrib: IJSONSchema = {
 									enum: ['Windows', 'Macintosh', 'Linux']
 								},
 								defaultValueOverride: {
-									type: 'any'
+									type: ['string', 'number', 'boolean', 'object', 'integer', 'null', 'array']
 								}
 							}
 						}
 					},
 					objectType: {
-						type: 'any'
+						type: ['string', 'number', 'boolean', 'object', 'integer', 'null', 'array']
 					},
 					categoryValues: {
-						type: 'any'
+						type: ['string', 'number', 'boolean', 'object', 'integer', 'null', 'array']
 					},
 					isRequired: {
 						type: 'boolean'
 					},
 					isArray: {
-						type: 'bolean'
+						type: 'boolean'
 					}
 				}
 			}
diff --git a/src/sql/workbench/contrib/dashboard/browser/contents/dashboardWidgetWrapper.component.ts b/src/sql/workbench/contrib/dashboard/browser/contents/dashboardWidgetWrapper.component.ts
index bcbe27c9f6..a2a2426e74 100644
--- a/src/sql/workbench/contrib/dashboard/browser/contents/dashboardWidgetWrapper.component.ts
+++ b/src/sql/workbench/contrib/dashboard/browser/contents/dashboardWidgetWrapper.component.ts
@@ -29,7 +29,7 @@ import { CommonServiceInterface } from 'sql/workbench/services/bootstrap/browser
 import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
 import * as colors from 'vs/platform/theme/common/colorRegistry';
 import * as themeColors from 'vs/workbench/common/theme';
-import { Action } from 'vs/base/common/actions';
+import { Action, IAction } from 'vs/base/common/actions';
 import { Registry } from 'vs/platform/registry/common/platform';
 import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
 import { memoize } from 'vs/base/common/decorators';
@@ -117,7 +117,7 @@ export class DashboardWidgetWrapper extends AngularDisposable implements OnInit
 				this._collapseAction = this.instantiationService.createInstance(CollapseWidgetAction, this._bootstrap.getUnderlyingUri(), this.guid, this.collapsed);
 				this._actionbar.push(this._collapseAction, { icon: true, label: false });
 			}
-			this._actionbar.push(this.instantiationService.createInstance(ToggleMoreWidgetAction, this._actions, this._component.actionsContext), { icon: true, label: false });
+			this._actionbar.push(this.instantiationService.createInstance(ToggleMoreWidgetAction, this._actions as Array, this._component.actionsContext), { icon: true, label: false });
 		}
 		this.layout();
 	}
diff --git a/src/sql/workbench/contrib/dashboard/browser/core/actions.ts b/src/sql/workbench/contrib/dashboard/browser/core/actions.ts
index 4df21f05a0..52775eb8d4 100644
--- a/src/sql/workbench/contrib/dashboard/browser/core/actions.ts
+++ b/src/sql/workbench/contrib/dashboard/browser/core/actions.ts
@@ -13,6 +13,7 @@ import { INewDashboardTabDialogService } from 'sql/workbench/services/dashboard/
 import { IDashboardTab } from 'sql/workbench/contrib/dashboard/browser/dashboardRegistry';
 import { subscriptionToDisposable } from 'sql/base/browser/lifecycle';
 import { find, firstIndex } from 'vs/base/common/arrays';
+import { CellContext } from 'sql/workbench/contrib/notebook/browser/cellViews/codeActions';
 
 export class EditDashboardAction extends Action {
 
@@ -81,9 +82,9 @@ export class ToggleMoreWidgetAction extends Action {
 	private static readonly ICON = 'toggle-more';
 
 	constructor(
-		private _actions: Array,
-		private _context: any,
-		@IContextMenuService private _contextMenuService: IContextMenuService
+		private readonly _actions: Array,
+		private readonly _context: CellContext,
+		@IContextMenuService private readonly _contextMenuService: IContextMenuService
 	) {
 		super(ToggleMoreWidgetAction.ID, ToggleMoreWidgetAction.LABEL, ToggleMoreWidgetAction.ICON);
 	}
@@ -104,9 +105,9 @@ export class DeleteWidgetAction extends Action {
 	private static readonly ICON = 'close';
 
 	constructor(
-		private _widgetId,
-		private _uri,
-		@IAngularEventingService private angularEventService: IAngularEventingService
+		private _widgetId: string,
+		private _uri: string,
+		@IAngularEventingService private readonly angularEventService: IAngularEventingService
 	) {
 		super(DeleteWidgetAction.ID, DeleteWidgetAction.LABEL, DeleteWidgetAction.ICON);
 	}
diff --git a/src/sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerTree.ts b/src/sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerTree.ts
index 5f35e6b088..4236e8dbb6 100644
--- a/src/sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerTree.ts
+++ b/src/sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerTree.ts
@@ -37,7 +37,7 @@ export class ExplorerController extends TreeDefaults.DefaultController {
 
 	constructor(
 		// URI for the dashboard for managing, should look into some other way of doing this
-		private _uri,
+		private _uri: string,
 		private _connectionService: SingleConnectionManagementService,
 		private _router: Router,
 		private readonly bootStrapService: CommonServiceInterface,
diff --git a/src/sql/workbench/contrib/dataExplorer/browser/connectionViewletPanel.ts b/src/sql/workbench/contrib/dataExplorer/browser/connectionViewletPanel.ts
index 7a601de1b3..a85167f3c3 100644
--- a/src/sql/workbench/contrib/dataExplorer/browser/connectionViewletPanel.ts
+++ b/src/sql/workbench/contrib/dataExplorer/browser/connectionViewletPanel.ts
@@ -10,7 +10,6 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
 import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
 import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
-import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
 import { IAction } from 'vs/base/common/actions';
 import { ServerTreeView } from 'sql/workbench/contrib/objectExplorer/browser/serverTreeView';
 import {
@@ -20,8 +19,9 @@ import {
 import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
 import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
 import { ITree } from 'vs/base/parts/tree/browser/tree';
+import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet';
 
-export class ConnectionViewletPanel extends ViewletPanel {
+export class ConnectionViewletPanel extends ViewletPane {
 
 	public static readonly ID = 'dataExplorer.servers';
 
@@ -40,7 +40,7 @@ export class ConnectionViewletPanel extends ViewletPanel {
 		@IObjectExplorerService private readonly objectExplorerService: IObjectExplorerService,
 		@IContextKeyService contextKeyService: IContextKeyService
 	) {
-		super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService);
+		super({ ...(options as IViewletPaneOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService);
 		this._addServerAction = this.instantiationService.createInstance(AddServerAction,
 			AddServerAction.ID,
 			AddServerAction.LABEL);
diff --git a/src/sql/workbench/contrib/dataExplorer/browser/dataExplorer.contribution.ts b/src/sql/workbench/contrib/dataExplorer/browser/dataExplorer.contribution.ts
index 7f548f13a1..a235876a61 100644
--- a/src/sql/workbench/contrib/dataExplorer/browser/dataExplorer.contribution.ts
+++ b/src/sql/workbench/contrib/dataExplorer/browser/dataExplorer.contribution.ts
@@ -18,7 +18,7 @@ import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
 import { DataExplorerContainerExtensionHandler } from 'sql/workbench/contrib/dataExplorer/browser/dataExplorerExtensionPoint';
 
 // Data Explorer Viewlet
-const viewletDescriptor = new ViewletDescriptor(
+const viewletDescriptor = ViewletDescriptor.create(
 	DataExplorerViewlet,
 	VIEWLET_ID,
 	localize('workbench.dataExplorer', "Connections"),
@@ -32,7 +32,7 @@ const workbenchRegistry = Registry.as(Workbench
 workbenchRegistry.registerWorkbenchContribution(DataExplorerViewletViewsContribution, LifecyclePhase.Starting);
 const registry = Registry.as(ActionExtensions.WorkbenchActions);
 registry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		OpenDataExplorerViewletAction,
 		OpenDataExplorerViewletAction.ID,
 		OpenDataExplorerViewletAction.LABEL,
diff --git a/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet.ts b/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet.ts
index 935db70560..03acfb6559 100644
--- a/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet.ts
+++ b/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet.ts
@@ -16,7 +16,6 @@ import { IStorageService } from 'vs/platform/storage/common/storage';
 import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
 import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
 import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views';
-import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet';
 import { ConnectionViewletPanel } from 'sql/workbench/contrib/dataExplorer/browser/connectionViewletPanel';
 import { Extensions as ViewContainerExtensions, IViewDescriptor, IViewsRegistry, IViewContainersRegistry } from 'vs/workbench/common/views';
 import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -27,6 +26,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
 import { ShowViewletAction } from 'vs/workbench/browser/viewlet';
 import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
 import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
+import { ViewletPane } from 'vs/workbench/browser/parts/views/paneViewlet';
 
 export const VIEWLET_ID = 'workbench.view.connections';
 
@@ -134,13 +134,13 @@ export class DataExplorerViewlet extends ViewContainerViewlet {
 		return actions;
 	}
 
-	protected onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPanel[] {
+	protected onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPane[] {
 		const addedViews = super.onDidAddViews(added);
 		return addedViews;
 	}
 
-	protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewletPanel {
-		let viewletPanel = this.instantiationService.createInstance(viewDescriptor.ctorDescriptor.ctor, options) as ViewletPanel;
+	protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewletPane {
+		let viewletPanel = this.instantiationService.createInstance(viewDescriptor.ctorDescriptor.ctor, options) as ViewletPane;
 		this._register(viewletPanel);
 		return viewletPanel;
 	}
diff --git a/src/sql/workbench/contrib/dataExplorer/browser/media/dataExplorer.contribution.css b/src/sql/workbench/contrib/dataExplorer/browser/media/dataExplorer.contribution.css
index 0e353e15e7..111e49c4bd 100644
--- a/src/sql/workbench/contrib/dataExplorer/browser/media/dataExplorer.contribution.css
+++ b/src/sql/workbench/contrib/dataExplorer/browser/media/dataExplorer.contribution.css
@@ -5,6 +5,11 @@
 
 /* Activity Bar */
 .monaco-workbench .activitybar .monaco-action-bar .action-label.dataExplorer {
-	-webkit-mask: url('server_page_inverse.svg') no-repeat 50% 50%;
-	-webkit-mask-size: 25px 25px;
-}
\ No newline at end of file
+	-webkit-mask: url('server_page_inverse.svg') 50% 50% / 24px no-repeat;
+	background-color: rgba(255, 255, 255, 0.4);
+}
+
+/* Activity Bar */
+.monaco-workbench .activitybar .monaco-action-bar .checked .action-label.dataExplorer {
+	background-color: rgb(255, 255, 255); /* this is a patch, will need to find a better long term fix*/
+}
diff --git a/src/sql/workbench/contrib/dataExplorer/test/browser/dataExplorerViewlet.test.ts b/src/sql/workbench/contrib/dataExplorer/test/browser/dataExplorerViewlet.test.ts
index 3651632f08..641efbdf35 100644
--- a/src/sql/workbench/contrib/dataExplorer/test/browser/dataExplorerViewlet.test.ts
+++ b/src/sql/workbench/contrib/dataExplorer/test/browser/dataExplorerViewlet.test.ts
@@ -22,7 +22,7 @@ suite('Data Explorer Viewlet', () => {
 	}
 
 	test('ViewletDescriptor API', function () {
-		let d = new ViewletDescriptor(DataExplorerTestViewlet, 'id', 'name', 'class', 1);
+		let d = ViewletDescriptor.create(DataExplorerTestViewlet, 'id', 'name', 'class', 1);
 		assert.strictEqual(d.id, 'id');
 		assert.strictEqual(d.name, 'name');
 		assert.strictEqual(d.cssClass, 'class');
@@ -30,11 +30,11 @@ suite('Data Explorer Viewlet', () => {
 	});
 
 	test('Editor Aware ViewletDescriptor API', function () {
-		let d = new ViewletDescriptor(DataExplorerTestViewlet, 'id', 'name', 'class', 5);
+		let d = ViewletDescriptor.create(DataExplorerTestViewlet, 'id', 'name', 'class', 5);
 		assert.strictEqual(d.id, 'id');
 		assert.strictEqual(d.name, 'name');
 
-		d = new ViewletDescriptor(DataExplorerTestViewlet, 'id', 'name', 'class', 5);
+		d = ViewletDescriptor.create(DataExplorerTestViewlet, 'id', 'name', 'class', 5);
 		assert.strictEqual(d.id, 'id');
 		assert.strictEqual(d.name, 'name');
 	});
@@ -45,11 +45,11 @@ suite('Data Explorer Viewlet', () => {
 		assert(Types.isFunction(Platform.Registry.as(Extensions.Viewlets).getViewlets));
 
 		let oldCount = Platform.Registry.as(Extensions.Viewlets).getViewlets().length;
-		let d = new ViewletDescriptor(DataExplorerTestViewlet, 'dataExplorer-test-id', 'name');
+		let d = ViewletDescriptor.create(DataExplorerTestViewlet, 'dataExplorer-test-id', 'name');
 		Platform.Registry.as(Extensions.Viewlets).registerViewlet(d);
 		let retrieved = Platform.Registry.as(Extensions.Viewlets).getViewlet('dataExplorer-test-id');
 		assert(d === retrieved);
 		let newCount = Platform.Registry.as(Extensions.Viewlets).getViewlets().length;
 		assert.equal(oldCount + 1, newCount);
 	});
-});
\ No newline at end of file
+});
diff --git a/src/sql/workbench/contrib/editData/browser/editDataEditor.ts b/src/sql/workbench/contrib/editData/browser/editDataEditor.ts
index 06035328d9..05eb44a1e8 100644
--- a/src/sql/workbench/contrib/editData/browser/editDataEditor.ts
+++ b/src/sql/workbench/contrib/editData/browser/editDataEditor.ts
@@ -29,7 +29,7 @@ import {
 import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor';
 import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
 import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 import { IFlexibleSash, HorizontalFlexibleSash } from 'sql/workbench/contrib/query/browser/flexibleSash';
 import { EditDataResultsEditor } from 'sql/workbench/contrib/editData/browser/editDataResultsEditor';
 import { EditDataResultsInput } from 'sql/workbench/contrib/editData/browser/editDataResultsInput';
@@ -491,7 +491,7 @@ export class EditDataEditor extends BaseEditor {
 	/**
 	 * Sets input for the SQL editor after it has been created.
 	 */
-	private _onSqlEditorCreated(sqlEditor: TextResourceEditor, sqlInput: UntitledEditorInput, options: EditorOptions): Thenable {
+	private _onSqlEditorCreated(sqlEditor: TextResourceEditor, sqlInput: UntitledTextEditorInput, options: EditorOptions): Thenable {
 		this._sqlEditor = sqlEditor;
 		return this._sqlEditor.setInput(sqlInput, options, CancellationToken.None);
 	}
@@ -522,7 +522,7 @@ export class EditDataEditor extends BaseEditor {
 			createEditors = () => {
 				return Promise.all([
 					this._createEditor(newInput.results, this._resultsEditorContainer),
-					this._createEditor(newInput.sql, this._sqlEditorContainer)
+					this._createEditor(newInput.sql, this._sqlEditorContainer)
 				]);
 			};
 			onEditorsCreated = (result: IEditor[]) => {
@@ -535,7 +535,7 @@ export class EditDataEditor extends BaseEditor {
 			// If only the sql editor exists, create a promise and wait for the sql editor to be created
 		} else {
 			createEditors = () => {
-				return this._createEditor(newInput.sql, this._sqlEditorContainer);
+				return this._createEditor(newInput.sql, this._sqlEditorContainer);
 			};
 			onEditorsCreated = (result: TextResourceEditor) => {
 				return Promise.all([
diff --git a/src/sql/workbench/contrib/editData/browser/editDataInput.ts b/src/sql/workbench/contrib/editData/browser/editDataInput.ts
index 61dbf73059..dd1a5754e7 100644
--- a/src/sql/workbench/contrib/editData/browser/editDataInput.ts
+++ b/src/sql/workbench/contrib/editData/browser/editDataInput.ts
@@ -3,7 +3,7 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
-import { EditorInput, EditorModel, ConfirmResult, EncodingMode } from 'vs/workbench/common/editor';
+import { EditorInput, EditorModel, EncodingMode } from 'vs/workbench/common/editor';
 import { IConnectionManagementService, IConnectableInput, INewConnectionParams } from 'sql/platform/connection/common/connectionManagement';
 import { IQueryModelService } from 'sql/platform/query/common/queryModel';
 import { Event, Emitter } from 'vs/base/common/event';
@@ -12,9 +12,9 @@ import { URI } from 'vs/base/common/uri';
 import * as nls from 'vs/nls';
 import { INotificationService } from 'vs/platform/notification/common/notification';
 import Severity from 'vs/base/common/severity';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { EditDataResultsInput } from 'sql/workbench/contrib/editData/browser/editDataResultsInput';
 import { IEditorViewState } from 'vs/editor/common/editorCommon';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 
 /**
  * Input for the EditDataEditor.
@@ -38,9 +38,9 @@ export class EditDataInput extends EditorInput implements IConnectableInput {
 
 	constructor(
 		private _uri: URI,
-		private _schemaName,
-		private _tableName,
-		private _sql: UntitledEditorInput,
+		private _schemaName: string,
+		private _tableName: string,
+		private _sql: UntitledTextEditorInput,
 		private _queryString: string,
 		private _results: EditDataResultsInput,
 		@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@@ -92,7 +92,7 @@ export class EditDataInput extends EditorInput implements IConnectableInput {
 	public get tableName(): string { return this._tableName; }
 	public get schemaName(): string { return this._schemaName; }
 	public get uri(): string { return this._uri.toString(); }
-	public get sql(): UntitledEditorInput { return this._sql; }
+	public get sql(): UntitledTextEditorInput { return this._sql; }
 	public get results(): EditDataResultsInput { return this._results; }
 	public getResultsInputResource(): string { return this._results.uri; }
 	public get updateTaskbarEvent(): Event { return this._updateTaskbar.event; }
@@ -108,7 +108,6 @@ export class EditDataInput extends EditorInput implements IConnectableInput {
 	public showResultsEditor(): void { this._showResultsEditor.fire(undefined); }
 	public isDirty(): boolean { return false; }
 	public save(): Promise { return Promise.resolve(false); }
-	public confirmSave(): Promise { return Promise.resolve(ConfirmResult.DONT_SAVE); }
 	public getTypeId(): string { return EditDataInput.ID; }
 	public setBootstrappedTrue(): void { this._hasBootstrapped = true; }
 	public getResource(): URI { return this._uri; }
diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/code.component.ts b/src/sql/workbench/contrib/notebook/browser/cellViews/code.component.ts
index abf9734df1..cbb7c08af1 100644
--- a/src/sql/workbench/contrib/notebook/browser/cellViews/code.component.ts
+++ b/src/sql/workbench/contrib/notebook/browser/cellViews/code.component.ts
@@ -21,7 +21,6 @@ import { SimpleEditorProgressService } from 'vs/editor/standalone/browser/simple
 import { IProgressService } from 'vs/platform/progress/common/progress';
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
 import { ITextModel } from 'vs/editor/common/model';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import * as DOM from 'vs/base/browser/dom';
 import { IModeService } from 'vs/editor/common/services/modeService';
 import { IModelService } from 'vs/editor/common/services/modelService';
@@ -30,11 +29,12 @@ import { Event, Emitter } from 'vs/base/common/event';
 import { CellTypes } from 'sql/workbench/contrib/notebook/common/models/contracts';
 import { OVERRIDE_EDITOR_THEMING_SETTING } from 'sql/workbench/services/notebook/browser/notebookService';
 import * as notebookUtils from 'sql/workbench/contrib/notebook/browser/models/notebookUtils';
-import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel';
 import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
 import { ILogService } from 'vs/platform/log/common/log';
 import { CollapseComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/collapse.component';
 import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
+import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel';
 
 export const CODE_SELECTOR: string = 'code-component';
 const MARKDOWN_CLASS = 'markdown';
@@ -93,7 +93,7 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
 	private readonly _maximumHeight = 4000;
 	private _cellModel: ICellModel;
 	private _editor: QueryTextEditor;
-	private _editorInput: UntitledEditorInput;
+	private _editorInput: UntitledTextEditorInput;
 	private _editorModel: ITextModel;
 	private _model: NotebookModel;
 	private _activeCellId: string;
@@ -197,11 +197,11 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
 		let uri = this.cellModel.cellUri;
 		let cellModelSource: string;
 		cellModelSource = Array.isArray(this.cellModel.source) ? this.cellModel.source.join('') : this.cellModel.source;
-		this._editorInput = instantiationService.createInstance(UntitledEditorInput, uri, false, this.cellModel.language, cellModelSource, '');
+		this._editorInput = instantiationService.createInstance(UntitledTextEditorInput, uri, false, this.cellModel.language, cellModelSource, '');
 		await this._editor.setInput(this._editorInput, undefined);
 		this.setFocusAndScroll();
 
-		let untitledEditorModel: UntitledEditorModel = await this._editorInput.resolve();
+		let untitledEditorModel: UntitledTextEditorModel = await this._editorInput.resolve();
 		this._editorModel = untitledEditorModel.textEditorModel;
 
 		let isActive = this.cellModel.id === this._activeCellId;
diff --git a/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts b/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts
index faa49b3484..d9567a1a63 100644
--- a/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts
+++ b/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts
@@ -3,8 +3,7 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
-import { IEditorModel } from 'vs/platform/editor/common/editor';
-import { EditorInput, EditorModel, ConfirmResult } from 'vs/workbench/common/editor';
+import { EditorInput, EditorModel, ISaveOptions } from 'vs/workbench/common/editor';
 import { Emitter, Event } from 'vs/base/common/event';
 import { URI } from 'vs/base/common/uri';
 import * as resources from 'vs/base/common/resources';
@@ -16,12 +15,10 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
 import { ITextModelService } from 'vs/editor/common/services/resolverService';
 import { INotebookModel, IContentManager, NotebookContentChange } from 'sql/workbench/contrib/notebook/browser/models/modelInterfaces';
 import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
-import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel';
 import { Schemas } from 'vs/base/common/network';
-import { ITextFileService, ISaveOptions, StateChange } from 'vs/workbench/services/textfile/common/textfiles';
+import { ITextFileService, StateChange } from 'vs/workbench/services/textfile/common/textfiles';
 import { LocalContentManager } from 'sql/workbench/services/notebook/common/localContentManager';
 import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
 import { IDisposable } from 'vs/base/common/lifecycle';
 import { NotebookChangeType } from 'sql/workbench/contrib/notebook/common/models/contracts';
@@ -29,6 +26,8 @@ import { Deferred } from 'sql/base/common/promise';
 import { NotebookTextFileModel } from 'sql/workbench/contrib/notebook/browser/models/notebookTextFileModel';
 import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
 import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel';
+import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
 import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
 import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
@@ -42,7 +41,7 @@ export class NotebookEditorModel extends EditorModel {
 	private readonly _onDidChangeDirty: Emitter = this._register(new Emitter());
 	private _lastEditFullReplacement: boolean;
 	constructor(public readonly notebookUri: URI,
-		private textEditorModel: TextFileEditorModel | UntitledEditorModel | ResourceEditorModel,
+		private textEditorModel: TextFileEditorModel | UntitledTextEditorModel | ResourceEditorModel,
 		@INotebookService private notebookService: INotebookService,
 		@ITextFileService private textFileService: ITextFileService,
 		@ITextResourcePropertiesService private textResourcePropertiesService: ITextResourcePropertiesService
@@ -67,7 +66,7 @@ export class NotebookEditorModel extends EditorModel {
 				}, err => undefined);
 			}
 		}));
-		if (this.textEditorModel instanceof UntitledEditorModel) {
+		if (this.textEditorModel instanceof UntitledTextEditorModel) {
 			this._register(this.textEditorModel.onDidChangeDirty(e => {
 				let dirty = this.textEditorModel instanceof ResourceEditorModel ? false : this.textEditorModel.isDirty();
 				this.setDirty(dirty);
@@ -198,7 +197,7 @@ export class NotebookEditorModel extends EditorModel {
 	}
 }
 
-type TextInput = ResourceEditorInput | UntitledEditorInput | FileEditorInput;
+type TextInput = ResourceEditorInput | UntitledTextEditorInput | FileEditorInput;
 
 export abstract class NotebookInput extends EditorInput {
 	private _providerId: string;
@@ -211,7 +210,7 @@ export abstract class NotebookInput extends EditorInput {
 	private _parentContainer: HTMLElement;
 	private readonly _layoutChanged: Emitter = this._register(new Emitter());
 	private _model: NotebookEditorModel;
-	private _untitledEditorModel: UntitledEditorModel;
+	private _untitledEditorModel: UntitledTextEditorModel;
 	private _contentManager: IContentManager;
 	private _providersLoaded: Promise;
 	private _dirtyListener: IDisposable;
@@ -241,10 +240,6 @@ export abstract class NotebookInput extends EditorInput {
 		return this._textInput;
 	}
 
-	public confirmSave(): Promise {
-		return this._textInput.confirmSave();
-	}
-
 	public revert(): Promise {
 		return this._textInput.revert();
 	}
@@ -329,11 +324,11 @@ export abstract class NotebookInput extends EditorInput {
 		return this.resource;
 	}
 
-	public get untitledEditorModel(): UntitledEditorModel {
+	public get untitledEditorModel(): UntitledTextEditorModel {
 		return this._untitledEditorModel;
 	}
 
-	public set untitledEditorModel(value: UntitledEditorModel) {
+	public set untitledEditorModel(value: UntitledTextEditorModel) {
 		this._untitledEditorModel = value;
 	}
 
@@ -347,7 +342,7 @@ export abstract class NotebookInput extends EditorInput {
 		if (this._model) {
 			return Promise.resolve(this._model);
 		} else {
-			let textOrUntitledEditorModel: UntitledEditorModel | IEditorModel;
+			let textOrUntitledEditorModel: TextFileEditorModel | UntitledTextEditorModel | ResourceEditorModel;
 			if (this.resource.scheme === Schemas.untitled) {
 				if (this._untitledEditorModel) {
 					this._untitledEditorModel.textEditorModel.onBeforeAttached();
@@ -357,12 +352,12 @@ export abstract class NotebookInput extends EditorInput {
 					if (!(resolvedInput instanceof BinaryEditorModel)) {
 						resolvedInput.textEditorModel.onBeforeAttached();
 					}
-					textOrUntitledEditorModel = resolvedInput;
+					textOrUntitledEditorModel = resolvedInput as TextFileEditorModel | UntitledTextEditorModel | ResourceEditorModel;
 				}
 			} else {
 				const textEditorModelReference = await this.textModelService.createModelReference(this.resource);
 				textEditorModelReference.object.textEditorModel.onBeforeAttached();
-				textOrUntitledEditorModel = await textEditorModelReference.object.load();
+				textOrUntitledEditorModel = await textEditorModelReference.object.load() as TextFileEditorModel | ResourceEditorModel;
 			}
 			this._model = this._register(this.instantiationService.createInstance(NotebookEditorModel, this.resource, textOrUntitledEditorModel));
 			this.hookDirtyListener(this._model.onDidChangeDirty, () => this._onDidChangeDirty.fire());
diff --git a/src/sql/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/sql/workbench/contrib/notebook/browser/notebook.contribution.ts
index 063c57c059..e64aa2b642 100644
--- a/src/sql/workbench/contrib/notebook/browser/notebook.contribution.ts
+++ b/src/sql/workbench/contrib/notebook/browser/notebook.contribution.ts
@@ -7,7 +7,6 @@ import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } fro
 import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
 import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { localize } from 'vs/nls';
 import { IEditorInputFactoryRegistry, Extensions as EditorInputFactoryExtensions } from 'vs/workbench/common/editor';
 
@@ -46,6 +45,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host';
 import { MarkdownOutputComponent } from 'sql/workbench/contrib/notebook/browser/outputs/markdownOutput.component';
 import { registerCellComponent } from 'sql/platform/notebooks/common/outputRegistry';
 import { TextCellComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/textCell.component';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 
 Registry.as(EditorInputFactoryExtensions.EditorInputFactories)
 	.registerEditorInputFactory(FileNotebookInput.ID, FileNoteBookEditorInputFactory);
@@ -58,7 +58,7 @@ Registry.as(LanguageAssociationExtensions.Language
 		const instantiationService = accessor.get(IInstantiationService);
 		if (editor instanceof FileEditorInput) {
 			return instantiationService.createInstance(FileNotebookInput, editor.getName(), editor.getResource(), editor);
-		} else if (editor instanceof UntitledEditorInput) {
+		} else if (editor instanceof UntitledTextEditorInput) {
 			return instantiationService.createInstance(UntitledNotebookInput, editor.getName(), editor.getResource(), editor);
 		} else {
 			return undefined;
@@ -73,7 +73,7 @@ Registry.as(EditorExtensions.Editors)
 const actionRegistry = Registry.as(WorkbenchActionsExtensions.WorkbenchActions);
 
 actionRegistry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		NewNotebookAction,
 		NewNotebookAction.ID,
 		NewNotebookAction.LABEL,
diff --git a/src/sql/workbench/contrib/notebook/browser/outputs/gridOutput.component.ts b/src/sql/workbench/contrib/notebook/browser/outputs/gridOutput.component.ts
index a26545cdab..9cbec76c5a 100644
--- a/src/sql/workbench/contrib/notebook/browser/outputs/gridOutput.component.ts
+++ b/src/sql/workbench/contrib/notebook/browser/outputs/gridOutput.component.ts
@@ -10,7 +10,6 @@ import { IGridDataProvider, getResultsString } from 'sql/platform/query/common/g
 import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
 import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
-import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
 import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
 import { SaveFormat } from 'sql/workbench/contrib/grid/common/interfaces';
 import { IDataResource } from 'sql/workbench/services/notebook/browser/sql/sqlSessionManager';
@@ -35,6 +34,7 @@ import { ResultSerializer, SaveResultsResponse } from 'sql/workbench/contrib/que
 import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
 import { values } from 'vs/base/common/collections';
 import { assign } from 'vs/base/common/objects';
+import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
 
 @Component({
 	selector: GridOutputComponent.SELECTOR,
@@ -138,7 +138,7 @@ class DataResourceTable extends GridTableBase {
 		@IContextMenuService contextMenuService: IContextMenuService,
 		@IInstantiationService instantiationService: IInstantiationService,
 		@IEditorService editorService: IEditorService,
-		@IUntitledEditorService untitledEditorService: IUntitledEditorService,
+		@IUntitledTextEditorService untitledEditorService: IUntitledTextEditorService,
 		@IConfigurationService configurationService: IConfigurationService,
 		@ISerializationService private _serializationService: ISerializationService
 	) {
diff --git a/src/sql/workbench/contrib/notebook/common/models/nodebookInputFactory.ts b/src/sql/workbench/contrib/notebook/common/models/nodebookInputFactory.ts
index f27c67d542..a22f81ac36 100644
--- a/src/sql/workbench/contrib/notebook/common/models/nodebookInputFactory.ts
+++ b/src/sql/workbench/contrib/notebook/common/models/nodebookInputFactory.ts
@@ -6,11 +6,11 @@
 import { IEditorInputFactory, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } from 'vs/workbench/common/editor';
 import { Registry } from 'vs/platform/registry/common/platform';
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { FILE_EDITOR_INPUT_ID } from 'vs/workbench/contrib/files/common/files';
 import { FileNotebookInput } from 'sql/workbench/contrib/notebook/common/models/fileNotebookInput';
 import { UntitledNotebookInput } from 'sql/workbench/contrib/notebook/common/models/untitledNotebookInput';
 import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 
 const editorInputFactoryRegistry = Registry.as(EditorInputExtensions.EditorInputFactories);
 
@@ -32,7 +32,7 @@ export class FileNoteBookEditorInputFactory implements IEditorInputFactory {
 
 export class UntitledNoteBookEditorInputFactory implements IEditorInputFactory {
 	serialize(editorInput: UntitledNotebookInput): string {
-		const factory = editorInputFactoryRegistry.getEditorInputFactory(UntitledEditorInput.ID);
+		const factory = editorInputFactoryRegistry.getEditorInputFactory(UntitledTextEditorInput.ID);
 		if (factory) {
 			return factory.serialize(editorInput.textInput); // serialize based on the underlying input
 		}
@@ -40,8 +40,8 @@ export class UntitledNoteBookEditorInputFactory implements IEditorInputFactory {
 	}
 
 	deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): UntitledNotebookInput | undefined {
-		const factory = editorInputFactoryRegistry.getEditorInputFactory(UntitledEditorInput.ID);
-		const untitledEditorInput = factory.deserialize(instantiationService, serializedEditorInput) as UntitledEditorInput;
+		const factory = editorInputFactoryRegistry.getEditorInputFactory(UntitledTextEditorInput.ID);
+		const untitledEditorInput = factory.deserialize(instantiationService, serializedEditorInput) as UntitledTextEditorInput;
 		return instantiationService.createInstance(UntitledNotebookInput, untitledEditorInput.getName(), untitledEditorInput.getResource(), untitledEditorInput);
 	}
 }
diff --git a/src/sql/workbench/contrib/notebook/common/models/untitledNotebookInput.ts b/src/sql/workbench/contrib/notebook/common/models/untitledNotebookInput.ts
index adf0f50cbb..9f2b54c858 100644
--- a/src/sql/workbench/contrib/notebook/common/models/untitledNotebookInput.ts
+++ b/src/sql/workbench/contrib/notebook/common/models/untitledNotebookInput.ts
@@ -7,9 +7,9 @@ import { URI } from 'vs/base/common/uri';
 import { ITextModelService } from 'vs/editor/common/services/resolverService';
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
 import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { NotebookInput } from 'sql/workbench/contrib/notebook/browser/models/notebookInput';
 import { INotebookService } from 'sql/workbench/services/notebook/browser/notebookService';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 
 export class UntitledNotebookInput extends NotebookInput {
 	public static ID: string = 'workbench.editorinputs.untitledNotebookInput';
@@ -17,7 +17,7 @@ export class UntitledNotebookInput extends NotebookInput {
 	constructor(
 		title: string,
 		resource: URI,
-		textInput: UntitledEditorInput,
+		textInput: UntitledTextEditorInput,
 		@ITextModelService textModelService: ITextModelService,
 		@IInstantiationService instantiationService: IInstantiationService,
 		@INotebookService notebookService: INotebookService,
@@ -26,14 +26,19 @@ export class UntitledNotebookInput extends NotebookInput {
 		super(title, resource, textInput, textModelService, instantiationService, notebookService, extensionService);
 	}
 
-	public get textInput(): UntitledEditorInput {
-		return super.textInput as UntitledEditorInput;
+	public get textInput(): UntitledTextEditorInput {
+		return super.textInput as UntitledTextEditorInput;
 	}
 
 	public setMode(mode: string): void {
 		this.textInput.setMode(mode);
 	}
 
+	isUntitled(): boolean {
+		// Subclasses need to explicitly opt-in to being untitled.
+		return true;
+	}
+
 	public getTypeId(): string {
 		return UntitledNotebookInput.ID;
 	}
diff --git a/src/sql/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts b/src/sql/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts
index caaa043c37..598b52fe07 100644
--- a/src/sql/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts
+++ b/src/sql/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts
@@ -867,7 +867,7 @@ suite('Notebook Editor Model', function (): void {
 
 	async function createTextEditorModel(self: Mocha.ITestCallbackContext): Promise {
 		let textFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(self, defaultUri.toString()), 'utf8', undefined);
-		(accessor.textFileService.models).add(textFileEditorModel.getResource(), textFileEditorModel);
+		(accessor.textFileService.models).add(textFileEditorModel.resource, textFileEditorModel);
 		await textFileEditorModel.load();
 		return new NotebookEditorModel(defaultUri, textFileEditorModel, mockNotebookService.object, accessor.textFileService, testResourcePropertiesService);
 	}
diff --git a/src/sql/workbench/contrib/notebook/test/electron-browser/contentManagers.test.ts b/src/sql/workbench/contrib/notebook/test/electron-browser/contentManagers.test.ts
index e0011e8d85..52d210566a 100644
--- a/src/sql/workbench/contrib/notebook/test/electron-browser/contentManagers.test.ts
+++ b/src/sql/workbench/contrib/notebook/test/electron-browser/contentManagers.test.ts
@@ -53,11 +53,11 @@ suite('Local Content Manager', function (): void {
 		const fileService = new class extends TestFileService {
 			async readFile(resource: URI, options?: IReadFileOptions | undefined): Promise {
 				const content = await pfs.readFile(resource.fsPath);
-				return { name: ',', size: 0, etag: '', mtime: 0, value: VSBuffer.fromString(content.toString()), resource };
+				return { name: ',', size: 0, etag: '', mtime: 0, value: VSBuffer.fromString(content.toString()), resource, ctime: 0 };
 			}
 			async writeFile(resource: URI, bufferOrReadable: VSBuffer | VSBufferReadable, options?: IWriteFileOptions): Promise {
 				await pfs.writeFile(resource.fsPath, bufferOrReadable.toString());
-				return { resource: resource, mtime: 0, etag: '', size: 0, name: '', isDirectory: false };
+				return { resource: resource, mtime: 0, etag: '', size: 0, name: '', isDirectory: false, ctime: 0, isFile: true, isSymbolicLink: false };
 			}
 		};
 		instantiationService.set(IFileService, fileService);
diff --git a/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts b/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts
index 9cad6218ba..129e563a4d 100644
--- a/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts
+++ b/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts
@@ -20,7 +20,6 @@ import { ProfilerResourceEditor } from 'sql/workbench/contrib/profiler/browser/p
 import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
 import { ITextModel } from 'vs/editor/common/model';
 import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { URI } from 'vs/base/common/uri';
 import { Schemas } from 'vs/base/common/network';
 import * as nls from 'vs/nls';
@@ -52,6 +51,7 @@ import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelect
 import { handleCopyRequest } from 'sql/workbench/contrib/profiler/browser/profilerCopyHandler';
 import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
 import { find } from 'vs/base/common/arrays';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 
 class BasicView implements IView {
 	public get element(): HTMLElement {
@@ -120,7 +120,7 @@ export class ProfilerEditor extends BaseEditor {
 
 	private _editor: ProfilerResourceEditor;
 	private _editorModel: ITextModel;
-	private _editorInput: UntitledEditorInput;
+	private _editorInput: UntitledTextEditorInput;
 	private _splitView: SplitView;
 	private _container: HTMLElement;
 	private _body: HTMLElement;
@@ -432,7 +432,7 @@ export class ProfilerEditor extends BaseEditor {
 		editorContainer.className = 'profiler-editor';
 		this._editor.create(editorContainer);
 		this._editor.setVisible(true);
-		this._editorInput = this._instantiationService.createInstance(UntitledEditorInput, URI.from({ scheme: Schemas.untitled }), false, 'sql', '', '');
+		this._editorInput = this._instantiationService.createInstance(UntitledTextEditorInput, URI.from({ scheme: Schemas.untitled }), false, 'sql', '', '');
 		this._editor.setInput(this._editorInput, undefined);
 		this._editorInput.resolve().then(model => this._editorModel = model.textEditorModel);
 		return editorContainer;
diff --git a/src/sql/workbench/contrib/profiler/browser/profilerInput.ts b/src/sql/workbench/contrib/profiler/browser/profilerInput.ts
index 7305dcfb0c..acce74e513 100644
--- a/src/sql/workbench/contrib/profiler/browser/profilerInput.ts
+++ b/src/sql/workbench/contrib/profiler/browser/profilerInput.ts
@@ -11,15 +11,13 @@ import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
 import * as azdata from 'azdata';
 import * as nls from 'vs/nls';
 
-import { EditorInput, ConfirmResult } from 'vs/workbench/common/editor';
+import { EditorInput } from 'vs/workbench/common/editor';
 import { IEditorModel } from 'vs/platform/editor/common/editor';
 import { INotificationService } from 'vs/platform/notification/common/notification';
 import { Event, Emitter } from 'vs/base/common/event';
 import { generateUuid } from 'vs/base/common/uuid';
-import { IDialogService, IShowResult } from 'vs/platform/dialogs/common/dialogs';
 import * as types from 'vs/base/common/types';
 import { URI } from 'vs/base/common/uri';
-import Severity from 'vs/base/common/severity';
 import { FilterData } from 'sql/workbench/services/profiler/browser/profilerFilter';
 import { uriPrefixes } from 'sql/platform/connection/common/utils';
 import { find } from 'vs/base/common/arrays';
@@ -46,8 +44,7 @@ export class ProfilerInput extends EditorInput implements IProfilerSession {
 	constructor(
 		public connection: IConnectionProfile,
 		@IProfilerService private _profilerService: IProfilerService,
-		@INotificationService private _notificationService: INotificationService,
-		@IDialogService private _dialogService: IDialogService
+		@INotificationService private _notificationService: INotificationService
 	) {
 		super();
 		this._state = new ProfilerState();
@@ -282,29 +279,6 @@ export class ProfilerInput extends EditorInput implements IProfilerSession {
 		this.data.clearFilter();
 	}
 
-	confirmSave(): Promise {
-		if (this.state.isRunning || this.state.isPaused) {
-			return this._dialogService.show(Severity.Warning,
-				nls.localize('confirmStopProfilerSession', "Would you like to stop the running XEvent session?"),
-				[
-					nls.localize('profilerClosingActions.yes', "Yes"),
-					nls.localize('profilerClosingActions.no', "No"),
-					nls.localize('profilerClosingActions.cancel', "Cancel")
-				]).then((selection: IShowResult) => {
-					if (selection.choice === 0) {
-						this._profilerService.stopSession(this.id);
-						return ConfirmResult.DONT_SAVE;
-					} else if (selection.choice === 1) {
-						return ConfirmResult.DONT_SAVE;
-					} else {
-						return ConfirmResult.CANCEL;
-					}
-				});
-		} else {
-			return Promise.resolve(ConfirmResult.DONT_SAVE);
-		}
-	}
-
 	isDirty(): boolean {
 		return this.state.isRunning || this.state.isPaused;
 	}
diff --git a/src/sql/workbench/contrib/profiler/browser/profilerResourceEditor.ts b/src/sql/workbench/contrib/profiler/browser/profilerResourceEditor.ts
index a5d4ee024d..694fab568e 100644
--- a/src/sql/workbench/contrib/profiler/browser/profilerResourceEditor.ts
+++ b/src/sql/workbench/contrib/profiler/browser/profilerResourceEditor.ts
@@ -6,7 +6,6 @@
 import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
 import * as nls from 'vs/nls';
 import * as DOM from 'vs/base/browser/dom';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel';
 import * as editorCommon from 'vs/editor/common/editorCommon';
 
@@ -16,13 +15,12 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
 import { IThemeService } from 'vs/platform/theme/common/themeService';
 import { IStorageService } from 'vs/platform/storage/common/storage';
 import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
-import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
 import { EditorOptions } from 'vs/workbench/common/editor';
 import { StandaloneCodeEditor } from 'vs/editor/standalone/browser/standaloneCodeEditor';
 import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
 import { CancellationToken } from 'vs/base/common/cancellation';
 import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
-import { IHostService } from 'vs/workbench/services/host/browser/host';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 
 class ProfilerResourceCodeEditor extends StandaloneCodeEditor {
 
@@ -47,13 +45,11 @@ export class ProfilerResourceEditor extends BaseTextEditor {
 		@IStorageService storageService: IStorageService,
 		@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
 		@IThemeService themeService: IThemeService,
-		@ITextFileService textFileService: ITextFileService,
 		@IEditorService protected editorService: IEditorService,
-		@IEditorGroupsService editorGroupService: IEditorGroupsService,
-		@IHostService hostService: IHostService
+		@IEditorGroupsService editorGroupService: IEditorGroupsService
 
 	) {
-		super(ProfilerResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, hostService);
+		super(ProfilerResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorService, editorGroupService);
 	}
 
 	public createEditorControl(parent: HTMLElement, configuration: IEditorOptions): editorCommon.IEditor {
@@ -79,7 +75,7 @@ export class ProfilerResourceEditor extends BaseTextEditor {
 		return options;
 	}
 
-	setInput(input: UntitledEditorInput, options: EditorOptions): Promise {
+	setInput(input: UntitledTextEditorInput, options: EditorOptions): Promise {
 		return super.setInput(input, options, CancellationToken.None)
 			.then(() => this.input.resolve()
 				.then(editorModel => editorModel.load())
diff --git a/src/sql/workbench/contrib/query/browser/gridPanel.ts b/src/sql/workbench/contrib/query/browser/gridPanel.ts
index 8512550054..f6357105ea 100644
--- a/src/sql/workbench/contrib/query/browser/gridPanel.ts
+++ b/src/sql/workbench/contrib/query/browser/gridPanel.ts
@@ -37,7 +37,6 @@ import { generateUuid } from 'vs/base/common/uuid';
 import { Separator, ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
 import { isInDOM, Dimension } from 'vs/base/browser/dom';
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
-import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
 import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
 import { IAction } from 'vs/base/common/actions';
 import { ScrollbarVisibility } from 'vs/base/common/scrollable';
@@ -47,6 +46,7 @@ import { IGridDataProvider } from 'sql/platform/query/common/gridDataProvider';
 import { formatDocumentWithSelectedProvider, FormattingMode } from 'vs/editor/contrib/format/format';
 import { CancellationToken } from 'vs/base/common/cancellation';
 import { GridPanelState, GridTableState } from 'sql/workbench/contrib/query/common/gridPanelState';
+import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
 
 const ROW_HEIGHT = 29;
 const HEADER_HEIGHT = 26;
@@ -356,7 +356,7 @@ export abstract class GridTableBase extends Disposable implements IView {
 		protected contextMenuService: IContextMenuService,
 		protected instantiationService: IInstantiationService,
 		protected editorService: IEditorService,
-		protected untitledEditorService: IUntitledEditorService,
+		protected untitledEditorService: IUntitledTextEditorService,
 		protected configurationService: IConfigurationService
 	) {
 		super();
@@ -751,7 +751,7 @@ class GridTable extends GridTableBase {
 		@IInstantiationService instantiationService: IInstantiationService,
 		@IContextKeyService private contextKeyService: IContextKeyService,
 		@IEditorService editorService: IEditorService,
-		@IUntitledEditorService untitledEditorService: IUntitledEditorService,
+		@IUntitledTextEditorService untitledEditorService: IUntitledTextEditorService,
 		@IConfigurationService configurationService: IConfigurationService
 	) {
 		super(state, resultSet, contextMenuService, instantiationService, editorService, untitledEditorService, configurationService);
diff --git a/src/sql/workbench/contrib/query/browser/messagePanel.ts b/src/sql/workbench/contrib/query/browser/messagePanel.ts
index 59f96dc155..78c9227095 100644
--- a/src/sql/workbench/contrib/query/browser/messagePanel.ts
+++ b/src/sql/workbench/contrib/query/browser/messagePanel.ts
@@ -24,7 +24,6 @@ import { isArray } from 'vs/base/common/types';
 import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
 import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
 import { ScrollbarVisibility } from 'vs/base/common/scrollable';
-import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
 import { $, Dimension, createStyleSheet } from 'vs/base/browser/dom';
 import { QueryEditor } from 'sql/workbench/contrib/query/browser/queryEditor';
 import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
@@ -76,7 +75,6 @@ export class MessagePanel extends Disposable {
 	constructor(
 		@IInstantiationService instantiationService: IInstantiationService,
 		@IThemeService private readonly themeService: IThemeService,
-		@IClipboardService private readonly clipboardService: IClipboardService,
 		@IContextMenuService private readonly contextMenuService: IContextMenuService
 	) {
 		super();
@@ -107,7 +105,7 @@ export class MessagePanel extends Disposable {
 					selection: document.getSelection(),
 					tree: this.tree,
 				};
-				let copyMessageAction = instantiationService.createInstance(CopyMessagesAction, this.clipboardService);
+				let copyMessageAction = instantiationService.createInstance(CopyMessagesAction);
 				copyMessageAction.run(context);
 				event.preventDefault();
 				event.stopPropagation();
@@ -134,8 +132,8 @@ export class MessagePanel extends Disposable {
 				},
 				getActions: () => {
 					return [
-						instantiationService.createInstance(CopyMessagesAction, this.clipboardService),
-						instantiationService.createInstance(CopyAllMessagesAction, this.tree, this.clipboardService)
+						instantiationService.createInstance(CopyMessagesAction),
+						instantiationService.createInstance(CopyAllMessagesAction, this.tree)
 					];
 				},
 				getActionsContext: () => {
diff --git a/src/sql/workbench/contrib/query/browser/query.contribution.ts b/src/sql/workbench/contrib/query/browser/query.contribution.ts
index f1cff45c6e..76d6dbf2ef 100644
--- a/src/sql/workbench/contrib/query/browser/query.contribution.ts
+++ b/src/sql/workbench/contrib/query/browser/query.contribution.ts
@@ -8,7 +8,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
 import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
 import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
 import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions';
-import { IConfigurationRegistry, Extensions as ConfigExtensions } from 'vs/platform/configuration/common/configurationRegistry';
+import { IConfigurationRegistry, Extensions as ConfigExtensions, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry';
 import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
 import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes';
 import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
@@ -39,13 +39,13 @@ import { UntitledQueryEditorInput } from 'sql/workbench/contrib/query/common/unt
 import { ILanguageAssociationRegistry, Extensions as LanguageAssociationExtensions } from 'sql/workbench/common/languageAssociation';
 import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
 import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { NewQueryTask, OE_NEW_QUERY_ACTION_ID, DE_NEW_QUERY_COMMAND_ID } from 'sql/workbench/contrib/query/browser/queryActions';
 import { TreeNodeContextKey } from 'sql/workbench/contrib/objectExplorer/common/treeNodeContextKey';
 import { MssqlNodeContext } from 'sql/workbench/contrib/dataExplorer/browser/mssqlNodeContext';
 import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
 import { ManageActionContext } from 'sql/workbench/browser/actions';
 import { ItemContextKey } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerTreeContext';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 
 export const QueryEditorVisibleCondition = ContextKeyExpr.has(queryContext.queryEditorVisibleId);
 export const ResultsGridFocusCondition = ContextKeyExpr.and(ContextKeyExpr.has(queryContext.resultsVisibleId), ContextKeyExpr.has(queryContext.resultsGridFocussedId));
@@ -63,7 +63,7 @@ Registry.as(LanguageAssociationExtensions.Language
 		const queryResultsInput = instantiationService.createInstance(QueryResultsInput, editor.getResource().toString(true));
 		if (editor instanceof FileEditorInput) {
 			return instantiationService.createInstance(FileQueryEditorInput, '', editor, queryResultsInput);
-		} else if (editor instanceof UntitledEditorInput) {
+		} else if (editor instanceof UntitledTextEditorInput) {
 			return instantiationService.createInstance(UntitledQueryEditorInput, '', editor, queryResultsInput);
 		} else {
 			return undefined;
@@ -118,7 +118,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerWidgetContext, {
 
 // Query Actions
 actionRegistry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		RunQueryKeyboardAction,
 		RunQueryKeyboardAction.ID,
 		RunQueryKeyboardAction.LABEL,
@@ -135,7 +135,7 @@ MenuRegistry.appendMenuItem(MenuId.TouchBarContext, {
 });
 
 actionRegistry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		RunCurrentQueryKeyboardAction,
 		RunCurrentQueryKeyboardAction.ID,
 		RunCurrentQueryKeyboardAction.LABEL,
@@ -145,7 +145,7 @@ actionRegistry.registerWorkbenchAction(
 );
 
 actionRegistry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		RunCurrentQueryWithActualPlanKeyboardAction,
 		RunCurrentQueryWithActualPlanKeyboardAction.ID,
 		RunCurrentQueryWithActualPlanKeyboardAction.LABEL,
@@ -155,7 +155,7 @@ actionRegistry.registerWorkbenchAction(
 );
 
 actionRegistry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		CancelQueryKeyboardAction,
 		CancelQueryKeyboardAction.ID,
 		CancelQueryKeyboardAction.LABEL,
@@ -165,7 +165,7 @@ actionRegistry.registerWorkbenchAction(
 );
 
 actionRegistry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		RefreshIntellisenseKeyboardAction,
 		RefreshIntellisenseKeyboardAction.ID,
 		RefreshIntellisenseKeyboardAction.LABEL
@@ -174,7 +174,7 @@ actionRegistry.registerWorkbenchAction(
 );
 
 actionRegistry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		FocusOnCurrentQueryKeyboardAction,
 		FocusOnCurrentQueryKeyboardAction.ID,
 		FocusOnCurrentQueryKeyboardAction.LABEL,
@@ -184,7 +184,7 @@ actionRegistry.registerWorkbenchAction(
 );
 
 actionRegistry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		ParseSyntaxAction,
 		ParseSyntaxAction.ID,
 		ParseSyntaxAction.LABEL
@@ -195,7 +195,7 @@ actionRegistry.registerWorkbenchAction(
 // Grid actions
 
 actionRegistry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		ToggleQueryResultsKeyboardAction,
 		ToggleQueryResultsKeyboardAction.ID,
 		ToggleQueryResultsKeyboardAction.LABEL,
@@ -310,7 +310,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
 });
 
 // Intellisense and other configuration options
-const registryProperties = {
+const registryProperties: { [path: string]: IConfigurationPropertySchema; } = {
 	'sql.saveAsCsv.includeHeaders': {
 		'type': 'boolean',
 		'description': localize('sql.saveAsCsv.includeHeaders', "[Optional] When true, column headers are included when saving results as CSV"),
@@ -322,7 +322,7 @@ const registryProperties = {
 		'default': ','
 	},
 	'sql.saveAsCsv.lineSeperator': {
-		'type': '',
+		'type': 'string',
 		'description': localize('sql.saveAsCsv.lineSeperator', "[Optional] Character(s) used for seperating rows when saving results as CSV"),
 		'default': null
 	},
diff --git a/src/sql/workbench/contrib/query/common/fileQueryEditorInput.ts b/src/sql/workbench/contrib/query/common/fileQueryEditorInput.ts
index 67457a6d05..fa13c88cfe 100644
--- a/src/sql/workbench/contrib/query/common/fileQueryEditorInput.ts
+++ b/src/sql/workbench/contrib/query/common/fileQueryEditorInput.ts
@@ -14,6 +14,7 @@ import { EncodingMode } from 'vs/workbench/common/editor';
 import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
 import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
 import { IFileService } from 'vs/platform/files/common/files';
+import { ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles';
 
 type PublicPart = { [K in keyof T]: T[K] };
 
@@ -81,6 +82,14 @@ export class FileQueryEditorInput extends QueryEditorInput implements PublicPart
 		this.text.setForceOpenAsBinary();
 	}
 
+	save(groupId: number, options?: ITextFileSaveOptions): Promise {
+		return this.text.save(groupId, options);
+	}
+
+	saveAs(group: number, options?: ITextFileSaveOptions): Promise {
+		return this.text.saveAs(group, options);
+	}
+
 	public isResolved(): boolean {
 		return this.text.isResolved();
 	}
diff --git a/src/sql/workbench/contrib/query/common/queryEditorInput.ts b/src/sql/workbench/contrib/query/common/queryEditorInput.ts
index 44b4c2f8e2..ff1876180b 100644
--- a/src/sql/workbench/contrib/query/common/queryEditorInput.ts
+++ b/src/sql/workbench/contrib/query/common/queryEditorInput.ts
@@ -7,7 +7,7 @@ import { localize } from 'vs/nls';
 import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
 import { Emitter } from 'vs/base/common/event';
 import { URI } from 'vs/base/common/uri';
-import { EditorInput, ConfirmResult } from 'vs/workbench/common/editor';
+import { EditorInput } from 'vs/workbench/common/editor';
 import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
 import { IFileService } from 'vs/platform/files/common/files';
 
@@ -186,9 +186,7 @@ export abstract class QueryEditorInput extends EditorInput implements IConnectab
 	}
 
 	// Forwarding resource functions to the inline sql file editor
-	public save(): Promise { return this._text.save(); }
 	public isDirty(): boolean { return this._text.isDirty(); }
-	public confirmSave(): Promise { return this._text.confirmSave(); }
 	public getResource(): URI { return this._text.getResource(); }
 
 	public matchInputInstanceType(inputType: any): boolean {
@@ -295,15 +293,6 @@ export abstract class QueryEditorInput extends EditorInput implements IConnectab
 		this.state.executing = false;
 	}
 
-	public close(): void {
-		this.queryModelService.disposeQuery(this.uri);
-		this.connectionManagementService.disconnectEditor(this, true);
-
-		this._text.close();
-		this._results.close();
-		super.close();
-	}
-
 	/**
 	 * Get the color that should be displayed
 	 */
@@ -311,6 +300,13 @@ export abstract class QueryEditorInput extends EditorInput implements IConnectab
 		return this.connectionManagementService.getTabColorForUri(this.uri);
 	}
 
+	public dispose() {
+		this.queryModelService.disposeQuery(this.uri);
+		this.connectionManagementService.disconnectEditor(this, true);
+
+		super.dispose();
+	}
+
 	public get isSharedSession(): boolean {
 		return !!(this.uri && startsWith(this.uri, 'vsls:'));
 	}
diff --git a/src/sql/workbench/contrib/query/common/queryInputFactory.ts b/src/sql/workbench/contrib/query/common/queryInputFactory.ts
index e8790d78c6..48fcff22d4 100644
--- a/src/sql/workbench/contrib/query/common/queryInputFactory.ts
+++ b/src/sql/workbench/contrib/query/common/queryInputFactory.ts
@@ -6,12 +6,12 @@
 import { IEditorInputFactory, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } from 'vs/workbench/common/editor';
 import { Registry } from 'vs/platform/registry/common/platform';
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { QueryResultsInput } from 'sql/workbench/contrib/query/common/queryResultsInput';
 import { FILE_EDITOR_INPUT_ID } from 'vs/workbench/contrib/files/common/files';
 import { UntitledQueryEditorInput } from 'sql/workbench/contrib/query/common/untitledQueryEditorInput';
 import { FileQueryEditorInput } from 'sql/workbench/contrib/query/common/fileQueryEditorInput';
 import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 
 const editorInputFactoryRegistry = Registry.as(EditorInputExtensions.EditorInputFactories);
 
@@ -34,7 +34,7 @@ export class FileQueryEditorInputFactory implements IEditorInputFactory {
 
 export class UntitledQueryEditorInputFactory implements IEditorInputFactory {
 	serialize(editorInput: UntitledQueryEditorInput): string {
-		const factory = editorInputFactoryRegistry.getEditorInputFactory(UntitledEditorInput.ID);
+		const factory = editorInputFactoryRegistry.getEditorInputFactory(UntitledTextEditorInput.ID);
 		if (factory) {
 			return factory.serialize(editorInput.text); // serialize based on the underlying input
 		}
@@ -42,8 +42,8 @@ export class UntitledQueryEditorInputFactory implements IEditorInputFactory {
 	}
 
 	deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): UntitledQueryEditorInput | undefined {
-		const factory = editorInputFactoryRegistry.getEditorInputFactory(UntitledEditorInput.ID);
-		const untitledEditorInput = factory.deserialize(instantiationService, serializedEditorInput) as UntitledEditorInput;
+		const factory = editorInputFactoryRegistry.getEditorInputFactory(UntitledTextEditorInput.ID);
+		const untitledEditorInput = factory.deserialize(instantiationService, serializedEditorInput) as UntitledTextEditorInput;
 		const queryResultsInput = instantiationService.createInstance(QueryResultsInput, untitledEditorInput.getResource().toString());
 		return instantiationService.createInstance(UntitledQueryEditorInput, '', untitledEditorInput, queryResultsInput);
 	}
diff --git a/src/sql/workbench/contrib/query/common/queryResultsInput.ts b/src/sql/workbench/contrib/query/common/queryResultsInput.ts
index 48f5fb3a09..5fc16c4c45 100644
--- a/src/sql/workbench/contrib/query/common/queryResultsInput.ts
+++ b/src/sql/workbench/contrib/query/common/queryResultsInput.ts
@@ -52,12 +52,6 @@ export class QueryResultsInput extends EditorInput {
 		super();
 	}
 
-	close() {
-		this.state!.dispose();
-		this._state = undefined;
-		super.close();
-	}
-
 	getTypeId(): string {
 		return QueryResultsInput.ID;
 	}
diff --git a/src/sql/workbench/contrib/query/common/untitledQueryEditorInput.ts b/src/sql/workbench/contrib/query/common/untitledQueryEditorInput.ts
index 361683453e..7ff7ab8fd5 100644
--- a/src/sql/workbench/contrib/query/common/untitledQueryEditorInput.ts
+++ b/src/sql/workbench/contrib/query/common/untitledQueryEditorInput.ts
@@ -9,15 +9,16 @@ import { IConnectionManagementService } from 'sql/platform/connection/common/con
 import { IQueryModelService } from 'sql/platform/query/common/queryModel';
 
 import { IEncodingSupport, EncodingMode } from 'vs/workbench/common/editor';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
-import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel';
 import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService';
 import { IFileService } from 'vs/platform/files/common/files';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
+import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel';
+import { ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles';
 
 type PublicPart = { [K in keyof T]: T[K] };
 
-export class UntitledQueryEditorInput extends QueryEditorInput implements IEncodingSupport, PublicPart {
+export class UntitledQueryEditorInput extends QueryEditorInput implements IEncodingSupport, PublicPart {
 
 	public static readonly ID = 'workbench.editorInput.untitledQueryInput';
 
@@ -26,7 +27,7 @@ export class UntitledQueryEditorInput extends QueryEditorInput implements IEncod
 
 	constructor(
 		description: string,
-		text: UntitledEditorInput,
+		text: UntitledTextEditorInput,
 		results: QueryResultsInput,
 		@IConnectionManagementService connectionManagementService: IConnectionManagementService,
 		@IQueryModelService queryModelService: IQueryModelService,
@@ -36,12 +37,12 @@ export class UntitledQueryEditorInput extends QueryEditorInput implements IEncod
 		super(description, text, results, connectionManagementService, queryModelService, configurationService, fileService);
 	}
 
-	public resolve(): Promise {
+	public resolve(): Promise {
 		return this.text.resolve();
 	}
 
-	public get text(): UntitledEditorInput {
-		return this._text as UntitledEditorInput;
+	public get text(): UntitledTextEditorInput {
+		return this._text as UntitledTextEditorInput;
 	}
 
 	public get hasAssociatedFilePath(): boolean {
@@ -72,6 +73,19 @@ export class UntitledQueryEditorInput extends QueryEditorInput implements IEncod
 		this.text.setEncoding(encoding, mode);
 	}
 
+	save(groupId: number, options?: ITextFileSaveOptions): Promise {
+		return this.text.save(groupId, options);
+	}
+
+	saveAs(group: number, options?: ITextFileSaveOptions): Promise {
+		return this.text.saveAs(group, options);
+	}
+
+	isUntitled(): boolean {
+		// Subclasses need to explicitly opt-in to being untitled.
+		return true;
+	}
+
 	hasBackup(): boolean {
 		if (this.text) {
 			return this.text.hasBackup();
diff --git a/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts b/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts
index 75ac627e21..f4a1ba74a3 100644
--- a/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts
+++ b/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts
@@ -28,11 +28,12 @@ import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKe
 import { UntitledQueryEditorInput } from 'sql/workbench/contrib/query/common/untitledQueryEditorInput';
 import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
 import { TestQueryModelService } from 'sql/platform/query/test/common/testQueryModelService';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { URI } from 'vs/base/common/uri';
 import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
 import { TestConnectionManagementService } from 'sql/platform/connection/test/common/testConnectionManagementService';
 import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
+import { SimpleUriLabelService } from 'vs/editor/standalone/browser/simpleServices';
 
 suite('SQL QueryAction Tests', () => {
 
@@ -69,7 +70,7 @@ suite('SQL QueryAction Tests', () => {
 		connectionManagementService = TypeMoq.Mock.ofType(TestConnectionManagementService);
 		connectionManagementService.setup(q => q.onDisconnect).returns(() => Event.None);
 		const instantiationService = new TestInstantiationService();
-		let fileInput = new UntitledEditorInput(URI.parse('file://testUri'), false, '', '', '', instantiationService, undefined, undefined);
+		let fileInput = new UntitledTextEditorInput(URI.parse('file://testUri'), false, '', '', '', instantiationService, undefined, new SimpleUriLabelService(), undefined, undefined, undefined);
 		// Setup a reusable mock QueryInput
 		testQueryInput = TypeMoq.Mock.ofType(UntitledQueryEditorInput, TypeMoq.MockBehavior.Strict, undefined, fileInput, undefined, connectionManagementService.object, queryModelService.object, configurationService.object);
 		testQueryInput.setup(x => x.uri).returns(() => testUri);
@@ -177,7 +178,7 @@ suite('SQL QueryAction Tests', () => {
 		queryModelService.setup(x => x.onRunQueryStart).returns(() => Event.None);
 		queryModelService.setup(x => x.onRunQueryComplete).returns(() => Event.None);
 		const instantiationService = new TestInstantiationService();
-		let fileInput = new UntitledEditorInput(URI.parse('file://testUri'), false, '', '', '', instantiationService, undefined, undefined);
+		let fileInput = new UntitledTextEditorInput(URI.parse('file://testUri'), false, '', '', '', instantiationService, undefined, new SimpleUriLabelService(), undefined, undefined, undefined);
 
 		// ... Mock "isSelectionEmpty" in QueryEditor
 		let queryInput = TypeMoq.Mock.ofType(UntitledQueryEditorInput, TypeMoq.MockBehavior.Strict, undefined, fileInput, undefined, connectionManagementService.object, queryModelService.object, configurationService.object);
@@ -227,7 +228,7 @@ suite('SQL QueryAction Tests', () => {
 
 		// ... Mock "getSelection" in QueryEditor
 		const instantiationService = new TestInstantiationService();
-		let fileInput = new UntitledEditorInput(URI.parse('file://testUri'), false, '', '', '', instantiationService, undefined, undefined);
+		let fileInput = new UntitledTextEditorInput(URI.parse('file://testUri'), false, '', '', '', instantiationService, undefined, new SimpleUriLabelService(), undefined, undefined, undefined);
 
 		let queryInput = TypeMoq.Mock.ofType(UntitledQueryEditorInput, TypeMoq.MockBehavior.Loose, undefined, fileInput, undefined, connectionManagementService.object, queryModelService.object, configurationService.object);
 		queryInput.setup(x => x.uri).returns(() => testUri);
diff --git a/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts b/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts
index ca88ff41a1..2e9c9f6236 100644
--- a/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts
+++ b/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts
@@ -7,7 +7,6 @@ import { InstantiationService } from 'vs/platform/instantiation/common/instantia
 import { IEditorDescriptor } from 'vs/workbench/browser/editor';
 import { URI } from 'vs/base/common/uri';
 import { Memento } from 'vs/workbench/common/memento';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 
 import { QueryResultsInput } from 'sql/workbench/contrib/query/common/queryResultsInput';
 import { INewConnectionParams, ConnectionType, RunQueryOnConnectionMode } from 'sql/platform/connection/common/connectionManagement';
@@ -21,9 +20,11 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
 import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
 import { TestStorageService } from 'vs/workbench/test/workbenchTestServices';
 import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 import { UntitledQueryEditorInput } from 'sql/workbench/contrib/query/common/untitledQueryEditorInput';
 import { TestQueryModelService } from 'sql/platform/query/test/common/testQueryModelService';
 import { Event } from 'vs/base/common/event';
+import { SimpleUriLabelService } from 'vs/editor/standalone/browser/simpleServices';
 
 suite('SQL QueryEditor Tests', () => {
 	let instantiationService: TypeMoq.Mock;
@@ -284,7 +285,7 @@ suite('SQL QueryEditor Tests', () => {
 					return new RunQueryAction(undefined, undefined, undefined);
 				});
 
-			let fileInput = new UntitledEditorInput(URI.parse('file://testUri'), false, '', '', '', instantiationService.object, undefined, undefined);
+			let fileInput = new UntitledTextEditorInput(URI.parse('file://testUri'), false, '', '', '', instantiationService.object, undefined, new SimpleUriLabelService(), undefined, undefined, undefined);
 			queryModelService = TypeMoq.Mock.ofType(TestQueryModelService, TypeMoq.MockBehavior.Strict);
 			queryModelService.setup(x => x.disposeQuery(TypeMoq.It.isAny()));
 			queryModelService.setup(x => x.onRunQueryComplete).returns(() => Event.None);
@@ -321,7 +322,7 @@ suite('SQL QueryEditor Tests', () => {
 		test('Test that we attempt to dispose query when the queryInput is disposed', () => {
 			let queryResultsInput = new QueryResultsInput('testUri');
 			queryInput['_results'] = queryResultsInput;
-			queryInput.close();
+			queryInput.dispose();
 			queryModelService.verify(x => x.disposeQuery(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
 		});
 	});
diff --git a/src/sql/workbench/contrib/queryHistory/browser/queryHistoryActions.ts b/src/sql/workbench/contrib/queryHistory/browser/queryHistoryActions.ts
index b54d7165bc..eedac0f222 100644
--- a/src/sql/workbench/contrib/queryHistory/browser/queryHistoryActions.ts
+++ b/src/sql/workbench/contrib/queryHistory/browser/queryHistoryActions.ts
@@ -75,14 +75,14 @@ export class OpenQueryAction extends Action {
 	constructor(
 		id: string,
 		label: string,
-		@IInstantiationService private _instantiationService
+		@IInstantiationService private _instantiationService: IInstantiationService
 	) {
 		super(id, label);
 	}
 
 	public async run(element: QueryHistoryNode): Promise {
 		if (element instanceof QueryHistoryNode && element.info) {
-			return this._instantiationService.invokeFunction(openNewQuery, element.info.connectionProfile, element.info.queryText, RunQueryOnConnectionMode.none).then(() => true, () => false);
+			return this._instantiationService.invokeFunction(openNewQuery, element.info.connectionProfile, element.info.queryText, RunQueryOnConnectionMode.none).then();
 		}
 	}
 }
@@ -94,14 +94,14 @@ export class RunQueryAction extends Action {
 	constructor(
 		id: string,
 		label: string,
-		@IInstantiationService private _instantiationService
+		@IInstantiationService private _instantiationService: IInstantiationService
 	) {
 		super(id, label);
 	}
 
 	public async run(element: QueryHistoryNode): Promise {
 		if (element instanceof QueryHistoryNode && element.info) {
-			return this._instantiationService.invokeFunction(openNewQuery, element.info.connectionProfile, element.info.queryText, RunQueryOnConnectionMode.executeQuery).catch(() => true, () => false);
+			return this._instantiationService.invokeFunction(openNewQuery, element.info.connectionProfile, element.info.queryText, RunQueryOnConnectionMode.executeQuery).then();
 		}
 	}
 }
diff --git a/src/sql/workbench/contrib/queryHistory/electron-browser/queryHistory.ts b/src/sql/workbench/contrib/queryHistory/electron-browser/queryHistory.ts
index 02423b794e..b941509d92 100644
--- a/src/sql/workbench/contrib/queryHistory/electron-browser/queryHistory.ts
+++ b/src/sql/workbench/contrib/queryHistory/electron-browser/queryHistory.ts
@@ -73,7 +73,7 @@ export class QueryHistoryWorkbenchContribution implements IWorkbenchContribution
 
 					const registry = Registry.as(ActionExtensions.WorkbenchActions);
 					registry.registerWorkbenchAction(
-						new SyncActionDescriptor(
+						SyncActionDescriptor.create(
 							ToggleQueryHistoryAction,
 							ToggleQueryHistoryAction.ID,
 							ToggleQueryHistoryAction.LABEL,
@@ -83,7 +83,7 @@ export class QueryHistoryWorkbenchContribution implements IWorkbenchContribution
 					);
 
 					// Register Output Panel
-					Registry.as(PanelExtensions.Panels).registerPanel(new PanelDescriptor(
+					Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create(
 						QueryHistoryPanel,
 						QUERY_HISTORY_PANEL_ID,
 						localize('queryHistory', "Query History"),
diff --git a/src/sql/workbench/contrib/queryPlan/common/queryPlanInput.ts b/src/sql/workbench/contrib/queryPlan/common/queryPlanInput.ts
index 62a52db921..6334a29c52 100644
--- a/src/sql/workbench/contrib/queryPlan/common/queryPlanInput.ts
+++ b/src/sql/workbench/contrib/queryPlan/common/queryPlanInput.ts
@@ -4,10 +4,10 @@
  *--------------------------------------------------------------------------------------------*/
 
 import { EditorInput, EditorModel } from 'vs/workbench/common/editor';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
 import { IFileService } from 'vs/platform/files/common/files';
 import { URI } from 'vs/base/common/uri';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 
 export class QueryPlanInput extends EditorInput {
 
@@ -29,7 +29,7 @@ export class QueryPlanInput extends EditorInput {
 	}
 
 	public getTypeId(): string {
-		return UntitledEditorInput.ID;
+		return UntitledTextEditorInput.ID;
 	}
 
 	public getName(): string {
diff --git a/src/sql/workbench/contrib/tasks/browser/tasks.contribution.ts b/src/sql/workbench/contrib/tasks/browser/tasks.contribution.ts
index 300476a166..12b013deac 100644
--- a/src/sql/workbench/contrib/tasks/browser/tasks.contribution.ts
+++ b/src/sql/workbench/contrib/tasks/browser/tasks.contribution.ts
@@ -61,7 +61,7 @@ export class StatusUpdater extends lifecycle.Disposable implements ext.IWorkbenc
 
 const registry = Registry.as(ActionExtensions.WorkbenchActions);
 registry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		ToggleTasksAction,
 		ToggleTasksAction.ID,
 		ToggleTasksAction.LABEL,
@@ -71,7 +71,7 @@ registry.registerWorkbenchAction(
 );
 
 // Register Output Panel
-Registry.as(PanelExtensions.Panels).registerPanel(new PanelDescriptor(
+Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create(
 	TasksPanel,
 	TASKS_PANEL_ID,
 	localize('tasks', "Tasks"),
diff --git a/src/sql/workbench/services/dialog/browser/dialogPane.ts b/src/sql/workbench/services/dialog/browser/dialogPane.ts
index dc3ce1cd33..879579ed0b 100644
--- a/src/sql/workbench/services/dialog/browser/dialogPane.ts
+++ b/src/sql/workbench/services/dialog/browser/dialogPane.ts
@@ -15,12 +15,12 @@ import { DialogModule } from 'sql/workbench/services/dialog/browser/dialog.modul
 import { DialogComponentParams, LayoutRequestParams } from 'sql/workbench/services/dialog/browser/dialogContainer.component';
 
 import * as DOM from 'vs/base/browser/dom';
-import { IThemable } from 'vs/platform/theme/common/styler';
 import { Disposable } from 'vs/base/common/lifecycle';
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
 import { Emitter } from 'vs/base/common/event';
 import { attachTabbedPanelStyler } from 'sql/platform/theme/common/styler';
 import { IThemeService } from 'vs/platform/theme/common/themeService';
+import { IThemable } from 'vs/base/common/styler';
 
 export class DialogPane extends Disposable implements IThemable {
 	private _tabbedPanel: TabbedPanel;
diff --git a/src/sql/workbench/services/insights/browser/insightsDialogView.ts b/src/sql/workbench/services/insights/browser/insightsDialogView.ts
index 97023ce925..67d1bf79fd 100644
--- a/src/sql/workbench/services/insights/browser/insightsDialogView.ts
+++ b/src/sql/workbench/services/insights/browser/insightsDialogView.ts
@@ -33,7 +33,6 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
 import { ICommandService } from 'vs/platform/commands/common/commands';
 import { MenuRegistry, ExecuteCommandAction } from 'vs/platform/actions/common/actions';
 import { SplitView, Orientation, Sizing } from 'vs/base/browser/ui/splitview/splitview';
-import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
 import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
 import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
 import { ILogService } from 'vs/platform/log/common/log';
@@ -42,13 +41,14 @@ import { IInsightsConfigDetails } from 'sql/platform/dashboard/browser/insightRe
 import { TaskRegistry } from 'sql/platform/tasks/browser/tasksRegistry';
 import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
 import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
+import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet';
 import { onUnexpectedError } from 'vs/base/common/errors';
 
 const labelDisplay = nls.localize("insights.item", "Item");
 const valueDisplay = nls.localize("insights.value", "Value");
 const iconClass = 'codicon';
 
-class InsightTableView extends ViewletPanel {
+class InsightTableView extends ViewletPane {
 	private _table: Table;
 	public get table(): Table {
 		return this._table;
@@ -58,7 +58,7 @@ class InsightTableView extends ViewletPanel {
 		private columns: Slick.Column[],
 		private data: IDisposableDataProvider | Array,
 		private tableOptions: Slick.GridOptions,
-		options: IViewletPanelOptions,
+		options: IViewletPaneOptions,
 		@IKeybindingService keybindingService: IKeybindingService,
 		@IContextMenuService contextMenuService: IContextMenuService,
 		@IConfigurationService configurationService: IConfigurationService,
@@ -401,7 +401,7 @@ export class InsightsDialogView extends Modal {
 			let task = tasks.some(x => x === action);
 			let commandAction = MenuRegistry.getCommand(action);
 			if (task) {
-				returnActions.push(this._instantiationService.createInstance(ExecuteCommandAction, commandAction.id, commandAction.title));
+				returnActions.push(this._instantiationService.createInstance(ExecuteCommandAction, commandAction.id as string, commandAction.title as string));
 			}
 		}
 		return returnActions;
diff --git a/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts b/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts
index 86a324ebde..6141c9d1ab 100644
--- a/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts
+++ b/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts
@@ -25,6 +25,7 @@ import { getRandomTestPath } from 'vs/base/test/node/testUtils';
 import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api';
 
 class TestEnvironmentService implements IWorkbenchEnvironmentService {
+	keybindingsSyncPreviewResource: URI;
 	argvResource: URI;
 	userDataSyncLogResource: URI;
 	settingsSyncPreviewResource: URI;
diff --git a/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts b/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts
index 7abb7c726b..9384208d39 100644
--- a/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts
+++ b/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts
@@ -10,7 +10,6 @@ import { IConnectableInput, IConnectionManagementService } from 'sql/platform/co
 import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService';
 import { UntitledQueryEditorInput } from 'sql/workbench/contrib/query/common/untitledQueryEditorInput';
 
-import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
 import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
 import { URI } from 'vs/base/common/uri';
@@ -21,6 +20,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
 import { replaceConnection } from 'sql/workbench/browser/taskUtilities';
 import { EditDataResultsInput } from 'sql/workbench/contrib/editData/browser/editDataResultsInput';
 import { ILogService } from 'vs/platform/log/common/log';
+import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
 
 /**
  * Service wrapper for opening and creating SQL documents as sql editor inputs
@@ -30,7 +30,7 @@ export class QueryEditorService implements IQueryEditorService {
 	public _serviceBrand: undefined;
 
 	constructor(
-		@IUntitledEditorService private _untitledEditorService: IUntitledEditorService,
+		@IUntitledTextEditorService private _untitledEditorService: IUntitledTextEditorService,
 		@IInstantiationService private _instantiationService: IInstantiationService,
 		@IEditorService private _editorService: IEditorService,
 		@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
diff --git a/src/sql/workbench/update/electron-browser/releaseNotes.contribution.ts b/src/sql/workbench/update/electron-browser/releaseNotes.contribution.ts
index 1cea49cb68..1b804af876 100644
--- a/src/sql/workbench/update/electron-browser/releaseNotes.contribution.ts
+++ b/src/sql/workbench/update/electron-browser/releaseNotes.contribution.ts
@@ -10,4 +10,4 @@ import { Registry } from 'vs/platform/registry/common/platform';
 
 // add product update and release notes contributions
 Registry.as(ActionExtensions.WorkbenchActions)
-	.registerWorkbenchAction(new SyncActionDescriptor(ShowCurrentReleaseNotesAction, ShowCurrentReleaseNotesAction.ID, ShowCurrentReleaseNotesAction.LABEL), 'Show Getting Started');
+	.registerWorkbenchAction(SyncActionDescriptor.create(ShowCurrentReleaseNotesAction, ShowCurrentReleaseNotesAction.ID, ShowCurrentReleaseNotesAction.LABEL), 'Show Getting Started');
diff --git a/src/tsconfig.base.json b/src/tsconfig.base.json
index e67b902c3c..6d87bff67d 100644
--- a/src/tsconfig.base.json
+++ b/src/tsconfig.base.json
@@ -10,6 +10,7 @@
 		"alwaysStrict": true,
 		"strictBindCallApply": true,
 		"strictNullChecks": false,
+		"strictPropertyInitialization": false,
 		"forceConsistentCasingInFileNames": true,
 		"baseUrl": ".",
 		"paths": {
diff --git a/src/tsconfig.monaco.json b/src/tsconfig.monaco.json
index aa0af48b34..a6430a44cc 100644
--- a/src/tsconfig.monaco.json
+++ b/src/tsconfig.monaco.json
@@ -14,7 +14,6 @@
 	},
 	"include": [
 		"typings/require.d.ts",
-		"typings/require-monaco.d.ts",
 		"typings/thenable.d.ts",
 		"typings/es6-promise.d.ts",
 		"typings/lib.es2018.promise.d.ts",
diff --git a/src/typings/applicationInsights.d.ts b/src/typings/applicationInsights.d.ts
deleted file mode 100644
index 5c6cb717a2..0000000000
--- a/src/typings/applicationInsights.d.ts
+++ /dev/null
@@ -1,218 +0,0 @@
-/**
- * The singleton meta class for the default client of the client. This class is used to setup/start and configure
- * the auto-collection behavior of the application insights module.
- */
-declare module ApplicationInsights {
-
-    /**
-    * The default client, initialized when setup was called. To initialize a different client
-    * with its own configuration, use `new TelemetryClient(instrumentationKey?)`.
-    */
-    var defaultClient: TelemetryClient;
-    /**
-     * Initializes the default client. Should be called after setting
-     * configuration options.
-     *
-     * @param instrumentationKey the instrumentation key to use. Optional, if
-     * this is not specified, the value will be read from the environment
-     * variable APPINSIGHTS_INSTRUMENTATIONKEY.
-     * @returns {Configuration} the configuration class to initialize
-     * and start the SDK.
-     */
-    function setup(instrumentationKey?: string): typeof Configuration;
-    /**
-     * Starts automatic collection of telemetry. Prior to calling start no
-     * telemetry will be *automatically* collected, though manual collection
-     * is enabled.
-     * @returns {ApplicationInsights} this class
-     */
-    function start(): typeof Configuration;
-    /**
-     * The active configuration for global SDK behaviors, such as autocollection.
-     */
-    class Configuration {
-        static start: typeof start;
-        /**
-         * Sets the state of console and logger tracking (enabled by default for third-party loggers only)
-         * @param value if true logger activity will be sent to Application Insights
-         * @param collectConsoleLog if true, logger autocollection will include console.log calls (default false)
-         * @returns {Configuration} this class
-         */
-        static setAutoCollectConsole(value: boolean, collectConsoleLog?: boolean): typeof Configuration;
-        /**
-         * Sets the state of exception tracking (enabled by default)
-         * @param value if true uncaught exceptions will be sent to Application Insights
-         * @returns {Configuration} this class
-         */
-        static setAutoCollectExceptions(value: boolean): typeof Configuration;
-        /**
-         * Sets the state of performance tracking (enabled by default)
-         * @param value if true performance counters will be collected every second and sent to Application Insights
-         * @returns {Configuration} this class
-         */
-        static setAutoCollectPerformance(value: boolean): typeof Configuration;
-        /**
-         * Sets the state of request tracking (enabled by default)
-         * @param value if true requests will be sent to Application Insights
-         * @returns {Configuration} this class
-         */
-        static setAutoCollectRequests(value: boolean): typeof Configuration;
-        /**
-         * Sets the state of dependency tracking (enabled by default)
-         * @param value if true dependencies will be sent to Application Insights
-         * @returns {Configuration} this class
-         */
-        static setAutoCollectDependencies(value: boolean): typeof Configuration;
-        /**
-         * Sets the state of automatic dependency correlation (enabled by default)
-         * @param value if true dependencies will be correlated with requests
-         * @returns {Configuration} this class
-         */
-        static setAutoDependencyCorrelation(value: boolean): typeof Configuration;
-        /**
-         * Enable or disable disk-backed retry caching to cache events when client is offline (enabled by default)
-         * Note that this method only applies to the default client. Disk-backed retry caching is disabled by default for additional clients.
-         * For enable for additional clients, use client.channel.setUseDiskRetryCaching(true).
-         * These cached events are stored in your system or user's temporary directory and access restricted to your user when possible.
-         * @param value if true events that occured while client is offline will be cached on disk
-         * @param resendInterval The wait interval for resending cached events.
-         * @param maxBytesOnDisk The maximum size (in bytes) that the created temporary directory for cache events can grow to, before caching is disabled.
-         * @returns {Configuration} this class
-         */
-        static setUseDiskRetryCaching(value: boolean, resendInterval?: number, maxBytesOnDisk?: number): typeof Configuration;
-        /**
-         * Enables debug and warning logging for AppInsights itself.
-         * @param enableDebugLogging if true, enables debug logging
-         * @param enableWarningLogging if true, enables warning logging
-         * @returns {Configuration} this class
-         */
-        static setInternalLogging(enableDebugLogging?: boolean, enableWarningLogging?: boolean): typeof Configuration;
-    }
-    /**
-     * Disposes the default client and all the auto collectors so they can be reinitialized with different configuration
-    */
-    function dispose(): void;
-
-    interface ITelemetryClient {
-        config: Config;
-        channel: Channel;
-        /**
-         * Log a user action or other occurrence.
-         * @param telemetry      Object encapsulating tracking options
-         */
-        trackEvent(telemetry: EventTelemetry): void;
-        /**
-         * Immediately send all queued telemetry.
-         * @param options Flush options, including indicator whether app is crashing and callback
-         */
-        flush(options?: FlushOptions): void;
-
-    }
-
-    class TelemetryClient implements ITelemetryClient {
-        config: Config;
-        channel: Channel;
-        /**
-         * Constructs a new client of the client
-         * @param iKey the instrumentation key to use (read from environment variable if not specified)
-         */
-        constructor(iKey?: string);
-        /**
-         * Log a user action or other occurrence.
-         * @param telemetry      Object encapsulating tracking options
-         */
-        trackEvent(telemetry: EventTelemetry): void;
-        /**
-         * Immediately send all queued telemetry.
-         * @param options Flush options, including indicator whether app is crashing and callback
-         */
-        flush(options?: FlushOptions): void;
-
-    }
-
-    class Config {
-        static ENV_azurePrefix: string;
-        static ENV_iKey: string;
-        static legacy_ENV_iKey: string;
-        static ENV_profileQueryEndpoint: string;
-        static ENV_http_proxy: string;
-        static ENV_https_proxy: string;
-        /** An identifier for your Application Insights resource */
-        instrumentationKey: string;
-        /** The id for cross-component correlation. READ ONLY. */
-        correlationId: string;
-        /** The ingestion endpoint to send telemetry payloads to */
-        endpointUrl: string;
-        /** The maximum number of telemetry items to include in a payload to the ingestion endpoint (Default 250) */
-        maxBatchSize: number;
-        /** The maximum amount of time to wait for a payload to reach maxBatchSize (Default 15000) */
-        maxBatchIntervalMs: number;
-        /** A flag indicating if telemetry transmission is disabled (Default false) */
-        disableAppInsights: boolean;
-        /** The percentage of telemetry items tracked that should be transmitted (Default 100) */
-        samplingPercentage: number;
-        /** The time to wait before retrying to retrieve the id for cross-component correlation (Default 30000) */
-        correlationIdRetryIntervalMs: number;
-        /** A list of domains to exclude from cross-component header injection */
-        correlationHeaderExcludedDomains: string[];
-        /** A proxy server for SDK HTTP traffic (Optional, Default pulled from `http_proxy` environment variable) */
-        proxyHttpUrl: string;
-        /** A proxy server for SDK HTTPS traffic (Optional, Default pulled from `https_proxy` environment variable) */
-        proxyHttpsUrl: string;
-    }
-
-    interface Channel {
-        /**
-    * Enable or disable disk-backed retry caching to cache events when client is offline (enabled by default)
-    * These cached events are stored in your system or user's temporary directory and access restricted to your user when possible.
-    * @param value if true events that occured while client is offline will be cached on disk
-    * @param resendInterval The wait interval for resending cached events.
-    * @param maxBytesOnDisk The maximum size (in bytes) that the created temporary directory for cache events can grow to, before caching is disabled.
-    * @returns {Configuration} this class
-    */
-        setUseDiskRetryCaching(value: boolean, resendInterval?: number, maxBytesOnDisk?: number): void;
-    }
-
-    /**
-     * Telemetry about the custom event of interest, such application workflow event, business logic event (purchase) and anything that
-     * you would like to track and aggregate by count. Event can contain measurements such as purchase amount associated with purchase event
-     */
-    interface EventTelemetry {
-        /**
-         * Name of the event
-         */
-        name: string;
-        /**
-         * Metrics associated with this event, displayed in Metrics Explorer on the portal.
-         */
-        measurements?: {
-            [key: string]: number;
-        };
-        /**
-         * Additional data used to filter events and metrics in the portal. Defaults to empty.
-         */
-        properties?: {
-            [key: string]: string;
-        };
-    }
-
-    /**
-     * Encapsulates options passed into client.flush() function
-     */
-    interface FlushOptions {
-        /**
-         * Flag indicating whether application is crashing. When this flag is set to true
-         * and storing data locally is enabled, Node.JS SDK will attempt to store data on disk
-         */
-        isAppCrashing?: boolean;
-        /**
-         * Callback that will be invoked with the response from server, in case of isAppCrashing set to true,
-         * with immediate notification that data was stored
-         */
-        callback?: (v: string) => void;
-    }
-}
-
-declare module 'applicationinsights' {
-    export = ApplicationInsights;
-}
diff --git a/src/typings/applicationinsights-web.d.ts b/src/typings/applicationinsights-web.d.ts
deleted file mode 100644
index a069fed7c6..0000000000
--- a/src/typings/applicationinsights-web.d.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module '@microsoft/applicationinsights-web' {
-	export interface IConfig {
-		instrumentationKey?: string;
-		endpointUrl?: string;
-		emitLineDelimitedJson?: boolean;
-		accountId?: string;
-		sessionRenewalMs?: number;
-		sessionExpirationMs?: number;
-		maxBatchSizeInBytes?: number;
-		maxBatchInterval?: number;
-		enableDebug?: boolean;
-		disableExceptionTracking?: boolean;
-		disableTelemetry?: boolean;
-		verboseLogging?: boolean;
-		diagnosticLogInterval?: number;
-		samplingPercentage?: number;
-		autoTrackPageVisitTime?: boolean;
-		disableAjaxTracking?: boolean;
-		overridePageViewDuration?: boolean;
-		maxAjaxCallsPerView?: number;
-		disableDataLossAnalysis?: boolean;
-		disableCorrelationHeaders?: boolean;
-		correlationHeaderExcludedDomains?: string[];
-		disableFlushOnBeforeUnload?: boolean;
-		enableSessionStorageBuffer?: boolean;
-		isCookieUseDisabled?: boolean;
-		cookieDomain?: string;
-		isRetryDisabled?: boolean;
-		url?: string;
-		isStorageUseDisabled?: boolean;
-		isBeaconApiDisabled?: boolean;
-		sdkExtension?: string;
-		isBrowserLinkTrackingEnabled?: boolean;
-		appId?: string;
-		enableCorsCorrelation?: boolean;
-	}
-
-	export interface ISnippet {
-		config: IConfig;
-	}
-
-	export interface IEventTelemetry {
-		name: string;
-		properties?: { [key: string]: string };
-		measurements?: { [key: string]: number };
-	}
-
-	export class ApplicationInsights {
-		constructor(config: ISnippet);
-		loadAppInsights(): void;
-		trackEvent(data: IEventTelemetry): void;
-		flush(): void;
-	}
-}
diff --git a/src/typings/chokidar.d.ts b/src/typings/chokidar.d.ts
deleted file mode 100644
index 87d93fd5ab..0000000000
--- a/src/typings/chokidar.d.ts
+++ /dev/null
@@ -1,200 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module 'chokidar' {
-
-	// TypeScript Version: 3.0
-
-	import * as fs from "fs";
-	import { EventEmitter } from "events";
-
-	/**
-	 * The object's keys are all the directories (using absolute paths unless the `cwd` option was
-	 * used), and the values are arrays of the names of the items contained in each directory.
-	 */
-	export interface WatchedPaths {
-		[directory: string]: string[];
-	}
-
-	export class FSWatcher extends EventEmitter implements fs.FSWatcher {
-
-		readonly options?: WatchOptions;
-
-		/**
-		 * Constructs a new FSWatcher instance with optional WatchOptions parameter.
-		 */
-		constructor(options?: WatchOptions);
-
-		/**
-		 * Add files, directories, or glob patterns for tracking. Takes an array of strings or just one
-		 * string.
-		 */
-		add(paths: string | string[]): void;
-
-		/**
-		 * Stop watching files, directories, or glob patterns. Takes an array of strings or just one
-		 * string.
-		 */
-		unwatch(paths: string | string[]): void;
-
-		/**
-		 * Returns an object representing all the paths on the file system being watched by this
-		 * `FSWatcher` instance. The object's keys are all the directories (using absolute paths unless
-		 * the `cwd` option was used), and the values are arrays of the names of the items contained in
-		 * each directory.
-		 */
-		getWatched(): WatchedPaths;
-
-		/**
-		 * Removes all listeners from watched files.
-		 */
-		close(): void;
-
-		on(event: 'add' | 'addDir' | 'change', listener: (path: string, stats?: fs.Stats) => void): this;
-
-		on(event: 'all', listener: (eventName: 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir', path: string, stats?: fs.Stats) => void): this;
-
-		/**
-		 * Error occured
-		 */
-		on(event: 'error', listener: (error: Error) => void): this;
-
-		/**
-		 * Exposes the native Node `fs.FSWatcher events`
-		 */
-		on(event: 'raw', listener: (eventName: string, path: string, details: any) => void): this;
-
-		/**
-		 * Fires when the initial scan is complete
-		 */
-		on(event: 'ready', listener: () => void): this;
-
-		on(event: 'unlink' | 'unlinkDir', listener: (path: string) => void): this;
-
-		on(event: string, listener: (...args: any[]) => void): this;
-	}
-
-	export interface WatchOptions {
-		/**
-		 * Indicates whether the process should continue to run as long as files are being watched. If
-		 * set to `false` when using `fsevents` to watch, no more events will be emitted after `ready`,
-		 * even if the process continues to run.
-		 */
-		persistent?: boolean;
-
-		/**
-		 * ([anymatch](https://github.com/es128/anymatch)-compatible definition) Defines files/paths to
-		 * be ignored. The whole relative or absolute path is tested, not just filename. If a function
-		 * with two arguments is provided, it gets called twice per path - once with a single argument
-		 * (the path), second time with two arguments (the path and the
-		 * [`fs.Stats`](http://nodejs.org/api/fs.html#fs_class_fs_stats) object of that path).
-		 */
-		ignored?: any;
-
-		/**
-		 * If set to `false` then `add`/`addDir` events are also emitted for matching paths while
-		 * instantiating the watching as chokidar discovers these file paths (before the `ready` event).
-		 */
-		ignoreInitial?: boolean;
-
-		/**
-		 * When `false`, only the symlinks themselves will be watched for changes instead of following
-		 * the link references and bubbling events through the link's path.
-		 */
-		followSymlinks?: boolean;
-
-		/**
-		 * The base directory from which watch `paths` are to be derived. Paths emitted with events will
-		 * be relative to this.
-		 */
-		cwd?: string;
-
-		/**
-		 *  If set to true then the strings passed to .watch() and .add() are treated as literal path
-		 *  names, even if they look like globs. Default: false.
-		 */
-		disableGlobbing?: boolean;
-
-		/**
-		 * Whether to use fs.watchFile (backed by polling), or fs.watch. If polling leads to high CPU
-		 * utilization, consider setting this to `false`. It is typically necessary to **set this to
-		 * `true` to successfully watch files over a network**, and it may be necessary to successfully
-		 * watch files in other non-standard situations. Setting to `true` explicitly on OS X overrides
-		 * the `useFsEvents` default.
-		 */
-		usePolling?: boolean;
-
-		/**
-		 * Whether to use the `fsevents` watching interface if available. When set to `true` explicitly
-		 * and `fsevents` is available this supercedes the `usePolling` setting. When set to `false` on
-		 * OS X, `usePolling: true` becomes the default.
-		 */
-		useFsEvents?: boolean;
-
-		/**
-		 * If relying upon the [`fs.Stats`](http://nodejs.org/api/fs.html#fs_class_fs_stats) object that
-		 * may get passed with `add`, `addDir`, and `change` events, set this to `true` to ensure it is
-		 * provided even in cases where it wasn't already available from the underlying watch events.
-		 */
-		alwaysStat?: boolean;
-
-		/**
-		 * If set, limits how many levels of subdirectories will be traversed.
-		 */
-		depth?: number;
-
-		/**
-		 * Interval of file system polling.
-		 */
-		interval?: number;
-
-		/**
-		 * Interval of file system polling for binary files. ([see list of binary extensions](https://gi
-		 * thub.com/sindresorhus/binary-extensions/blob/master/binary-extensions.json))
-		 */
-		binaryInterval?: number;
-
-		/**
-		 *  Indicates whether to watch files that don't have read permissions if possible. If watching
-		 *  fails due to `EPERM` or `EACCES` with this set to `true`, the errors will be suppressed
-		 *  silently.
-		 */
-		ignorePermissionErrors?: boolean;
-
-		/**
-		 * `true` if `useFsEvents` and `usePolling` are `false`). Automatically filters out artifacts
-		 * that occur when using editors that use "atomic writes" instead of writing directly to the
-		 * source file. If a file is re-added within 100 ms of being deleted, Chokidar emits a `change`
-		 * event rather than `unlink` then `add`. If the default of 100 ms does not work well for you,
-		 * you can override it by setting `atomic` to a custom value, in milliseconds.
-		 */
-		atomic?: boolean | number;
-
-		/**
-		 * can be set to an object in order to adjust timing params:
-		 */
-		awaitWriteFinish?: AwaitWriteFinishOptions | boolean;
-	}
-
-	export interface AwaitWriteFinishOptions {
-		/**
-		 * Amount of time in milliseconds for a file size to remain constant before emitting its event.
-		 */
-		stabilityThreshold?: number;
-
-		/**
-		 * File size polling interval.
-		 */
-		pollInterval?: number;
-	}
-
-	/**
-	 * produces an instance of `FSWatcher`.
-	 */
-	export function watch(
-		paths: string | string[],
-		options?: WatchOptions
-	): FSWatcher;
-}
diff --git a/src/typings/electron.d.ts b/src/typings/electron.d.ts
index f1d41613b5..369817dcd1 100644
--- a/src/typings/electron.d.ts
+++ b/src/typings/electron.d.ts
@@ -1,4 +1,4 @@
-// Type definitions for Electron 6.0.12
+// Type definitions for Electron 6.1.5
 // Project: http://electronjs.org/
 // Definitions by: The Electron Team 
 // Definitions: https://github.com/electron/electron-typescript-definitions
@@ -70,6 +70,7 @@ declare namespace Electron {
 
 	interface RendererInterface extends CommonInterface {
 		BrowserWindowProxy: typeof BrowserWindowProxy;
+		contextBridge: ContextBridge;
 		desktopCapturer: DesktopCapturer;
 		ipcRenderer: IpcRenderer;
 		remote: Remote;
@@ -83,6 +84,7 @@ declare namespace Electron {
 	const autoUpdater: AutoUpdater;
 	const clipboard: Clipboard;
 	const contentTracing: ContentTracing;
+	const contextBridge: ContextBridge;
 	const crashReporter: CrashReporter;
 	const desktopCapturer: DesktopCapturer;
 	const dialog: Dialog;
@@ -2511,6 +2513,13 @@ declare namespace Electron {
 		stopRecording(resultFilePath: string): Promise;
 	}
 
+	interface ContextBridge extends EventEmitter {
+
+		// Docs: http://electronjs.org/docs/api/context-bridge
+
+		exposeInMainWorld(apiKey: string, api: Record): void;
+	}
+
 	interface Cookie {
 
 		// Docs: http://electronjs.org/docs/api/structures/cookie
diff --git a/src/typings/http-proxy-agent.d.ts b/src/typings/http-proxy-agent.d.ts
deleted file mode 100644
index 062771cd75..0000000000
--- a/src/typings/http-proxy-agent.d.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module 'http-proxy-agent' {
-
-	interface IHttpProxyAgentOptions {
-		host: string;
-		port: number;
-		auth?: string;
-	}
-
-	class HttpProxyAgent {
-		constructor(proxy: string);
-		constructor(opts: IHttpProxyAgentOptions);
-	}
-
-	export = HttpProxyAgent;
-}
\ No newline at end of file
diff --git a/src/typings/iconv-lite.d.ts b/src/typings/iconv-lite.d.ts
deleted file mode 100644
index 15be5a0ca6..0000000000
--- a/src/typings/iconv-lite.d.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-/// 
-
-declare module 'iconv-lite' {
-	export function decode(buffer: Buffer, encoding: string): string;
-
-	export function encode(content: string | Buffer, encoding: string, options?: { addBOM?: boolean }): Buffer;
-
-	export function encodingExists(encoding: string): boolean;
-
-	export function decodeStream(encoding: string): NodeJS.ReadWriteStream;
-
-	export function encodeStream(encoding: string, options?: { addBOM?: boolean }): NodeJS.ReadWriteStream;
-}
\ No newline at end of file
diff --git a/src/typings/jschardet.d.ts b/src/typings/jschardet.d.ts
deleted file mode 100644
index f252a47fd0..0000000000
--- a/src/typings/jschardet.d.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-declare module 'jschardet' {
-	export interface IDetectedMap {
-		encoding: string,
-		confidence: number
-	}
-	export function detect(buffer: Buffer): IDetectedMap;
-
-	export const Constants: {
-		MINIMUM_THRESHOLD: number,
-	}
-}
\ No newline at end of file
diff --git a/src/typings/native-is-elevated.d.ts b/src/typings/native-is-elevated.d.ts
deleted file mode 100644
index 87c86d53f9..0000000000
--- a/src/typings/native-is-elevated.d.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module 'native-is-elevated' {
-	function isElevated(): boolean;
-
-	export = isElevated;
-}
\ No newline at end of file
diff --git a/src/typings/native-keymap.d.ts b/src/typings/native-keymap.d.ts
deleted file mode 100644
index f51f589287..0000000000
--- a/src/typings/native-keymap.d.ts
+++ /dev/null
@@ -1,93 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module 'native-keymap' {
-
-	export interface IWindowsKeyMapping {
-		vkey: string;
-		value: string;
-		withShift: string;
-		withAltGr: string;
-		withShiftAltGr: string;
-	}
-	export interface IWindowsKeyboardMapping {
-		[code: string]: IWindowsKeyMapping;
-	}
-	export interface ILinuxKeyMapping {
-		value: string;
-		withShift: string;
-		withAltGr: string;
-		withShiftAltGr: string;
-	}
-	export interface ILinuxKeyboardMapping {
-		[code: string]: ILinuxKeyMapping;
-	}
-	export interface IMacKeyMapping {
-		value: string;
-		withShift: string;
-		withAltGr: string;
-		withShiftAltGr: string;
-		valueIsDeadKey: boolean;
-		withShiftIsDeadKey: boolean;
-		withAltGrIsDeadKey: boolean;
-		withShiftAltGrIsDeadKey: boolean;
-	}
-	export interface IMacKeyboardMapping {
-		[code: string]: IMacKeyMapping;
-	}
-
-	export type IKeyboardMapping = IWindowsKeyboardMapping | ILinuxKeyboardMapping | IMacKeyboardMapping;
-
-	export function getKeyMap(): IKeyboardMapping;
-
-	/* __GDPR__FRAGMENT__
-		"IKeyboardLayoutInfo" : {
-			"name" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
-			"id": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
-			"text": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
-		}
-	*/
-	export interface IWindowsKeyboardLayoutInfo {
-		name: string;
-		id: string;
-		text: string;
-	}
-
-	/* __GDPR__FRAGMENT__
-		"IKeyboardLayoutInfo" : {
-			"model" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
-			"layout": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
-			"variant": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
-			"options": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
-			"rules": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
-		}
-	*/
-	export interface ILinuxKeyboardLayoutInfo {
-		model: string;
-		layout: string;
-		variant: string;
-		options: string;
-		rules: string;
-	}
-
-	/* __GDPR__FRAGMENT__
-		"IKeyboardLayoutInfo" : {
-			"id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
-			"lang": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
-		}
-	*/
-	export interface IMacKeyboardLayoutInfo {
-		id: string;
-		lang: string;
-	}
-
-	export type IKeyboardLayoutInfo = IWindowsKeyboardLayoutInfo | ILinuxKeyboardLayoutInfo | IMacKeyboardLayoutInfo;
-
-	export function getCurrentKeyboardLayout(): IKeyboardLayoutInfo;
-
-	export function onDidChangeKeyboardLayout(callback: () => void): void;
-
-	export function isISOKeyboard(): boolean;
-}
\ No newline at end of file
diff --git a/src/typings/native-watchdog.d.ts b/src/typings/native-watchdog.d.ts
deleted file mode 100644
index 0694dd2db0..0000000000
--- a/src/typings/native-watchdog.d.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module 'native-watchdog' {
-
-	/**
-	 * Start monitoring for a certain pid to exist.
-	 * If the process indicated by pid ceases to execute,
-	 * the current process will exit in 6 seconds with exit code 87
-	 */
-	export function start(pid: number): void;
-
-	export function exit(exitCode: number): void;
-
-}
diff --git a/src/typings/node-pty.d.ts b/src/typings/node-pty.d.ts
deleted file mode 100644
index 100e9eef87..0000000000
--- a/src/typings/node-pty.d.ts
+++ /dev/null
@@ -1,143 +0,0 @@
-/**
- * Copyright (c) 2017, Daniel Imms (Source EULA).
- * Copyright (c) 2018, Microsoft Corporation (Source EULA).
- */
-
-declare module 'node-pty' {
-	/**
-	 * Forks a process as a pseudoterminal.
-	 * @param file The file to launch.
-	 * @param args The file's arguments as argv (string[]) or in a pre-escaped CommandLine format
-	 * (string). Note that the CommandLine option is only available on Windows and is expected to be
-	 * escaped properly.
-	 * @param options The options of the terminal.
-	 * @see CommandLineToArgvW https://msdn.microsoft.com/en-us/library/windows/desktop/bb776391(v=vs.85).aspx
-	 * @see Parsing C++ Comamnd-Line Arguments https://msdn.microsoft.com/en-us/library/17w5ykft.aspx
-	 * @see GetCommandLine https://msdn.microsoft.com/en-us/library/windows/desktop/ms683156.aspx
-	 */
-	export function spawn(file: string, args: string[] | string, options: IPtyForkOptions | IWindowsPtyForkOptions): IPty;
-
-	export interface IPtyForkOptions {
-		name?: string;
-		cols?: number;
-		rows?: number;
-		cwd?: string;
-		env?: { [key: string]: string };
-		uid?: number;
-		gid?: number;
-		encoding?: string;
-	}
-
-	export interface IWindowsPtyForkOptions {
-		name?: string;
-		cols?: number;
-		rows?: number;
-		cwd?: string;
-		env?: { [key: string]: string };
-		encoding?: string;
-		/**
-		 * Whether to use the experimental ConPTY system on Windows. When this is not set, ConPTY will
-		 * be used when the Windows build number is >= 18309 (it's available in 17134 and 17692 but is
-		 * too unstable to enable by default).
-		 *
-		 * This setting does nothing on non-Windows.
-		 */
-		experimentalUseConpty?: boolean;
-		/**
-		 * Whether to use PSEUDOCONSOLE_INHERIT_CURSOR in conpty.
-		 * @see https://docs.microsoft.com/en-us/windows/console/createpseudoconsole
-		 */
-		conptyInheritCursor?: boolean;
-	}
-
-	/**
-	 * An interface representing a pseudoterminal, on Windows this is emulated via the winpty library.
-	 */
-	export interface IPty {
-		/**
-		 * The process ID of the outer process.
-		 */
-		readonly pid: number;
-
-		/**
-		 * The column size in characters.
-		 */
-		readonly cols: number;
-
-		/**
-		 * The row size in characters.
-		 */
-		readonly rows: number;
-
-		/**
-		 * The title of the active process.
-		 */
-		readonly process: string;
-
-		/**
-		 * Adds an event listener for when a data event fires. This happens when data is returned from
-		 * the pty.
-		 * @returns an `IDisposable` to stop listening.
-		 */
-		readonly onData: IEvent;
-
-		/**
-		 * Adds an event listener for when an exit event fires. This happens when the pty exits.
-		 * @returns an `IDisposable` to stop listening.
-		 */
-		readonly onExit: IEvent<{ exitCode: number, signal?: number }>;
-
-		/**
-		 * Adds a listener to the data event, fired when data is returned from the pty.
-		 * @param event The name of the event.
-		 * @param listener The callback function.
-		 * @deprecated Use IPty.onData
-		 */
-		on(event: 'data', listener: (data: string) => void): void;
-
-		/**
-		 * Adds a listener to the exit event, fired when the pty exits.
-		 * @param event The name of the event.
-		 * @param listener The callback function, exitCode is the exit code of the process and signal is
-		 * the signal that triggered the exit. signal is not supported on Windows.
-		 * @deprecated Use IPty.onExit
-		 */
-		on(event: 'exit', listener: (exitCode: number, signal?: number) => void): void;
-
-		/**
-		 * Resizes the dimensions of the pty.
-		 * @param columns THe number of columns to use.
-		 * @param rows The number of rows to use.
-		 */
-		resize(columns: number, rows: number): void;
-
-		/**
-		 * Writes data to the pty.
-		 * @param data The data to write.
-		 */
-		write(data: string): void;
-
-		/**
-		 * Kills the pty.
-		 * @param signal The signal to use, defaults to SIGHUP. This parameter is not supported on
-		 * Windows.
-		 * @throws Will throw when signal is used on Windows.
-		 */
-		kill(signal?: string): void;
-	}
-
-	/**
-	 * An object that can be disposed via a dispose function.
-	 */
-	export interface IDisposable {
-		dispose(): void;
-	}
-
-	/**
-	 * An event that can be listened to.
-	 * @returns an `IDisposable` to stop listening.
-	 */
-	export interface IEvent {
-		(listener: (e: T) => any): IDisposable;
-	}
-}
diff --git a/src/typings/nsfw.d.ts b/src/typings/nsfw.d.ts
deleted file mode 100644
index 37c4c2ec92..0000000000
--- a/src/typings/nsfw.d.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module 'nsfw' {
-	interface NsfwWatcher {
-		start(): any;
-		stop(): any;
-	}
-
-	interface NsfwWatchingPromise {
-		then(): void;
-	}
-
-	interface NsfwStartWatchingPromise {
-		then(fn: (watcher: NsfwWatcher) => void): NsfwWatchingPromise;
-	}
-
-	interface NsfwEvent {
-		action: number;
-		directory: string;
-		file?: string;
-		newFile?: string;
-		newDirectory?: string;
-		oldFile?: string;
-	}
-
-	interface NsfwFunction {
-		(dir: string, eventHandler: (events: NsfwEvent[]) => void, options?: any): NsfwStartWatchingPromise;
-		actions: {
-			CREATED: number;
-			DELETED: number;
-			MODIFIED: number;
-			RENAMED: number;
-		}
-	}
-
-	var nsfw: NsfwFunction;
-	export = nsfw;
-}
diff --git a/src/typings/onigasm-umd.d.ts b/src/typings/onigasm-umd.d.ts
deleted file mode 100644
index 24396aafa9..0000000000
--- a/src/typings/onigasm-umd.d.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module "onigasm-umd" {
-
-	function loadWASM(data: string | ArrayBuffer): Promise;
-
-	class OnigString {
-		constructor(content: string);
-		readonly content: string;
-		readonly dispose?: () => void;
-	}
-
-	class OnigScanner {
-		constructor(patterns: string[]);
-		findNextMatchSync(string: string | OnigString, startPosition: number): IOnigMatch;
-	}
-
-	export interface IOnigCaptureIndex {
-		index: number
-		start: number
-		end: number
-		length: number
-	}
-
-	export interface IOnigMatch {
-		index: number
-		captureIndices: IOnigCaptureIndex[]
-		scanner: OnigScanner
-	}
-}
\ No newline at end of file
diff --git a/src/typings/require.d.ts b/src/typings/require.d.ts
index 5b98dc2f4b..b11fa0b660 100644
--- a/src/typings/require.d.ts
+++ b/src/typings/require.d.ts
@@ -46,5 +46,7 @@ interface NodeRequire {
 	config(data: any): any;
 	onError: Function;
 	__$__nodeRequire(moduleName: string): T;
-	getStats(): ReadonlyArray
+	getStats(): ReadonlyArray;
 }
+
+declare var require: NodeRequire;
diff --git a/src/typings/spdlog.d.ts b/src/typings/spdlog.d.ts
deleted file mode 100644
index 32b3c756ac..0000000000
--- a/src/typings/spdlog.d.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module 'spdlog' {
-
-	export const version: string;
-	export function setAsyncMode(bufferSize: number, flushInterval: number): void;
-	export function createRotatingLogger(name: string, filename: string, filesize: number, filecount: number): RotatingLogger;
-	export function createRotatingLoggerAsync(name: string, filename: string, filesize: number, filecount: number): Promise;
-
-	export enum LogLevel {
-		CRITICAL,
-		ERROR,
-		WARN,
-		INFO,
-		DEBUG,
-		TRACE,
-		OFF
-	}
-
-	export class RotatingLogger {
-		constructor(name: string, filename: string, filesize: number, filecount: number);
-
-		trace(message: string): void;
-		debug(message: string): void;
-		info(message: string): void;
-		warn(message: string): void;
-		error(message: string): void;
-		critical(message: string): void;
-		setLevel(level: number): void;
-		clearFormatters(): void;
-		/**
-		 * A synchronous operation to flush the contents into file
-		*/
-		flush(): void;
-		drop(): void;
-	}
-}
\ No newline at end of file
diff --git a/src/typings/sudo-prompt.d.ts b/src/typings/sudo-prompt.d.ts
deleted file mode 100644
index 8bf5e71785..0000000000
--- a/src/typings/sudo-prompt.d.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module 'sudo-prompt' {
-	type SudoOptions = {
-		name?: string;
-		icns?: string;
-		env?: NodeJS.ProcessEnv;
-	};
-	export function exec(cmd: string, options: SudoOptions, callback: (error: string, stdout: string, stderr: string) => void): void;
-}
diff --git a/src/typings/v8-inspect-profiler.d.ts b/src/typings/v8-inspect-profiler.d.ts
deleted file mode 100644
index 59d7f81cfa..0000000000
--- a/src/typings/v8-inspect-profiler.d.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-declare module 'v8-inspect-profiler' {
-
-	export interface ProfileResult {
-		profile: Profile;
-	}
-
-	export interface Profile {
-		nodes: ProfileNode[];
-		samples?: number[];
-		timeDeltas?: number[];
-		startTime: number;
-		endTime: number;
-	}
-
-	export interface ProfileNode {
-		id: number;
-		hitCount?: number;
-		children?: number[];
-		callFrame: {
-			url: string;
-			scriptId: string;
-			functionName: string;
-			lineNumber: number;
-			columnNumber: number;
-		};
-		deoptReason?: string;
-		positionTicks?: { line: number; ticks: number }[];
-	}
-
-	export interface ProfilingSession {
-		stop(afterDelay?: number): PromiseLike;
-	}
-
-	export interface Target {
-		description: string;
-		devtoolsFrontendUrl: string;
-		id: string;
-		title: string;
-		type: string;
-		url: string;
-		webSocketDebuggerUrl: string;
-	}
-
-	export interface StartOptions {
-		port: number;
-		tries?: number;
-		retyWait?: number;
-		checkForPaused?: boolean;
-		target?: (targets: Target[]) => Target;
-	}
-
-	export function startProfiling(options: StartOptions): PromiseLike;
-	export function writeProfile(profile: ProfileResult, name?: string): PromiseLike;
-	export function rewriteAbsolutePaths(profile: ProfileResult, replaceWith?: string): ProfileResult;
-}
diff --git a/src/typings/vscode-minimist.d.ts b/src/typings/vscode-minimist.d.ts
deleted file mode 100644
index 17558a1a73..0000000000
--- a/src/typings/vscode-minimist.d.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-// Type definitions for minimist 1.2.0
-// Project: https://github.com/substack/minimist
-// Definitions by: Bart van der Schoor , Necroskillz , kamranayub 
-// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
-
-/**
- * Return an argument object populated with the array arguments from args
- *
- * @param args An optional argument array (typically `process.argv.slice(2)`)
- * @param opts An optional options object to customize the parsing
- */
-declare function minimist(args?: string[], opts?: minimist.Opts): minimist.ParsedArgs;
-
-/**
- * Return an argument object populated with the array arguments from args. Strongly-typed
- * to be the intersect of type T with minimist.ParsedArgs.
- *
- * @type T The type that will be intersected with minimist.ParsedArgs to represent the argument object
- * @param args An optional argument array (typically `process.argv.slice(2)`)
- * @param opts An optional options object to customize the parsing
- */
-declare function minimist(args?: string[], opts?: minimist.Opts): T & minimist.ParsedArgs;
-
-/**
- * Return an argument object populated with the array arguments from args. Strongly-typed
- * to be the the type T which should extend minimist.ParsedArgs
- *
- * @type T The type that extends minimist.ParsedArgs and represents the argument object
- * @param args An optional argument array (typically `process.argv.slice(2)`)
- * @param opts An optional options object to customize the parsing
- */
-declare function minimist(args?: string[], opts?: minimist.Opts): T;
-
-declare namespace minimist {
-	export interface Opts {
-		/**
-		 * A string or array of strings argument names to always treat as strings
-		 */
-		string?: string | string[];
-
-		/**
-		 * A boolean, string or array of strings to always treat as booleans. If true will treat
-		 * all double hyphenated arguments without equals signs as boolean (e.g. affects `--foo`, not `-f` or `--foo=bar`)
-		 */
-		boolean?: boolean | string | string[];
-
-		/**
-		 * An object mapping string names to strings or arrays of string argument names to use as aliases
-		 */
-		alias?: { [key: string]: string | string[] };
-
-		/**
-		 * An object mapping string argument names to default values
-		 */
-		default?: { [key: string]: any };
-
-		/**
-		 * When true, populate argv._ with everything after the first non-option
-		 */
-		stopEarly?: boolean;
-
-		/**
-		 * A function which is invoked with a command line parameter not defined in the opts
-		 * configuration object. If the function returns false, the unknown option is not added to argv
-		 */
-		unknown?: (arg: string) => boolean;
-
-		/**
-		 * When true, populate argv._ with everything before the -- and argv['--'] with everything after the --.
-		 * Note that with -- set, parsing for arguments still stops after the `--`.
-		 */
-		'--'?: boolean;
-	}
-
-	export interface ParsedArgs {
-		[arg: string]: any;
-
-		/**
-		 * If opts['--'] is true, populated with everything after the --
-		 */
-		'--'?: string[];
-
-		/**
-		 * Contains all the arguments that didn't have an option associated with them
-		 */
-		_: string[];
-	}
-}
-
-declare module "vscode-minimist" {
-	export = minimist;
-}
diff --git a/src/typings/vscode-proxy-agent.d.ts b/src/typings/vscode-proxy-agent.d.ts
deleted file mode 100644
index 959c106c98..0000000000
--- a/src/typings/vscode-proxy-agent.d.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module 'vscode-proxy-agent';
diff --git a/src/typings/vscode-ripgrep.d.ts b/src/typings/vscode-ripgrep.d.ts
deleted file mode 100644
index 4c5c89c3ca..0000000000
--- a/src/typings/vscode-ripgrep.d.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-declare module 'vscode-ripgrep' {
-	export const rgPath: string;
-}
diff --git a/src/typings/vscode-sqlite3.d.ts b/src/typings/vscode-sqlite3.d.ts
deleted file mode 100644
index 6791f2e967..0000000000
--- a/src/typings/vscode-sqlite3.d.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-// Type definitions for sqlite3 3.1
-// Project: http://github.com/mapbox/node-sqlite3
-// Definitions by: Nick Malaguti 
-//                 Sumant Manne 
-//                 Behind The Math 
-// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
-
-/// 
-
-declare module 'vscode-sqlite3' {
-	import events = require("events");
-
-	export const OPEN_READONLY: number;
-	export const OPEN_READWRITE: number;
-	export const OPEN_CREATE: number;
-	export const OPEN_SHAREDCACHE: number;
-	export const OPEN_PRIVATECACHE: number;
-	export const OPEN_URI: number;
-
-	export const cached: {
-		Database(filename: string, callback?: (this: Database, err: Error | null) => void): Database;
-		Database(filename: string, mode?: number, callback?: (this: Database, err: Error | null) => void): Database;
-	};
-
-	export interface RunResult extends Statement {
-		lastID: number;
-		changes: number;
-	}
-
-	export class Statement extends events.EventEmitter {
-		bind(callback?: (err: Error | null) => void): this;
-		bind(...params: any[]): this;
-
-		reset(callback?: (err: null) => void): this;
-
-		finalize(callback?: (err: Error) => void): Database;
-
-		run(callback?: (err: Error | null) => void): this;
-		run(params: any, callback?: (this: RunResult, err: Error | null) => void): this;
-		run(...params: any[]): this;
-
-		get(callback?: (err: Error | null, row?: any) => void): this;
-		get(params: any, callback?: (this: RunResult, err: Error | null, row?: any) => void): this;
-		get(...params: any[]): this;
-
-		all(callback?: (err: Error | null, rows: any[]) => void): this;
-		all(params: any, callback?: (this: RunResult, err: Error | null, rows: any[]) => void): this;
-		all(...params: any[]): this;
-
-		each(callback?: (err: Error | null, row: any) => void, complete?: (err: Error | null, count: number) => void): this;
-		each(params: any, callback?: (this: RunResult, err: Error | null, row: any) => void, complete?: (err: Error | null, count: number) => void): this;
-		each(...params: any[]): this;
-	}
-
-	export class Database extends events.EventEmitter {
-		constructor(filename: string, callback?: (err: Error | null) => void);
-		constructor(filename: string, mode?: number, callback?: (err: Error | null) => void);
-
-		close(callback?: (err: Error | null) => void): void;
-
-		run(sql: string, callback?: (this: RunResult, err: Error | null) => void): this;
-		run(sql: string, params: any, callback?: (this: RunResult, err: Error | null) => void): this;
-		run(sql: string, ...params: any[]): this;
-
-		get(sql: string, callback?: (this: Statement, err: Error | null, row: any) => void): this;
-		get(sql: string, params: any, callback?: (this: Statement, err: Error | null, row: any) => void): this;
-		get(sql: string, ...params: any[]): this;
-
-		all(sql: string, callback?: (this: Statement, err: Error | null, rows: any[]) => void): this;
-		all(sql: string, params: any, callback?: (this: Statement, err: Error | null, rows: any[]) => void): this;
-		all(sql: string, ...params: any[]): this;
-
-		each(sql: string, callback?: (this: Statement, err: Error | null, row: any) => void, complete?: (err: Error | null, count: number) => void): this;
-		each(sql: string, params: any, callback?: (this: Statement, err: Error | null, row: any) => void, complete?: (err: Error | null, count: number) => void): this;
-		each(sql: string, ...params: any[]): this;
-
-		exec(sql: string, callback?: (this: Statement, err: Error | null) => void): this;
-
-		prepare(sql: string, callback?: (this: Statement, err: Error | null) => void): Statement;
-		prepare(sql: string, params: any, callback?: (this: Statement, err: Error | null) => void): Statement;
-		prepare(sql: string, ...params: any[]): Statement;
-
-		serialize(callback?: () => void): void;
-		parallelize(callback?: () => void): void;
-
-		on(event: "trace", listener: (sql: string) => void): this;
-		on(event: "profile", listener: (sql: string, time: number) => void): this;
-		on(event: "error", listener: (err: Error) => void): this;
-		on(event: "open" | "close", listener: () => void): this;
-		on(event: string, listener: (...args: any[]) => void): this;
-
-		configure(option: "busyTimeout", value: number): void;
-	}
-
-	export function verbose(): sqlite3;
-
-	export interface sqlite3 {
-		OPEN_READONLY: number;
-		OPEN_READWRITE: number;
-		OPEN_CREATE: number;
-		OPEN_SHAREDCACHE: number;
-		OPEN_PRIVATECACHE: number;
-		OPEN_URI: number;
-		cached: typeof cached;
-		RunResult: RunResult;
-		Statement: typeof Statement;
-		Database: typeof Database;
-		verbose(): this;
-	}
-}
\ No newline at end of file
diff --git a/src/typings/vscode-textmate.d.ts b/src/typings/vscode-textmate.d.ts
deleted file mode 100644
index 28f28fbbf1..0000000000
--- a/src/typings/vscode-textmate.d.ts
+++ /dev/null
@@ -1,256 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module "vscode-textmate" {
-	/**
-	 * A single theme setting.
-	 */
-	export interface IRawThemeSetting {
-		readonly name?: string;
-		readonly scope?: string | string[];
-		readonly settings: {
-			readonly fontStyle?: string;
-			readonly foreground?: string;
-			readonly background?: string;
-		};
-	}
-	/**
-	 * A TextMate theme.
-	 */
-	export interface IRawTheme {
-		readonly name?: string;
-		readonly settings: IRawThemeSetting[];
-	}
-	export interface Thenable extends PromiseLike {
-	}
-	/**
-	 * A registry helper that can locate grammar file paths given scope names.
-	 */
-	export interface RegistryOptions {
-		theme?: IRawTheme;
-		loadGrammar(scopeName: string): Thenable;
-		getInjections?(scopeName: string): string[];
-		getOnigLib?(): Thenable;
-	}
-	/**
-	 * A map from scope name to a language id. Please do not use language id 0.
-	 */
-	export interface IEmbeddedLanguagesMap {
-		[scopeName: string]: number;
-	}
-	/**
-	 * A map from selectors to token types.
-	 */
-	export interface ITokenTypeMap {
-		[selector: string]: StandardTokenType;
-	}
-	export const enum StandardTokenType {
-		Other = 0,
-		Comment = 1,
-		String = 2,
-		RegEx = 4,
-	}
-	export interface IGrammarConfiguration {
-		embeddedLanguages?: IEmbeddedLanguagesMap;
-		tokenTypes?: ITokenTypeMap;
-	}
-	/**
-	 * The registry that will hold all grammars.
-	 */
-	export class Registry {
-		private readonly _locator;
-		private readonly _syncRegistry;
-		constructor(locator?: RegistryOptions);
-		/**
-		 * Change the theme. Once called, no previous `ruleStack` should be used anymore.
-		 */
-		setTheme(theme: IRawTheme): void;
-		/**
-		 * Returns a lookup array for color ids.
-		 */
-		getColorMap(): string[];
-		/**
-		 * Load the grammar for `scopeName` and all referenced included grammars asynchronously.
-		 * Please do not use language id 0.
-		 */
-		loadGrammarWithEmbeddedLanguages(initialScopeName: string, initialLanguage: number, embeddedLanguages: IEmbeddedLanguagesMap): Thenable;
-		/**
-		 * Load the grammar for `scopeName` and all referenced included grammars asynchronously.
-		 * Please do not use language id 0.
-		 */
-		loadGrammarWithConfiguration(initialScopeName: string, initialLanguage: number, configuration: IGrammarConfiguration): Thenable;
-		/**
-		 * Load the grammar for `scopeName` and all referenced included grammars asynchronously.
-		 */
-		loadGrammar(initialScopeName: string): Thenable;
-		private _loadGrammar;
-		/**
-		 * Adds a rawGrammar.
-		 */
-		addGrammar(rawGrammar: IRawGrammar, injections?: string[], initialLanguage?: number, embeddedLanguages?: IEmbeddedLanguagesMap): Thenable;
-		/**
-		 * Get the grammar for `scopeName`. The grammar must first be created via `loadGrammar` or `addGrammar`.
-		 */
-		grammarForScopeName(scopeName: string, initialLanguage?: number, embeddedLanguages?: IEmbeddedLanguagesMap, tokenTypes?: ITokenTypeMap): Thenable;
-	}
-	/**
-	 * A grammar
-	 */
-	export interface IGrammar {
-		/**
-		 * Tokenize `lineText` using previous line state `prevState`.
-		 */
-		tokenizeLine(lineText: string, prevState: StackElement | null): ITokenizeLineResult;
-		/**
-		 * Tokenize `lineText` using previous line state `prevState`.
-		 * The result contains the tokens in binary format, resolved with the following information:
-		 *  - language
-		 *  - token type (regex, string, comment, other)
-		 *  - font style
-		 *  - foreground color
-		 *  - background color
-		 * e.g. for getting the languageId: `(metadata & MetadataConsts.LANGUAGEID_MASK) >>> MetadataConsts.LANGUAGEID_OFFSET`
-		 */
-		tokenizeLine2(lineText: string, prevState: StackElement | null): ITokenizeLineResult2;
-	}
-	export interface ITokenizeLineResult {
-		readonly tokens: IToken[];
-		/**
-		 * The `prevState` to be passed on to the next line tokenization.
-		 */
-		readonly ruleStack: StackElement;
-	}
-	/**
-	 * Helpers to manage the "collapsed" metadata of an entire StackElement stack.
-	 * The following assumptions have been made:
-	 *  - languageId < 256 => needs 8 bits
-	 *  - unique color count < 512 => needs 9 bits
-	 *
-	 * The binary format is:
-	 * - -------------------------------------------
-	 *     3322 2222 2222 1111 1111 1100 0000 0000
-	 *     1098 7654 3210 9876 5432 1098 7654 3210
-	 * - -------------------------------------------
-	 *     xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx
-	 *     bbbb bbbb bfff ffff ffFF FTTT LLLL LLLL
-	 * - -------------------------------------------
-	 *  - L = LanguageId (8 bits)
-	 *  - T = StandardTokenType (3 bits)
-	 *  - F = FontStyle (3 bits)
-	 *  - f = foreground color (9 bits)
-	 *  - b = background color (9 bits)
-	 */
-	export const enum MetadataConsts {
-		LANGUAGEID_MASK = 255,
-		TOKEN_TYPE_MASK = 1792,
-		FONT_STYLE_MASK = 14336,
-		FOREGROUND_MASK = 8372224,
-		BACKGROUND_MASK = 4286578688,
-		LANGUAGEID_OFFSET = 0,
-		TOKEN_TYPE_OFFSET = 8,
-		FONT_STYLE_OFFSET = 11,
-		FOREGROUND_OFFSET = 14,
-		BACKGROUND_OFFSET = 23,
-	}
-	export interface ITokenizeLineResult2 {
-		/**
-		 * The tokens in binary format. Each token occupies two array indices. For token i:
-		 *  - at offset 2*i => startIndex
-		 *  - at offset 2*i + 1 => metadata
-		 *
-		 */
-		readonly tokens: Uint32Array;
-		/**
-		 * The `prevState` to be passed on to the next line tokenization.
-		 */
-		readonly ruleStack: StackElement;
-	}
-	export interface IToken {
-		startIndex: number;
-		readonly endIndex: number;
-		readonly scopes: string[];
-	}
-	/**
-	 * **IMPORTANT** - Immutable!
-	 */
-	export interface StackElement {
-		_stackElementBrand: void;
-		readonly depth: number;
-		clone(): StackElement;
-		equals(other: StackElement): boolean;
-	}
-	export const INITIAL: StackElement;
-	export const parseRawGrammar: (content: string, filePath?: string) => IRawGrammar;
-	export interface ILocation {
-		readonly filename: string;
-		readonly line: number;
-		readonly char: number;
-	}
-	export interface ILocatable {
-		readonly $vscodeTextmateLocation?: ILocation;
-	}
-	export interface IRawGrammar extends ILocatable {
-		repository: IRawRepository;
-		readonly scopeName: string;
-		readonly patterns: IRawRule[];
-		readonly injections?: {
-			[expression: string]: IRawRule;
-		};
-		readonly injectionSelector?: string;
-		readonly fileTypes?: string[];
-		readonly name?: string;
-		readonly firstLineMatch?: string;
-	}
-	export interface IRawRepositoryMap {
-		[name: string]: IRawRule;
-		$self: IRawRule;
-		$base: IRawRule;
-	}
-	export type IRawRepository = IRawRepositoryMap & ILocatable;
-	export interface IRawRule extends ILocatable {
-		id?: number;
-		readonly include?: string;
-		readonly name?: string;
-		readonly contentName?: string;
-		readonly match?: string;
-		readonly captures?: IRawCaptures;
-		readonly begin?: string;
-		readonly beginCaptures?: IRawCaptures;
-		readonly end?: string;
-		readonly endCaptures?: IRawCaptures;
-		readonly while?: string;
-		readonly whileCaptures?: IRawCaptures;
-		readonly patterns?: IRawRule[];
-		readonly repository?: IRawRepository;
-		readonly applyEndPatternLast?: boolean;
-	}
-	export interface IRawCapturesMap {
-		[captureId: string]: IRawRule;
-	}
-	export type IRawCaptures = IRawCapturesMap & ILocatable;
-	export interface IOnigLib {
-		createOnigScanner(sources: string[]): OnigScanner;
-		createOnigString(sources: string): OnigString;
-	}
-	export interface IOnigCaptureIndex {
-		start: number;
-		end: number;
-		length: number;
-	}
-	export interface IOnigMatch {
-		index: number;
-		captureIndices: IOnigCaptureIndex[];
-		scanner: OnigScanner;
-	}
-	export interface OnigScanner {
-		findNextMatchSync(string: string | OnigString, startPosition: number): IOnigMatch;
-	}
-	export interface OnigString {
-		readonly content: string;
-		readonly dispose?: () => void;
-	}
-
-
-}
diff --git a/src/typings/vscode-windows-ca-certs.d.ts b/src/typings/vscode-windows-ca-certs.d.ts
deleted file mode 100644
index 37b2282827..0000000000
--- a/src/typings/vscode-windows-ca-certs.d.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module 'vscode-windows-ca-certs';
diff --git a/src/typings/windows-process-tree.d.ts b/src/typings/windows-process-tree.d.ts
deleted file mode 100644
index 689ddb77d9..0000000000
--- a/src/typings/windows-process-tree.d.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module 'windows-process-tree' {
-	export enum ProcessDataFlag {
-		None = 0,
-		Memory = 1,
-		CommandLine = 2
-	}
-
-	export interface IProcessInfo {
-		pid: number;
-		ppid: number;
-		name: string;
-
-		/**
-		 * The working set size of the process, in bytes.
-		 */
-		memory?: number;
-
-		/**
-		 * The string returned is at most 512 chars, strings exceeding this length are truncated.
-		 */
-		commandLine?: string;
-	}
-
-	export interface IProcessCpuInfo extends IProcessInfo {
-		cpu?: number;
-	}
-
-	export interface IProcessTreeNode {
-		pid: number;
-		name: string;
-		memory?: number;
-		commandLine?: string;
-		children: IProcessTreeNode[];
-	}
-
-	/**
-	 * Returns a tree of processes with the rootPid process as the root.
-	 * @param rootPid - The pid of the process that will be the root of the tree.
-	 * @param callback - The callback to use with the returned list of processes.
-	 * @param flags - The flags for what process data should be included.
-	 */
-	export function getProcessTree(rootPid: number, callback: (tree: IProcessTreeNode) => void, flags?: ProcessDataFlag): void;
-
-	/**
-	 * Returns a list of processes containing the rootPid process and all of its descendants.
-	 * @param rootPid - The pid of the process of interest.
-	 * @param callback - The callback to use with the returned set of processes.
-	 * @param flags - The flags for what process data should be included.
-	 */
-	export function getProcessList(rootPid: number, callback: (processList: IProcessInfo[]) => void, flags?: ProcessDataFlag): void;
-
-	/**
-	 * Returns the list of processes annotated with cpu usage information.
-	 * @param processList - The list of processes.
-	 * @param callback - The callback to use with the returned list of processes.
-	 */
-	export function getProcessCpuUsage(processList: IProcessInfo[], callback: (processListWithCpu: IProcessCpuInfo[]) => void): void;
-}
diff --git a/src/typings/xterm-addon-search.d.ts b/src/typings/xterm-addon-search.d.ts
deleted file mode 100644
index 2f3be6f82b..0000000000
--- a/src/typings/xterm-addon-search.d.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-/**
- * Copyright (c) 2017 The xterm.js authors. All rights reserved.
- * @license MIT
- */
-
-// HACK: gulp-tsb doesn't play nice with importing from typings
-// import { Terminal, ITerminalAddon } from 'xterm';
-
-declare module 'xterm-addon-search' {
-	/**
-	 * Options for a search.
-	 */
-	export interface ISearchOptions {
-		/**
-		 * Whether the search term is a regex.
-		 */
-		regex?: boolean;
-
-		/**
-		 * Whether to search for a whole word, the result is only valid if it's
-		 * suppounded in "non-word" characters such as `_`, `(`, `)` or space.
-		 */
-		wholeWord?: boolean;
-
-		/**
-		 * Whether the search is case sensitive.
-		 */
-		caseSensitive?: boolean;
-
-		/**
-		 * Whether to do an indcremental search, this will expand the selection if it
-		 * still matches the term the user typed. Note that this only affects
-		 * `findNext`, not `findPrevious`.
-		 */
-		incremental?: boolean;
-	}
-
-	/**
-	 * An xterm.js addon that provides search functionality.
-	 */
-	export class SearchAddon {
-		/**
-		 * Activates the addon
-		 * @param terminal The terminal the addon is being loaded in.
-		 */
-		public activate(terminal: any): void;
-
-		/**
-		 * Disposes the addon.
-		 */
-		public dispose(): void;
-
-		/**
-		 * Search forwards for the next result that matches the search term and
-		 * options.
-		 * @param term The search term.
-		 * @param searchOptions The options for the search.
-		 */
-		public findNext(term: string, searchOptions?: ISearchOptions): boolean;
-
-		/**
-		 * Search backwards for the previous result that matches the search term and
-		 * options.
-		 * @param term The search term.
-		 * @param searchOptions The options for the search.
-		 */
-		public findPrevious(term: string, searchOptions?: ISearchOptions): boolean;
-	}
-}
diff --git a/src/typings/xterm-addon-web-links.d.ts b/src/typings/xterm-addon-web-links.d.ts
deleted file mode 100644
index da348f8c82..0000000000
--- a/src/typings/xterm-addon-web-links.d.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
- * Copyright (c) 2017 The xterm.js authors. All rights reserved.
- * @license MIT
- */
-
-// HACK: gulp-tsb doesn't play nice with importing from typings
-// import { Terminal, ITerminalAddon } from 'xterm';
-interface ILinkMatcherOptions {
-	/**
-	 * The index of the link from the regex.match(text) call. This defaults to 0
-	 * (for regular expressions without capture groups).
-	 */
-	matchIndex?: number;
-
-	/**
-	 * A callback that validates whether to create an individual link, pass
-	 * whether the link is valid to the callback.
-	 */
-	validationCallback?: (uri: string, callback: (isValid: boolean) => void) => void;
-
-	/**
-	 * A callback that fires when the mouse hovers over a link for a moment.
-	 */
-	tooltipCallback?: (event: MouseEvent, uri: string) => boolean | void;
-
-	/**
-	 * A callback that fires when the mouse leaves a link. Note that this can
-	 * happen even when tooltipCallback hasn't fired for the link yet.
-	 */
-	leaveCallback?: () => void;
-
-	/**
-	 * The priority of the link matcher, this defines the order in which the link
-	 * matcher is evaluated relative to others, from highest to lowest. The
-	 * default value is 0.
-	 */
-	priority?: number;
-
-	/**
-	 * A callback that fires when the mousedown and click events occur that
-	 * determines whether a link will be activated upon click. This enables
-	 * only activating a link when a certain modifier is held down, if not the
-	 * mouse event will continue propagation (eg. double click to select word).
-	 */
-	willLinkActivate?: (event: MouseEvent, uri: string) => boolean;
-}
-
-declare module 'xterm-addon-web-links' {
-	/**
-	 * An xterm.js addon that enables web links.
-	 */
-	export class WebLinksAddon {
-		/**
-		 * Creates a new web links addon.
-		 * @param handler The callback when the link is called.
-		 * @param options Options for the link matcher.
-		 */
-		constructor(handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions);
-
-		/**
-		 * Activates the addon
-		 * @param terminal The terminal the addon is being loaded in.
-		 */
-		public activate(terminal: any): void;
-
-		/**
-		 * Disposes the addon.
-		 */
-		public dispose(): void;
-	}
-}
diff --git a/src/typings/xterm.d.ts b/src/typings/xterm.d.ts
deleted file mode 100644
index e163c25672..0000000000
--- a/src/typings/xterm.d.ts
+++ /dev/null
@@ -1,1098 +0,0 @@
-/**
- * @license MIT
- *
- * This contains the type declarations for the xterm.js library. Note that
- * some interfaces differ between this file and the actual implementation in
- * src/, that's because this file declares the *public* API which is intended
- * to be stable and consumed by external programs.
- */
-
-/// 
-
-declare module 'xterm' {
-	/**
-	 * A string representing text font weight.
-	 */
-	export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900';
-
-	/**
-	 * A string representing log level.
-	 */
-	export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'off';
-
-	/**
-	 * A string representing a renderer type.
-	 */
-	export type RendererType = 'dom' | 'canvas';
-
-	/**
-	 * An object containing start up options for the terminal.
-	 */
-	export interface ITerminalOptions {
-		/**
-		 * Whether background should support non-opaque color. It must be set before
-		 * executing the `Terminal.open()` method and can't be changed later without
-		 * executing it again. Note that enabling this can negatively impact
-		 * performance.
-		 */
-		allowTransparency?: boolean;
-
-		/**
-		 * A data uri of the sound to use for the bell when `bellStyle = 'sound'`.
-		 */
-		bellSound?: string;
-
-		/**
-		 * The type of the bell notification the terminal will use.
-		 */
-		bellStyle?: 'none' /*| 'visual'*/ | 'sound' /*| 'both'*/;
-
-		/**
-		 * When enabled the cursor will be set to the beginning of the next line
-		 * with every new line. This equivalent to sending '\r\n' for each '\n'.
-		 * Normally the termios settings of the underlying PTY deals with the
-		 * translation of '\n' to '\r\n' and this setting should not be used. If you
-		 * deal with data from a non-PTY related source, this settings might be
-		 * useful.
-		 */
-		convertEol?: boolean;
-
-		/**
-		 * The number of columns in the terminal.
-		 */
-		cols?: number;
-
-		/**
-		 * Whether the cursor blinks.
-		 */
-		cursorBlink?: boolean;
-
-		/**
-		 * The style of the cursor.
-		 */
-		cursorStyle?: 'block' | 'underline' | 'bar';
-
-		/**
-		 * Whether input should be disabled.
-		 */
-		disableStdin?: boolean;
-
-		/**
-		 * Whether to draw bold text in bright colors. The default is true.
-		 */
-		drawBoldTextInBrightColors?: boolean;
-
-		/**
-		 * The modifier key hold to multiply scroll speed.
-		 */
-		fastScrollModifier?: 'alt' | 'ctrl' | 'shift' | undefined;
-
-		/**
-		 * The scroll speed multiplier used for fast scrolling.
-		 */
-		fastScrollSensitivity?: number;
-
-		/**
-		 * The font size used to render text.
-		 */
-		fontSize?: number;
-
-		/**
-		 * The font family used to render text.
-		 */
-		fontFamily?: string;
-
-		/**
-		 * The font weight used to render non-bold text.
-		 */
-		fontWeight?: FontWeight;
-
-		/**
-		 * The font weight used to render bold text.
-		 */
-		fontWeightBold?: FontWeight;
-
-		/**
-		 * The spacing in whole pixels between characters..
-		 */
-		letterSpacing?: number;
-
-		/**
-		 * The line height used to render text.
-		 */
-		lineHeight?: number;
-
-		/**
-		 * What log level to use, this will log for all levels below and including
-		 * what is set:
-		 *
-		 * 1. debug
-		 * 2. info (default)
-		 * 3. warn
-		 * 4. error
-		 * 5. off
-		 */
-		logLevel?: LogLevel;
-
-		/**
-		 * Whether to treat option as the meta key.
-		 */
-		macOptionIsMeta?: boolean;
-
-		/**
-		 * Whether holding a modifier key will force normal selection behavior,
-		 * regardless of whether the terminal is in mouse events mode. This will
-		 * also prevent mouse events from being emitted by the terminal. For
-		 * example, this allows you to use xterm.js' regular selection inside tmux
-		 * with mouse mode enabled.
-		 */
-		macOptionClickForcesSelection?: boolean;
-
-		/**
-		 * The type of renderer to use, this allows using the fallback DOM renderer
-		 * when canvas is too slow for the environment. The following features do
-		 * not work when the DOM renderer is used:
-		 *
-		 * - Letter spacing
-		 * - Cursor blink
-		 */
-		rendererType?: RendererType;
-
-		/**
-		 * Whether to select the word under the cursor on right click, this is
-		 * standard behavior in a lot of macOS applications.
-		 */
-		rightClickSelectsWord?: boolean;
-
-		/**
-		 * The number of rows in the terminal.
-		 */
-		rows?: number;
-
-		/**
-		 * Whether screen reader support is enabled. When on this will expose
-		 * supporting elements in the DOM to support NVDA on Windows and VoiceOver
-		 * on macOS.
-		 */
-		screenReaderMode?: boolean;
-
-		/**
-		 * The amount of scrollback in the terminal. Scrollback is the amount of
-		 * rows that are retained when lines are scrolled beyond the initial
-		 * viewport.
-		 */
-		scrollback?: number;
-
-		/**
-		 * The scrolling speed multiplier used for adjusting normal scrolling speed.
-		 */
-		scrollSensitivity?: number;
-
-		/**
-		 * The size of tab stops in the terminal.
-		 */
-		tabStopWidth?: number;
-
-		/**
-		 * The color theme of the terminal.
-		 */
-		theme?: ITheme;
-
-		/**
-		 * Whether "Windows mode" is enabled. Because Windows backends winpty and
-		 * conpty operate by doing line wrapping on their side, xterm.js does not
-		 * have access to wrapped lines. When Windows mode is enabled the following
-		 * changes will be in effect:
-		 *
-		 * - Reflow is disabled.
-		 * - Lines are assumed to be wrapped if the last character of the line is
-		 *   not whitespace.
-		 */
-		windowsMode?: boolean;
-
-		/**
-		 * A string containing all characters that are considered word separated by the
-		 * double click to select work logic.
-		*/
-		wordSeparator?: string;
-	}
-
-	/**
-	 * Contains colors to theme the terminal with.
-	 */
-	export interface ITheme {
-		/** The default foreground color */
-		foreground?: string;
-		/** The default background color */
-		background?: string;
-		/** The cursor color */
-		cursor?: string;
-		/** The accent color of the cursor (fg color for a block cursor) */
-		cursorAccent?: string;
-		/** The selection background color (can be transparent) */
-		selection?: string;
-		/** ANSI black (eg. `\x1b[30m`) */
-		black?: string;
-		/** ANSI red (eg. `\x1b[31m`) */
-		red?: string;
-		/** ANSI green (eg. `\x1b[32m`) */
-		green?: string;
-		/** ANSI yellow (eg. `\x1b[33m`) */
-		yellow?: string;
-		/** ANSI blue (eg. `\x1b[34m`) */
-		blue?: string;
-		/** ANSI magenta (eg. `\x1b[35m`) */
-		magenta?: string;
-		/** ANSI cyan (eg. `\x1b[36m`) */
-		cyan?: string;
-		/** ANSI white (eg. `\x1b[37m`) */
-		white?: string;
-		/** ANSI bright black (eg. `\x1b[1;30m`) */
-		brightBlack?: string;
-		/** ANSI bright red (eg. `\x1b[1;31m`) */
-		brightRed?: string;
-		/** ANSI bright green (eg. `\x1b[1;32m`) */
-		brightGreen?: string;
-		/** ANSI bright yellow (eg. `\x1b[1;33m`) */
-		brightYellow?: string;
-		/** ANSI bright blue (eg. `\x1b[1;34m`) */
-		brightBlue?: string;
-		/** ANSI bright magenta (eg. `\x1b[1;35m`) */
-		brightMagenta?: string;
-		/** ANSI bright cyan (eg. `\x1b[1;36m`) */
-		brightCyan?: string;
-		/** ANSI bright white (eg. `\x1b[1;37m`) */
-		brightWhite?: string;
-	}
-
-	/**
-	 * An object containing options for a link matcher.
-	 */
-	export interface ILinkMatcherOptions {
-		/**
-		 * The index of the link from the regex.match(text) call. This defaults to 0
-		 * (for regular expressions without capture groups).
-		 */
-		matchIndex?: number;
-
-		/**
-		 * A callback that validates whether to create an individual link, pass
-		 * whether the link is valid to the callback.
-		 */
-		validationCallback?: (uri: string, callback: (isValid: boolean) => void) => void;
-
-		/**
-		 * A callback that fires when the mouse hovers over a link for a moment.
-		 */
-		tooltipCallback?: (event: MouseEvent, uri: string, location: IViewportRange) => boolean | void;
-
-		/**
-		 * A callback that fires when the mouse leaves a link. Note that this can
-		 * happen even when tooltipCallback hasn't fired for the link yet.
-		 */
-		leaveCallback?: () => void;
-
-		/**
-		 * The priority of the link matcher, this defines the order in which the
-		 * link matcher is evaluated relative to others, from highest to lowest. The
-		 * default value is 0.
-		 */
-		priority?: number;
-
-		/**
-		 * A callback that fires when the mousedown and click events occur that
-		 * determines whether a link will be activated upon click. This enables
-		 * only activating a link when a certain modifier is held down, if not the
-		 * mouse event will continue propagation (eg. double click to select word).
-		 */
-		willLinkActivate?: (event: MouseEvent, uri: string) => boolean;
-	}
-
-	/**
-	 * An object that can be disposed via a dispose function.
-	 */
-	export interface IDisposable {
-		dispose(): void;
-	}
-
-	/**
-	 * An event that can be listened to.
-	 * @returns an `IDisposable` to stop listening.
-	 */
-	export interface IEvent {
-		(listener: (e: T) => any): IDisposable;
-	}
-
-	/**
-	 * Represents a specific line in the terminal that is tracked when scrollback
-	 * is trimmed and lines are added or removed.
-	 */
-	export interface IMarker extends IDisposable {
-		/**
-		 * A unique identifier for this marker.
-		 */
-		readonly id: number;
-
-		/**
-		 * Whether this marker is disposed.
-		 */
-		readonly isDisposed: boolean;
-
-		/**
-		 * The actual line index in the buffer at this point in time.
-		 */
-		readonly line: number;
-	}
-
-	/**
-	 * The set of localizable strings.
-	 */
-	export interface ILocalizableStrings {
-		/**
-		 * The aria label for the underlying input textarea for the terminal.
-		 */
-		promptLabel: string;
-
-		/**
-		 * Announcement for when line reading is suppressed due to too many lines
-		 * being printed to the terminal when `screenReaderMode` is enabled.
-		 */
-		tooMuchOutput: string;
-	}
-
-	/**
-	 * The class that represents an xterm.js terminal.
-	 */
-	export class Terminal implements IDisposable {
-		/**
-		 * The element containing the terminal.
-		 */
-		readonly element: HTMLElement | undefined;
-
-		/**
-		 * The textarea that accepts input for the terminal.
-		 */
-		readonly textarea: HTMLTextAreaElement | undefined;
-
-		/**
-		 * The number of rows in the terminal's viewport. Use
-		 * `ITerminalOptions.rows` to set this in the constructor and
-		 * `Terminal.resize` for when the terminal exists.
-		 */
-		readonly rows: number;
-
-		/**
-		 * The number of columns in the terminal's viewport. Use
-		 * `ITerminalOptions.cols` to set this in the constructor and
-		 * `Terminal.resize` for when the terminal exists.
-		 */
-		readonly cols: number;
-
-		/**
-		 * (EXPERIMENTAL) The terminal's current buffer, this might be either the
-		 * normal buffer or the alt buffer depending on what's running in the
-		 * terminal.
-		 */
-		readonly buffer: IBuffer;
-
-		/**
-		 * (EXPERIMENTAL) Get all markers registered against the buffer. If the alt
-		 * buffer is active this will always return [].
-		 */
-		readonly markers: ReadonlyArray;
-
-		/**
-		 * (EXPERIMENTAL) Get the parser interface to register
-		 * custom escape sequence handlers.
-		 */
-		readonly parser: IParser;
-
-		/**
-		 * Natural language strings that can be localized.
-		 */
-		static strings: ILocalizableStrings;
-
-		/**
-		 * Creates a new `Terminal` object.
-		 *
-		 * @param options An object containing a set of options.
-		 */
-		constructor(options?: ITerminalOptions);
-
-		/**
-		 * Adds an event listener for the cursor moves.
-		 * @returns an `IDisposable` to stop listening.
-		 */
-		onCursorMove: IEvent;
-
-		/**
-		 * Adds an event listener for when a data event fires. This happens for
-		 * example when the user types or pastes into the terminal. The event value
-		 * is whatever `string` results, in a typical setup, this should be passed
-		 * on to the backing pty.
-		 * @returns an `IDisposable` to stop listening.
-		 */
-		onData: IEvent;
-
-		/**
-		 * Adds an event listener for a key is pressed. The event value contains the
-		 * string that will be sent in the data event as well as the DOM event that
-		 * triggered it.
-		 * @returns an `IDisposable` to stop listening.
-		 */
-		onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>;
-
-		/**
-		 * Adds an event listener for when a line feed is added.
-		 * @returns an `IDisposable` to stop listening.
-		 */
-		onLineFeed: IEvent;
-
-		/**
-		 * Adds an event listener for when a scroll occurs. The  event value is the
-		 * new position of the viewport.
-		 * @returns an `IDisposable` to stop listening.
-		 */
-		onScroll: IEvent;
-
-		/**
-		 * Adds an event listener for when a selection change occurs.
-		 * @returns an `IDisposable` to stop listening.
-		 */
-		onSelectionChange: IEvent;
-
-		/**
-		 * Adds an event listener for when rows are rendered. The event value
-		 * contains the start row and end rows of the rendered area (ranges from `0`
-		 * to `Terminal.rows - 1`).
-		 * @returns an `IDisposable` to stop listening.
-		 */
-		onRender: IEvent<{ start: number, end: number }>;
-
-		/**
-		 * Adds an event listener for when the terminal is resized. The event value
-		 * contains the new size.
-		 * @returns an `IDisposable` to stop listening.
-		 */
-		onResize: IEvent<{ cols: number, rows: number }>;
-
-		/**
-		 * Adds an event listener for when an OSC 0 or OSC 2 title change occurs.
-		 * The event value is the new title.
-		 * @returns an `IDisposable` to stop listening.
-		 */
-		onTitleChange: IEvent;
-
-		/**
-		 * Unfocus the terminal.
-		 */
-		blur(): void;
-
-		/**
-		 * Focus the terminal.
-		 */
-		focus(): void;
-
-		/**
-		 * Resizes the terminal. It's best practice to debounce calls to resize,
-		 * this will help ensure that the pty can respond to the resize event
-		 * before another one occurs.
-		 * @param x The number of columns to resize to.
-		 * @param y The number of rows to resize to.
-		 */
-		resize(columns: number, rows: number): void;
-
-		/**
-		 * Opens the terminal within an element.
-		 * @param parent The element to create the terminal within. This element
-		 * must be visible (have dimensions) when `open` is called as several DOM-
-		 * based measurements need to be performed when this function is called.
-		 */
-		open(parent: HTMLElement): void;
-
-		/**
-		 * Attaches a custom key event handler which is run before keys are
-		 * processed, giving consumers of xterm.js ultimate control as to what keys
-		 * should be processed by the terminal and what keys should not.
-		 * @param customKeyEventHandler The custom KeyboardEvent handler to attach.
-		 * This is a function that takes a KeyboardEvent, allowing consumers to stop
-		 * propagation and/or prevent the default action. The function returns
-		 * whether the event should be processed by xterm.js.
-		 */
-		attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void;
-
-		/**
-		 * (EXPERIMENTAL) Registers a link matcher, allowing custom link patterns to
-		 * be matched and handled.
-		 * @param regex The regular expression to search for, specifically this
-		 * searches the textContent of the rows. You will want to use \s to match a
-		 * space ' ' character for example.
-		 * @param handler The callback when the link is called.
-		 * @param options Options for the link matcher.
-		 * @return The ID of the new matcher, this can be used to deregister.
-		 */
-		registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number;
-
-		/**
-		 * (EXPERIMENTAL) Deregisters a link matcher if it has been registered.
-		 * @param matcherId The link matcher's ID (returned after register)
-		 */
-		deregisterLinkMatcher(matcherId: number): void;
-
-		/**
-		 * (EXPERIMENTAL) Registers a character joiner, allowing custom sequences of
-		 * characters to be rendered as a single unit. This is useful in particular
-		 * for rendering ligatures and graphemes, among other things.
-		 *
-		 * Each registered character joiner is called with a string of text
-		 * representing a portion of a line in the terminal that can be rendered as
-		 * a single unit. The joiner must return a sorted array, where each entry is
-		 * itself an array of length two, containing the start (inclusive) and end
-		 * (exclusive) index of a substring of the input that should be rendered as
-		 * a single unit. When multiple joiners are provided, the results of each
-		 * are collected. If there are any overlapping substrings between them, they
-		 * are combined into one larger unit that is drawn together.
-		 *
-		 * All character joiners that are registered get called every time a line is
-		 * rendered in the terminal, so it is essential for the handler function to
-		 * run as quickly as possible to avoid slowdowns when rendering. Similarly,
-		 * joiners should strive to return the smallest possible substrings to
-		 * render together, since they aren't drawn as optimally as individual
-		 * characters.
-		 *
-		 * NOTE: character joiners are only used by the canvas renderer.
-		 *
-		 * @param handler The function that determines character joins. It is called
-		 * with a string of text that is eligible for joining and returns an array
-		 * where each entry is an array containing the start (inclusive) and end
-		 * (exclusive) indexes of ranges that should be rendered as a single unit.
-		 * @return The ID of the new joiner, this can be used to deregister
-		 */
-		registerCharacterJoiner(handler: (text: string) => [number, number][]): number;
-
-		/**
-		 * (EXPERIMENTAL) Deregisters the character joiner if one was registered.
-		 * NOTE: character joiners are only used by the canvas renderer.
-		 * @param joinerId The character joiner's ID (returned after register)
-		 */
-		deregisterCharacterJoiner(joinerId: number): void;
-
-		/**
-		 * (EXPERIMENTAL) Adds a marker to the normal buffer and returns it. If the
-		 * alt buffer is active, undefined is returned.
-		 * @param cursorYOffset The y position offset of the marker from the cursor.
-		 */
-		addMarker(cursorYOffset: number): IMarker;
-
-		/**
-		 * Gets whether the terminal has an active selection.
-		 */
-		hasSelection(): boolean;
-
-		/**
-		 * Gets the terminal's current selection, this is useful for implementing
-		 * copy behavior outside of xterm.js.
-		 */
-		getSelection(): string;
-
-		/**
-		 * Gets the selection position or undefined if there is no selection.
-		 */
-		getSelectionPosition(): ISelectionPosition | undefined;
-
-		/**
-		 * Clears the current terminal selection.
-		 */
-		clearSelection(): void;
-
-		/**
-		 * Selects text within the terminal.
-		 * @param column The column the selection starts at..
-		 * @param row The row the selection starts at.
-		 * @param length The length of the selection.
-		 */
-		select(column: number, row: number, length: number): void;
-
-		/**
-		 * Selects all text within the terminal.
-		 */
-		selectAll(): void;
-
-		/**
-		 * Selects text in the buffer between 2 lines.
-		 * @param start The 0-based line index to select from (inclusive).
-		 * @param end The 0-based line index to select to (inclusive).
-		 */
-		selectLines(start: number, end: number): void;
-
-		/*
-		 * Disposes of the terminal, detaching it from the DOM and removing any
-		 * active listeners.
-		 */
-		dispose(): void;
-
-		/**
-		 * Scroll the display of the terminal
-		 * @param amount The number of lines to scroll down (negative scroll up).
-		 */
-		scrollLines(amount: number): void;
-
-		/**
-		 * Scroll the display of the terminal by a number of pages.
-		 * @param pageCount The number of pages to scroll (negative scrolls up).
-		 */
-		scrollPages(pageCount: number): void;
-
-		/**
-		 * Scrolls the display of the terminal to the top.
-		 */
-		scrollToTop(): void;
-
-		/**
-		 * Scrolls the display of the terminal to the bottom.
-		 */
-		scrollToBottom(): void;
-
-		/**
-		 * Scrolls to a line within the buffer.
-		 * @param line The 0-based line index to scroll to.
-		 */
-		scrollToLine(line: number): void;
-
-		/**
-		 * Clear the entire buffer, making the prompt line the new first line.
-		 */
-		clear(): void;
-
-		/**
-		 * Write data to the terminal.
-		 * @param data The data to write to the terminal. This can either be raw
-		 * bytes given as Uint8Array from the pty or a string. Raw bytes will always
-		 * be treated as UTF-8 encoded, string data as UTF-16.
-		 * @param callback Optional callback that fires when the data was processed
-		 * by the parser.
-		 */
-		write(data: string | Uint8Array, callback?: () => void): void;
-
-		/**
-		 * Writes data to the terminal, followed by a break line character (\n).
-		 * @param data The data to write to the terminal. This can either be raw
-		 * bytes given as Uint8Array from the pty or a string. Raw bytes will always
-		 * be treated as UTF-8 encoded, string data as UTF-16.
-		 * @param callback Optional callback that fires when the data was processed
-		 * by the parser.
-		 */
-		writeln(data: string | Uint8Array, callback?: () => void): void;
-
-		/**
-		 * Write UTF8 data to the terminal.
-		 * @param data The data to write to the terminal.
-		 * @param callback Optional callback when data was processed.
-		 * @deprecated use `write` instead
-		 */
-		writeUtf8(data: Uint8Array, callback?: () => void): void;
-
-		/**
-		 * Writes text to the terminal, performing the necessary transformations for pasted text.
-		 * @param data The text to write to the terminal.
-		 */
-		paste(data: string): void;
-
-		/**
-		 * Retrieves an option's value from the terminal.
-		 * @param key The option key.
-		 */
-		getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'fontWeight' | 'fontWeightBold' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string;
-		/**
-		 * Retrieves an option's value from the terminal.
-		 * @param key The option key.
-		 */
-		getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell' | 'windowsMode'): boolean;
-		/**
-		 * Retrieves an option's value from the terminal.
-		 * @param key The option key.
-		 */
-		getOption(key: 'colors'): string[];
-		/**
-		 * Retrieves an option's value from the terminal.
-		 * @param key The option key.
-		 */
-		getOption(key: 'cols' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number;
-		/**
-		 * Retrieves an option's value from the terminal.
-		 * @param key The option key.
-		 */
-		getOption(key: 'handler'): (data: string) => void;
-		/**
-		 * Retrieves an option's value from the terminal.
-		 * @param key The option key.
-		 */
-		getOption(key: string): any;
-
-		/**
-		 * Sets an option on the terminal.
-		 * @param key The option key.
-		 * @param value The option value.
-		 */
-		setOption(key: 'fontFamily' | 'termName' | 'bellSound' | 'wordSeparator', value: string): void;
-		/**
-		* Sets an option on the terminal.
-		* @param key The option key.
-		* @param value The option value.
-		*/
-		setOption(key: 'fontWeight' | 'fontWeightBold', value: null | 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900'): void;
-		/**
-		* Sets an option on the terminal.
-		* @param key The option key.
-		* @param value The option value.
-		*/
-		setOption(key: 'logLevel', value: LogLevel): void;
-		/**
-		 * Sets an option on the terminal.
-		 * @param key The option key.
-		 * @param value The option value.
-		 */
-		setOption(key: 'bellStyle', value: null | 'none' | 'visual' | 'sound' | 'both'): void;
-		/**
-		 * Sets an option on the terminal.
-		 * @param key The option key.
-		 * @param value The option value.
-		 */
-		setOption(key: 'cursorStyle', value: null | 'block' | 'underline' | 'bar'): void;
-		/**
-		 * Sets an option on the terminal.
-		 * @param key The option key.
-		 * @param value The option value.
-		 */
-		setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'popOnBell' | 'rightClickSelectsWord' | 'screenKeys' | 'useFlowControl' | 'visualBell' | 'windowsMode', value: boolean): void;
-		/**
-		 * Sets an option on the terminal.
-		 * @param key The option key.
-		 * @param value The option value.
-		 */
-		setOption(key: 'colors', value: string[]): void;
-		/**
-		 * Sets an option on the terminal.
-		 * @param key The option key.
-		 * @param value The option value.
-		 */
-		setOption(key: 'fontSize' | 'letterSpacing' | 'lineHeight' | 'tabStopWidth' | 'scrollback', value: number): void;
-		/**
-		 * Sets an option on the terminal.
-		 * @param key The option key.
-		 * @param value The option value.
-		 */
-		setOption(key: 'handler', value: (data: string) => void): void;
-		/**
-		 * Sets an option on the terminal.
-		 * @param key The option key.
-		 * @param value The option value.
-		 */
-		setOption(key: 'theme', value: ITheme): void;
-		/**
-		 * Sets an option on the terminal.
-		 * @param key The option key.
-		 * @param value The option value.
-		 */
-		setOption(key: 'cols' | 'rows', value: number): void;
-		/**
-		 * Sets an option on the terminal.
-		 * @param key The option key.
-		 * @param value The option value.
-		 */
-		setOption(key: string, value: any): void;
-
-		/**
-		 * Tells the renderer to refresh terminal content between two rows
-		 * (inclusive) at the next opportunity.
-		 * @param start The row to start from (between 0 and this.rows - 1).
-		 * @param end The row to end at (between start and this.rows - 1).
-		 */
-		refresh(start: number, end: number): void;
-
-		/**
-		 * Perform a full reset (RIS, aka '\x1bc').
-		 */
-		reset(): void;
-
-		/**
-		 * Loads an addon into this instance of xterm.js.
-		 * @param addon The addon to load.
-		 */
-		loadAddon(addon: ITerminalAddon): void;
-	}
-
-	/**
-	 * An addon that can provide additional functionality to the terminal.
-	 */
-	export interface ITerminalAddon extends IDisposable {
-		/**
-		 * This is called when the addon is activated.
-		 */
-		activate(terminal: Terminal): void;
-	}
-
-	/**
-	 * An object representing a selection within the terminal.
-	 */
-	interface ISelectionPosition {
-		/**
-		 * The start column of the selection.
-		 */
-		startColumn: number;
-
-		/**
-		 * The start row of the selection.
-		 */
-		startRow: number;
-
-		/**
-		 * The end column of the selection.
-		 */
-		endColumn: number;
-
-		/**
-		 * The end row of the selection.
-		 */
-		endRow: number;
-	}
-
-	/**
-	 * An object representing a range within the viewport of the terminal.
-	 */
-	interface IViewportRange {
-		/**
-		 * The start cell of the range.
-		 */
-		start: IViewportCellPosition;
-
-		/**
-		 * The end cell of the range.
-		 */
-		end: IViewportCellPosition;
-	}
-
-	/**
-	 * An object representing a cell position within the viewport of the terminal.
-	 */
-	interface IViewportCellPosition {
-		/**
-		 * The column of the cell. Note that this is 1-based; the first column is column 1.
-		 */
-		col: number;
-
-		/**
-		 * The row of the cell. Note that this is 1-based; the first row is row 1.
-		 */
-		row: number;
-	}
-
-	/**
-	 * Represents a terminal buffer.
-	 */
-	interface IBuffer {
-		/**
-		 * The y position of the cursor. This ranges between `0` (when the
-		 * cursor is at baseY) and `Terminal.rows - 1` (when the cursor is on the
-		 * last row).
-		 */
-		readonly cursorY: number;
-
-		/**
-		 * The x position of the cursor. This ranges between `0` (left side) and
-		 * `Terminal.cols - 1` (right side).
-		 */
-		readonly cursorX: number;
-
-		/**
-		 * The line within the buffer where the top of the viewport is.
-		 */
-		readonly viewportY: number;
-
-		/**
-		 * The line within the buffer where the top of the bottom page is (when
-		 * fully scrolled down);
-		 */
-		readonly baseY: number;
-
-		/**
-		 * The amount of lines in the buffer.
-		 */
-		readonly length: number;
-
-		/**
-		 * Gets a line from the buffer, or undefined if the line index does not
-		 * exist.
-		 *
-		 * Note that the result of this function should be used immediately after
-		 * calling as when the terminal updates it could lead to unexpected
-		 * behavior.
-		 *
-		 * @param y The line index to get.
-		 */
-		getLine(y: number): IBufferLine | undefined;
-	}
-
-	/**
-	 * Represents a line in the terminal's buffer.
-	 */
-	interface IBufferLine {
-		/**
-		 * Whether the line is wrapped from the previous line.
-		 */
-		readonly isWrapped: boolean;
-
-		/**
-		 * Gets a cell from the line, or undefined if the line index does not exist.
-		 *
-		 * Note that the result of this function should be used immediately after
-		 * calling as when the terminal updates it could lead to unexpected
-		 * behavior.
-		 *
-		 * @param x The character index to get.
-		 */
-		getCell(x: number): IBufferCell | undefined;
-
-		/**
-		 * Gets the line as a string. Note that this is gets only the string for the
-		 * line, not taking isWrapped into account.
-		 *
-		 * @param trimRight Whether to trim any whitespace at the right of the line.
-		 * @param startColumn The column to start from (inclusive).
-		 * @param endColumn The column to end at (exclusive).
-		 */
-		translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string;
-	}
-
-	/**
-	 * Represents a single cell in the terminal's buffer.
-	 */
-	interface IBufferCell {
-		/**
-		 * The character within the cell.
-		 */
-		readonly char: string;
-
-		/**
-		 * The width of the character. Some examples:
-		 *
-		 * - This is `1` for most cells.
-		 * - This is `2` for wide character like CJK glyphs.
-		 * - This is `0` for cells immediately following cells with a width of `2`.
-		 */
-		readonly width: number;
-	}
-
-	/**
-	 * (EXPERIMENTAL) Data type to register a CSI, DCS or ESC callback in the parser
-	 * in the form:
-	 *    ESC I..I F
-	 *    CSI Prefix P..P I..I F
-	 *    DCS Prefix P..P I..I F data_bytes ST
-	 *
-	 * with these rules/restrictions:
-	 * - prefix can only be used with CSI and DCS
-	 * - only one leading prefix byte is recognized by the parser
-	 *   before any other parameter bytes (P..P)
-	 * - intermediate bytes are recognized up to 2
-	 *
-	 * For custom sequences make sure to read ECMA-48 and the resources at
-	 * vt100.net to not clash with existing sequences or reserved address space.
-	 * General recommendations:
-	 * - use private address space (see ECMA-48)
-	 * - use max one intermediate byte (technically not limited by the spec,
-	 *   in practice there are no sequences with more than one intermediate byte,
-	 *   thus parsers might get confused with more intermediates)
-	 * - test against other common emulators to check whether they escape/ignore
-	 *   the sequence correctly
-	 *
-	 * Notes: OSC command registration is handled differently (see addOscHandler)
-	 *        APC, PM or SOS is currently not supported.
-	 */
-	export interface IFunctionIdentifier {
-		/**
-		 * Optional prefix byte, must be in range \x3c .. \x3f.
-		 * Usable in CSI and DCS.
-		 */
-		prefix?: string;
-		/**
-		 * Optional intermediate bytes, must be in range \x20 .. \x2f.
-		 * Usable in CSI, DCS and ESC.
-		 */
-		intermediates?: string;
-		/**
-		 * Final byte, must be in range \x40 .. \x7e for CSI and DCS,
-		 * \x30 .. \x7e for ESC.
-		 */
-		final: string;
-	}
-
-	/**
-	 * (EXPERIMENTAL) Parser interface.
-	 */
-	export interface IParser {
-		/**
-		 * Adds a handler for CSI escape sequences.
-		 * @param id Specifies the function identifier under which the callback
-		 * gets registered, e.g. {final: 'm'} for SGR.
-		 * @param callback The function to handle the sequence. The callback is
-		 * called with the numerical params. If the sequence has subparams the
-		 * array will contain subarrays with their numercial values.
-		 * Return true if the sequence was handled; false if we should try
-		 * a previous handler (set by addCsiHandler or setCsiHandler).
-		 * The most recently-added handler is tried first.
-		 * @return An IDisposable you can call to remove this handler.
-		 */
-		addCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable;
-
-		/**
-		 * Adds a handler for DCS escape sequences.
-		 * @param id Specifies the function identifier under which the callback
-		 * gets registered, e.g. {intermediates: '$' final: 'q'} for DECRQSS.
-		 * @param callback The function to handle the sequence. Note that the
-		 * function will only be called once if the sequence finished sucessfully.
-		 * There is currently no way to intercept smaller data chunks, data chunks
-		 * will be stored up until the sequence is finished. Since DCS sequences
-		 * are not limited by the amount of data this might impose a problem for
-		 * big payloads. Currently xterm.js limits DCS payload to 10 MB
-		 * which should give enough room for most use cases.
-		 * The function gets the payload and numerical parameters as arguments.
-		 * Return true if the sequence was handled; false if we should try
-		 * a previous handler (set by addDcsHandler or setDcsHandler).
-		 * The most recently-added handler is tried first.
-		 * @return An IDisposable you can call to remove this handler.
-		 */
-		addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable;
-
-		/**
-		 * Adds a handler for ESC escape sequences.
-		 * @param id Specifies the function identifier under which the callback
-		 * gets registered, e.g. {intermediates: '%' final: 'G'} for
-		 * default charset selection.
-		 * @param callback The function to handle the sequence.
-		 * Return true if the sequence was handled; false if we should try
-		 * a previous handler (set by addEscHandler or setEscHandler).
-		 * The most recently-added handler is tried first.
-		 * @return An IDisposable you can call to remove this handler.
-		 */
-		addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable;
-
-		/**
-		 * Adds a handler for OSC escape sequences.
-		 * @param ident The number (first parameter) of the sequence.
-		 * @param callback The function to handle the sequence. Note that the
-		 * function will only be called once if the sequence finished sucessfully.
-		 * There is currently no way to intercept smaller data chunks, data chunks
-		 * will be stored up until the sequence is finished. Since OSC sequences
-		 * are not limited by the amount of data this might impose a problem for
-		 * big payloads. Currently xterm.js limits OSC payload to 10 MB
-		 * which should give enough room for most use cases.
-		 * The callback is called with OSC data string.
-		 * Return true if the sequence was handled; false if we should try
-		 * a previous handler (set by addOscHandler or setOscHandler).
-		 * The most recently-added handler is tried first.
-		 * @return An IDisposable you can call to remove this handler.
-		 */
-		addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable;
-	}
-}
diff --git a/src/typings/yauzl.d.ts b/src/typings/yauzl.d.ts
deleted file mode 100644
index 93163a2f21..0000000000
--- a/src/typings/yauzl.d.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module 'yauzl' {
-
-	import { EventEmitter } from 'events';
-	import { Readable } from 'stream';
-
-	export class Entry {
-		fileName: string;
-		extraFields: { id: number; data: Buffer; }[];
-		comment: string;
-		versionMadeBy: number;
-		versionNeededToExtract: number;
-		generalPurposeBitFlag: number;
-		compressionMethod: number;
-		lastModFileTime: number;
-		lastModFileDate: number;
-		crc32: number;
-		compressedSize: number;
-		uncompressedSize: number;
-		fileNameLength: number;
-		extraFieldLength: number;
-		fileCommentLength: number;
-		internalFileAttributes: number;
-		externalFileAttributes: number;
-		relativeOffsetOfLocalHeader: number;
-		getLastModDate(): Date;
-	}
-
-	export class ZipFile extends EventEmitter {
-		readEntry(): void;
-		openReadStream(entry: Entry, callback: (err?: Error, stream?: Readable) => void): void;
-		close(): void;
-		isOpen: boolean;
-		entryCount: number;
-		comment: string;
-	}
-
-	export interface IOptions {
-		autoClose?: boolean;
-		lazyEntries?: boolean;
-	}
-
-	export function open(path: string, callback: (err?: Error, zipfile?: ZipFile) => void): void;
-	export function open(path: string, options: IOptions | undefined, callback: (err?: Error, zipfile?: ZipFile) => void): void;
-}
\ No newline at end of file
diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts
index d683c99d55..e5600bb4f4 100644
--- a/src/vs/base/browser/browser.ts
+++ b/src/vs/base/browser/browser.ts
@@ -123,20 +123,3 @@ export const isWebkitWebView = (!isChrome && !isSafari && isWebKit);
 export const isIPad = (userAgent.indexOf('iPad') >= 0);
 export const isEdgeWebView = isEdge && (userAgent.indexOf('WebView/') >= 0);
 export const isStandalone = (window.matchMedia && window.matchMedia('(display-mode: standalone)').matches);
-
-export function hasClipboardSupport() {
-	if (isIE) {
-		return false;
-	}
-
-	if (isEdge) {
-		let index = userAgent.indexOf('Edge/');
-		let version = parseInt(userAgent.substring(index + 5, userAgent.indexOf('.', index)), 10);
-
-		if (!version || (version >= 12 && version <= 16)) {
-			return false;
-		}
-	}
-
-	return true;
-}
diff --git a/src/vs/base/browser/canIUse.ts b/src/vs/base/browser/canIUse.ts
new file mode 100644
index 0000000000..c44476cd5b
--- /dev/null
+++ b/src/vs/base/browser/canIUse.ts
@@ -0,0 +1,60 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as browser from 'vs/base/browser/browser';
+import * as platform from 'vs/base/common/platform';
+
+export const enum KeyboardSupport {
+	Always,
+	FullScreen,
+	None
+}
+
+/**
+ * Browser feature we can support in current platform, browser and environment.
+ */
+export const BrowserFeatures = {
+	clipboard: {
+		writeText: (
+			platform.isNative
+			|| (document.queryCommandSupported && document.queryCommandSupported('copy'))
+			|| !!(navigator && navigator.clipboard && navigator.clipboard.writeText)
+		),
+		readText: (
+			platform.isNative
+			|| !!(navigator && navigator.clipboard && navigator.clipboard.readText)
+		),
+		richText: (() => {
+			if (browser.isIE) {
+				return false;
+			}
+
+			if (browser.isEdge) {
+				let index = navigator.userAgent.indexOf('Edge/');
+				let version = parseInt(navigator.userAgent.substring(index + 5, navigator.userAgent.indexOf('.', index)), 10);
+
+				if (!version || (version >= 12 && version <= 16)) {
+					return false;
+				}
+			}
+
+			return true;
+		})()
+	},
+	keyboard: (() => {
+		if (platform.isNative || browser.isStandalone) {
+			return KeyboardSupport.Always;
+		}
+
+		if ((navigator).keyboard || browser.isSafari) {
+			return KeyboardSupport.FullScreen;
+		}
+
+		return KeyboardSupport.None;
+	})(),
+
+	touch: 'ontouchstart' in window || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0,
+	pointerEvents: window.PointerEvent && ('ontouchstart' in window || window.navigator.maxTouchPoints > 0 || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0)
+};
diff --git a/src/vs/base/browser/contextmenu.ts b/src/vs/base/browser/contextmenu.ts
index 184b9c6f1f..c52d1e8129 100644
--- a/src/vs/base/browser/contextmenu.ts
+++ b/src/vs/base/browser/contextmenu.ts
@@ -10,10 +10,10 @@ import { SubmenuAction } from 'vs/base/browser/ui/menu/menu';
 import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
 
 export interface IContextMenuEvent {
-	shiftKey?: boolean;
-	ctrlKey?: boolean;
-	altKey?: boolean;
-	metaKey?: boolean;
+	readonly shiftKey?: boolean;
+	readonly ctrlKey?: boolean;
+	readonly altKey?: boolean;
+	readonly metaKey?: boolean;
 }
 
 export class ContextSubMenu extends SubmenuAction {
diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts
index e322f00ad8..7a886f900a 100644
--- a/src/vs/base/browser/dom.ts
+++ b/src/vs/base/browser/dom.ts
@@ -16,6 +16,7 @@ import * as platform from 'vs/base/common/platform';
 import { coalesce } from 'vs/base/common/arrays';
 import { URI } from 'vs/base/common/uri';
 import { Schemas, RemoteAuthorities } from 'vs/base/common/network';
+import { BrowserFeatures } from 'vs/base/browser/canIUse';
 
 export function clearNode(node: HTMLElement): void {
 	while (node.firstChild) {
@@ -266,6 +267,23 @@ export let addStandardDisposableListener: IAddStandardDisposableListenerSignatur
 	return addDisposableListener(node, type, wrapHandler, useCapture);
 };
 
+export let addStandardDisposableGenericMouseDownListner = function addStandardDisposableListener(node: HTMLElement, handler: (event: any) => void, useCapture?: boolean): IDisposable {
+	let wrapHandler = _wrapAsStandardMouseEvent(handler);
+
+	return addDisposableGenericMouseDownListner(node, wrapHandler, useCapture);
+};
+
+export function addDisposableGenericMouseDownListner(node: EventTarget, handler: (event: any) => void, useCapture?: boolean): IDisposable {
+	return addDisposableListener(node, platform.isIOS && BrowserFeatures.pointerEvents ? EventType.POINTER_DOWN : EventType.MOUSE_DOWN, handler, useCapture);
+}
+
+export function addDisposableGenericMouseMoveListner(node: EventTarget, handler: (event: any) => void, useCapture?: boolean): IDisposable {
+	return addDisposableListener(node, platform.isIOS && BrowserFeatures.pointerEvents ? EventType.POINTER_MOVE : EventType.MOUSE_MOVE, handler, useCapture);
+}
+
+export function addDisposableGenericMouseUpListner(node: EventTarget, handler: (event: any) => void, useCapture?: boolean): IDisposable {
+	return addDisposableListener(node, platform.isIOS && BrowserFeatures.pointerEvents ? EventType.POINTER_UP : EventType.MOUSE_UP, handler, useCapture);
+}
 export function addDisposableNonBubblingMouseOutListener(node: Element, handler: (event: MouseEvent) => void): IDisposable {
 	return addDisposableListener(node, 'mouseout', (e: MouseEvent) => {
 		// Mouse out bubbles, so this is an attempt to ignore faux mouse outs coming from children elements
@@ -281,6 +299,21 @@ export function addDisposableNonBubblingMouseOutListener(node: Element, handler:
 	});
 }
 
+export function addDisposableNonBubblingPointerOutListener(node: Element, handler: (event: MouseEvent) => void): IDisposable {
+	return addDisposableListener(node, 'pointerout', (e: MouseEvent) => {
+		// Mouse out bubbles, so this is an attempt to ignore faux mouse outs coming from children elements
+		let toElement: Node | null = (e.relatedTarget || e.target);
+		while (toElement && toElement !== node) {
+			toElement = toElement.parentNode;
+		}
+		if (toElement === node) {
+			return;
+		}
+
+		handler(e);
+	});
+}
+
 interface IRequestAnimationFrame {
 	(callback: (time: number) => void): number;
 }
@@ -428,7 +461,7 @@ export interface DOMEvent {
 }
 
 const MINIMUM_TIME_MS = 16;
-const DEFAULT_EVENT_MERGER: IEventMerger = function (lastEvent: DOMEvent, currentEvent: DOMEvent) {
+const DEFAULT_EVENT_MERGER: IEventMerger = function (lastEvent: DOMEvent | null, currentEvent: DOMEvent) {
 	return currentEvent;
 };
 
@@ -477,6 +510,20 @@ export function getClientArea(element: HTMLElement): Dimension {
 		return new Dimension(element.clientWidth, element.clientHeight);
 	}
 
+	// If visual view port exits and it's on mobile, it should be used instead of window innerWidth / innerHeight, or document.body.clientWidth / document.body.clientHeight
+	if (platform.isIOS && (window).visualViewport) {
+		const width = (window).visualViewport.width;
+		const height = (window).visualViewport.height - (
+			browser.isStandalone
+				// in PWA mode, the visual viewport always includes the safe-area-inset-bottom (which is for the home indicator)
+				// even when you are using the onscreen monitor, the visual viewport will include the area between system statusbar and the onscreen keyboard
+				// plus the area between onscreen keyboard and the bottom bezel, which is 20px on iOS.
+				? (20 + 4) // + 4px for body margin
+				: 0
+		);
+		return new Dimension(width, height);
+	}
+
 	// Try innerWidth / innerHeight
 	if (window.innerWidth && window.innerHeight) {
 		return new Dimension(window.innerWidth, window.innerHeight);
@@ -852,6 +899,9 @@ export const EventType = {
 	MOUSE_OUT: 'mouseout',
 	MOUSE_ENTER: 'mouseenter',
 	MOUSE_LEAVE: 'mouseleave',
+	POINTER_UP: 'pointerup',
+	POINTER_DOWN: 'pointerdown',
+	POINTER_MOVE: 'pointermove',
 	CONTEXT_MENU: 'contextmenu',
 	WHEEL: 'wheel',
 	// Keyboard
@@ -1029,6 +1079,11 @@ function _$(namespace: Namespace, description: string, attrs?
 
 	Object.keys(attrs).forEach(name => {
 		const value = attrs![name];
+
+		if (typeof value === 'undefined') {
+			return;
+		}
+
 		if (/^on\w+$/.test(name)) {
 			(result)[name] = value;
 		} else if (name === 'selected') {
@@ -1212,3 +1267,18 @@ export function asCSSUrl(uri: URI): string {
 	}
 	return `url('${asDomUri(uri).toString(true).replace(/'/g, '%27')}')`;
 }
+
+export function triggerDownload(uri: URI, name: string): void {
+	// In order to download from the browser, the only way seems
+	// to be creating a  element with download attribute that
+	// points to the file to download.
+	// See also https://developers.google.com/web/updates/2011/08/Downloading-resources-in-HTML5-a-download
+	const anchor = document.createElement('a');
+	document.body.appendChild(anchor);
+	anchor.download = name;
+	anchor.href = uri.toString(true);
+	anchor.click();
+
+	// Ensure to remove the element from DOM eventually
+	setTimeout(() => document.body.removeChild(anchor));
+}
diff --git a/src/vs/base/browser/fastDomNode.ts b/src/vs/base/browser/fastDomNode.ts
index 5e12e5b7c2..202be357f3 100644
--- a/src/vs/base/browser/fastDomNode.ts
+++ b/src/vs/base/browser/fastDomNode.ts
@@ -26,6 +26,7 @@ export class FastDomNode {
 	private _position: string;
 	private _visibility: string;
 	private _layerHint: boolean;
+	private _contain: 'none' | 'strict' | 'content' | 'size' | 'layout' | 'style' | 'paint';
 
 	constructor(domNode: T) {
 		this.domNode = domNode;
@@ -47,6 +48,7 @@ export class FastDomNode {
 		this._position = '';
 		this._visibility = '';
 		this._layerHint = false;
+		this._contain = 'none';
 	}
 
 	public setMaxWidth(maxWidth: number): void {
@@ -203,7 +205,15 @@ export class FastDomNode {
 			return;
 		}
 		this._layerHint = layerHint;
-		(this.domNode.style).willChange = this._layerHint ? 'transform' : 'auto';
+		this.domNode.style.transform = this._layerHint ? 'translate3d(0px, 0px, 0px)' : '';
+	}
+
+	public setContain(contain: 'none' | 'strict' | 'content' | 'size' | 'layout' | 'style' | 'paint'): void {
+		if (this._contain === contain) {
+			return;
+		}
+		this._contain = contain;
+		(this.domNode.style).contain = this._contain;
 	}
 
 	public setAttribute(name: string, value: string): void {
diff --git a/src/vs/base/browser/globalMouseMoveMonitor.ts b/src/vs/base/browser/globalMouseMoveMonitor.ts
index b29d574cfd..98a5274b19 100644
--- a/src/vs/base/browser/globalMouseMoveMonitor.ts
+++ b/src/vs/base/browser/globalMouseMoveMonitor.ts
@@ -4,9 +4,11 @@
  *--------------------------------------------------------------------------------------------*/
 
 import * as dom from 'vs/base/browser/dom';
+import * as platform from 'vs/base/common/platform';
 import { IframeUtils } from 'vs/base/browser/iframe';
 import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
 import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
+import { BrowserFeatures } from 'vs/base/browser/canIUse';
 
 export interface IStandardMouseMoveEventData {
 	leftButton: boolean;
@@ -15,7 +17,7 @@ export interface IStandardMouseMoveEventData {
 }
 
 export interface IEventMerger {
-	(lastEvent: R, currentEvent: MouseEvent): R;
+	(lastEvent: R | null, currentEvent: MouseEvent): R;
 }
 
 export interface IMouseMoveCallback {
@@ -26,7 +28,7 @@ export interface IOnStopCallback {
 	(): void;
 }
 
-export function standardMouseMoveMerger(lastEvent: IStandardMouseMoveEventData, currentEvent: MouseEvent): IStandardMouseMoveEventData {
+export function standardMouseMoveMerger(lastEvent: IStandardMouseMoveEventData | null, currentEvent: MouseEvent): IStandardMouseMoveEventData {
 	let ev = new StandardMouseEvent(currentEvent);
 	ev.preventDefault();
 	return {
@@ -38,10 +40,10 @@ export function standardMouseMoveMerger(lastEvent: IStandardMouseMoveEventData,
 
 export class GlobalMouseMoveMonitor implements IDisposable {
 
-	private readonly hooks = new DisposableStore();
-	private mouseMoveEventMerger: IEventMerger | null = null;
-	private mouseMoveCallback: IMouseMoveCallback | null = null;
-	private onStopCallback: IOnStopCallback | null = null;
+	protected readonly hooks = new DisposableStore();
+	protected mouseMoveEventMerger: IEventMerger | null = null;
+	protected mouseMoveCallback: IMouseMoveCallback | null = null;
+	protected onStopCallback: IOnStopCallback | null = null;
 
 	public dispose(): void {
 		this.stopMonitoring(false);
@@ -84,12 +86,14 @@ export class GlobalMouseMoveMonitor implements IDisposable {
 		this.onStopCallback = onStopCallback;
 
 		let windowChain = IframeUtils.getSameOriginWindowChain();
+		const mouseMove = platform.isIOS && BrowserFeatures.pointerEvents ? 'pointermove' : 'mousemove';
+		const mouseUp = platform.isIOS && BrowserFeatures.pointerEvents ? 'pointerup' : 'mouseup';
 		for (const element of windowChain) {
-			this.hooks.add(dom.addDisposableThrottledListener(element.window.document, 'mousemove',
+			this.hooks.add(dom.addDisposableThrottledListener(element.window.document, mouseMove,
 				(data: R) => this.mouseMoveCallback!(data),
-				(lastEvent: R, currentEvent) => this.mouseMoveEventMerger!(lastEvent, currentEvent as MouseEvent)
+				(lastEvent: R | null, currentEvent) => this.mouseMoveEventMerger!(lastEvent, currentEvent as MouseEvent)
 			));
-			this.hooks.add(dom.addDisposableListener(element.window.document, 'mouseup', (e: MouseEvent) => this.stopMonitoring(true)));
+			this.hooks.add(dom.addDisposableListener(element.window.document, mouseUp, (e: MouseEvent) => this.stopMonitoring(true)));
 		}
 
 		if (IframeUtils.hasDifferentOriginAncestor()) {
diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts
index cc69129822..b75bdaae7a 100644
--- a/src/vs/base/browser/markdownRenderer.ts
+++ b/src/vs/base/browser/markdownRenderer.ts
@@ -15,6 +15,7 @@ import { cloneAndChange } from 'vs/base/common/objects';
 import { escape } from 'vs/base/common/strings';
 import { URI } from 'vs/base/common/uri';
 import { Schemas } from 'vs/base/common/network';
+import { renderCodicons } from 'vs/base/browser/ui/codiconLabel/codiconLabel';
 
 export interface MarkdownRenderOptions extends FormattedTextRenderOptions {
 	codeBlockRenderer?: (modeId: string, value: string) => Promise;
@@ -50,28 +51,32 @@ 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;
+			return href; // no uri exists
 		}
 		let uri = URI.revive(data);
+		if (URI.parse(href).toString() === uri.toString()) {
+			return href; // no tranformation performed
+		}
 		if (isDomUri) {
 			uri = DOM.asDomUri(uri);
 		}
 		if (uri.query) {
 			uri = uri.with({ query: _uriMassage(uri.query) });
 		}
-		if (data) {
-			href = uri.toString(true);
-		}
-		return href;
+		return uri.toString(true);
 	};
 
 	// signal to code-block render that the
 	// element has been created
 	let signalInnerHTML: () => void;
-	const withInnerHTML = new Promise(c => signalInnerHTML = c);
+	const withInnerHTML = new Promise(c => signalInnerHTML = c);
 
 	const renderer = new marked.Renderer();
 	renderer.image = (href: string, title: string, text: string) => {
+		if (href && href.indexOf('vscode-icon://codicon/') === 0) {
+			return renderCodicons(`$(${URI.parse(href).path.substr(1)})`);
+		}
+
 		let dimensions: string[] = [];
 		let attributes: string[] = [];
 		if (href) {
@@ -187,7 +192,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
 		renderer
 	};
 
-	const allowedSchemes = [Schemas.http, Schemas.https, Schemas.mailto, Schemas.data, Schemas.file, Schemas.vscodeRemote];
+	const allowedSchemes = [Schemas.http, Schemas.https, Schemas.mailto, Schemas.data, Schemas.file, Schemas.vscodeRemote, Schemas.vscodeRemoteResource];
 	if (markdown.isTrusted) {
 		allowedSchemes.push(Schemas.command);
 	}
@@ -199,7 +204,8 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
 			'a': ['href', 'name', 'target', 'data-href'],
 			'iframe': ['allowfullscreen', 'frameborder', 'src'],
 			'img': ['src', 'title', 'alt', 'width', 'height'],
-			'div': ['class', 'data-code']
+			'div': ['class', 'data-code'],
+			'span': ['class']
 		}
 	});
 
diff --git a/src/vs/base/browser/touch.ts b/src/vs/base/browser/touch.ts
index 4ab9f1ad08..fee95cb4c7 100644
--- a/src/vs/base/browser/touch.ts
+++ b/src/vs/base/browser/touch.ts
@@ -33,6 +33,7 @@ export interface GestureEvent extends MouseEvent {
 	translationY: number;
 	pageX: number;
 	pageY: number;
+	tapCount: number;
 }
 
 interface Touch {
@@ -71,16 +72,24 @@ export class Gesture extends Disposable {
 
 	private dispatched = false;
 	private targets: HTMLElement[];
+	private ignoreTargets: HTMLElement[];
 	private handle: IDisposable | null;
 
 	private activeTouches: { [id: number]: TouchData; };
 
+	private _lastSetTapCountTime: number;
+
+	private static readonly CLEAR_TAP_COUNT_TIME = 400; // ms
+
+
 	private constructor() {
 		super();
 
 		this.activeTouches = {};
 		this.handle = null;
 		this.targets = [];
+		this.ignoreTargets = [];
+		this._lastSetTapCountTime = 0;
 		this._register(DomUtils.addDisposableListener(document, 'touchstart', (e: TouchEvent) => this.onTouchStart(e)));
 		this._register(DomUtils.addDisposableListener(document, 'touchend', (e: TouchEvent) => this.onTouchEnd(e)));
 		this._register(DomUtils.addDisposableListener(document, 'touchmove', (e: TouchEvent) => this.onTouchMove(e)));
@@ -103,6 +112,23 @@ export class Gesture extends Disposable {
 		};
 	}
 
+	public static ignoreTarget(element: HTMLElement): IDisposable {
+		if (!Gesture.isTouchDevice()) {
+			return Disposable.None;
+		}
+		if (!Gesture.INSTANCE) {
+			Gesture.INSTANCE = new Gesture();
+		}
+
+		Gesture.INSTANCE.ignoreTargets.push(element);
+
+		return {
+			dispose: () => {
+				Gesture.INSTANCE.ignoreTargets = Gesture.INSTANCE.ignoreTargets.filter(t => t !== element);
+			}
+		};
+	}
+
 	@memoize
 	private static isTouchDevice(): boolean {
 		return 'ontouchstart' in window as any || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0;
@@ -224,10 +250,33 @@ export class Gesture extends Disposable {
 		let event = (document.createEvent('CustomEvent'));
 		event.initEvent(type, false, true);
 		event.initialTarget = initialTarget;
+		event.tapCount = 0;
 		return event;
 	}
 
 	private dispatchEvent(event: GestureEvent): void {
+		if (event.type === EventType.Tap) {
+			const currentTime = (new Date()).getTime();
+			let setTapCount = 0;
+			if (currentTime - this._lastSetTapCountTime > Gesture.CLEAR_TAP_COUNT_TIME) {
+				setTapCount = 1;
+			} else {
+				setTapCount = 2;
+			}
+
+			this._lastSetTapCountTime = currentTime;
+			event.tapCount = setTapCount;
+		} else if (event.type === EventType.Change || event.type === EventType.Contextmenu) {
+			// tap is canceled by scrolling or context menu
+			this._lastSetTapCountTime = 0;
+		}
+
+		for (let i = 0; i < this.ignoreTargets.length; i++) {
+			if (event.initialTarget instanceof Node && this.ignoreTargets[i].contains(event.initialTarget)) {
+				return;
+			}
+		}
+
 		this.targets.forEach(target => {
 			if (event.initialTarget instanceof Node && target.contains(event.initialTarget)) {
 				target.dispatchEvent(event);
diff --git a/src/vs/base/browser/ui/actionbar/actionbar.css b/src/vs/base/browser/ui/actionbar/actionbar.css
index 1d162aa21f..d93c4f6f06 100644
--- a/src/vs/base/browser/ui/actionbar/actionbar.css
+++ b/src/vs/base/browser/ui/actionbar/actionbar.css
@@ -90,4 +90,5 @@
 	display: flex;
 	align-items: center;
 	justify-content: center;
+	margin-right: 10px;
 }
diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts
index 7c29d8a942..8dc4c5ed2f 100644
--- a/src/vs/base/browser/ui/actionbar/actionbar.ts
+++ b/src/vs/base/browser/ui/actionbar/actionbar.ts
@@ -16,6 +16,8 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
 import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
 import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
 import { Event, Emitter } from 'vs/base/common/event';
+import { DataTransfers } from 'vs/base/browser/dnd';
+import { isFirefox } from 'vs/base/browser/browser';
 
 export interface IActionViewItem extends IDisposable {
 	actionRunner: IActionRunner;
@@ -113,6 +115,11 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem {
 		const enableDragging = this.options && this.options.draggable;
 		if (enableDragging) {
 			container.draggable = true;
+
+			if (isFirefox) {
+				// Firefox: requires to set a text data transfer to get going
+				this._register(DOM.addDisposableListener(container, DOM.EventType.DRAG_START, e => e.dataTransfer?.setData(DataTransfers.TEXT, this._action.label)));
+			}
 		}
 
 		this._register(DOM.addDisposableListener(element, EventType.Tap, e => this.onClick(e)));
diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css
index e4b41ab482..4ccd6ad7b2 100644
--- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css
+++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css
@@ -5,6 +5,8 @@
 
 .monaco-breadcrumbs {
 	user-select: none;
+	-webkit-user-select: none;
+	-ms-user-select: none;
 	display: flex;
 	flex-direction: row;
 	flex-wrap: nowrap;
@@ -23,6 +25,10 @@
 	outline: none;
 }
 
+.monaco-breadcrumbs .monaco-breadcrumb-item .codicon-chevron-right {
+	color: inherit;
+}
+
 .monaco-breadcrumbs .monaco-breadcrumb-item:first-of-type::before {
 	content: ' ';
 }
diff --git a/src/vs/base/browser/ui/button/button.css b/src/vs/base/browser/ui/button/button.css
index 09f83b2e70..104257ed9c 100644
--- a/src/vs/base/browser/ui/button/button.css
+++ b/src/vs/base/browser/ui/button/button.css
@@ -4,7 +4,6 @@
  *--------------------------------------------------------------------------------------------*/
 
 .monaco-text-button {
-	-moz-box-sizing: border-box;
 	box-sizing: border-box;
 	display: inline-block;
 	width: 100%;
@@ -21,4 +20,4 @@
 .monaco-button.disabled {
 	opacity: 0.4;
 	cursor: default;
-}
\ No newline at end of file
+}
diff --git a/src/vs/base/browser/ui/centered/centeredViewLayout.ts b/src/vs/base/browser/ui/centered/centeredViewLayout.ts
index a6affaab3e..64eabc1900 100644
--- a/src/vs/base/browser/ui/centered/centeredViewLayout.ts
+++ b/src/vs/base/browser/ui/centered/centeredViewLayout.ts
@@ -42,7 +42,7 @@ function toSplitViewView(view: IView, getHeight: () => number): ISplitViewView {
 		get maximumSize() { return view.maximumWidth; },
 		get minimumSize() { return view.minimumWidth; },
 		onDidChange: Event.map(view.onDidChange, e => e && e.width),
-		layout: size => view.layout(size, getHeight(), Orientation.HORIZONTAL)
+		layout: (size, offset) => view.layout(size, getHeight(), 0, offset)
 	};
 }
 
@@ -81,7 +81,7 @@ export class CenteredViewLayout implements IDisposable {
 				this.resizeMargins();
 			}
 		} else {
-			this.view.layout(width, height, Orientation.HORIZONTAL);
+			this.view.layout(width, height, 0, 0);
 		}
 		this.didLayout = true;
 	}
diff --git a/src/vs/base/browser/ui/checkbox/checkbox.css b/src/vs/base/browser/ui/checkbox/checkbox.css
index 9e592e1e20..39b792326d 100644
--- a/src/vs/base/browser/ui/checkbox/checkbox.css
+++ b/src/vs/base/browser/ui/checkbox/checkbox.css
@@ -13,19 +13,10 @@
 	height: 20px;
 	border: 1px solid transparent;
 	padding: 1px;
-
-	-webkit-box-sizing:	border-box;
-	-o-box-sizing:		border-box;
-	-moz-box-sizing:	border-box;
-	-ms-box-sizing:		border-box;
-	box-sizing:			border-box;
-
-	-webkit-user-select: none;
-	-khtml-user-select: none;
-	-moz-user-select: none;
-	-o-user-select: none;
-	-ms-user-select: none;
+	box-sizing:	border-box;
 	user-select: none;
+	-webkit-user-select: none;
+	-ms-user-select: none;
 }
 
 .monaco-custom-checkbox:hover,
diff --git a/src/vs/base/browser/ui/checkbox/checkbox.ts b/src/vs/base/browser/ui/checkbox/checkbox.ts
index e74d09c7e3..f91c4fe1cb 100644
--- a/src/vs/base/browser/ui/checkbox/checkbox.ts
+++ b/src/vs/base/browser/ui/checkbox/checkbox.ts
@@ -113,6 +113,8 @@ export class Checkbox extends Widget {
 			ev.preventDefault();
 		});
 
+		this.ignoreGesture(this.domNode);
+
 		this.onkeydown(this.domNode, (keyboardEvent) => {
 			if (keyboardEvent.keyCode === KeyCode.Space || keyboardEvent.keyCode === KeyCode.Enter) {
 				this.checked = !this._checked;
diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css
index 494225e647..0e31a773e9 100644
--- a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css
+++ b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css
@@ -5,7 +5,7 @@
 
 @font-face {
 	font-family: "codicon";
-	src: url("./codicon.ttf?3a05fcfc657285cdb4cd3eba790b7462") format("truetype");
+	src: url("./codicon.ttf?c4e66586cd3ad4acc55fc456c0760dec") format("truetype");
 }
 
 .codicon[class*='codicon-'] {
@@ -16,10 +16,9 @@
 	text-align: center;
 	-webkit-font-smoothing: antialiased;
 	-moz-osx-font-smoothing: grayscale;
-	-webkit-user-select: none;
-	-moz-user-select: none;
-	-ms-user-select: none;
 	user-select: none;
+	-webkit-user-select: none;
+	-ms-user-select: none;
 }
 
 
@@ -70,6 +69,10 @@
 .codicon-eye-watch:before { content: "\ea70" }
 .codicon-circle-filled:before { content: "\ea71" }
 .codicon-primitive-dot:before { content: "\ea71" }
+.codicon-close-dirty:before { content: "\ea71" }
+.codicon-debug-breakpoint:before { content: "\ea71" }
+.codicon-debug-breakpoint-disabled:before { content: "\ea71" }
+.codicon-debug-hint:before { content: "\ea71" }
 .codicon-primitive-square:before { content: "\ea72" }
 .codicon-edit:before { content: "\ea73" }
 .codicon-pencil:before { content: "\ea73" }
@@ -93,6 +96,7 @@
 .codicon-file:before { content: "\ea7b" }
 .codicon-file-text:before { content: "\ea7b" }
 .codicon-more:before { content: "\ea7c" }
+.codicon-ellipsis:before { content: "\ea7c" }
 .codicon-kebab-horizontal:before { content: "\ea7c" }
 .codicon-mail-reply:before { content: "\ea7d" }
 .codicon-reply:before { content: "\ea7d" }
@@ -139,7 +143,6 @@
 .codicon-symbol-parameter:before { content: "\ea92" }
 .codicon-symbol-type-parameter:before { content: "\ea92" }
 .codicon-symbol-key:before { content: "\ea93" }
-.codicon-symbol-string:before { content: "\ea93" }
 .codicon-symbol-text:before { content: "\ea93" }
 .codicon-symbol-reference:before { content: "\ea94" }
 .codicon-go-to-file:before { content: "\ea94" }
@@ -147,237 +150,259 @@
 .codicon-symbol-value:before { content: "\ea95" }
 .codicon-symbol-ruler:before { content: "\ea96" }
 .codicon-symbol-unit:before { content: "\ea96" }
-.codicon-activate-breakpoints:before { content: "\f101" }
-.codicon-archive:before { content: "\f102" }
-.codicon-arrow-both:before { content: "\f103" }
-.codicon-arrow-down:before { content: "\f104" }
-.codicon-arrow-left:before { content: "\f105" }
-.codicon-arrow-right:before { content: "\f106" }
-.codicon-arrow-small-down:before { content: "\f107" }
-.codicon-arrow-small-left:before { content: "\f108" }
-.codicon-arrow-small-right:before { content: "\f109" }
-.codicon-arrow-small-up:before { content: "\f10a" }
-.codicon-arrow-up:before { content: "\f10b" }
-.codicon-bell:before { content: "\f10c" }
-.codicon-bold:before { content: "\f10d" }
-.codicon-book:before { content: "\f10e" }
-.codicon-bookmark:before { content: "\f10f" }
-.codicon-breakpoint-conditional-unverified:before { content: "\f110" }
-.codicon-breakpoint-conditional:before { content: "\f111" }
-.codicon-breakpoint-data-unverified:before { content: "\f112" }
-.codicon-breakpoint-data:before { content: "\f113" }
-.codicon-breakpoint-log-unverified:before { content: "\f114" }
-.codicon-breakpoint-log:before { content: "\f115" }
-.codicon-briefcase:before { content: "\f116" }
-.codicon-broadcast:before { content: "\f117" }
-.codicon-browser:before { content: "\f118" }
-.codicon-bug:before { content: "\f119" }
-.codicon-calendar:before { content: "\f11a" }
-.codicon-case-sensitive:before { content: "\f11b" }
-.codicon-check:before { content: "\f11c" }
-.codicon-checklist:before { content: "\f11d" }
-.codicon-chevron-down:before { content: "\f11e" }
-.codicon-chevron-left:before { content: "\f11f" }
-.codicon-chevron-right:before { content: "\f120" }
-.codicon-chevron-up:before { content: "\f121" }
-.codicon-chrome-close:before { content: "\f122" }
-.codicon-chrome-maximize:before { content: "\f123" }
-.codicon-chrome-minimize:before { content: "\f124" }
-.codicon-chrome-restore:before { content: "\f125" }
-.codicon-circle-outline:before { content: "\f126" }
-.codicon-circle-slash:before { content: "\f127" }
-.codicon-circuit-board:before { content: "\f128" }
-.codicon-clear-all:before { content: "\f129" }
-.codicon-clippy:before { content: "\f12a" }
-.codicon-close-all:before { content: "\f12b" }
-.codicon-cloud-download:before { content: "\f12c" }
-.codicon-cloud-upload:before { content: "\f12d" }
-.codicon-code:before { content: "\f12e" }
-.codicon-collapse-all:before { content: "\f12f" }
-.codicon-color-mode:before { content: "\f130" }
-.codicon-comment-discussion:before { content: "\f131" }
-.codicon-compare-changes:before { content: "\f132" }
-.codicon-continue:before { content: "\f133" }
-.codicon-credit-card:before { content: "\f134" }
-.codicon-current-and-breakpoint:before { content: "\f135" }
-.codicon-current:before { content: "\f136" }
-.codicon-dash:before { content: "\f137" }
-.codicon-dashboard:before { content: "\f138" }
-.codicon-database:before { content: "\f139" }
-.codicon-debug-disconnect:before { content: "\f13a" }
-.codicon-debug-pause:before { content: "\f13b" }
-.codicon-debug-restart:before { content: "\f13c" }
-.codicon-debug-start:before { content: "\f13d" }
-.codicon-debug-step-into:before { content: "\f13e" }
-.codicon-debug-step-out:before { content: "\f13f" }
-.codicon-debug-step-over:before { content: "\f140" }
-.codicon-debug-stop:before { content: "\f141" }
-.codicon-debug:before { content: "\f142" }
-.codicon-device-camera-video:before { content: "\f143" }
-.codicon-device-camera:before { content: "\f144" }
-.codicon-device-mobile:before { content: "\f145" }
-.codicon-diff-added:before { content: "\f146" }
-.codicon-diff-ignored:before { content: "\f147" }
-.codicon-diff-modified:before { content: "\f148" }
-.codicon-diff-removed:before { content: "\f149" }
-.codicon-diff-renamed:before { content: "\f14a" }
-.codicon-diff:before { content: "\f14b" }
-.codicon-discard:before { content: "\f14c" }
-.codicon-editor-layout:before { content: "\f14d" }
-.codicon-ellipsis:before { content: "\f14e" }
-.codicon-empty-window:before { content: "\f14f" }
-.codicon-exclude:before { content: "\f150" }
-.codicon-extensions:before { content: "\f151" }
-.codicon-eye-closed:before { content: "\f152" }
-.codicon-file-binary:before { content: "\f153" }
-.codicon-file-code:before { content: "\f154" }
-.codicon-file-media:before { content: "\f155" }
-.codicon-file-pdf:before { content: "\f156" }
-.codicon-file-submodule:before { content: "\f157" }
-.codicon-file-symlink-directory:before { content: "\f158" }
-.codicon-file-symlink-file:before { content: "\f159" }
-.codicon-file-zip:before { content: "\f15a" }
-.codicon-files:before { content: "\f15b" }
-.codicon-filter:before { content: "\f15c" }
-.codicon-flame:before { content: "\f15d" }
-.codicon-fold-down:before { content: "\f15e" }
-.codicon-fold-up:before { content: "\f15f" }
-.codicon-fold:before { content: "\f160" }
-.codicon-folder-active:before { content: "\f161" }
-.codicon-folder-opened:before { content: "\f162" }
-.codicon-gear:before { content: "\f163" }
-.codicon-gift:before { content: "\f164" }
-.codicon-gist-secret:before { content: "\f165" }
-.codicon-gist:before { content: "\f166" }
-.codicon-git-commit:before { content: "\f167" }
-.codicon-git-compare:before { content: "\f168" }
-.codicon-git-merge:before { content: "\f169" }
-.codicon-github-action:before { content: "\f16a" }
-.codicon-github-alt:before { content: "\f16b" }
-.codicon-globe:before { content: "\f16c" }
-.codicon-grabber:before { content: "\f16d" }
-.codicon-graph:before { content: "\f16e" }
-.codicon-gripper:before { content: "\f16f" }
-.codicon-heart:before { content: "\f170" }
-.codicon-home:before { content: "\f171" }
-.codicon-horizontal-rule:before { content: "\f172" }
-.codicon-hubot:before { content: "\f173" }
-.codicon-inbox:before { content: "\f174" }
-.codicon-issue-closed:before { content: "\f175" }
-.codicon-issue-reopened:before { content: "\f176" }
-.codicon-issues:before { content: "\f177" }
-.codicon-italic:before { content: "\f178" }
-.codicon-jersey:before { content: "\f179" }
-.codicon-json:before { content: "\f17a" }
-.codicon-kebab-vertical:before { content: "\f17b" }
-.codicon-law:before { content: "\f17c" }
-.codicon-lightbulb-autofix:before { content: "\f17d" }
-.codicon-link-external:before { content: "\f17e" }
-.codicon-link:before { content: "\f17f" }
-.codicon-list-ordered:before { content: "\f180" }
-.codicon-list-unordered:before { content: "\f181" }
-.codicon-live-share:before { content: "\f182" }
-.codicon-loading:before { content: "\f183" }
-.codicon-location:before { content: "\f184" }
-.codicon-mail-read:before { content: "\f185" }
-.codicon-mail:before { content: "\f186" }
-.codicon-markdown:before { content: "\f187" }
-.codicon-megaphone:before { content: "\f188" }
-.codicon-mention:before { content: "\f189" }
-.codicon-milestone:before { content: "\f18a" }
-.codicon-mortar-board:before { content: "\f18b" }
-.codicon-move:before { content: "\f18c" }
-.codicon-multiple-windows:before { content: "\f18d" }
-.codicon-mute:before { content: "\f18e" }
-.codicon-no-newline:before { content: "\f18f" }
-.codicon-note:before { content: "\f190" }
-.codicon-octoface:before { content: "\f191" }
-.codicon-open-preview:before { content: "\f192" }
-.codicon-package:before { content: "\f193" }
-.codicon-paintcan:before { content: "\f194" }
-.codicon-pin:before { content: "\f195" }
-.codicon-play:before { content: "\f196" }
-.codicon-plug:before { content: "\f197" }
-.codicon-preserve-case:before { content: "\f198" }
-.codicon-preview:before { content: "\f199" }
-.codicon-project:before { content: "\f19a" }
-.codicon-pulse:before { content: "\f19b" }
-.codicon-question:before { content: "\f19c" }
-.codicon-quote:before { content: "\f19d" }
-.codicon-radio-tower:before { content: "\f19e" }
-.codicon-reactions:before { content: "\f19f" }
-.codicon-references:before { content: "\f1a0" }
-.codicon-refresh:before { content: "\f1a1" }
-.codicon-regex:before { content: "\f1a2" }
-.codicon-remote:before { content: "\f1a3" }
-.codicon-remove:before { content: "\f1a4" }
-.codicon-replace-all:before { content: "\f1a5" }
-.codicon-replace:before { content: "\f1a6" }
-.codicon-repo-clone:before { content: "\f1a7" }
-.codicon-repo-force-push:before { content: "\f1a8" }
-.codicon-repo-pull:before { content: "\f1a9" }
-.codicon-repo-push:before { content: "\f1aa" }
-.codicon-report:before { content: "\f1ab" }
-.codicon-request-changes:before { content: "\f1ac" }
-.codicon-rocket:before { content: "\f1ad" }
-.codicon-root-folder-opened:before { content: "\f1ae" }
-.codicon-root-folder:before { content: "\f1af" }
-.codicon-rss:before { content: "\f1b0" }
-.codicon-ruby:before { content: "\f1b1" }
-.codicon-save-all:before { content: "\f1b2" }
-.codicon-save-as:before { content: "\f1b3" }
-.codicon-save:before { content: "\f1b4" }
-.codicon-screen-full:before { content: "\f1b5" }
-.codicon-screen-normal:before { content: "\f1b6" }
-.codicon-search-stop:before { content: "\f1b7" }
-.codicon-selection:before { content: "\f1b8" }
-.codicon-server:before { content: "\f1b9" }
-.codicon-settings:before { content: "\f1ba" }
-.codicon-shield:before { content: "\f1bb" }
-.codicon-smiley:before { content: "\f1bc" }
-.codicon-sort-precedence:before { content: "\f1bd" }
-.codicon-split-horizontal:before { content: "\f1be" }
-.codicon-split-vertical:before { content: "\f1bf" }
-.codicon-squirrel:before { content: "\f1c0" }
-.codicon-star-full:before { content: "\f1c1" }
-.codicon-star-half:before { content: "\f1c2" }
-.codicon-symbol-class:before { content: "\f1c3" }
-.codicon-symbol-color:before { content: "\f1c4" }
-.codicon-symbol-constant:before { content: "\f1c5" }
-.codicon-symbol-enum-member:before { content: "\f1c6" }
-.codicon-symbol-field:before { content: "\f1c7" }
-.codicon-symbol-file:before { content: "\f1c8" }
-.codicon-symbol-interface:before { content: "\f1c9" }
-.codicon-symbol-keyword:before { content: "\f1ca" }
-.codicon-symbol-misc:before { content: "\f1cb" }
-.codicon-symbol-operator:before { content: "\f1cc" }
-.codicon-symbol-property:before { content: "\f1cd" }
-.codicon-symbol-snippet:before { content: "\f1ce" }
-.codicon-tasklist:before { content: "\f1cf" }
-.codicon-telescope:before { content: "\f1d0" }
-.codicon-text-size:before { content: "\f1d1" }
-.codicon-three-bars:before { content: "\f1d2" }
-.codicon-thumbsdown:before { content: "\f1d3" }
-.codicon-thumbsup:before { content: "\f1d4" }
-.codicon-tools:before { content: "\f1d5" }
-.codicon-triangle-down:before { content: "\f1d6" }
-.codicon-triangle-left:before { content: "\f1d7" }
-.codicon-triangle-right:before { content: "\f1d8" }
-.codicon-triangle-up:before { content: "\f1d9" }
-.codicon-twitter:before { content: "\f1da" }
-.codicon-unfold:before { content: "\f1db" }
-.codicon-unlock:before { content: "\f1dc" }
-.codicon-unmute:before { content: "\f1dd" }
-.codicon-unverified:before { content: "\f1de" }
-.codicon-verified:before { content: "\f1df" }
-.codicon-versions:before { content: "\f1e0" }
-.codicon-vm-active:before { content: "\f1e1" }
-.codicon-vm-outline:before { content: "\f1e2" }
-.codicon-vm-running:before { content: "\f1e3" }
-.codicon-watch:before { content: "\f1e4" }
-.codicon-whitespace:before { content: "\f1e5" }
-.codicon-whole-word:before { content: "\f1e6" }
-.codicon-window:before { content: "\f1e7" }
-.codicon-word-wrap:before { content: "\f1e8" }
-.codicon-zoom-in:before { content: "\f1e9" }
-.codicon-zoom-out:before { content: "\f1ea" }
+.codicon-activate-breakpoints:before { content: "\ea97" }
+.codicon-archive:before { content: "\ea98" }
+.codicon-arrow-both:before { content: "\ea99" }
+.codicon-arrow-down:before { content: "\ea9a" }
+.codicon-arrow-left:before { content: "\ea9b" }
+.codicon-arrow-right:before { content: "\ea9c" }
+.codicon-arrow-small-down:before { content: "\ea9d" }
+.codicon-arrow-small-left:before { content: "\ea9e" }
+.codicon-arrow-small-right:before { content: "\ea9f" }
+.codicon-arrow-small-up:before { content: "\eaa0" }
+.codicon-arrow-up:before { content: "\eaa1" }
+.codicon-bell:before { content: "\eaa2" }
+.codicon-bold:before { content: "\eaa3" }
+.codicon-book:before { content: "\eaa4" }
+.codicon-bookmark:before { content: "\eaa5" }
+.codicon-debug-breakpoint-conditional-unverified:before { content: "\eaa6" }
+.codicon-debug-breakpoint-conditional:before { content: "\eaa7" }
+.codicon-debug-breakpoint-conditional-disabled:before { content: "\eaa7" }
+.codicon-debug-breakpoint-data-unverified:before { content: "\eaa8" }
+.codicon-debug-breakpoint-data:before { content: "\eaa9" }
+.codicon-debug-breakpoint-data-disabled:before { content: "\eaa9" }
+.codicon-debug-breakpoint-log-unverified:before { content: "\eaaa" }
+.codicon-debug-breakpoint-log:before { content: "\eaab" }
+.codicon-debug-breakpoint-log-disabled:before { content: "\eaab" }
+.codicon-briefcase:before { content: "\eaac" }
+.codicon-broadcast:before { content: "\eaad" }
+.codicon-browser:before { content: "\eaae" }
+.codicon-bug:before { content: "\eaaf" }
+.codicon-calendar:before { content: "\eab0" }
+.codicon-case-sensitive:before { content: "\eab1" }
+.codicon-check:before { content: "\eab2" }
+.codicon-checklist:before { content: "\eab3" }
+.codicon-chevron-down:before { content: "\eab4" }
+.codicon-chevron-left:before { content: "\eab5" }
+.codicon-chevron-right:before { content: "\eab6" }
+.codicon-chevron-up:before { content: "\eab7" }
+.codicon-chrome-close:before { content: "\eab8" }
+.codicon-chrome-maximize:before { content: "\eab9" }
+.codicon-chrome-minimize:before { content: "\eaba" }
+.codicon-chrome-restore:before { content: "\eabb" }
+.codicon-circle-outline:before { content: "\eabc" }
+.codicon-debug-breakpoint-unverified:before { content: "\eabc" }
+.codicon-circle-slash:before { content: "\eabd" }
+.codicon-circuit-board:before { content: "\eabe" }
+.codicon-clear-all:before { content: "\eabf" }
+.codicon-clippy:before { content: "\eac0" }
+.codicon-close-all:before { content: "\eac1" }
+.codicon-cloud-download:before { content: "\eac2" }
+.codicon-cloud-upload:before { content: "\eac3" }
+.codicon-code:before { content: "\eac4" }
+.codicon-collapse-all:before { content: "\eac5" }
+.codicon-color-mode:before { content: "\eac6" }
+.codicon-comment-discussion:before { content: "\eac7" }
+.codicon-compare-changes:before { content: "\eac8" }
+.codicon-credit-card:before { content: "\eac9" }
+.codicon-dash:before { content: "\eacc" }
+.codicon-dashboard:before { content: "\eacd" }
+.codicon-database:before { content: "\eace" }
+.codicon-debug-continue:before { content: "\eacf" }
+.codicon-debug-disconnect:before { content: "\ead0" }
+.codicon-debug-pause:before { content: "\ead1" }
+.codicon-debug-restart:before { content: "\ead2" }
+.codicon-debug-start:before { content: "\ead3" }
+.codicon-debug-step-into:before { content: "\ead4" }
+.codicon-debug-step-out:before { content: "\ead5" }
+.codicon-debug-step-over:before { content: "\ead6" }
+.codicon-debug-stop:before { content: "\ead7" }
+.codicon-debug:before { content: "\ead8" }
+.codicon-device-camera-video:before { content: "\ead9" }
+.codicon-device-camera:before { content: "\eada" }
+.codicon-device-mobile:before { content: "\eadb" }
+.codicon-diff-added:before { content: "\eadc" }
+.codicon-diff-ignored:before { content: "\eadd" }
+.codicon-diff-modified:before { content: "\eade" }
+.codicon-diff-removed:before { content: "\eadf" }
+.codicon-diff-renamed:before { content: "\eae0" }
+.codicon-diff:before { content: "\eae1" }
+.codicon-discard:before { content: "\eae2" }
+.codicon-editor-layout:before { content: "\eae3" }
+.codicon-empty-window:before { content: "\eae4" }
+.codicon-exclude:before { content: "\eae5" }
+.codicon-extensions:before { content: "\eae6" }
+.codicon-eye-closed:before { content: "\eae7" }
+.codicon-file-binary:before { content: "\eae8" }
+.codicon-file-code:before { content: "\eae9" }
+.codicon-file-media:before { content: "\eaea" }
+.codicon-file-pdf:before { content: "\eaeb" }
+.codicon-file-submodule:before { content: "\eaec" }
+.codicon-file-symlink-directory:before { content: "\eaed" }
+.codicon-file-symlink-file:before { content: "\eaee" }
+.codicon-file-zip:before { content: "\eaef" }
+.codicon-files:before { content: "\eaf0" }
+.codicon-filter:before { content: "\eaf1" }
+.codicon-flame:before { content: "\eaf2" }
+.codicon-fold-down:before { content: "\eaf3" }
+.codicon-fold-up:before { content: "\eaf4" }
+.codicon-fold:before { content: "\eaf5" }
+.codicon-folder-active:before { content: "\eaf6" }
+.codicon-folder-opened:before { content: "\eaf7" }
+.codicon-gear:before { content: "\eaf8" }
+.codicon-gift:before { content: "\eaf9" }
+.codicon-gist-secret:before { content: "\eafa" }
+.codicon-gist:before { content: "\eafb" }
+.codicon-git-commit:before { content: "\eafc" }
+.codicon-git-compare:before { content: "\eafd" }
+.codicon-git-merge:before { content: "\eafe" }
+.codicon-github-action:before { content: "\eaff" }
+.codicon-github-alt:before { content: "\eb00" }
+.codicon-globe:before { content: "\eb01" }
+.codicon-grabber:before { content: "\eb02" }
+.codicon-graph:before { content: "\eb03" }
+.codicon-gripper:before { content: "\eb04" }
+.codicon-heart:before { content: "\eb05" }
+.codicon-home:before { content: "\eb06" }
+.codicon-horizontal-rule:before { content: "\eb07" }
+.codicon-hubot:before { content: "\eb08" }
+.codicon-inbox:before { content: "\eb09" }
+.codicon-issue-closed:before { content: "\eb0a" }
+.codicon-issue-reopened:before { content: "\eb0b" }
+.codicon-issues:before { content: "\eb0c" }
+.codicon-italic:before { content: "\eb0d" }
+.codicon-jersey:before { content: "\eb0e" }
+.codicon-json:before { content: "\eb0f" }
+.codicon-kebab-vertical:before { content: "\eb10" }
+.codicon-key:before { content: "\eb11" }
+.codicon-law:before { content: "\eb12" }
+.codicon-lightbulb-autofix:before { content: "\eb13" }
+.codicon-link-external:before { content: "\eb14" }
+.codicon-link:before { content: "\eb15" }
+.codicon-list-ordered:before { content: "\eb16" }
+.codicon-list-unordered:before { content: "\eb17" }
+.codicon-live-share:before { content: "\eb18" }
+.codicon-loading:before { content: "\eb19" }
+.codicon-location:before { content: "\eb1a" }
+.codicon-mail-read:before { content: "\eb1b" }
+.codicon-mail:before { content: "\eb1c" }
+.codicon-markdown:before { content: "\eb1d" }
+.codicon-megaphone:before { content: "\eb1e" }
+.codicon-mention:before { content: "\eb1f" }
+.codicon-milestone:before { content: "\eb20" }
+.codicon-mortar-board:before { content: "\eb21" }
+.codicon-move:before { content: "\eb22" }
+.codicon-multiple-windows:before { content: "\eb23" }
+.codicon-mute:before { content: "\eb24" }
+.codicon-no-newline:before { content: "\eb25" }
+.codicon-note:before { content: "\eb26" }
+.codicon-octoface:before { content: "\eb27" }
+.codicon-open-preview:before { content: "\eb28" }
+.codicon-package:before { content: "\eb29" }
+.codicon-paintcan:before { content: "\eb2a" }
+.codicon-pin:before { content: "\eb2b" }
+.codicon-play:before { content: "\eb2c" }
+.codicon-plug:before { content: "\eb2d" }
+.codicon-preserve-case:before { content: "\eb2e" }
+.codicon-preview:before { content: "\eb2f" }
+.codicon-project:before { content: "\eb30" }
+.codicon-pulse:before { content: "\eb31" }
+.codicon-question:before { content: "\eb32" }
+.codicon-quote:before { content: "\eb33" }
+.codicon-radio-tower:before { content: "\eb34" }
+.codicon-reactions:before { content: "\eb35" }
+.codicon-references:before { content: "\eb36" }
+.codicon-refresh:before { content: "\eb37" }
+.codicon-regex:before { content: "\eb38" }
+.codicon-remote-explorer:before { content: "\eb39" }
+.codicon-remote:before { content: "\eb3a" }
+.codicon-remove:before { content: "\eb3b" }
+.codicon-replace-all:before { content: "\eb3c" }
+.codicon-replace:before { content: "\eb3d" }
+.codicon-repo-clone:before { content: "\eb3e" }
+.codicon-repo-force-push:before { content: "\eb3f" }
+.codicon-repo-pull:before { content: "\eb40" }
+.codicon-repo-push:before { content: "\eb41" }
+.codicon-report:before { content: "\eb42" }
+.codicon-request-changes:before { content: "\eb43" }
+.codicon-rocket:before { content: "\eb44" }
+.codicon-root-folder-opened:before { content: "\eb45" }
+.codicon-root-folder:before { content: "\eb46" }
+.codicon-rss:before { content: "\eb47" }
+.codicon-ruby:before { content: "\eb48" }
+.codicon-save-all:before { content: "\eb49" }
+.codicon-save-as:before { content: "\eb4a" }
+.codicon-save:before { content: "\eb4b" }
+.codicon-screen-full:before { content: "\eb4c" }
+.codicon-screen-normal:before { content: "\eb4d" }
+.codicon-search-stop:before { content: "\eb4e" }
+.codicon-server:before { content: "\eb50" }
+.codicon-settings-gear:before { content: "\eb51" }
+.codicon-settings:before { content: "\eb52" }
+.codicon-shield:before { content: "\eb53" }
+.codicon-smiley:before { content: "\eb54" }
+.codicon-sort-precedence:before { content: "\eb55" }
+.codicon-split-horizontal:before { content: "\eb56" }
+.codicon-split-vertical:before { content: "\eb57" }
+.codicon-squirrel:before { content: "\eb58" }
+.codicon-star-full:before { content: "\eb59" }
+.codicon-star-half:before { content: "\eb5a" }
+.codicon-symbol-class:before { content: "\eb5b" }
+.codicon-symbol-color:before { content: "\eb5c" }
+.codicon-symbol-constant:before { content: "\eb5d" }
+.codicon-symbol-enum-member:before { content: "\eb5e" }
+.codicon-symbol-field:before { content: "\eb5f" }
+.codicon-symbol-file:before { content: "\eb60" }
+.codicon-symbol-interface:before { content: "\eb61" }
+.codicon-symbol-keyword:before { content: "\eb62" }
+.codicon-symbol-misc:before { content: "\eb63" }
+.codicon-symbol-operator:before { content: "\eb64" }
+.codicon-symbol-property:before { content: "\eb65" }
+.codicon-symbol-snippet:before { content: "\eb66" }
+.codicon-tasklist:before { content: "\eb67" }
+.codicon-telescope:before { content: "\eb68" }
+.codicon-text-size:before { content: "\eb69" }
+.codicon-three-bars:before { content: "\eb6a" }
+.codicon-thumbsdown:before { content: "\eb6b" }
+.codicon-thumbsup:before { content: "\eb6c" }
+.codicon-tools:before { content: "\eb6d" }
+.codicon-triangle-down:before { content: "\eb6e" }
+.codicon-triangle-left:before { content: "\eb6f" }
+.codicon-triangle-right:before { content: "\eb70" }
+.codicon-triangle-up:before { content: "\eb71" }
+.codicon-twitter:before { content: "\eb72" }
+.codicon-unfold:before { content: "\eb73" }
+.codicon-unlock:before { content: "\eb74" }
+.codicon-unmute:before { content: "\eb75" }
+.codicon-unverified:before { content: "\eb76" }
+.codicon-verified:before { content: "\eb77" }
+.codicon-versions:before { content: "\eb78" }
+.codicon-vm-active:before { content: "\eb79" }
+.codicon-vm-outline:before { content: "\eb7a" }
+.codicon-vm-running:before { content: "\eb7b" }
+.codicon-watch:before { content: "\eb7c" }
+.codicon-whitespace:before { content: "\eb7d" }
+.codicon-whole-word:before { content: "\eb7e" }
+.codicon-window:before { content: "\eb7f" }
+.codicon-word-wrap:before { content: "\eb80" }
+.codicon-zoom-in:before { content: "\eb81" }
+.codicon-zoom-out:before { content: "\eb82" }
+.codicon-list-filter:before { content: "\eb83" }
+.codicon-list-flat:before { content: "\eb84" }
+.codicon-list-selection:before { content: "\eb85" }
+.codicon-selection:before { content: "\eb85" }
+.codicon-list-tree:before { content: "\eb86" }
+.codicon-debug-breakpoint-function-unverified:before { content: "\eb87" }
+.codicon-debug-breakpoint-function:before { content: "\eb88" }
+.codicon-debug-breakpoint-function-disabled:before { content: "\eb88" }
+.codicon-debug-breakpoint-stackframe-active:before { content: "\eb89" }
+.codicon-debug-breakpoint-stackframe-dot:before { content: "\eb8a" }
+.codicon-debug-breakpoint-stackframe:before { content: "\eb8b" }
+.codicon-debug-breakpoint-stackframe-focused:before { content: "\eb8b" }
+.codicon-debug-breakpoint-unsupported:before { content: "\eb8c" }
+.codicon-symbol-string:before { content: "\eb8d" }
+.codicon-debug-reverse-continue:before { content: "\eb8e" }
+.codicon-debug-step-back:before { content: "\eb8f" }
+.codicon-debug-restart-frame:before { content: "\eb90" }
+.codicon-debug-alternate:before { content: "\eb91" }
+.codicon-debug-alt:before { content: "\f101" }
diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf
index beeea24d95..a51c284681 100644
Binary files a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf and b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf differ
diff --git a/src/vs/base/browser/ui/contextview/contextview.ts b/src/vs/base/browser/ui/contextview/contextview.ts
index c59a6dc632..2faf0490a3 100644
--- a/src/vs/base/browser/ui/contextview/contextview.ts
+++ b/src/vs/base/browser/ui/contextview/contextview.ts
@@ -5,8 +5,10 @@
 
 import 'vs/css!./contextview';
 import * as DOM from 'vs/base/browser/dom';
+import * as platform from 'vs/base/common/platform';
 import { IDisposable, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
 import { Range } from 'vs/base/common/range';
+import { BrowserFeatures } from 'vs/base/browser/canIUse';
 
 export interface IAnchor {
 	x: number;
@@ -178,7 +180,7 @@ export class ContextView extends Disposable {
 			return;
 		}
 
-		if (this.delegate!.canRelayout === false) {
+		if (this.delegate!.canRelayout === false && !(platform.isIOS && BrowserFeatures.pointerEvents)) {
 			this.hide();
 			return;
 		}
diff --git a/src/vs/base/browser/ui/countBadge/countBadge.ts b/src/vs/base/browser/ui/countBadge/countBadge.ts
index da5da53f5b..b31da96c06 100644
--- a/src/vs/base/browser/ui/countBadge/countBadge.ts
+++ b/src/vs/base/browser/ui/countBadge/countBadge.ts
@@ -8,6 +8,7 @@ import { $, append } from 'vs/base/browser/dom';
 import { format } from 'vs/base/common/strings';
 import { Color } from 'vs/base/common/color';
 import { mixin } from 'vs/base/common/objects';
+import { IThemable } from 'vs/base/common/styler';
 
 export interface ICountBadgeOptions extends ICountBadgetyles {
 	count?: number;
@@ -26,7 +27,7 @@ const defaultOpts = {
 	badgeForeground: Color.fromHex('#FFFFFF')
 };
 
-export class CountBadge {
+export class CountBadge implements IThemable {
 
 	private element: HTMLElement;
 	private count: number = 0;
diff --git a/src/vs/base/browser/ui/dialog/close-dark.svg b/src/vs/base/browser/ui/dialog/close-dark.svg
deleted file mode 100644
index 7305a8f099..0000000000
--- a/src/vs/base/browser/ui/dialog/close-dark.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/dialog/close-light.svg b/src/vs/base/browser/ui/dialog/close-light.svg
deleted file mode 100644
index ecddcd665b..0000000000
--- a/src/vs/base/browser/ui/dialog/close-light.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/dialog/dialog.css b/src/vs/base/browser/ui/dialog/dialog.css
index b8e657ee1d..3620363cdd 100644
--- a/src/vs/base/browser/ui/dialog/dialog.css
+++ b/src/vs/base/browser/ui/dialog/dialog.css
@@ -27,12 +27,12 @@
 	min-width: 500px;
 	max-width: 90%;
 	min-height: 75px;
-	padding: 5px;
+	padding: 10px;
 }
 
 /** Dialog: Title Actions Row */
 .monaco-workbench .dialog-box .dialog-toolbar-row {
-	padding-right: 1px;
+	padding-bottom: 4px;
 }
 
 .monaco-workbench .dialog-box .action-label {
@@ -46,73 +46,19 @@
 }
 
 
-.monaco-workbench .dialog-box .dialog-close-action {
-	background: url('close-light.svg') center center no-repeat;
-}
-
-.vs-dark .monaco-workbench .dialog-box .dialog-close-action,
-.hc-black .monaco-workbench .dialog-box .dialog-close-action {
-	background: url('close-dark.svg') center center no-repeat;
-}
-
 /** Dialog: Message Row */
 .monaco-workbench .dialog-box .dialog-message-row {
 	display: flex;
 	flex-grow: 1;
-	padding: 10px 15px 20px;
 	align-items: center;
+	padding: 0 10px;
 }
 
-.monaco-workbench .dialog-box .dialog-message-row .dialog-icon {
-	flex: 0 0 40px;
-	height: 40px;
+.monaco-workbench .dialog-box .dialog-message-row > .codicon {
+	flex: 0 0 48px;
+	height: 48px;
 	align-self: baseline;
-	background-position: center;
-	background-repeat: no-repeat;
-	background-size: 40px;
-}
-
-.vs .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-pending {
-	background-image: url('pending.svg');
-}
-
-.vs .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-info {
-	background-image: url('info-light.svg');
-}
-
-.vs .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-warning {
-	background-image: url('warning-light.svg');
-}
-
-.vs .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-error {
-	background-image: url('error-light.svg');
-}
-
-.vs-dark .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-pending {
-	background-image: url('pending-dark.svg');
-}
-
-.vs-dark .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-info,
-.hc-black .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-info {
-	background-image: url('info-dark.svg');
-}
-
-.vs-dark .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-warning,
-.hc-black .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-warning {
-	background-image: url('warning-dark.svg');
-}
-
-.vs-dark .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-error,
-.hc-black .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-error {
-	background-image: url('error-dark.svg');
-}
-
-.hc-black .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-pending {
-	background-image: url('pending-hc.svg');
-}
-
-.monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-pending {
-	background-size: 30px;
+	font-size: 48px;
 }
 
 /** Dialog: Message Container */
@@ -121,8 +67,10 @@
 	flex-direction: column;
 	overflow: hidden;
 	text-overflow: ellipsis;
-	padding-left: 20px;
+	padding-left: 24px;
 	user-select: text;
+	-webkit-user-select: text;
+	-ms-user-select: text;
 	word-wrap: break-word; /* never overflow long words, but break to next line */
 	white-space: normal;
 }
@@ -134,7 +82,10 @@
 	flex: 1; /* let the message always grow */
 	white-space: normal;
 	word-wrap: break-word; /* never overflow long words, but break to next line */
-	padding-bottom: 10px;
+	min-height: 48px; /* matches icon height */
+	margin-bottom: 8px;
+	display: flex;
+	align-items: center;
 }
 
 /** Dialog: Details */
@@ -154,6 +105,13 @@
 	display: flex;
 }
 
+.monaco-workbench .dialog-box .dialog-message-row .dialog-message-container .dialog-checkbox-row .dialog-checkbox-message {
+	cursor: pointer;
+	user-select: none;
+	-webkit-user-select: none;
+	-ms-user-select: none;
+}
+
 /** Dialog: Buttons Row */
 .monaco-workbench .dialog-box > .dialog-buttons-row {
 	display: flex;
@@ -166,6 +124,7 @@
 .monaco-workbench .dialog-box > .dialog-buttons-row {
 	display: flex;
 	white-space: nowrap;
+	padding: 20px 10px 10px;
 }
 
 /** Dialog: Buttons */
@@ -175,7 +134,8 @@
 }
 
 .monaco-workbench .dialog-box > .dialog-buttons-row > .dialog-buttons > .monaco-button {
-	max-width: fit-content;
+	width: fit-content;
+	width: -moz-fit-content;
 	padding: 5px 10px;
 	margin: 4px 5px; /* allows button focus outline to be visible */
 	overflow: hidden;
diff --git a/src/vs/base/browser/ui/dialog/dialog.ts b/src/vs/base/browser/ui/dialog/dialog.ts
index a9d75da7aa..55cdb2a3fa 100644
--- a/src/vs/base/browser/ui/dialog/dialog.ts
+++ b/src/vs/base/browser/ui/dialog/dialog.ts
@@ -6,7 +6,7 @@
 import 'vs/css!./dialog';
 import * as nls from 'vs/nls';
 import { Disposable } from 'vs/base/common/lifecycle';
-import { $, hide, show, EventHelper, clearNode, removeClasses, addClass, removeNode, isAncestor } from 'vs/base/browser/dom';
+import { $, hide, show, EventHelper, clearNode, removeClasses, addClass, addClasses, removeNode, isAncestor, addDisposableListener, EventType } from 'vs/base/browser/dom';
 import { domEvent } from 'vs/base/browser/event';
 import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
 import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
@@ -75,7 +75,8 @@ export class Dialog extends Disposable {
 
 		if (this.options.detail) {
 			const messageElement = messageContainer.appendChild($('.dialog-message'));
-			messageElement.innerText = this.message;
+			const messageTextElement = messageElement.appendChild($('.dialog-message-text'));
+			messageTextElement.innerText = this.message;
 		}
 
 		this.messageDetailElement = messageContainer.appendChild($('.dialog-message-detail'));
@@ -84,12 +85,13 @@ export class Dialog extends Disposable {
 		if (this.options.checkboxLabel) {
 			const checkboxRowElement = messageContainer.appendChild($('.dialog-checkbox-row'));
 
-			this.checkbox = this._register(new SimpleCheckbox(this.options.checkboxLabel, !!this.options.checkboxChecked));
+			const checkbox = this.checkbox = this._register(new SimpleCheckbox(this.options.checkboxLabel, !!this.options.checkboxChecked));
 
-			checkboxRowElement.appendChild(this.checkbox.domNode);
+			checkboxRowElement.appendChild(checkbox.domNode);
 
 			const checkboxMessageElement = checkboxRowElement.appendChild($('.dialog-checkbox-message'));
 			checkboxMessageElement.innerText = this.options.checkboxLabel;
+			this._register(addDisposableListener(checkboxMessageElement, EventType.CLICK, () => checkbox.checked = !checkbox.checked));
 		}
 
 		const toolbarRowElement = this.element.appendChild($('.dialog-toolbar-row'));
@@ -198,29 +200,30 @@ export class Dialog extends Disposable {
 				}
 			}));
 
-			removeClasses(this.iconElement, 'icon-error', 'icon-warning', 'icon-info');
+			addClass(this.iconElement, 'codicon');
+			removeClasses(this.iconElement, 'codicon-alert', 'codicon-warning', 'codicon-info');
 
 			switch (this.options.type) {
 				case 'error':
-					addClass(this.iconElement, 'icon-error');
+					addClass(this.iconElement, 'codicon-error');
 					break;
 				case 'warning':
-					addClass(this.iconElement, 'icon-warning');
+					addClass(this.iconElement, 'codicon-warning');
 					break;
 				case 'pending':
-					addClass(this.iconElement, 'icon-pending');
+					addClasses(this.iconElement, 'codicon-loading', 'codicon-animation-spin');
 					break;
 				case 'none':
 				case 'info':
 				case 'question':
 				default:
-					addClass(this.iconElement, 'icon-info');
+					addClass(this.iconElement, 'codicon-info');
 					break;
 			}
 
 			const actionBar = new ActionBar(this.toolbarContainer, {});
 
-			const action = new Action('dialog.close', nls.localize('dialogClose', "Close Dialog"), 'dialog-close-action', true, () => {
+			const action = new Action('dialog.close', nls.localize('dialogClose', "Close Dialog"), 'codicon codicon-close', true, () => {
 				resolve({ button: this.options.cancelId || 0, checkboxChecked: this.checkbox ? this.checkbox.checked : undefined });
 				return Promise.resolve();
 			});
diff --git a/src/vs/base/browser/ui/dialog/error-dark.svg b/src/vs/base/browser/ui/dialog/error-dark.svg
deleted file mode 100644
index efdc5f2ae2..0000000000
--- a/src/vs/base/browser/ui/dialog/error-dark.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/dialog/error-light.svg b/src/vs/base/browser/ui/dialog/error-light.svg
deleted file mode 100644
index d646c72c74..0000000000
--- a/src/vs/base/browser/ui/dialog/error-light.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/dialog/info-dark.svg b/src/vs/base/browser/ui/dialog/info-dark.svg
deleted file mode 100644
index bb851afdfe..0000000000
--- a/src/vs/base/browser/ui/dialog/info-dark.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/dialog/info-light.svg b/src/vs/base/browser/ui/dialog/info-light.svg
deleted file mode 100644
index 6faf670ccc..0000000000
--- a/src/vs/base/browser/ui/dialog/info-light.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/src/vs/base/browser/ui/dialog/pending-dark.svg b/src/vs/base/browser/ui/dialog/pending-dark.svg
deleted file mode 100644
index 5f38838116..0000000000
--- a/src/vs/base/browser/ui/dialog/pending-dark.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-	
-	
-		
-		
-		
-		
-		
-		
-		
-		
-	
-
diff --git a/src/vs/base/browser/ui/dialog/pending-hc.svg b/src/vs/base/browser/ui/dialog/pending-hc.svg
deleted file mode 100644
index c6d0ec7e29..0000000000
--- a/src/vs/base/browser/ui/dialog/pending-hc.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-	
-	
-		
-		
-		
-		
-		
-		
-		
-		
-	
-
diff --git a/src/vs/base/browser/ui/dialog/pending.svg b/src/vs/base/browser/ui/dialog/pending.svg
deleted file mode 100644
index 47ce444bb2..0000000000
--- a/src/vs/base/browser/ui/dialog/pending.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-	
-	
-		
-		
-		
-		
-		
-		
-		
-		
-	
-
diff --git a/src/vs/base/browser/ui/dialog/warning-dark.svg b/src/vs/base/browser/ui/dialog/warning-dark.svg
deleted file mode 100644
index a267963e58..0000000000
--- a/src/vs/base/browser/ui/dialog/warning-dark.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/dialog/warning-light.svg b/src/vs/base/browser/ui/dialog/warning-light.svg
deleted file mode 100644
index f2e2aa741e..0000000000
--- a/src/vs/base/browser/ui/dialog/warning-light.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/src/vs/base/browser/ui/dropdown/dropdown.css b/src/vs/base/browser/ui/dropdown/dropdown.css
index 7ac5df590a..3ba96c0be7 100644
--- a/src/vs/base/browser/ui/dropdown/dropdown.css
+++ b/src/vs/base/browser/ui/dropdown/dropdown.css
@@ -5,12 +5,10 @@
 
 .monaco-dropdown {
 	height: 100%;
-	display: inline-block;
 	padding: 0;
 }
 
 .monaco-dropdown > .dropdown-label {
-	display: inline-block;
 	cursor: pointer;
 	height: 100%;
 }
diff --git a/src/vs/base/browser/ui/findinput/findInput.ts b/src/vs/base/browser/ui/findinput/findInput.ts
index ae43efd76a..6cf8e814a6 100644
--- a/src/vs/base/browser/ui/findinput/findInput.ts
+++ b/src/vs/base/browser/ui/findinput/findInput.ts
@@ -396,30 +396,18 @@ export class FindInput extends Widget {
 	}
 
 	public validate(): void {
-		if (this.inputBox) {
-			this.inputBox.validate();
-		}
+		this.inputBox.validate();
 	}
 
 	public showMessage(message: InputBoxMessage): void {
-		if (this.inputBox) {
-			this.inputBox.showMessage(message);
-		}
+		this.inputBox.showMessage(message);
 	}
 
 	public clearMessage(): void {
-		if (this.inputBox) {
-			this.inputBox.hideMessage();
-		}
+		this.inputBox.hideMessage();
 	}
 
 	private clearValidation(): void {
-		if (this.inputBox) {
-			this.inputBox.hideMessage();
-		}
-	}
-
-	public dispose(): void {
-		super.dispose();
+		this.inputBox.hideMessage();
 	}
 }
diff --git a/src/vs/base/browser/ui/grid/grid.ts b/src/vs/base/browser/ui/grid/grid.ts
index 4478710b1f..4e09823bb0 100644
--- a/src/vs/base/browser/ui/grid/grid.ts
+++ b/src/vs/base/browser/ui/grid/grid.ts
@@ -605,7 +605,7 @@ export type GridNodeDescriptor = { size?: number, groups?: GridNodeDescriptor[]
 export type GridDescriptor = { orientation: Orientation, groups?: GridNodeDescriptor[] };
 
 export function sanitizeGridNodeDescriptor(nodeDescriptor: GridNodeDescriptor): void {
-	if (nodeDescriptor.groups && nodeDescriptor.groups.length === 0) {
+	if (nodeDescriptor.groups && nodeDescriptor.groups.length <= 1) {
 		nodeDescriptor.groups = undefined;
 	}
 
diff --git a/src/vs/base/browser/ui/grid/gridview.ts b/src/vs/base/browser/ui/grid/gridview.ts
index 078f6ad0eb..9f46f9ba78 100644
--- a/src/vs/base/browser/ui/grid/gridview.ts
+++ b/src/vs/base/browser/ui/grid/gridview.ts
@@ -30,7 +30,7 @@ export interface IView {
 	readonly onDidChange: Event;
 	readonly priority?: LayoutPriority;
 	readonly snap?: boolean;
-	layout(width: number, height: number, orientation: Orientation): void;
+	layout(width: number, height: number, top: number, left: number): void;
 	setVisible?(visible: boolean): void;
 }
 
@@ -69,10 +69,10 @@ export function orthogonal(orientation: Orientation): Orientation {
 }
 
 export interface Box {
-	top: number;
-	left: number;
-	width: number;
-	height: number;
+	readonly top: number;
+	readonly left: number;
+	readonly width: number;
+	readonly height: number;
 }
 
 export interface GridLeafNode {
@@ -117,11 +117,19 @@ export interface IGridViewOptions {
 	readonly layoutController?: ILayoutController;
 }
 
-class BranchNode implements ISplitView, IDisposable {
+interface ILayoutContext {
+	readonly orthogonalSize: number;
+	readonly absoluteOffset: number;
+	readonly absoluteOrthogonalOffset: number;
+	readonly absoluteSize: number;
+	readonly absoluteOrthogonalSize: number;
+}
+
+class BranchNode implements ISplitView, IDisposable {
 
 	readonly element: HTMLElement;
 	readonly children: Node[] = [];
-	private splitview: SplitView;
+	private splitview: SplitView;
 
 	private _size: number;
 	get size(): number { return this._size; }
@@ -129,6 +137,9 @@ class BranchNode implements ISplitView, IDisposable {
 	private _orthogonalSize: number;
 	get orthogonalSize(): number { return this._orthogonalSize; }
 
+	private absoluteOffset: number = 0;
+	private absoluteOrthogonalOffset: number = 0;
+
 	private _styles: IGridViewStyles;
 	get styles(): IGridViewStyles { return this._styles; }
 
@@ -140,6 +151,14 @@ class BranchNode implements ISplitView, IDisposable {
 		return this.orientation === Orientation.HORIZONTAL ? this.orthogonalSize : this.size;
 	}
 
+	get top(): number {
+		return this.orientation === Orientation.HORIZONTAL ? this.absoluteOffset : this.absoluteOrthogonalOffset;
+	}
+
+	get left(): number {
+		return this.orientation === Orientation.HORIZONTAL ? this.absoluteOrthogonalOffset : this.absoluteOffset;
+	}
+
 	get minimumSize(): number {
 		return this.children.length === 0 ? 0 : Math.max(...this.children.map(c => c.minimumOrthogonalSize));
 	}
@@ -221,7 +240,7 @@ class BranchNode implements ISplitView, IDisposable {
 		if (!childDescriptors) {
 			// Normal behavior, we have no children yet, just set up the splitview
 			this.splitview = new SplitView(this.element, { orientation, styles, proportionalLayout });
-			this.splitview.layout(size, orthogonalSize);
+			this.splitview.layout(size, { orthogonalSize, absoluteOffset: 0, absoluteOrthogonalOffset: 0, absoluteSize: size, absoluteOrthogonalSize: orthogonalSize });
 		} else {
 			// Reconstruction behavior, we want to reconstruct a splitview
 			const descriptor = {
@@ -268,20 +287,32 @@ class BranchNode implements ISplitView, IDisposable {
 		}
 	}
 
-	layout(size: number, orthogonalSize: number | undefined): void {
+	layout(size: number, offset: number, ctx: ILayoutContext | undefined): void {
 		if (!this.layoutController.isLayoutEnabled) {
 			return;
 		}
 
-		if (typeof orthogonalSize !== 'number') {
+		if (typeof ctx === 'undefined') {
 			throw new Error('Invalid state');
 		}
 
 		// branch nodes should flip the normal/orthogonal directions
-		this._size = orthogonalSize;
+		this._size = ctx.orthogonalSize;
 		this._orthogonalSize = size;
+		this.absoluteOffset = ctx.absoluteOffset + offset;
+		this.absoluteOrthogonalOffset = ctx.absoluteOrthogonalOffset;
 
-		this.splitview.layout(orthogonalSize, size);
+		this.splitview.layout(ctx.orthogonalSize, {
+			orthogonalSize: size,
+			absoluteOffset: this.absoluteOrthogonalOffset,
+			absoluteOrthogonalOffset: this.absoluteOffset,
+			absoluteSize: ctx.absoluteOrthogonalSize,
+			absoluteOrthogonalSize: ctx.absoluteSize
+		});
+
+		// Disable snapping on views which sit on the edges of the grid
+		this.splitview.startSnappingEnabled = this.absoluteOrthogonalOffset > 0;
+		this.splitview.endSnappingEnabled = this.absoluteOrthogonalOffset + ctx.orthogonalSize < ctx.absoluteOrthogonalSize;
 	}
 
 	setVisible(visible: boolean): void {
@@ -511,7 +542,7 @@ class BranchNode implements ISplitView, IDisposable {
 	}
 }
 
-class LeafNode implements ISplitView, IDisposable {
+class LeafNode implements ISplitView, IDisposable {
 
 	private _size: number = 0;
 	get size(): number { return this._size; }
@@ -519,6 +550,9 @@ class LeafNode implements ISplitView, IDisposable {
 	private _orthogonalSize: number;
 	get orthogonalSize(): number { return this._orthogonalSize; }
 
+	private absoluteOffset: number = 0;
+	private absoluteOrthogonalOffset: number = 0;
+
 	readonly onDidSashReset: Event = Event.None;
 
 	private _onDidLinkedWidthNodeChange = new Relay();
@@ -565,6 +599,14 @@ class LeafNode implements ISplitView, IDisposable {
 		return this.orientation === Orientation.HORIZONTAL ? this.size : this.orthogonalSize;
 	}
 
+	get top(): number {
+		return this.orientation === Orientation.HORIZONTAL ? this.absoluteOffset : this.absoluteOrthogonalOffset;
+	}
+
+	get left(): number {
+		return this.orientation === Orientation.HORIZONTAL ? this.absoluteOrthogonalOffset : this.absoluteOffset;
+	}
+
 	get element(): HTMLElement {
 		return this.view.element;
 	}
@@ -617,18 +659,20 @@ class LeafNode implements ISplitView, IDisposable {
 		// noop
 	}
 
-	layout(size: number, orthogonalSize: number | undefined): void {
+	layout(size: number, offset: number, ctx: ILayoutContext | undefined): void {
 		if (!this.layoutController.isLayoutEnabled) {
 			return;
 		}
 
-		if (typeof orthogonalSize !== 'number') {
+		if (typeof ctx === 'undefined') {
 			throw new Error('Invalid state');
 		}
 
 		this._size = size;
-		this._orthogonalSize = orthogonalSize;
-		this.view.layout(this.width, this.height, orthogonal(this.orientation));
+		this._orthogonalSize = ctx.orthogonalSize;
+		this.absoluteOffset = ctx.absoluteOffset + offset;
+		this.absoluteOrthogonalOffset = ctx.absoluteOrthogonalOffset;
+		this.view.layout(this.width, this.height, this.top, this.left);
 	}
 
 	setVisible(visible: boolean): void {
@@ -715,7 +759,7 @@ export class GridView implements IDisposable {
 
 		const { size, orthogonalSize } = this._root;
 		this.root = flipNode(this._root, orthogonalSize, size);
-		this.root.layout(size, orthogonalSize);
+		this.root.layout(size, 0, { orthogonalSize, absoluteOffset: 0, absoluteOrthogonalOffset: 0, absoluteSize: size, absoluteOrthogonalSize: orthogonalSize });
 	}
 
 	get width(): number { return this.root.width; }
@@ -771,7 +815,7 @@ export class GridView implements IDisposable {
 		this.firstLayoutController.isLayoutEnabled = true;
 
 		const [size, orthogonalSize] = this.root.orientation === Orientation.HORIZONTAL ? [height, width] : [width, height];
-		this.root.layout(size, orthogonalSize);
+		this.root.layout(size, 0, { orthogonalSize, absoluteOffset: 0, absoluteOrthogonalOffset: 0, absoluteSize: size, absoluteOrthogonalSize: orthogonalSize });
 	}
 
 	addView(view: IView, size: number | Sizing, location: number[]): void {
@@ -1032,7 +1076,7 @@ export class GridView implements IDisposable {
 	getView(location?: number[]): GridNode;
 	getView(location?: number[]): GridNode {
 		const node = location ? this.getNode(location)[1] : this._root;
-		return this._getViews(node, this.orientation, { top: 0, left: 0, width: this.width, height: this.height });
+		return this._getViews(node, this.orientation);
 	}
 
 	static deserialize(json: ISerializedGridView, deserializer: IViewDeserializer, options: IGridViewOptions = {}): GridView {
@@ -1076,24 +1120,20 @@ export class GridView implements IDisposable {
 		return result;
 	}
 
-	private _getViews(node: Node, orientation: Orientation, box: Box, cachedVisibleSize?: number): GridNode {
+	private _getViews(node: Node, orientation: Orientation, cachedVisibleSize?: number): GridNode {
+		const box = { top: node.top, left: node.left, width: node.width, height: node.height };
+
 		if (node instanceof LeafNode) {
 			return { view: node.view, box, cachedVisibleSize };
 		}
 
 		const children: GridNode[] = [];
-		let i = 0;
-		let offset = 0;
 
-		for (const child of node.children) {
-			const childOrientation = orthogonal(orientation);
-			const childBox: Box = orientation === Orientation.HORIZONTAL
-				? { top: box.top, left: box.left + offset, width: child.width, height: box.height }
-				: { top: box.top + offset, left: box.left, width: box.width, height: child.height };
-			const cachedVisibleSize = node.getChildCachedVisibleSize(i++);
+		for (let i = 0; i < node.children.length; i++) {
+			const child = node.children[i];
+			const cachedVisibleSize = node.getChildCachedVisibleSize(i);
 
-			children.push(this._getViews(child, childOrientation, childBox, cachedVisibleSize));
-			offset += orientation === Orientation.HORIZONTAL ? child.width : child.height;
+			children.push(this._getViews(child, orthogonal(orientation), cachedVisibleSize));
 		}
 
 		return { children, box };
diff --git a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts
index b950a8cf7b..c604bd4feb 100644
--- a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts
+++ b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts
@@ -84,7 +84,11 @@ export class HighlightedLabel {
 		}
 
 		this.domNode.innerHTML = htmlContent;
-		this.domNode.title = this.title;
+		if (this.title) {
+			this.domNode.title = this.title;
+		} else {
+			this.domNode.removeAttribute('title');
+		}
 		this.didEverRender = true;
 	}
 
diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts
index e873ac813d..04bcb1f416 100644
--- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts
+++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts
@@ -8,6 +8,7 @@ import * as dom from 'vs/base/browser/dom';
 import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
 import { IMatch } from 'vs/base/common/filters';
 import { Disposable } from 'vs/base/common/lifecycle';
+import { Range } from 'vs/base/common/range';
 
 export interface IIconLabelCreationOptions {
 	supportHighlights?: boolean;
@@ -24,6 +25,8 @@ export interface IIconLabelValueOptions {
 	matches?: IMatch[];
 	labelEscapeNewLines?: boolean;
 	descriptionMatches?: IMatch[];
+	readonly separator?: string;
+	readonly domId?: string;
 }
 
 class FastLabelNode {
@@ -86,9 +89,10 @@ class FastLabelNode {
 }
 
 export class IconLabel extends Disposable {
+
 	private domNode: FastLabelNode;
-	private labelDescriptionContainer: FastLabelNode;
-	private labelNode: FastLabelNode | HighlightedLabel;
+	private descriptionContainer: FastLabelNode;
+	private nameNode: Label | LabelWithHighlights;
 	private descriptionNode: FastLabelNode | HighlightedLabel | undefined;
 	private descriptionNodeFactory: () => FastLabelNode | HighlightedLabel;
 
@@ -97,18 +101,21 @@ export class IconLabel extends Disposable {
 
 		this.domNode = this._register(new FastLabelNode(dom.append(container, dom.$('.monaco-icon-label'))));
 
-		this.labelDescriptionContainer = this._register(new FastLabelNode(dom.append(this.domNode.element, dom.$('.monaco-icon-label-description-container'))));
+		const labelContainer = dom.append(this.domNode.element, dom.$('.monaco-icon-label-container'));
+
+		const nameContainer = dom.append(labelContainer, dom.$('span.monaco-icon-name-container'));
+		this.descriptionContainer = this._register(new FastLabelNode(dom.append(labelContainer, dom.$('span.monaco-icon-description-container'))));
 
 		if (options?.supportHighlights) {
-			this.labelNode = new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('a.label-name')), !!options.supportCodicons);
+			this.nameNode = new LabelWithHighlights(nameContainer, !!options.supportCodicons);
 		} else {
-			this.labelNode = this._register(new FastLabelNode(dom.append(this.labelDescriptionContainer.element, dom.$('a.label-name'))));
+			this.nameNode = new Label(nameContainer);
 		}
 
 		if (options?.supportDescriptionHighlights) {
-			this.descriptionNodeFactory = () => new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('span.label-description')), !!options.supportCodicons);
+			this.descriptionNodeFactory = () => new HighlightedLabel(dom.append(this.descriptionContainer.element, dom.$('span.label-description')), !!options.supportCodicons);
 		} else {
-			this.descriptionNodeFactory = () => this._register(new FastLabelNode(dom.append(this.labelDescriptionContainer.element, dom.$('span.label-description'))));
+			this.descriptionNodeFactory = () => this._register(new FastLabelNode(dom.append(this.descriptionContainer.element, dom.$('span.label-description'))));
 		}
 	}
 
@@ -116,7 +123,7 @@ export class IconLabel extends Disposable {
 		return this.domNode.element;
 	}
 
-	setLabel(label?: string, description?: string, options?: IIconLabelValueOptions): void {
+	setLabel(label: string | string[], description?: string, options?: IIconLabelValueOptions): void {
 		const classes = ['monaco-icon-label'];
 		if (options) {
 			if (options.extraClasses) {
@@ -131,11 +138,7 @@ export class IconLabel extends Disposable {
 		this.domNode.className = classes.join(' ');
 		this.domNode.title = options?.title || '';
 
-		if (this.labelNode instanceof HighlightedLabel) {
-			this.labelNode.set(label || '', options?.matches, options?.title, options?.labelEscapeNewLines);
-		} else {
-			this.labelNode.textContent = label || '';
-		}
+		this.nameNode.setLabel(label, options);
 
 		if (description || this.descriptionNode) {
 			if (!this.descriptionNode) {
@@ -157,3 +160,112 @@ export class IconLabel extends Disposable {
 		}
 	}
 }
+
+class Label {
+
+	private label: string | string[] | undefined = undefined;
+	private singleLabel: HTMLElement | undefined = undefined;
+
+	constructor(private container: HTMLElement) { }
+
+	setLabel(label: string | string[], options?: IIconLabelValueOptions): void {
+		if (this.label === label) {
+			return;
+		}
+
+		this.label = label;
+
+		if (typeof label === 'string') {
+			if (!this.singleLabel) {
+				this.container.innerHTML = '';
+				dom.removeClass(this.container, 'multiple');
+				this.singleLabel = dom.append(this.container, dom.$('a.label-name', { id: options?.domId }));
+			}
+
+			this.singleLabel.textContent = label;
+		} else {
+			this.container.innerHTML = '';
+			dom.addClass(this.container, 'multiple');
+			this.singleLabel = undefined;
+
+			for (let i = 0; i < label.length; i++) {
+				const l = label[i];
+				const id = options?.domId && `${options?.domId}_${i}`;
+
+				dom.append(this.container, dom.$('a.label-name', { id, 'data-icon-label-count': label.length, 'data-icon-label-index': i }, l));
+
+				if (i < label.length - 1) {
+					dom.append(this.container, dom.$('span.label-separator', undefined, options?.separator || '/'));
+				}
+			}
+		}
+	}
+}
+
+function splitMatches(labels: string[], separator: string, matches: IMatch[] | undefined): IMatch[][] | undefined {
+	if (!matches) {
+		return undefined;
+	}
+
+	let labelStart = 0;
+
+	return labels.map(label => {
+		const labelRange = { start: labelStart, end: labelStart + label.length };
+
+		const result = matches
+			.map(match => Range.intersect(labelRange, match))
+			.filter(range => !Range.isEmpty(range))
+			.map(({ start, end }) => ({ start: start - labelStart, end: end - labelStart }));
+
+		labelStart = labelRange.end + separator.length;
+		return result;
+	});
+}
+
+class LabelWithHighlights {
+
+	private label: string | string[] | undefined = undefined;
+	private singleLabel: HighlightedLabel | undefined = undefined;
+
+	constructor(private container: HTMLElement, private supportCodicons: boolean) { }
+
+	setLabel(label: string | string[], options?: IIconLabelValueOptions): void {
+		if (this.label === label) {
+			return;
+		}
+
+		this.label = label;
+
+		if (typeof label === 'string') {
+			if (!this.singleLabel) {
+				this.container.innerHTML = '';
+				dom.removeClass(this.container, 'multiple');
+				this.singleLabel = new HighlightedLabel(dom.append(this.container, dom.$('a.label-name', { id: options?.domId })), this.supportCodicons);
+			}
+
+			this.singleLabel.set(label, options?.matches, options?.title, options?.labelEscapeNewLines);
+		} else {
+
+			this.container.innerHTML = '';
+			dom.addClass(this.container, 'multiple');
+			this.singleLabel = undefined;
+
+			const separator = options?.separator || '/';
+			const matches = splitMatches(label, separator, options?.matches);
+
+			for (let i = 0; i < label.length; i++) {
+				const l = label[i];
+				const m = matches ? matches[i] : undefined;
+				const id = options?.domId && `${options?.domId}_${i}`;
+
+				const name = dom.$('a.label-name', { id, 'data-icon-label-count': label.length, 'data-icon-label-index': i });
+				const highlightedLabel = new HighlightedLabel(dom.append(this.container, name), this.supportCodicons);
+				highlightedLabel.set(l, m, options?.title, options?.labelEscapeNewLines);
+
+				if (i < label.length - 1) {
+					dom.append(name, dom.$('span.label-separator', undefined, separator));
+				}
+			}
+		}
+	}
+}
diff --git a/src/vs/base/browser/ui/iconLabel/iconlabel.css b/src/vs/base/browser/ui/iconLabel/iconlabel.css
index 5fa0615f0d..39950a7cda 100644
--- a/src/vs/base/browser/ui/iconLabel/iconlabel.css
+++ b/src/vs/base/browser/ui/iconLabel/iconlabel.css
@@ -25,30 +25,38 @@
 
 	/* fonts icons */
 	-webkit-font-smoothing: antialiased;
+	-moz-osx-font-smoothing: grayscale;
 	vertical-align: top;
 
 	flex-shrink: 0; /* fix for https://github.com/Microsoft/vscode/issues/13787 */
 }
 
-.monaco-icon-label > .monaco-icon-label-description-container {
-	overflow: hidden; /* this causes the label/description to shrink first if decorations are enabled */
+.monaco-icon-label > .monaco-icon-label-container {
+	min-width: 0;
+	overflow: hidden;
 	text-overflow: ellipsis;
+	flex: 1;
 }
 
-.monaco-icon-label > .monaco-icon-label-description-container > .label-name {
+.monaco-icon-label > .monaco-icon-label-container > .monaco-icon-name-container > .label-name {
 	color: inherit;
 	white-space: pre; /* enable to show labels that include multiple whitespaces */
 }
 
-.monaco-icon-label > .monaco-icon-label-description-container > .label-description {
+.monaco-icon-label > .monaco-icon-label-container > .monaco-icon-name-container > .label-name > .label-separator {
+	margin: 0 2px;
+	opacity: 0.5;
+}
+
+.monaco-icon-label > .monaco-icon-label-container > .monaco-icon-description-container > .label-description {
 	opacity: .7;
 	margin-left: 0.5em;
 	font-size: 0.9em;
 	white-space: pre; /* enable to show labels that include multiple whitespaces */
 }
 
-.monaco-icon-label.italic > .monaco-icon-label-description-container > .label-name,
-.monaco-icon-label.italic > .monaco-icon-label-description-container > .label-description {
+.monaco-icon-label.italic > .monaco-icon-label-container > .monaco-icon-name-container > .label-name,
+.monaco-icon-label.italic > .monaco-icon-description-container > .label-description {
 	font-style: italic;
 }
 
@@ -57,7 +65,6 @@
 	font-size: 90%;
 	font-weight: 600;
 	padding: 0 16px 0 5px;
-	margin-left: auto;
 	text-align: center;
 }
 
diff --git a/src/vs/base/browser/ui/inputbox/inputBox.css b/src/vs/base/browser/ui/inputbox/inputBox.css
index a56bf03fbe..d7bcfe6669 100644
--- a/src/vs/base/browser/ui/inputbox/inputBox.css
+++ b/src/vs/base/browser/ui/inputbox/inputBox.css
@@ -7,12 +7,7 @@
 	position: relative;
 	display: block;
 	padding: 0;
-	-webkit-box-sizing:	border-box;
-	-o-box-sizing:		border-box;
-	-moz-box-sizing:	border-box;
-	-ms-box-sizing:		border-box;
-	box-sizing:			border-box;
-	line-height: auto !important;
+	box-sizing:	border-box;
 
 	/* Customizable */
 	font-size: inherit;
@@ -37,11 +32,7 @@
 
 .monaco-inputbox > .wrapper > .input {
 	display: inline-block;
-	-webkit-box-sizing:	border-box;
-	-o-box-sizing:		border-box;
-	-moz-box-sizing:	border-box;
-	-ms-box-sizing:		border-box;
-	box-sizing:			border-box;
+	box-sizing:	border-box;
 	width: 100%;
 	height: 100%;
 	line-height: inherit;
@@ -58,31 +49,26 @@
 
 .monaco-inputbox > .wrapper > textarea.input {
 	display: block;
-	-ms-overflow-style: none; /* IE 10+ */
-	overflow: -moz-scrollbars-none; /* Firefox */
-	scrollbar-width: none; /* Firefox ^64 */
+	-ms-overflow-style: none; /* IE 10+: hide scrollbars */
+	scrollbar-width: none; /* Firefox: hide scrollbars */
 	outline: none;
 }
 
+.monaco-inputbox > .wrapper > textarea.input::-webkit-scrollbar {
+	display: none; /* Chrome + Safari: hide scrollbar */
+}
+
 .monaco-inputbox > .wrapper > textarea.input.empty {
 	white-space: nowrap;
 }
 
-.monaco-inputbox > .wrapper > textarea.input::-webkit-scrollbar {
-	display: none;
-}
-
 .monaco-inputbox > .wrapper > .mirror {
 	position: absolute;
 	display: inline-block;
 	width: 100%;
 	top: 0;
 	left: 0;
-	-webkit-box-sizing:	border-box;
-	-o-box-sizing:		border-box;
-	-moz-box-sizing:	border-box;
-	-ms-box-sizing:		border-box;
-	box-sizing:			border-box;
+	box-sizing: border-box;
 	white-space: pre-wrap;
 	visibility: hidden;
 	word-wrap: break-word;
@@ -99,11 +85,7 @@
 	overflow: hidden;
 	text-align: left;
 	width: 100%;
-	-webkit-box-sizing:	border-box;
-	-o-box-sizing:		border-box;
-	-moz-box-sizing:	border-box;
-	-ms-box-sizing:		border-box;
-	box-sizing:			border-box;
+	box-sizing:	border-box;
 	padding: 0.4em;
 	font-size: 12px;
 	line-height: 17px;
diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts
index cba0a2751a..d8e0e46aca 100644
--- a/src/vs/base/browser/ui/inputbox/inputBox.ts
+++ b/src/vs/base/browser/ui/inputbox/inputBox.ts
@@ -183,7 +183,7 @@ export class InputBox extends Widget {
 			this.maxHeight = typeof this.options.flexibleMaxHeight === 'number' ? this.options.flexibleMaxHeight : Number.POSITIVE_INFINITY;
 
 			this.mirror = dom.append(wrapper, $('div.mirror'));
-			this.mirror.innerHTML = ' ';
+			this.mirror.innerHTML = ' ';
 
 			this.scrollableElement = new ScrollableElement(this.element, { vertical: ScrollbarVisibility.Auto });
 
@@ -242,6 +242,8 @@ export class InputBox extends Widget {
 			});
 		}
 
+		this.ignoreGesture(this.input);
+
 		setTimeout(() => this.updateMirror(), 0);
 
 		// Support actions
@@ -327,6 +329,7 @@ export class InputBox extends Widget {
 	}
 
 	public disable(): void {
+		this.blur();
 		this.input.disabled = true;
 		this._hideMessage();
 	}
@@ -561,7 +564,7 @@ export class InputBox extends Widget {
 		if (mirrorTextContent) {
 			this.mirror.textContent = value + suffix;
 		} else {
-			this.mirror.innerHTML = ' ';
+			this.mirror.innerHTML = ' ';
 		}
 
 		this.layout();
diff --git a/src/vs/base/browser/ui/list/list.css b/src/vs/base/browser/ui/list/list.css
index b2cbdd591b..6e8e13f9e0 100644
--- a/src/vs/base/browser/ui/list/list.css
+++ b/src/vs/base/browser/ui/list/list.css
@@ -11,12 +11,9 @@
 }
 
 .monaco-list.mouse-support {
-	-webkit-user-select: none;
-	-khtml-user-select: none;
-	-moz-user-select: -moz-none;
-	-ms-user-select: none;
-	-o-user-select: none;
 	user-select: none;
+	-webkit-user-select: none;
+	-ms-user-select: none;
 }
 
 .monaco-list > .monaco-scrollable-element {
@@ -36,10 +33,7 @@
 
 .monaco-list-row {
 	position: absolute;
-	-moz-box-sizing:	border-box;
-	-o-box-sizing:		border-box;
-	-ms-box-sizing:		border-box;
-	box-sizing:			border-box;
+	box-sizing:	border-box;
 	overflow: hidden;
 	width: 100%;
 }
@@ -59,6 +53,10 @@
 	outline: 0 !important;
 }
 
+.monaco-list:focus .monaco-list-row.selected .codicon {
+	color: inherit;
+}
+
 /* Dnd */
 .monaco-drag-image {
 	display: inline-block;
@@ -115,54 +113,28 @@
 }
 
 .monaco-list-type-filter > .controls > * {
+	border: none;
 	box-sizing: border-box;
+	-webkit-appearance: none;
+	-moz-appearance: none;
+	background: none;
 	width: 16px;
 	height: 16px;
-	margin: 0 0 0 2px;
 	flex-shrink: 0;
+	margin: 0;
+	padding: 0;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	cursor: pointer;
+}
+
+.monaco-list-type-filter > .controls > .filter:checked::before {
+	content: "\eb83" !important; /* codicon-list-filter */
 }
 
 .monaco-list-type-filter > .controls > .filter {
-	-webkit-appearance: none;
-	width: 16px;
-	height: 16px;
-	background: url("media/no-filter-light.svg");
-	background-position: 50% 50%;
-	cursor: pointer;
-}
-
-.monaco-list-type-filter > .controls > .filter:checked {
-	background-image: url("media/filter-light.svg");
-}
-
-.vs-dark .monaco-list-type-filter > .controls > .filter {
-	background-image: url("media/no-filter-dark.svg");
-}
-
-.vs-dark .monaco-list-type-filter > .controls > .filter:checked {
-	background-image: url("media/filter-dark.svg");
-}
-
-.hc-black .monaco-list-type-filter > .controls > .filter {
-	background-image: url("media/no-filter-hc.svg");
-}
-
-.hc-black .monaco-list-type-filter > .controls > .filter:checked {
-	background-image: url("media/filter-hc.svg");
-}
-
-.monaco-list-type-filter > .controls > .clear {
-	border: none;
-	background: url("media/close-light.svg");
-	cursor: pointer;
-}
-
-.vs-dark .monaco-list-type-filter > .controls > .clear {
-	background-image: url("media/close-dark.svg");
-}
-
-.hc-black .monaco-list-type-filter > .controls > .clear {
-	background-image: url("media/close-hc.svg");
+	margin-left: 4px;
 }
 
 .monaco-list-type-filter-message {
@@ -191,4 +163,4 @@
 
 .monaco-list-type-filter.dragging {
 	cursor: grabbing;
-}
\ No newline at end of file
+}
diff --git a/src/vs/base/browser/ui/list/list.ts b/src/vs/base/browser/ui/list/list.ts
index 9f38f5e1ee..cb0918724b 100644
--- a/src/vs/base/browser/ui/list/list.ts
+++ b/src/vs/base/browser/ui/list/list.ts
@@ -103,10 +103,11 @@ export const ListDragOverReactions = {
 
 export interface IListDragAndDrop {
 	getDragURI(element: T): string | null;
-	getDragLabel?(elements: T[]): string | undefined;
+	getDragLabel?(elements: T[], originalEvent: DragEvent): string | undefined;
 	onDragStart?(data: IDragAndDropData, originalEvent: DragEvent): void;
 	onDragOver(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | IListDragOverReaction;
 	drop(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void;
+	onDragEnd?(originalEvent: DragEvent): void;
 }
 
 export class ListError extends Error {
diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts
index 3ecb53271a..aae604189a 100644
--- a/src/vs/base/browser/ui/list/listView.ts
+++ b/src/vs/base/browser/ui/list/listView.ts
@@ -20,6 +20,7 @@ import { Range, IRange } from 'vs/base/common/range';
 import { equals, distinct } from 'vs/base/common/arrays';
 import { DataTransfers, StaticDND, IDragAndDropData } from 'vs/base/browser/dnd';
 import { disposableTimeout, Delayer } from 'vs/base/common/async';
+import { isFirefox } from 'vs/base/browser/browser';
 
 interface IItem {
 	readonly id: string;
@@ -73,9 +74,10 @@ const DefaultOptions = {
 	horizontalScrolling: false
 };
 
-export class ElementsDragAndDropData implements IDragAndDropData {
+export class ElementsDragAndDropData implements IDragAndDropData {
 
 	readonly elements: T[];
+	context: TContext | undefined;
 
 	constructor(elements: T[]) {
 		this.elements = elements;
@@ -83,7 +85,7 @@ export class ElementsDragAndDropData implements IDragAndDropData {
 
 	update(): void { }
 
-	getData(): any {
+	getData(): T[] {
 		return this.elements;
 	}
 }
@@ -98,7 +100,7 @@ export class ExternalElementsDragAndDropData implements IDragAndDropData {
 
 	update(): void { }
 
-	getData(): any {
+	getData(): T[] {
 		return this.elements;
 	}
 }
@@ -233,7 +235,7 @@ export class ListView implements ISpliceable, IDisposable {
 
 		this.rowsContainer = document.createElement('div');
 		this.rowsContainer.className = 'monaco-list-rows';
-		this.rowsContainer.style.willChange = 'transform';
+		this.rowsContainer.style.transform = 'translate3d(0px, 0px, 0px)';
 		this.disposables.add(Gesture.addTarget(this.rowsContainer));
 
 		this.scrollableElement = this.disposables.add(new ScrollableElement(this.rowsContainer, {
@@ -595,7 +597,7 @@ export class ListView implements ISpliceable, IDisposable {
 			return;
 		}
 
-		item.row.domNode.style.width = 'fit-content';
+		item.row.domNode.style.width = isFirefox ? '-moz-fit-content' : 'fit-content';
 		item.width = DOM.getContentWidth(item.row.domNode);
 		const style = window.getComputedStyle(item.row.domNode);
 
@@ -765,7 +767,7 @@ export class ListView implements ISpliceable, IDisposable {
 			let label: string | undefined;
 
 			if (this.dnd.getDragLabel) {
-				label = this.dnd.getDragLabel(elements);
+				label = this.dnd.getDragLabel(elements, event);
 			}
 
 			if (typeof label === 'undefined') {
@@ -845,10 +847,6 @@ export class ListView implements ISpliceable, IDisposable {
 		feedback = distinct(feedback).filter(i => i >= -1 && i < this.length).sort();
 		feedback = feedback[0] === -1 ? [-1] : feedback;
 
-		if (feedback.length === 0) {
-			throw new Error('Invalid empty feedback list');
-		}
-
 		if (equalsDragFeedback(this.currentDragFeedback, feedback)) {
 			return true;
 		}
@@ -858,7 +856,11 @@ export class ListView implements ISpliceable, IDisposable {
 
 		if (feedback[0] === -1) { // entire list feedback
 			DOM.addClass(this.domNode, 'drop-target');
-			this.currentDragFeedbackDisposable = toDisposable(() => DOM.removeClass(this.domNode, 'drop-target'));
+			DOM.addClass(this.rowsContainer, 'drop-target');
+			this.currentDragFeedbackDisposable = toDisposable(() => {
+				DOM.removeClass(this.domNode, 'drop-target');
+				DOM.removeClass(this.rowsContainer, 'drop-target');
+			});
 		} else {
 			for (const index of feedback) {
 				const item = this.items[index]!;
@@ -909,12 +911,16 @@ export class ListView implements ISpliceable, IDisposable {
 		this.dnd.drop(dragData, event.element, event.index, event.browserEvent);
 	}
 
-	private onDragEnd(): void {
+	private onDragEnd(event: DragEvent): void {
 		this.canDrop = false;
 		this.teardownDragAndDropScrollTopAnimation();
 		this.clearDragOverFeedback();
 		this.currentDragData = undefined;
 		StaticDND.CurrentDragAndDropData = undefined;
+
+		if (this.dnd.onDragEnd) {
+			this.dnd.onDragEnd(event);
+		}
 	}
 
 	private clearDragOverFeedback(): void {
diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts
index 2054db0e1e..4f4aa38b9b 100644
--- a/src/vs/base/browser/ui/list/listWidget.ts
+++ b/src/vs/base/browser/ui/list/listWidget.ts
@@ -702,16 +702,27 @@ export interface IAccessibilityProvider {
 	 * https://www.w3.org/TR/wai-aria/#aria-level
 	 */
 	getAriaLevel?(element: T): number | undefined;
+
+	onDidChangeActiveDescendant?: Event;
+	getActiveDescendantId?(element: T): string | undefined;
 }
 
 export class DefaultStyleController implements IStyleController {
 
-	constructor(private styleElement: HTMLStyleElement, private selectorSuffix?: string) { }
+	constructor(private styleElement: HTMLStyleElement, private selectorSuffix: string) { }
 
 	style(styles: IListStyles): void {
-		const suffix = this.selectorSuffix ? `.${this.selectorSuffix}` : '';
+		const suffix = this.selectorSuffix && `.${this.selectorSuffix}`;
 		const content: string[] = [];
 
+		if (styles.listBackground) {
+			if (styles.listBackground.isOpaque()) {
+				content.push(`.monaco-list${suffix} .monaco-list-rows { background: ${styles.listBackground}; }`);
+			} else if (!platform.isMacintosh) { // subpixel AA doesn't exist in macOS
+				console.warn(`List with id '${this.selectorSuffix}' was styled with a non-opaque background color. This will break sub-pixel antialiasing.`);
+			}
+		}
+
 		if (styles.listFocusBackground) {
 			content.push(`.monaco-list${suffix}:focus .monaco-list-row.focused { background-color: ${styles.listFocusBackground}; }`);
 			content.push(`.monaco-list${suffix}:focus .monaco-list-row.focused:hover { background-color: ${styles.listFocusBackground}; }`); // overwrite :hover style in this case!
@@ -788,6 +799,7 @@ export class DefaultStyleController implements IStyleController {
 		if (styles.listDropBackground) {
 			content.push(`
 				.monaco-list${suffix}.drop-target,
+				.monaco-list${suffix} .monaco-list-rows.drop-target,
 				.monaco-list${suffix} .monaco-list-row.drop-target { background-color: ${styles.listDropBackground} !important; color: inherit !important; }
 			`);
 		}
@@ -815,7 +827,7 @@ export class DefaultStyleController implements IStyleController {
 	}
 }
 
-export interface IListOptions extends IListStyles {
+export interface IListOptions {
 	readonly identityProvider?: IIdentityProvider;
 	readonly dnd?: IListDragAndDrop;
 	readonly enableKeyboardNavigation?: boolean;
@@ -828,7 +840,7 @@ export interface IListOptions extends IListStyles {
 	readonly multipleSelectionSupport?: boolean;
 	readonly multipleSelectionController?: IMultipleSelectionController;
 	readonly openController?: IOpenController;
-	readonly styleController?: IStyleController;
+	readonly styleController?: (suffix: string) => IStyleController;
 	readonly accessibilityProvider?: IAccessibilityProvider;
 
 	// list view options
@@ -842,6 +854,7 @@ export interface IListOptions extends IListStyles {
 }
 
 export interface IListStyles {
+	listBackground?: Color;
 	listFocusBackground?: Color;
 	listFocusForeground?: Color;
 	listActiveSelectionBackground?: Color;
@@ -1062,9 +1075,9 @@ class ListViewDragAndDrop implements IListViewDragAndDrop {
 		return this.dnd.getDragURI(element);
 	}
 
-	getDragLabel?(elements: T[]): string | undefined {
+	getDragLabel?(elements: T[], originalEvent: DragEvent): string | undefined {
 		if (this.dnd.getDragLabel) {
-			return this.dnd.getDragLabel(elements);
+			return this.dnd.getDragLabel(elements, originalEvent);
 		}
 
 		return undefined;
@@ -1080,6 +1093,12 @@ class ListViewDragAndDrop implements IListViewDragAndDrop {
 		return this.dnd.onDragOver(data, targetElement, targetIndex, originalEvent);
 	}
 
+	onDragEnd(originalEvent: DragEvent): void {
+		if (this.dnd.onDragEnd) {
+			this.dnd.onDragEnd(originalEvent);
+		}
+	}
+
 	drop(data: IDragAndDropData, targetElement: T, targetIndex: number, originalEvent: DragEvent): void {
 		this.dnd.drop(data, targetElement, targetIndex, originalEvent);
 	}
@@ -1097,9 +1116,9 @@ export class List implements ISpliceable, IDisposable {
 	private eventBufferer = new EventBufferer();
 	private view: ListView;
 	private spliceable: ISpliceable;
-	private styleElement: HTMLStyleElement;
 	private styleController: IStyleController;
 	private typeLabelController?: TypeLabelController;
+	private accessibilityProvider?: IAccessibilityProvider;
 
 	protected readonly disposables = new DisposableStore();
 
@@ -1185,8 +1204,14 @@ export class List implements ISpliceable, IDisposable {
 
 		const baseRenderers: IListRenderer[] = [this.focus.renderer, this.selection.renderer];
 
-		if (_options.accessibilityProvider) {
-			baseRenderers.push(new AccessibiltyRenderer(_options.accessibilityProvider));
+		this.accessibilityProvider = _options.accessibilityProvider;
+
+		if (this.accessibilityProvider) {
+			baseRenderers.push(new AccessibiltyRenderer(this.accessibilityProvider));
+
+			if (this.accessibilityProvider.onDidChangeActiveDescendant) {
+				this.accessibilityProvider.onDidChangeActiveDescendant(this.onDidChangeActiveDescendant, this, this.disposables);
+			}
 		}
 
 		renderers = renderers.map(r => new PipelineRenderer(r.templateId, [...baseRenderers, r]));
@@ -1198,11 +1223,18 @@ export class List implements ISpliceable, IDisposable {
 
 		this.view = new ListView(container, virtualDelegate, renderers, viewOptions);
 
-		this.updateAriaRole();
+		if (typeof _options.ariaRole !== 'string') {
+			this.view.domNode.setAttribute('role', ListAriaRootRole.TREE);
+		} else {
+			this.view.domNode.setAttribute('role', _options.ariaRole);
+		}
 
-		this.styleElement = DOM.createStyleSheet(this.view.domNode);
-
-		this.styleController = _options.styleController || new DefaultStyleController(this.styleElement, this.view.domId);
+		if (_options.styleController) {
+			this.styleController = _options.styleController(this.view.domId);
+		} else {
+			const styleElement = DOM.createStyleSheet(this.view.domNode);
+			this.styleController = new DefaultStyleController(styleElement, this.view.domId);
+		}
 
 		this.spliceable = new CombinedSpliceable([
 			new TraitSpliceable(this.focus, this.view, _options.identityProvider),
@@ -1239,8 +1271,6 @@ export class List implements ISpliceable, IDisposable {
 		if (_options.ariaLabel) {
 			this.view.domNode.setAttribute('aria-label', localize('aria list', "{0}. Use the navigation keys to navigate.", _options.ariaLabel));
 		}
-
-		this.style(_options);
 	}
 
 	protected createMouseController(options: IListOptions): MouseController {
@@ -1603,23 +1633,23 @@ export class List implements ISpliceable, IDisposable {
 
 	private _onFocusChange(): void {
 		const focus = this.focus.get();
-
-		if (focus.length > 0) {
-			this.view.domNode.setAttribute('aria-activedescendant', this.view.getElementDomId(focus[0]));
-		} else {
-			this.view.domNode.removeAttribute('aria-activedescendant');
-		}
-
-		this.updateAriaRole();
-
 		DOM.toggleClass(this.view.domNode, 'element-focused', focus.length > 0);
+		this.onDidChangeActiveDescendant();
 	}
 
-	private updateAriaRole(): void {
-		if (typeof this.options.ariaRole !== 'string') {
-			this.view.domNode.setAttribute('role', ListAriaRootRole.TREE);
+	private onDidChangeActiveDescendant(): void {
+		const focus = this.focus.get();
+
+		if (focus.length > 0) {
+			let id: string | undefined;
+
+			if (this.accessibilityProvider?.getActiveDescendantId) {
+				id = this.accessibilityProvider.getActiveDescendantId(this.view.element(focus[0]));
+			}
+
+			this.view.domNode.setAttribute('aria-activedescendant', id || this.view.getElementDomId(focus[0]));
 		} else {
-			this.view.domNode.setAttribute('role', this.options.ariaRole);
+			this.view.domNode.removeAttribute('aria-activedescendant');
 		}
 	}
 
diff --git a/src/vs/base/browser/ui/list/media/close-dark.svg b/src/vs/base/browser/ui/list/media/close-dark.svg
deleted file mode 100644
index 7305a8f099..0000000000
--- a/src/vs/base/browser/ui/list/media/close-dark.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/list/media/close-hc.svg b/src/vs/base/browser/ui/list/media/close-hc.svg
deleted file mode 100644
index 7305a8f099..0000000000
--- a/src/vs/base/browser/ui/list/media/close-hc.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/list/media/close-light.svg b/src/vs/base/browser/ui/list/media/close-light.svg
deleted file mode 100644
index ecddcd665b..0000000000
--- a/src/vs/base/browser/ui/list/media/close-light.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/list/media/filter-dark.svg b/src/vs/base/browser/ui/list/media/filter-dark.svg
deleted file mode 100644
index 46c35f4374..0000000000
--- a/src/vs/base/browser/ui/list/media/filter-dark.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/list/media/filter-hc.svg b/src/vs/base/browser/ui/list/media/filter-hc.svg
deleted file mode 100644
index d7b6bdd392..0000000000
--- a/src/vs/base/browser/ui/list/media/filter-hc.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/list/media/filter-light.svg b/src/vs/base/browser/ui/list/media/filter-light.svg
deleted file mode 100644
index 2550d80cb7..0000000000
--- a/src/vs/base/browser/ui/list/media/filter-light.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/list/media/no-filter-dark.svg b/src/vs/base/browser/ui/list/media/no-filter-dark.svg
deleted file mode 100644
index 6fc07d81a5..0000000000
--- a/src/vs/base/browser/ui/list/media/no-filter-dark.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/list/media/no-filter-hc.svg b/src/vs/base/browser/ui/list/media/no-filter-hc.svg
deleted file mode 100644
index 6fc07d81a5..0000000000
--- a/src/vs/base/browser/ui/list/media/no-filter-hc.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/list/media/no-filter-light.svg b/src/vs/base/browser/ui/list/media/no-filter-light.svg
deleted file mode 100644
index 3608b15d29..0000000000
--- a/src/vs/base/browser/ui/list/media/no-filter-light.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/menu/menu.css b/src/vs/base/browser/ui/menu/menu.css
index b0e3961a14..8547e8b39a 100644
--- a/src/vs/base/browser/ui/menu/menu.css
+++ b/src/vs/base/browser/ui/menu/menu.css
@@ -15,7 +15,6 @@
 .monaco-menu .monaco-action-bar.vertical .action-item {
 	padding: 0;
 	transform: none;
-	display: -ms-flexbox;
 	display: flex;
 }
 
@@ -24,9 +23,7 @@
 }
 
 .monaco-menu .monaco-action-bar.vertical .action-menu-item {
-	-ms-flex: 1 1 auto;
 	flex: 1 1 auto;
-	display: -ms-flexbox;
 	display: flex;
 	height: 2em;
 	align-items: center;
@@ -34,7 +31,6 @@
 }
 
 .monaco-menu .monaco-action-bar.vertical .action-label {
-	-ms-flex: 1 1 auto;
 	flex: 1 1 auto;
 	text-decoration: none;
 	padding: 0 1em;
@@ -46,7 +42,6 @@
 .monaco-menu .monaco-action-bar.vertical .keybinding,
 .monaco-menu .monaco-action-bar.vertical .submenu-indicator {
 	display: inline-block;
-	-ms-flex: 2 1 auto;
 	flex: 2 1 auto;
 	padding: 0 1em;
 	text-align: right;
@@ -56,8 +51,8 @@
 
 .monaco-menu .monaco-action-bar.vertical .submenu-indicator {
 	height: 100%;
-	-webkit-mask: url('submenu.svg') no-repeat 90% 50%/13px 13px;
 	mask: url('submenu.svg') no-repeat 90% 50%/13px 13px;
+	-webkit-mask: url('submenu.svg') no-repeat 90% 50%/13px 13px;
 }
 
 .monaco-menu .monaco-action-bar.vertical .action-item.disabled .keybinding,
@@ -67,11 +62,7 @@
 
 .monaco-menu .monaco-action-bar.vertical .action-label:not(.separator) {
 	display: inline-block;
-	-webkit-box-sizing:	border-box;
-	-o-box-sizing:		border-box;
-	-moz-box-sizing:	border-box;
-	-ms-box-sizing:		border-box;
-	box-sizing:			border-box;
+	box-sizing: border-box;
 	margin: 0;
 }
 
@@ -80,7 +71,6 @@
 	overflow: visible;
 }
 
-
 .monaco-menu .monaco-action-bar.vertical .action-item .monaco-submenu {
 	position: absolute;
 }
@@ -104,8 +94,8 @@
 .monaco-menu .monaco-action-bar.vertical .menu-item-check {
 	position: absolute;
 	visibility: hidden;
-	-webkit-mask: url('check.svg') no-repeat 50% 56%/15px 15px;
 	mask: url('check.svg') no-repeat 50% 56%/15px 15px;
+	-webkit-mask: url('check.svg') no-repeat 50% 56%/15px 15px;
 	width: 1em;
 	height: 100%;
 }
@@ -119,10 +109,6 @@
 .context-view.monaco-menu-container {
 	outline: 0;
 	border: none;
-	-webkit-animation: fadeIn 0.083s linear;
-	-o-animation: fadeIn 0.083s linear;
-	-moz-animation: fadeIn 0.083s linear;
-	-ms-animation: fadeIn 0.083s linear;
 	animation: fadeIn 0.083s linear;
 }
 
@@ -173,6 +159,10 @@
 	outline: 0;
 }
 
+.menubar.compact {
+	flex-shrink: 0;
+}
+
 .menubar.compact > .menubar-menu-button {
 	width: 100%;
 	height: 100%;
@@ -204,22 +194,24 @@
 }
 
 .menubar.compact .toolbar-toggle-more {
+	position: absolute;
+	left: 0px;
+	top: 0px;
 	background-position: center;
 	background-repeat: no-repeat;
 	background-size: 16px;
 	cursor: pointer;
 	width: 100%;
-	height: 100%;
 }
 
 .menubar .toolbar-toggle-more {
 	display: inline-block;
 	padding: 0;
-	-webkit-mask: url('ellipsis.svg') no-repeat 50% 55%/14px 14px;
 	mask: url('ellipsis.svg') no-repeat 50% 55%/14px 14px;
+	-webkit-mask: url('ellipsis.svg') no-repeat 50% 55%/14px 14px;
 }
 
 .menubar.compact .toolbar-toggle-more {
-	-webkit-mask: url('menu.svg') no-repeat 50% 55%/16px 16px;
 	mask: url('menu.svg') no-repeat 50% 55%/16px 16px;
+	-webkit-mask: url('menu.svg') no-repeat 50% 55%/16px 16px;
 }
diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts
index 528be4b3f5..8d08e4ae84 100644
--- a/src/vs/base/browser/ui/menu/menu.ts
+++ b/src/vs/base/browser/ui/menu/menu.ts
@@ -6,7 +6,7 @@
 import 'vs/css!./menu';
 import * as nls from 'vs/nls';
 import * as strings from 'vs/base/common/strings';
-import { IActionRunner, IAction, Action } from 'vs/base/common/actions';
+import { IActionRunner, IAction, Action, IActionViewItem } from 'vs/base/common/actions';
 import { ActionBar, IActionViewItemProvider, ActionsOrientation, Separator, ActionViewItem, IActionViewItemOptions, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
 import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes';
 import { addClass, EventType, EventHelper, EventLike, removeTabIndexAndUpdateFocus, isAncestor, hasClass, addDisposableListener, removeClass, append, $, addClasses, removeClasses } from 'vs/base/browser/dom';
@@ -205,8 +205,8 @@ export class Menu extends ActionBar {
 		container.appendChild(this.scrollableElement.getDomNode());
 		this.scrollableElement.scanDomNode();
 
-		this.viewItems.filter(item => !(item instanceof MenuSeparatorActionViewItem)).forEach((item: BaseMenuActionViewItem, index: number, array: any[]) => {
-			item.updatePositionInSet(index + 1, array.length);
+		this.viewItems.filter(item => !(item instanceof MenuSeparatorActionViewItem)).forEach((item: IActionViewItem, index: number, array: any[]) => {
+			(item as BaseMenuActionViewItem).updatePositionInSet(index + 1, array.length);
 		});
 	}
 
@@ -215,7 +215,7 @@ export class Menu extends ActionBar {
 
 		const fgColor = style.foregroundColor ? `${style.foregroundColor}` : '';
 		const bgColor = style.backgroundColor ? `${style.backgroundColor}` : '';
-		const border = style.borderColor ? `2px solid ${style.borderColor}` : '';
+		const border = style.borderColor ? `1px solid ${style.borderColor}` : '';
 		const shadow = style.shadowColor ? `0 2px 4px ${style.shadowColor}` : '';
 
 		container.style.border = border;
@@ -661,7 +661,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
 		if (this.item) {
 			addClass(this.item, 'monaco-submenu-item');
 			this.item.setAttribute('aria-haspopup', 'true');
-
+			this.updateAriaExpanded('false');
 			this.submenuIndicator = append(this.item, $('span.submenu-indicator'));
 			this.submenuIndicator.setAttribute('aria-hidden', 'true');
 		}
@@ -726,7 +726,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
 		if (this.parentData.submenu && (force || (this.parentData.submenu !== this.mysubmenu))) {
 			this.parentData.submenu.dispose();
 			this.parentData.submenu = undefined;
-
+			this.updateAriaExpanded('false');
 			if (this.submenuContainer) {
 				this.submenuDisposables.clear();
 				this.submenuContainer = undefined;
@@ -740,6 +740,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
 		}
 
 		if (!this.parentData.submenu) {
+			this.updateAriaExpanded('true');
 			this.submenuContainer = append(this.element, $('div.monaco-submenu'));
 			addClasses(this.submenuContainer, 'menubar-menu-items-holder', 'context-view');
 
@@ -778,13 +779,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
 
 					this.parentData.parent.focus();
 
-					if (this.parentData.submenu) {
-						this.parentData.submenu.dispose();
-						this.parentData.submenu = undefined;
-					}
-
-					this.submenuDisposables.clear();
-					this.submenuContainer = undefined;
+					this.cleanupExistingSubmenu(true);
 				}
 			}));
 
@@ -799,13 +794,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
 			this.submenuDisposables.add(this.parentData.submenu.onDidCancel(() => {
 				this.parentData.parent.focus();
 
-				if (this.parentData.submenu) {
-					this.parentData.submenu.dispose();
-					this.parentData.submenu = undefined;
-				}
-
-				this.submenuDisposables.clear();
-				this.submenuContainer = undefined;
+				this.cleanupExistingSubmenu(true);
 			}));
 
 			this.parentData.submenu.focus(selectFirstItem);
@@ -816,6 +805,12 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
 		}
 	}
 
+	private updateAriaExpanded(value: string): void {
+		if (this.item) {
+			this.item?.setAttribute('aria-expanded', value);
+		}
+	}
+
 	protected applyStyle(): void {
 		super.applyStyle();
 
diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts
index 0d60556918..73e591815b 100644
--- a/src/vs/base/browser/ui/menu/menubar.ts
+++ b/src/vs/base/browser/ui/menu/menubar.ts
@@ -309,7 +309,7 @@ export class MenuBar extends Disposable {
 
 	createOverflowMenu(): void {
 		const label = this.options.compactMode !== undefined ? nls.localize('mAppMenu', 'Application Menu') : nls.localize('mMore', "...");
-		const buttonElement = $('div.menubar-menu-button', { 'role': 'menuitem', 'tabindex': -1, 'aria-label': label, 'aria-haspopup': true });
+		const buttonElement = $('div.menubar-menu-button', { 'role': 'menuitem', 'tabindex': -1, 'aria-label': label, 'title': label, 'aria-haspopup': true });
 		const titleElement = $('div.menubar-menu-title.toolbar-toggle-more', { 'role': 'none', 'aria-hidden': true });
 
 		buttonElement.appendChild(titleElement);
diff --git a/src/vs/base/browser/ui/progressbar/progressbar.css b/src/vs/base/browser/ui/progressbar/progressbar.css
index 032d7b4dd9..2a950f9aaf 100644
--- a/src/vs/base/browser/ui/progressbar/progressbar.css
+++ b/src/vs/base/browser/ui/progressbar/progressbar.css
@@ -35,19 +35,7 @@
 	animation-duration: 4s;
 	animation-iteration-count: infinite;
 	animation-timing-function: linear;
-	-ms-animation-name: progress;
-	-ms-animation-duration: 4s;
-	-ms-animation-iteration-count: infinite;
-	-ms-animation-timing-function: linear;
-	-webkit-animation-name: progress;
-	-webkit-animation-duration: 4s;
-	-webkit-animation-iteration-count: infinite;
-	-webkit-animation-timing-function: linear;
-	-moz-animation-name: progress;
-	-moz-animation-duration: 4s;
-	-moz-animation-iteration-count: infinite;
-	-moz-animation-timing-function: linear;
-	will-change: transform;
+	transform: translate3d(0px, 0px, 0px);
 }
 
 /**
@@ -58,6 +46,3 @@
  * 100%: 50 * 100 - 50 (do not overflow): 4950%
  */
 @keyframes progress { from { transform: translateX(0%) scaleX(1) } 50% { transform: translateX(2500%) scaleX(3) } to { transform: translateX(4950%) scaleX(1) } }
-@-ms-keyframes progress { from { transform: translateX(0%) scaleX(1) }	50% { transform: translateX(2500%) scaleX(3) } to { transform: translateX(4950%) scaleX(1) } }
-@-webkit-keyframes progress { from { transform: translateX(0%) scaleX(1) }	50% { transform: translateX(2500%) scaleX(3) } to { transform: translateX(4950%) scaleX(1) } }
-@-moz-keyframes progress { from { transform: translateX(0%) scaleX(1) }	50% { transform: translateX(2500%) scaleX(3) } to { transform: translateX(4950%) scaleX(1) } }
\ No newline at end of file
diff --git a/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts b/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts
index e50958988b..7d8b18d59c 100644
--- a/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts
+++ b/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts
@@ -99,6 +99,7 @@ export abstract class AbstractScrollbar extends Widget {
 			this.slider.setHeight(height);
 		}
 		this.slider.setLayerHinting(true);
+		this.slider.setContain('strict');
 
 		this.domNode.domNode.appendChild(this.slider.domNode);
 
diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts
index 7f3b977818..c59bc26f03 100644
--- a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts
+++ b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts
@@ -64,7 +64,7 @@ export class MouseWheelClassifier {
 			return false;
 		}
 
-		// 0.5 * last + 0.25 * before last + 0.125 * before before last + ...
+		// 0.5 * last + 0.25 * 2nd last + 0.125 * 3rd last + ...
 		let remainingInfluence = 1;
 		let score = 0;
 		let iteration = 1;
diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.css b/src/vs/base/browser/ui/selectBox/selectBoxCustom.css
index 24d2ad857b..40f7dce9ba 100644
--- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.css
+++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.css
@@ -16,11 +16,7 @@
 
 .monaco-select-box-dropdown-container {
 	display: none;
-	-webkit-box-sizing:	border-box;
-	-o-box-sizing:		border-box;
-	-moz-box-sizing:	border-box;
-	-ms-box-sizing:		border-box;
-	box-sizing:			border-box;
+	box-sizing:	border-box;
 }
 
 .monaco-select-box-dropdown-container > .select-box-details-pane > .select-box-description-markdown * {
@@ -55,11 +51,7 @@
 	padding-right: 1px;
 	width: 100%;
 	overflow: hidden;
-	-webkit-box-sizing:	border-box;
-	-o-box-sizing:		border-box;
-	-moz-box-sizing:	border-box;
-	-ms-box-sizing:		border-box;
-	box-sizing:			border-box;
+	box-sizing:	border-box;
 }
 
 .monaco-select-box-dropdown-container > .select-box-details-pane {
diff --git a/src/vs/base/browser/ui/selectBox/selectBoxNative.ts b/src/vs/base/browser/ui/selectBox/selectBoxNative.ts
index 09b3f34254..9ca0996221 100644
--- a/src/vs/base/browser/ui/selectBox/selectBoxNative.ts
+++ b/src/vs/base/browser/ui/selectBox/selectBoxNative.ts
@@ -10,6 +10,7 @@ import * as dom from 'vs/base/browser/dom';
 import * as arrays from 'vs/base/common/arrays';
 import { ISelectBoxDelegate, ISelectOptionItem, ISelectBoxOptions, ISelectBoxStyles, ISelectData } from 'vs/base/browser/ui/selectBox/selectBox';
 import { isMacintosh } from 'vs/base/common/platform';
+import { Gesture, EventType } from 'vs/base/browser/touch';
 
 export class SelectBoxNative extends Disposable implements ISelectBoxDelegate {
 
@@ -44,6 +45,12 @@ export class SelectBoxNative extends Disposable implements ISelectBoxDelegate {
 	}
 
 	private registerListeners() {
+		this._register(Gesture.addTarget(this.selectElement));
+		[EventType.Tap].forEach(eventType => {
+			this._register(dom.addDisposableListener(this.selectElement, eventType, (e) => {
+				this.selectElement.focus();
+			}));
+		});
 
 		this._register(dom.addStandardDisposableListener(this.selectElement, 'change', (e) => {
 			this.selectElement.title = e.target.value;
diff --git a/src/vs/base/browser/ui/splitview/panelview.css b/src/vs/base/browser/ui/splitview/panelview.css
deleted file mode 100644
index 9ded1c239b..0000000000
--- a/src/vs/base/browser/ui/splitview/panelview.css
+++ /dev/null
@@ -1,100 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-.monaco-panel-view {
-	width: 100%;
-	height: 100%;
-}
-
-.monaco-panel-view .panel {
-	overflow: hidden;
-	width: 100%;
-	height: 100%;
-	display: flex;
-	flex-direction: column;
-}
-
-.monaco-panel-view .panel > .panel-header {
-	font-size: 11px;
-	font-weight: bold;
-	text-transform: uppercase;
-	overflow: hidden;
-	display: flex;
-	cursor: pointer;
-}
-
-.monaco-panel-view .panel > .panel-header > .twisties {
-	width: 20px;
-	display: flex;
-	align-items: center;
-	justify-content: center;
-	transform-origin: center;
-	color: inherit;
-	flex-shrink: 0;
-}
-
-.monaco-panel-view .panel > .panel-header.expanded > .twisties::before {
-	transform: rotate(90deg);
-}
-
-/* TODO: actions should be part of the panel, but they aren't yet */
-.monaco-panel-view .panel > .panel-header > .actions {
-	display: none;
-	flex: 1;
-}
-
-/* TODO: actions should be part of the panel, but they aren't yet */
-.monaco-panel-view .panel:hover > .panel-header.expanded > .actions,
-.monaco-panel-view .panel > .panel-header.actions-always-visible.expanded > .actions,
-.monaco-panel-view .panel > .panel-header.focused.expanded > .actions {
-	display: initial;
-}
-
-/* TODO: actions should be part of the panel, but they aren't yet */
-.monaco-panel-view .panel > .panel-header > .actions .action-label.icon,
-.monaco-panel-view .panel > .panel-header > .actions .action-label.codicon {
-	width: 28px;
-	height: 22px;
-	background-size: 16px;
-	background-position: center center;
-	background-repeat: no-repeat;
-	margin-right: 0;
-	display: flex;
-	align-items: center;
-	justify-content: center;
-	color: inherit;
-}
-
-/* Bold font style does not go well with CJK fonts */
-.monaco-panel-view:lang(zh-Hans) .panel > .panel-header,
-.monaco-panel-view:lang(zh-Hant) .panel > .panel-header,
-.monaco-panel-view:lang(ja) .panel > .panel-header,
-.monaco-panel-view:lang(ko) .panel > .panel-header {
-	font-weight: normal;
-}
-
-.monaco-panel-view .panel > .panel-header.hidden {
-	display: none;
-}
-
-.monaco-panel-view .panel > .panel-body {
-	overflow: hidden;
-	flex: 1;
-}
-
-/* Animation */
-
-.monaco-panel-view.animated .split-view-view {
-	transition-duration: 0.15s;
-	transition-timing-function: ease-out;
-}
-
-.monaco-panel-view.animated.vertical .split-view-view {
-	transition-property: height;
-}
-
-.monaco-panel-view.animated.horizontal .split-view-view {
-	transition-property: width;
-}
diff --git a/src/vs/base/browser/ui/splitview/paneview.css b/src/vs/base/browser/ui/splitview/paneview.css
new file mode 100644
index 0000000000..add6aa45e9
--- /dev/null
+++ b/src/vs/base/browser/ui/splitview/paneview.css
@@ -0,0 +1,101 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+.monaco-pane-view {
+	width: 100%;
+	height: 100%;
+}
+
+.monaco-pane-view .pane {
+	overflow: hidden;
+	width: 100%;
+	height: 100%;
+	display: flex;
+	flex-direction: column;
+}
+
+.monaco-pane-view .pane > .pane-header {
+	font-size: 11px;
+	font-weight: bold;
+	text-transform: uppercase;
+	overflow: hidden;
+	display: flex;
+	cursor: pointer;
+	align-items: center;
+}
+
+.monaco-pane-view .pane > .pane-header > .twisties {
+	width: 20px;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	transform-origin: center;
+	color: inherit;
+	flex-shrink: 0;
+}
+
+.monaco-pane-view .pane > .pane-header.expanded > .twisties::before {
+	transform: rotate(90deg);
+}
+
+/* TODO: actions should be part of the pane, but they aren't yet */
+.monaco-pane-view .pane > .pane-header > .actions {
+	display: none;
+	flex: 1;
+}
+
+/* TODO: actions should be part of the pane, but they aren't yet */
+.monaco-pane-view .pane:hover > .pane-header.expanded > .actions,
+.monaco-pane-view .pane > .pane-header.actions-always-visible.expanded > .actions,
+.monaco-pane-view .pane > .pane-header.focused.expanded > .actions {
+	display: initial;
+}
+
+/* TODO: actions should be part of the pane, but they aren't yet */
+.monaco-pane-view .pane > .pane-header > .actions .action-label.icon,
+.monaco-pane-view .pane > .pane-header > .actions .action-label.codicon {
+	width: 28px;
+	height: 22px;
+	background-size: 16px;
+	background-position: center center;
+	background-repeat: no-repeat;
+	margin-right: 0;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	color: inherit;
+}
+
+/* Bold font style does not go well with CJK fonts */
+.monaco-pane-view:lang(zh-Hans) .pane > .pane-header,
+.monaco-pane-view:lang(zh-Hant) .pane > .pane-header,
+.monaco-pane-view:lang(ja) .pane > .pane-header,
+.monaco-pane-view:lang(ko) .pane > .pane-header {
+	font-weight: normal;
+}
+
+.monaco-pane-view .pane > .pane-header.hidden {
+	display: none;
+}
+
+.monaco-pane-view .pane > .pane-body {
+	overflow: hidden;
+	flex: 1;
+}
+
+/* Animation */
+
+.monaco-pane-view.animated .split-view-view {
+	transition-duration: 0.15s;
+	transition-timing-function: ease-out;
+}
+
+.monaco-pane-view.animated.vertical .split-view-view {
+	transition-property: height;
+}
+
+.monaco-pane-view.animated.horizontal .split-view-view {
+	transition-property: width;
+}
diff --git a/src/vs/base/browser/ui/splitview/panelview.ts b/src/vs/base/browser/ui/splitview/paneview.ts
similarity index 70%
rename from src/vs/base/browser/ui/splitview/panelview.ts
rename to src/vs/base/browser/ui/splitview/paneview.ts
index 9e3bd31ca4..54b84489f2 100644
--- a/src/vs/base/browser/ui/splitview/panelview.ts
+++ b/src/vs/base/browser/ui/splitview/paneview.ts
@@ -3,25 +3,27 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
-import 'vs/css!./panelview';
+import 'vs/css!./paneview';
 import { IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
 import { Event, Emitter } from 'vs/base/common/event';
 import { domEvent } from 'vs/base/browser/event';
 import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
 import { KeyCode } from 'vs/base/common/keyCodes';
-import { $, append, addClass, removeClass, toggleClass, trackFocus } from 'vs/base/browser/dom';
+import { $, append, addClass, removeClass, toggleClass, trackFocus, EventHelper } from 'vs/base/browser/dom';
 import { firstIndex } from 'vs/base/common/arrays';
 import { Color, RGBA } from 'vs/base/common/color';
 import { SplitView, IView } from './splitview';
+import { isFirefox } from 'vs/base/browser/browser';
+import { DataTransfers } from 'vs/base/browser/dnd';
 
-export interface IPanelOptions {
+export interface IPaneOptions {
 	ariaHeaderLabel?: string;
 	minimumBodySize?: number;
 	maximumBodySize?: number;
 	expanded?: boolean;
 }
 
-export interface IPanelStyles {
+export interface IPaneStyles {
 	dropBackground?: Color;
 	headerForeground?: Color;
 	headerBackground?: Color;
@@ -29,7 +31,7 @@ export interface IPanelStyles {
 }
 
 /**
- * A Panel is a structured SplitView view.
+ * A Pane is a structured SplitView view.
  *
  * WARNING: You must call `render()` after you contruct it.
  * It can't be done automatically at the end of the ctor
@@ -37,7 +39,7 @@ export interface IPanelStyles {
  * Subclasses wouldn't be able to set own properties
  * before the `render()` call, thus forbiding their use.
  */
-export abstract class Panel extends Disposable implements IView {
+export abstract class Pane extends Disposable implements IView {
 
 	private static readonly HEADER_SIZE = 22;
 
@@ -52,7 +54,7 @@ export abstract class Panel extends Disposable implements IView {
 	private _minimumBodySize: number;
 	private _maximumBodySize: number;
 	private ariaHeaderLabel: string;
-	private styles: IPanelStyles = {};
+	private styles: IPaneStyles = {};
 	private animationTimer: number | undefined = undefined;
 
 	private readonly _onDidChange = this._register(new Emitter());
@@ -93,7 +95,7 @@ export abstract class Panel extends Disposable implements IView {
 	}
 
 	private get headerSize(): number {
-		return this.headerVisible ? Panel.HEADER_SIZE : 0;
+		return this.headerVisible ? Pane.HEADER_SIZE : 0;
 	}
 
 	get minimumSize(): number {
@@ -114,14 +116,14 @@ export abstract class Panel extends Disposable implements IView {
 
 	width: number = 0;
 
-	constructor(options: IPanelOptions = {}) {
+	constructor(options: IPaneOptions = {}) {
 		super();
 		this._expanded = typeof options.expanded === 'undefined' ? true : !!options.expanded;
 		this.ariaHeaderLabel = options.ariaHeaderLabel || '';
 		this._minimumBodySize = typeof options.minimumBodySize === 'number' ? options.minimumBodySize : 120;
 		this._maximumBodySize = typeof options.maximumBodySize === 'number' ? options.maximumBodySize : Number.POSITIVE_INFINITY;
 
-		this.element = $('.panel');
+		this.element = $('.pane');
 	}
 
 	isExpanded(): boolean {
@@ -167,7 +169,7 @@ export abstract class Panel extends Disposable implements IView {
 	}
 
 	render(): void {
-		this.header = $('.panel-header');
+		this.header = $('.pane-header');
 		append(this.element, this.header);
 		this.header.setAttribute('tabindex', '0');
 		this.header.setAttribute('role', 'toolbar');
@@ -196,12 +198,12 @@ export abstract class Panel extends Disposable implements IView {
 		this._register(domEvent(this.header, 'click')
 			(() => this.setExpanded(!this.isExpanded()), null));
 
-		this.body = append(this.element, $('.panel-body'));
+		this.body = append(this.element, $('.pane-body'));
 		this.renderBody(this.body);
 	}
 
 	layout(height: number): void {
-		const headerSize = this.headerVisible ? Panel.HEADER_SIZE : 0;
+		const headerSize = this.headerVisible ? Pane.HEADER_SIZE : 0;
 
 		if (this.isExpanded()) {
 			this.layoutBody(height - headerSize, this.width);
@@ -209,7 +211,7 @@ export abstract class Panel extends Disposable implements IView {
 		}
 	}
 
-	style(styles: IPanelStyles): void {
+	style(styles: IPaneStyles): void {
 		this.styles = styles;
 
 		if (!this.header) {
@@ -240,31 +242,31 @@ export abstract class Panel extends Disposable implements IView {
 }
 
 interface IDndContext {
-	draggable: PanelDraggable | null;
+	draggable: PaneDraggable | null;
 }
 
-class PanelDraggable extends Disposable {
+class PaneDraggable extends Disposable {
 
 	private static readonly DefaultDragOverBackgroundColor = new Color(new RGBA(128, 128, 128, 0.5));
 
 	private dragOverCounter = 0; // see https://github.com/Microsoft/vscode/issues/14470
 
-	private _onDidDrop = this._register(new Emitter<{ from: Panel, to: Panel }>());
+	private _onDidDrop = this._register(new Emitter<{ from: Pane, to: Pane }>());
 	readonly onDidDrop = this._onDidDrop.event;
 
-	constructor(private panel: Panel, private dnd: IPanelDndController, private context: IDndContext) {
+	constructor(private pane: Pane, private dnd: IPaneDndController, private context: IDndContext) {
 		super();
 
-		panel.draggableElement.draggable = true;
-		this._register(domEvent(panel.draggableElement, 'dragstart')(this.onDragStart, this));
-		this._register(domEvent(panel.dropTargetElement, 'dragenter')(this.onDragEnter, this));
-		this._register(domEvent(panel.dropTargetElement, 'dragleave')(this.onDragLeave, this));
-		this._register(domEvent(panel.dropTargetElement, 'dragend')(this.onDragEnd, this));
-		this._register(domEvent(panel.dropTargetElement, 'drop')(this.onDrop, this));
+		pane.draggableElement.draggable = true;
+		this._register(domEvent(pane.draggableElement, 'dragstart')(this.onDragStart, this));
+		this._register(domEvent(pane.dropTargetElement, 'dragenter')(this.onDragEnter, this));
+		this._register(domEvent(pane.dropTargetElement, 'dragleave')(this.onDragLeave, this));
+		this._register(domEvent(pane.dropTargetElement, 'dragend')(this.onDragEnd, this));
+		this._register(domEvent(pane.dropTargetElement, 'drop')(this.onDrop, this));
 	}
 
 	private onDragStart(e: DragEvent): void {
-		if (!this.dnd.canDrag(this.panel) || !e.dataTransfer) {
+		if (!this.dnd.canDrag(this.pane) || !e.dataTransfer) {
 			e.preventDefault();
 			e.stopPropagation();
 			return;
@@ -272,7 +274,12 @@ class PanelDraggable extends Disposable {
 
 		e.dataTransfer.effectAllowed = 'move';
 
-		const dragImage = append(document.body, $('.monaco-drag-image', {}, this.panel.draggableElement.textContent || ''));
+		if (isFirefox) {
+			// Firefox: requires to set a text data transfer to get going
+			e.dataTransfer?.setData(DataTransfers.TEXT, this.pane.draggableElement.textContent || '');
+		}
+
+		const dragImage = append(document.body, $('.monaco-drag-image', {}, this.pane.draggableElement.textContent || ''));
 		e.dataTransfer.setDragImage(dragImage, -10, -10);
 		setTimeout(() => document.body.removeChild(dragImage), 0);
 
@@ -284,7 +291,7 @@ class PanelDraggable extends Disposable {
 			return;
 		}
 
-		if (!this.dnd.canDrop(this.context.draggable.panel, this.panel)) {
+		if (!this.dnd.canDrop(this.context.draggable.pane, this.pane)) {
 			return;
 		}
 
@@ -297,7 +304,7 @@ class PanelDraggable extends Disposable {
 			return;
 		}
 
-		if (!this.dnd.canDrop(this.context.draggable.panel, this.panel)) {
+		if (!this.dnd.canDrop(this.context.draggable.pane, this.pane)) {
 			return;
 		}
 
@@ -323,11 +330,13 @@ class PanelDraggable extends Disposable {
 			return;
 		}
 
+		EventHelper.stop(e);
+
 		this.dragOverCounter = 0;
 		this.render();
 
-		if (this.dnd.canDrop(this.context.draggable.panel, this.panel) && this.context.draggable !== this) {
-			this._onDidDrop.fire({ from: this.context.draggable.panel, to: this.panel });
+		if (this.dnd.canDrop(this.context.draggable.pane, this.pane) && this.context.draggable !== this) {
+			this._onDidDrop.fire({ from: this.context.draggable.pane, to: this.pane });
 		}
 
 		this.context.draggable = null;
@@ -337,106 +346,106 @@ class PanelDraggable extends Disposable {
 		let backgroundColor: string | null = null;
 
 		if (this.dragOverCounter > 0) {
-			backgroundColor = (this.panel.dropBackground || PanelDraggable.DefaultDragOverBackgroundColor).toString();
+			backgroundColor = (this.pane.dropBackground || PaneDraggable.DefaultDragOverBackgroundColor).toString();
 		}
 
-		this.panel.dropTargetElement.style.backgroundColor = backgroundColor || '';
+		this.pane.dropTargetElement.style.backgroundColor = backgroundColor || '';
 	}
 }
 
-export interface IPanelDndController {
-	canDrag(panel: Panel): boolean;
-	canDrop(panel: Panel, overPanel: Panel): boolean;
+export interface IPaneDndController {
+	canDrag(pane: Pane): boolean;
+	canDrop(pane: Pane, overPane: Pane): boolean;
 }
 
-export class DefaultPanelDndController implements IPanelDndController {
+export class DefaultPaneDndController implements IPaneDndController {
 
-	canDrag(panel: Panel): boolean {
+	canDrag(pane: Pane): boolean {
 		return true;
 	}
 
-	canDrop(panel: Panel, overPanel: Panel): boolean {
+	canDrop(pane: Pane, overPane: Pane): boolean {
 		return true;
 	}
 }
 
-export interface IPanelViewOptions {
-	dnd?: IPanelDndController;
+export interface IPaneViewOptions {
+	dnd?: IPaneDndController;
 }
 
-interface IPanelItem {
-	panel: Panel;
+interface IPaneItem {
+	pane: Pane;
 	disposable: IDisposable;
 }
 
-export class PanelView extends Disposable {
+export class PaneView extends Disposable {
 
-	private dnd: IPanelDndController | undefined;
+	private dnd: IPaneDndController | undefined;
 	private dndContext: IDndContext = { draggable: null };
 	private el: HTMLElement;
-	private panelItems: IPanelItem[] = [];
+	private paneItems: IPaneItem[] = [];
 	private width: number = 0;
 	private splitview: SplitView;
 	private animationTimer: number | undefined = undefined;
 
-	private _onDidDrop = this._register(new Emitter<{ from: Panel, to: Panel }>());
-	readonly onDidDrop: Event<{ from: Panel, to: Panel }> = this._onDidDrop.event;
+	private _onDidDrop = this._register(new Emitter<{ from: Pane, to: Pane }>());
+	readonly onDidDrop: Event<{ from: Pane, to: Pane }> = this._onDidDrop.event;
 
 	readonly onDidSashChange: Event;
 
-	constructor(container: HTMLElement, options: IPanelViewOptions = {}) {
+	constructor(container: HTMLElement, options: IPaneViewOptions = {}) {
 		super();
 
 		this.dnd = options.dnd;
-		this.el = append(container, $('.monaco-panel-view'));
+		this.el = append(container, $('.monaco-pane-view'));
 		this.splitview = this._register(new SplitView(this.el));
 		this.onDidSashChange = this.splitview.onDidSashChange;
 	}
 
-	addPanel(panel: Panel, size: number, index = this.splitview.length): void {
+	addPane(pane: Pane, size: number, index = this.splitview.length): void {
 		const disposables = new DisposableStore();
-		panel.onDidChangeExpansionState(this.setupAnimation, this, disposables);
+		pane.onDidChangeExpansionState(this.setupAnimation, this, disposables);
 
-		const panelItem = { panel, disposable: disposables };
-		this.panelItems.splice(index, 0, panelItem);
-		panel.width = this.width;
-		this.splitview.addView(panel, size, index);
+		const paneItem = { pane: pane, disposable: disposables };
+		this.paneItems.splice(index, 0, paneItem);
+		pane.width = this.width;
+		this.splitview.addView(pane, size, index);
 
 		if (this.dnd) {
-			const draggable = new PanelDraggable(panel, this.dnd, this.dndContext);
+			const draggable = new PaneDraggable(pane, this.dnd, this.dndContext);
 			disposables.add(draggable);
 			disposables.add(draggable.onDidDrop(this._onDidDrop.fire, this._onDidDrop));
 		}
 	}
 
-	removePanel(panel: Panel): void {
-		const index = firstIndex(this.panelItems, item => item.panel === panel);
+	removePane(pane: Pane): void {
+		const index = firstIndex(this.paneItems, item => item.pane === pane);
 
 		if (index === -1) {
 			return;
 		}
 
 		this.splitview.removeView(index);
-		const panelItem = this.panelItems.splice(index, 1)[0];
-		panelItem.disposable.dispose();
+		const paneItem = this.paneItems.splice(index, 1)[0];
+		paneItem.disposable.dispose();
 	}
 
-	movePanel(from: Panel, to: Panel): void {
-		const fromIndex = firstIndex(this.panelItems, item => item.panel === from);
-		const toIndex = firstIndex(this.panelItems, item => item.panel === to);
+	movePane(from: Pane, to: Pane): void {
+		const fromIndex = firstIndex(this.paneItems, item => item.pane === from);
+		const toIndex = firstIndex(this.paneItems, item => item.pane === to);
 
 		if (fromIndex === -1 || toIndex === -1) {
 			return;
 		}
 
-		const [panelItem] = this.panelItems.splice(fromIndex, 1);
-		this.panelItems.splice(toIndex, 0, panelItem);
+		const [paneItem] = this.paneItems.splice(fromIndex, 1);
+		this.paneItems.splice(toIndex, 0, paneItem);
 
 		this.splitview.moveView(fromIndex, toIndex);
 	}
 
-	resizePanel(panel: Panel, size: number): void {
-		const index = firstIndex(this.panelItems, item => item.panel === panel);
+	resizePane(pane: Pane, size: number): void {
+		const index = firstIndex(this.paneItems, item => item.pane === pane);
 
 		if (index === -1) {
 			return;
@@ -445,8 +454,8 @@ export class PanelView extends Disposable {
 		this.splitview.resizeView(index, size);
 	}
 
-	getPanelSize(panel: Panel): number {
-		const index = firstIndex(this.panelItems, item => item.panel === panel);
+	getPaneSize(pane: Pane): number {
+		const index = firstIndex(this.paneItems, item => item.pane === pane);
 
 		if (index === -1) {
 			return -1;
@@ -458,8 +467,8 @@ export class PanelView extends Disposable {
 	layout(height: number, width: number): void {
 		this.width = width;
 
-		for (const panelItem of this.panelItems) {
-			panelItem.panel.width = width;
+		for (const paneItem of this.paneItems) {
+			paneItem.pane.width = width;
 		}
 
 		this.splitview.layout(height);
@@ -481,6 +490,6 @@ export class PanelView extends Disposable {
 	dispose(): void {
 		super.dispose();
 
-		this.panelItems.forEach(i => i.disposable.dispose());
+		this.paneItems.forEach(i => i.disposable.dispose());
 	}
 }
diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts
index 41200e6e69..569314ea66 100644
--- a/src/vs/base/browser/ui/splitview/splitview.ts
+++ b/src/vs/base/browser/ui/splitview/splitview.ts
@@ -23,14 +23,14 @@ const defaultStyles: ISplitViewStyles = {
 	separatorBorder: Color.transparent
 };
 
-export interface ISplitViewOptions {
+export interface ISplitViewOptions {
 	readonly orientation?: Orientation; // default Orientation.VERTICAL
 	readonly styles?: ISplitViewStyles;
 	readonly orthogonalStartSash?: Sash;
 	readonly orthogonalEndSash?: Sash;
 	readonly inverseAltBehavior?: boolean;
 	readonly proportionalLayout?: boolean; // default true,
-	readonly descriptor?: ISplitViewDescriptor;
+	readonly descriptor?: ISplitViewDescriptor;
 }
 
 /**
@@ -42,14 +42,14 @@ export const enum LayoutPriority {
 	High
 }
 
-export interface IView {
+export interface IView {
 	readonly element: HTMLElement;
 	readonly minimumSize: number;
 	readonly maximumSize: number;
 	readonly onDidChange: Event;
 	readonly priority?: LayoutPriority;
 	readonly snap?: boolean;
-	layout(size: number, orthogonalSize: number | undefined): void;
+	layout(size: number, offset: number, context: TLayoutContext | undefined): void;
 	setVisible?(visible: boolean): void;
 }
 
@@ -62,7 +62,7 @@ interface ISashEvent {
 
 type ViewItemSize = number | { cachedVisibleSize: number };
 
-abstract class ViewItem {
+abstract class ViewItem {
 
 	private _size: number;
 	set size(size: number) {
@@ -109,9 +109,13 @@ abstract class ViewItem {
 	get priority(): LayoutPriority | undefined { return this.view.priority; }
 	get snap(): boolean { return !!this.view.snap; }
 
+	set enabled(enabled: boolean) {
+		this.container.style.pointerEvents = enabled ? null : 'none';
+	}
+
 	constructor(
 		protected container: HTMLElement,
-		private view: IView,
+		private view: IView,
 		size: ViewItemSize,
 		private disposable: IDisposable
 	) {
@@ -125,31 +129,31 @@ abstract class ViewItem {
 		}
 	}
 
-	layout(position: number, orthogonalSize: number | undefined): void {
-		this.layoutContainer(position);
-		this.view.layout(this.size, orthogonalSize);
+	layout(offset: number, layoutContext: TLayoutContext | undefined): void {
+		this.layoutContainer(offset);
+		this.view.layout(this.size, offset, layoutContext);
 	}
 
-	abstract layoutContainer(position: number): void;
+	abstract layoutContainer(offset: number): void;
 
-	dispose(): IView {
+	dispose(): IView {
 		this.disposable.dispose();
 		return this.view;
 	}
 }
 
-class VerticalViewItem extends ViewItem {
+class VerticalViewItem extends ViewItem {
 
-	layoutContainer(position: number): void {
-		this.container.style.top = `${position}px`;
+	layoutContainer(offset: number): void {
+		this.container.style.top = `${offset}px`;
 		this.container.style.height = `${this.size}px`;
 	}
 }
 
-class HorizontalViewItem extends ViewItem {
+class HorizontalViewItem extends ViewItem {
 
-	layoutContainer(position: number): void {
-		this.container.style.left = `${position}px`;
+	layoutContainer(offset: number): void {
+		this.container.style.left = `${offset}px`;
 		this.container.style.width = `${this.size}px`;
 	}
 }
@@ -194,26 +198,26 @@ export namespace Sizing {
 	export function Invisible(cachedVisibleSize: number): InvisibleSizing { return { type: 'invisible', cachedVisibleSize }; }
 }
 
-export interface ISplitViewDescriptor {
+export interface ISplitViewDescriptor {
 	size: number;
 	views: {
 		visible?: boolean;
 		size: number;
-		view: IView;
+		view: IView;
 	}[];
 }
 
-export class SplitView extends Disposable {
+export class SplitView extends Disposable {
 
 	readonly orientation: Orientation;
 	readonly el: HTMLElement;
 	private sashContainer: HTMLElement;
 	private viewContainer: HTMLElement;
 	private size = 0;
-	private orthogonalSize: number | undefined;
+	private layoutContext: TLayoutContext | undefined;
 	private contentSize = 0;
 	private proportions: undefined | number[] = undefined;
-	private viewItems: ViewItem[] = [];
+	private viewItems: ViewItem[] = [];
 	private sashItems: ISashItem[] = [];
 	private sashDragState: ISashDragState | undefined;
 	private state: State = State.Idle;
@@ -262,7 +266,29 @@ export class SplitView extends Disposable {
 		return this.sashItems.map(s => s.sash);
 	}
 
-	constructor(container: HTMLElement, options: ISplitViewOptions = {}) {
+	private _startSnappingEnabled = true;
+	get startSnappingEnabled(): boolean { return this._startSnappingEnabled; }
+	set startSnappingEnabled(startSnappingEnabled: boolean) {
+		if (this._startSnappingEnabled === startSnappingEnabled) {
+			return;
+		}
+
+		this._startSnappingEnabled = startSnappingEnabled;
+		this.updateSashEnablement();
+	}
+
+	private _endSnappingEnabled = true;
+	get endSnappingEnabled(): boolean { return this._endSnappingEnabled; }
+	set endSnappingEnabled(endSnappingEnabled: boolean) {
+		if (this._endSnappingEnabled === endSnappingEnabled) {
+			return;
+		}
+
+		this._endSnappingEnabled = endSnappingEnabled;
+		this.updateSashEnablement();
+	}
+
+	constructor(container: HTMLElement, options: ISplitViewOptions = {}) {
 		super();
 
 		this.orientation = types.isUndefined(options.orientation) ? Orientation.VERTICAL : options.orientation;
@@ -305,11 +331,11 @@ export class SplitView extends Disposable {
 		}
 	}
 
-	addView(view: IView, size: number | Sizing, index = this.viewItems.length): void {
+	addView(view: IView, size: number | Sizing, index = this.viewItems.length): void {
 		this.doAddView(view, size, index, false);
 	}
 
-	removeView(index: number, sizing?: Sizing): IView {
+	removeView(index: number, sizing?: Sizing): IView {
 		if (this.state !== State.Idle) {
 			throw new Error('Cant modify splitview');
 		}
@@ -401,10 +427,10 @@ export class SplitView extends Disposable {
 		return viewItem.cachedVisibleSize;
 	}
 
-	layout(size: number, orthogonalSize?: number): void {
+	layout(size: number, layoutContext?: TLayoutContext): void {
 		const previousSize = Math.max(this.size, this.contentSize);
 		this.size = size;
-		this.orthogonalSize = orthogonalSize;
+		this.layoutContext = layoutContext;
 
 		if (!this.proportions) {
 			const indexes = range(this.viewItems.length);
@@ -430,6 +456,10 @@ export class SplitView extends Disposable {
 	}
 
 	private onSashStart({ sash, start, alt }: ISashEvent): void {
+		for (const item of this.viewItems) {
+			item.enabled = false;
+		}
+
 		const index = firstIndex(this.sashItems, item => item.sash === sash);
 
 		// This way, we can press Alt while we resize a sash, macOS style!
@@ -535,9 +565,13 @@ export class SplitView extends Disposable {
 		this._onDidSashChange.fire(index);
 		this.sashDragState!.disposable.dispose();
 		this.saveProportions();
+
+		for (const item of this.viewItems) {
+			item.enabled = true;
+		}
 	}
 
-	private onViewChange(item: ViewItem, size: number | undefined): void {
+	private onViewChange(item: ViewItem, size: number | undefined): void {
 		const index = this.viewItems.indexOf(item);
 
 		if (index < 0 || index >= this.viewItems.length) {
@@ -584,7 +618,7 @@ export class SplitView extends Disposable {
 	}
 
 	distributeViewSizes(): void {
-		const flexibleViewItems: ViewItem[] = [];
+		const flexibleViewItems: ViewItem[] = [];
 		let flexibleSize = 0;
 
 		for (const item of this.viewItems) {
@@ -615,7 +649,7 @@ export class SplitView extends Disposable {
 		return this.viewItems[index].size;
 	}
 
-	private doAddView(view: IView, size: number | Sizing, index = this.viewItems.length, skipLayout?: boolean): void {
+	private doAddView(view: IView, size: number | Sizing, index = this.viewItems.length, skipLayout?: boolean): void {
 		if (this.state !== State.Idle) {
 			throw new Error('Cant modify splitview');
 		}
@@ -849,17 +883,19 @@ export class SplitView extends Disposable {
 		this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
 
 		// Layout views
-		let position = 0;
+		let offset = 0;
 
 		for (const viewItem of this.viewItems) {
-			viewItem.layout(position, this.orthogonalSize);
-			position += viewItem.size;
+			viewItem.layout(offset, this.layoutContext);
+			offset += viewItem.size;
 		}
 
 		// Layout sashes
 		this.sashItems.forEach(item => item.sash.layout());
+		this.updateSashEnablement();
+	}
 
-		// Update sashes enablement
+	private updateSashEnablement(): void {
 		let previous = false;
 		const collapsesDown = this.viewItems.map(i => previous = (i.size - i.minimumSize > 0) || previous);
 
@@ -873,7 +909,12 @@ export class SplitView extends Disposable {
 		previous = false;
 		const expandsUp = reverseViews.map(i => previous = (i.maximumSize - i.size > 0) || previous).reverse();
 
-		this.sashItems.forEach(({ sash }, index) => {
+		let position = 0;
+		for (let index = 0; index < this.sashItems.length; index++) {
+			const { sash } = this.sashItems[index];
+			const viewItem = this.viewItems[index];
+			position += viewItem.size;
+
 			const min = !(collapsesDown[index] && expandsUp[index + 1]);
 			const max = !(expandsDown[index] && collapsesUp[index + 1]);
 
@@ -886,9 +927,9 @@ export class SplitView extends Disposable {
 				const snappedBefore = typeof snapBeforeIndex === 'number' && !this.viewItems[snapBeforeIndex].visible;
 				const snappedAfter = typeof snapAfterIndex === 'number' && !this.viewItems[snapAfterIndex].visible;
 
-				if (snappedBefore && collapsesUp[index]) {
+				if (snappedBefore && collapsesUp[index] && (position > 0 || this.startSnappingEnabled)) {
 					sash.state = SashState.Minimum;
-				} else if (snappedAfter && collapsesDown[index]) {
+				} else if (snappedAfter && collapsesDown[index] && (position < this.contentSize || this.endSnappingEnabled)) {
 					sash.state = SashState.Maximum;
 				} else {
 					sash.state = SashState.Disabled;
@@ -900,8 +941,7 @@ export class SplitView extends Disposable {
 			} else {
 				sash.state = SashState.Enabled;
 			}
-			// }
-		});
+		}
 	}
 
 	private getSashPosition(sash: Sash): number {
diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts
index 1ef430d158..21a3b47728 100644
--- a/src/vs/base/browser/ui/toolbar/toolbar.ts
+++ b/src/vs/base/browser/ui/toolbar/toolbar.ts
@@ -52,7 +52,7 @@ export class ToolBar extends Disposable {
 			orientation: options.orientation,
 			ariaLabel: options.ariaLabel,
 			actionRunner: options.actionRunner,
-			actionViewItemProvider: (action: Action) => {
+			actionViewItemProvider: (action: IAction) => {
 
 				// Return special action item for the toggle menu action
 				if (action.id === ToggleMenuAction.ID) {
diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts
index 0a9e363f68..9a058a8643 100644
--- a/src/vs/base/browser/ui/tree/abstractTree.ts
+++ b/src/vs/base/browser/ui/tree/abstractTree.ts
@@ -7,7 +7,7 @@ import 'vs/css!./media/tree';
 import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
 import { IListOptions, List, IListStyles, MouseController, DefaultKeyboardNavigationDelegate } from 'vs/base/browser/ui/list/listWidget';
 import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListEvent, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IKeyboardNavigationLabelProvider, IIdentityProvider, IKeyboardNavigationDelegate } from 'vs/base/browser/ui/list/list';
-import { append, $, toggleClass, getDomNodePagePosition, removeClass, addClass, hasClass, hasParentWithClass, createStyleSheet, clearNode } from 'vs/base/browser/dom';
+import { append, $, toggleClass, getDomNodePagePosition, removeClass, addClass, hasClass, hasParentWithClass, createStyleSheet, clearNode, addClasses, removeClasses } from 'vs/base/browser/dom';
 import { Event, Relay, Emitter, EventBufferer } from 'vs/base/common/event';
 import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
 import { KeyCode } from 'vs/base/common/keyCodes';
@@ -27,10 +27,24 @@ import { clamp } from 'vs/base/common/numbers';
 import { ScrollEvent } from 'vs/base/common/scrollable';
 import { SetMap } from 'vs/base/common/collections';
 
+class TreeElementsDragAndDropData extends ElementsDragAndDropData {
+
+	set context(context: TContext | undefined) {
+		this.data.context = context;
+	}
+
+	get context(): TContext | undefined {
+		return this.data.context;
+	}
+
+	constructor(private data: ElementsDragAndDropData, TContext>) {
+		super(data.elements.map(node => node.element));
+	}
+}
+
 function asTreeDragAndDropData(data: IDragAndDropData): IDragAndDropData {
 	if (data instanceof ElementsDragAndDropData) {
-		const nodes = (data as ElementsDragAndDropData>).elements;
-		return new ElementsDragAndDropData(nodes.map(node => node.element));
+		return new TreeElementsDragAndDropData(data);
 	}
 
 	return data;
@@ -47,9 +61,9 @@ class TreeNodeListDragAndDrop implements IListDragAndDrop<
 		return this.dnd.getDragURI(node.element);
 	}
 
-	getDragLabel(nodes: ITreeNode[]): string | undefined {
+	getDragLabel(nodes: ITreeNode[], originalEvent: DragEvent): string | undefined {
 		if (this.dnd.getDragLabel) {
-			return this.dnd.getDragLabel(nodes.map(node => node.element));
+			return this.dnd.getDragLabel(nodes.map(node => node.element), originalEvent);
 		}
 
 		return undefined;
@@ -87,7 +101,7 @@ class TreeNodeListDragAndDrop implements IListDragAndDrop<
 			}, 500);
 		}
 
-		if (typeof result === 'boolean' || !result.accept || typeof result.bubble === 'undefined') {
+		if (typeof result === 'boolean' || !result.accept || typeof result.bubble === 'undefined' || result.feedback) {
 			if (!raw) {
 				const accept = typeof result === 'boolean' ? result : result.accept;
 				const effect = typeof result === 'boolean' ? undefined : result.effect;
@@ -121,6 +135,12 @@ class TreeNodeListDragAndDrop implements IListDragAndDrop<
 
 		this.dnd.drop(asTreeDragAndDropData(data), targetNode && targetNode.element, targetIndex, originalEvent);
 	}
+
+	onDragEnd(originalEvent: DragEvent): void {
+		if (this.dnd.onDragEnd) {
+			this.dnd.onDragEnd(originalEvent);
+		}
+	}
 }
 
 function asListOptions(modelProvider: () => ITreeModel, options?: IAbstractTreeOptions): IListOptions> | undefined {
@@ -141,12 +161,16 @@ function asListOptions(modelProvider: () => ITreeModel {
+				return options.accessibilityProvider!.getActiveDescendantId!(node.element);
+			})
 		},
 		keyboardNavigationLabelProvider: options.keyboardNavigationLabelProvider && {
 			...options.keyboardNavigationLabelProvider,
@@ -211,6 +235,8 @@ export enum RenderIndentGuides {
 interface ITreeRendererOptions {
 	readonly indent?: number;
 	readonly renderIndentGuides?: RenderIndentGuides;
+	// TODO@joao replace this with collapsible: boolean | 'ondemand'
+	readonly hideTwistiesOfChildlessElements?: boolean;
 }
 
 interface IRenderData {
@@ -244,6 +270,7 @@ class TreeRenderer implements IListRenderer
 	private renderedElements = new Map>();
 	private renderedNodes = new Map, IRenderData>();
 	private indent: number = TreeRenderer.DefaultIndent;
+	private hideTwistiesOfChildlessElements: boolean = false;
 
 	private shouldRenderIndentGuides: boolean = false;
 	private renderedIndentGuides = new SetMap, HTMLDivElement>();
@@ -290,6 +317,10 @@ class TreeRenderer implements IListRenderer
 				}
 			}
 		}
+
+		if (typeof options.hideTwistiesOfChildlessElements !== 'undefined') {
+			this.hideTwistiesOfChildlessElements = options.hideTwistiesOfChildlessElements;
+		}
 	}
 
 	renderTemplate(container: HTMLElement): ITreeListTemplateData {
@@ -309,7 +340,7 @@ class TreeRenderer implements IListRenderer
 		}
 
 		const indent = TreeRenderer.DefaultIndent + (node.depth - 1) * this.indent;
-		templateData.twistie.style.marginLeft = `${indent}px`;
+		templateData.twistie.style.paddingLeft = `${indent}px`;
 		templateData.indent.style.width = `${indent + this.indent - 16}px`;
 
 		this.renderTwistie(node, templateData);
@@ -365,10 +396,12 @@ class TreeRenderer implements IListRenderer
 			this.renderer.renderTwistie(node.element, templateData.twistie);
 		}
 
-		toggleClass(templateData.twistie, 'codicon', node.collapsible);
-		toggleClass(templateData.twistie, 'codicon-chevron-down', node.collapsible);
-		toggleClass(templateData.twistie, 'collapsible', node.collapsible);
-		toggleClass(templateData.twistie, 'collapsed', node.collapsible && node.collapsed);
+		if (node.collapsible && (!this.hideTwistiesOfChildlessElements || node.visibleChildrenCount > 0)) {
+			addClasses(templateData.twistie, 'codicon', 'codicon-chevron-down', 'collapsible');
+			toggleClass(templateData.twistie, 'collapsed', node.collapsed);
+		} else {
+			removeClasses(templateData.twistie, 'codicon', 'codicon-chevron-down', 'collapsible', 'collapsed');
+		}
 
 		if (node.collapsible) {
 			templateData.container.setAttribute('aria-expanded', String(!node.collapsed));
@@ -430,12 +463,16 @@ class TreeRenderer implements IListRenderer
 
 		nodes.forEach(node => {
 			const ref = model.getNodeLocation(node);
-			const parentRef = model.getParentNodeLocation(ref);
+			try {
+				const parentRef = model.getParentNodeLocation(ref);
 
-			if (node.collapsible && node.children.length > 0 && !node.collapsed) {
-				set.add(node);
-			} else if (parentRef) {
-				set.add(model.getNode(parentRef));
+				if (node.collapsible && node.children.length > 0 && !node.collapsed) {
+					set.add(node);
+				} else if (parentRef) {
+					set.add(model.getNode(parentRef));
+				}
+			} catch {
+				// noop
 			}
 		});
 
@@ -601,14 +638,14 @@ class TypeFilterController implements IDisposable {
 		const controls = append(this.domNode, $('.controls'));
 
 		this._filterOnType = !!tree.options.filterOnType;
-		this.filterOnTypeDomNode = append(controls, $('input.filter'));
+		this.filterOnTypeDomNode = append(controls, $('input.filter.codicon.codicon-list-selection'));
 		this.filterOnTypeDomNode.type = 'checkbox';
 		this.filterOnTypeDomNode.checked = this._filterOnType;
 		this.filterOnTypeDomNode.tabIndex = -1;
 		this.updateFilterOnTypeTitle();
 		domEvent(this.filterOnTypeDomNode, 'input')(this.onDidChangeFilterOnType, this, this.disposables);
 
-		this.clearDomNode = append(controls, $('button.clear'));
+		this.clearDomNode = append(controls, $('button.clear.codicon.codicon-close'));
 		this.clearDomNode.tabIndex = -1;
 		this.clearDomNode.title = localize('clear', "Clear");
 
@@ -657,7 +694,7 @@ class TypeFilterController implements IDisposable {
 
 		const onKeyDown = Event.chain(domEvent(this.view.getHTMLElement(), 'keydown'))
 			.filter(e => !isInputElement(e.target as HTMLElement) || e.target === this.filterOnTypeDomNode)
-			.filter(e => e.key !== 'Dead')
+			.filter(e => e.key !== 'Dead' && !/^Media/.test(e.key))
 			.map(e => new StandardKeyboardEvent(e))
 			.filter(this.keyboardNavigationEventFilter || (() => true))
 			.filter(() => this.automaticKeyboardNavigation || this.triggered)
@@ -918,7 +955,6 @@ export interface IAbstractTreeOptions extends IAbstractTr
 	readonly collapseByDefault?: boolean; // defaults to false
 	readonly filter?: ITreeFilter;
 	readonly dnd?: ITreeDragAndDrop;
-	readonly autoExpandSingleChildren?: boolean;
 	readonly keyboardNavigationEventFilter?: IKeyboardNavigationEventFilter;
 	readonly expandOnlyOnTwistieClick?: boolean | ((e: T) => boolean);
 	readonly additionalScrollHeight?: number;
@@ -1385,8 +1421,13 @@ export abstract class AbstractTree implements IDisposable
 		return this.view.renderHeight;
 	}
 
-	get firstVisibleElement(): T {
+	get firstVisibleElement(): T | undefined {
 		const index = this.view.firstVisibleIndex;
+
+		if (index < 0 || index >= this.view.length) {
+			return undefined;
+		}
+
 		const node = this.view.element(index);
 		return node.element;
 	}
diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts
index 05ae77ac46..58a24e40dd 100644
--- a/src/vs/base/browser/ui/tree/asyncDataTree.ts
+++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts
@@ -6,7 +6,7 @@
 import { ComposedTreeDelegate, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree';
 import { ObjectTree, IObjectTreeOptions, CompressibleObjectTree, ICompressibleTreeRenderer, ICompressibleKeyboardNavigationLabelProvider, ICompressibleObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree';
 import { IListVirtualDelegate, IIdentityProvider, IListDragAndDrop, IListDragOverReaction } from 'vs/base/browser/ui/list/list';
-import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop, TreeError, WeakMapper } from 'vs/base/browser/ui/tree/tree';
+import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop, TreeError, WeakMapper, ITreeFilter, TreeVisibility, TreeFilterResult } from 'vs/base/browser/ui/tree/tree';
 import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle';
 import { Emitter, Event } from 'vs/base/common/event';
 import { timeout, CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
@@ -19,6 +19,8 @@ import { toggleClass } from 'vs/base/browser/dom';
 import { values } from 'vs/base/common/map';
 import { ScrollEvent } from 'vs/base/common/scrollable';
 import { ICompressedTreeNode, ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
+import { IThemable } from 'vs/base/common/styler';
+import { isFilterResult, getVisibleState } from 'vs/base/browser/ui/tree/indexTreeModel';
 
 interface IAsyncDataTreeNode {
 	element: TInput | T;
@@ -149,20 +151,24 @@ function asTreeContextMenuEvent(e: ITreeContextMenuEvent extends ElementsDragAndDropData {
 
-export interface IChildrenResolutionEvent {
-	readonly element: T | null;
-	readonly reason: ChildrenResolutionReason;
+	set context(context: TContext | undefined) {
+		this.data.context = context;
+	}
+
+	get context(): TContext | undefined {
+		return this.data.context;
+	}
+
+	constructor(private data: ElementsDragAndDropData, TContext>) {
+		super(data.elements.map(node => node.element as T));
+	}
 }
 
 function asAsyncDataTreeDragAndDropData(data: IDragAndDropData): IDragAndDropData {
 	if (data instanceof ElementsDragAndDropData) {
-		const nodes = (data as ElementsDragAndDropData>).elements;
-		return new ElementsDragAndDropData(nodes.map(node => node.element));
+		return new AsyncDataTreeElementsDragAndDropData(data);
 	}
 
 	return data;
@@ -176,9 +182,9 @@ class AsyncDataTreeNodeListDragAndDrop implements IListDragAndDrop[]): string | undefined {
+	getDragLabel(nodes: IAsyncDataTreeNode[], originalEvent: DragEvent): string | undefined {
 		if (this.dnd.getDragLabel) {
-			return this.dnd.getDragLabel(nodes.map(node => node.element as T));
+			return this.dnd.getDragLabel(nodes.map(node => node.element as T), originalEvent);
 		}
 
 		return undefined;
@@ -197,6 +203,12 @@ class AsyncDataTreeNodeListDragAndDrop implements IListDragAndDrop | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void {
 		this.dnd.drop(asAsyncDataTreeDragAndDropData(data), targetNode && targetNode.element as T, targetIndex, originalEvent);
 	}
+
+	onDragEnd(originalEvent: DragEvent): void {
+		if (this.dnd.onDragEnd) {
+			this.dnd.onDragEnd(originalEvent);
+		}
+	}
 }
 
 function asObjectTreeOptions(options?: IAsyncDataTreeOptions): IObjectTreeOptions, TFilterData> | undefined {
@@ -218,9 +230,16 @@ function asObjectTreeOptions(options?: IAsyncDataTreeOpt
 			}
 		},
 		accessibilityProvider: options.accessibilityProvider && {
+			...options.accessibilityProvider,
 			getAriaLabel(e) {
 				return options.accessibilityProvider!.getAriaLabel(e.element as T);
-			}
+			},
+			getAriaLevel: options.accessibilityProvider!.getAriaLevel && (node => {
+				return options.accessibilityProvider!.getAriaLevel!(node.element as T);
+			}),
+			getActiveDescendantId: options.accessibilityProvider.getActiveDescendantId && (node => {
+				return options.accessibilityProvider!.getActiveDescendantId!(node.element as T);
+			})
 		},
 		filter: options.filter && {
 			filter(e, parentVisibility) {
@@ -271,10 +290,10 @@ function dfs(node: IAsyncDataTreeNode, fn: (node: IAsyncDa
 	node.children.forEach(child => dfs(child, fn));
 }
 
-export class AsyncDataTree implements IDisposable {
+export class AsyncDataTree implements IDisposable, IThemable {
 
-	private readonly tree: ObjectTree, TFilterData>;
-	private readonly root: IAsyncDataTreeNode;
+	protected readonly tree: ObjectTree, TFilterData>;
+	protected readonly root: IAsyncDataTreeNode;
 	private readonly nodes = new Map>();
 	private readonly sorter?: ITreeSorter;
 	private readonly collapseByDefault?: { (e: T): boolean; };
@@ -282,7 +301,7 @@ export class AsyncDataTree implements IDisposable
 	private readonly subTreeRefreshPromises = new Map, Promise>();
 	private readonly refreshPromises = new Map, CancelablePromise>();
 
-	private readonly identityProvider?: IIdentityProvider;
+	protected readonly identityProvider?: IIdentityProvider;
 	private readonly autoExpandSingleChildren: boolean;
 
 	private readonly _onDidRender = new Emitter();
@@ -323,7 +342,7 @@ export class AsyncDataTree implements IDisposable
 	get onDidDispose(): Event { return this.tree.onDidDispose; }
 
 	constructor(
-		private user: string,
+		protected user: string,
 		container: HTMLElement,
 		delegate: IListVirtualDelegate,
 		renderers: ITreeRenderer[],
@@ -411,10 +430,6 @@ export class AsyncDataTree implements IDisposable
 		return this.tree.renderHeight;
 	}
 
-	get firstVisibleElement(): T {
-		return this.tree.firstVisibleElement!.element as T;
-	}
-
 	get lastVisibleElement(): T {
 		return this.tree.lastVisibleElement!.element as T;
 	}
@@ -445,7 +460,7 @@ export class AsyncDataTree implements IDisposable
 
 		const viewStateContext = viewState && { viewState, focus: [], selection: [] } as IAsyncDataTreeViewStateContext;
 
-		await this._updateChildren(input, true, viewStateContext);
+		await this._updateChildren(input, true, false, viewStateContext);
 
 		if (viewStateContext) {
 			this.tree.setFocus(viewStateContext.focus);
@@ -457,11 +472,11 @@ export class AsyncDataTree implements IDisposable
 		}
 	}
 
-	async updateChildren(element: TInput | T = this.root.element, recursive = true): Promise {
-		await this._updateChildren(element, recursive);
+	async updateChildren(element: TInput | T = this.root.element, recursive = true, rerender = false): Promise {
+		await this._updateChildren(element, recursive, rerender);
 	}
 
-	private async _updateChildren(element: TInput | T = this.root.element, recursive = true, viewStateContext?: IAsyncDataTreeViewStateContext): Promise {
+	private async _updateChildren(element: TInput | T = this.root.element, recursive = true, rerender = false, viewStateContext?: IAsyncDataTreeViewStateContext): Promise {
 		if (typeof this.root.element === 'undefined') {
 			throw new TreeError(this.user, 'Tree input not set');
 		}
@@ -471,7 +486,17 @@ export class AsyncDataTree implements IDisposable
 			await Event.toPromise(this._onDidRender.event);
 		}
 
-		await this.refreshAndRenderNode(this.getDataNode(element), recursive, ChildrenResolutionReason.Refresh, viewStateContext);
+		const node = this.getDataNode(element);
+		await this.refreshAndRenderNode(node, recursive, viewStateContext);
+
+		if (rerender) {
+			try {
+				this.tree.rerender(node);
+			} catch {
+				// missing nodes are fine, this could've resulted from
+				// parallel refresh calls, removing `node` altogether
+			}
+		}
 	}
 
 	resort(element: TInput | T = this.root.element, recursive = true): void {
@@ -653,18 +678,9 @@ export class AsyncDataTree implements IDisposable
 		return node;
 	}
 
-	private async refreshAndRenderNode(node: IAsyncDataTreeNode, recursive: boolean, reason: ChildrenResolutionReason, viewStateContext?: IAsyncDataTreeViewStateContext): Promise {
+	private async refreshAndRenderNode(node: IAsyncDataTreeNode, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext): Promise {
 		await this.refreshNode(node, recursive, viewStateContext);
 		this.render(node, viewStateContext);
-
-		if (node !== this.root && this.autoExpandSingleChildren && reason === ChildrenResolutionReason.Expand) {
-			const treeNode = this.tree.getNode(node);
-			const visibleChildren = treeNode.children.filter(node => node.visible);
-
-			if (visibleChildren.length === 1) {
-				await this.tree.expand(visibleChildren[0].element, false);
-			}
-		}
 	}
 
 	private async refreshNode(node: IAsyncDataTreeNode, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext): Promise {
@@ -752,12 +768,7 @@ export class AsyncDataTree implements IDisposable
 
 		result = createCancelablePromise(async () => {
 			const children = await this.dataSource.getChildren(node.element!);
-
-			if (this.sorter) {
-				children.sort(this.sorter.compare.bind(this.sorter));
-			}
-
-			return children;
+			return this.processChildren(children);
 		});
 
 		this.refreshPromises.set(node, result);
@@ -770,7 +781,7 @@ export class AsyncDataTree implements IDisposable
 			if (deep) {
 				this.collapse(node.element.element as T);
 			} else {
-				this.refreshAndRenderNode(node.element, false, ChildrenResolutionReason.Expand)
+				this.refreshAndRenderNode(node.element, false)
 					.catch(onUnexpectedError);
 			}
 		}
@@ -783,13 +794,14 @@ export class AsyncDataTree implements IDisposable
 		}
 
 		const nodesToForget = new Map>();
-		const childrenTreeNodesById = new Map | null, TFilterData>>();
+		const childrenTreeNodesById = new Map, collapsed: boolean }>();
 
 		for (const child of node.children) {
 			nodesToForget.set(child.element as T, child);
 
 			if (this.identityProvider) {
-				childrenTreeNodesById.set(child.id!, this.tree.getNode(child));
+				const collapsed = this.tree.isCollapsed(child);
+				childrenTreeNodesById.set(child.id!, { node: child, collapsed });
 			}
 		}
 
@@ -810,10 +822,10 @@ export class AsyncDataTree implements IDisposable
 			}
 
 			const id = this.identityProvider.getId(element).toString();
-			const childNode = childrenTreeNodesById.get(id);
+			const result = childrenTreeNodesById.get(id);
 
-			if (childNode) {
-				const asyncDataTreeNode = childNode.element!;
+			if (result) {
+				const asyncDataTreeNode = result.node;
 
 				nodesToForget.delete(asyncDataTreeNode.element as T);
 				this.nodes.delete(asyncDataTreeNode.element as T);
@@ -823,8 +835,10 @@ export class AsyncDataTree implements IDisposable
 				asyncDataTreeNode.hasChildren = hasChildren;
 
 				if (recursive) {
-					if (childNode.collapsed) {
-						dfs(asyncDataTreeNode, node => node.stale = true);
+					if (result.collapsed) {
+						asyncDataTreeNode.children.forEach(node => dfs(node, node => this.nodes.delete(node.element as T)));
+						asyncDataTreeNode.children.splice(0, asyncDataTreeNode.children.length);
+						asyncDataTreeNode.stale = true;
 					} else {
 						childrenToRefresh.push(asyncDataTreeNode);
 					}
@@ -866,10 +880,16 @@ export class AsyncDataTree implements IDisposable
 
 		node.children.splice(0, node.children.length, ...children);
 
+		// TODO@joao this doesn't take filter into account
+		if (node !== this.root && this.autoExpandSingleChildren && children.length === 1 && childrenToRefresh.length === 0) {
+			children[0].collapsedByDefault = false;
+			childrenToRefresh.push(children[0]);
+		}
+
 		return childrenToRefresh;
 	}
 
-	private render(node: IAsyncDataTreeNode, viewStateContext?: IAsyncDataTreeViewStateContext): void {
+	protected render(node: IAsyncDataTreeNode, viewStateContext?: IAsyncDataTreeViewStateContext): void {
 		const children = node.children.map(node => this.asTreeElement(node, viewStateContext));
 		this.tree.setChildren(node === this.root ? null : node, children);
 
@@ -881,6 +901,14 @@ export class AsyncDataTree implements IDisposable
 	}
 
 	protected asTreeElement(node: IAsyncDataTreeNode, viewStateContext?: IAsyncDataTreeViewStateContext): ITreeElement> {
+		if (node.stale) {
+			return {
+				element: node,
+				collapsible: node.hasChildren,
+				collapsed: true
+			};
+		}
+
 		let collapsed: boolean | undefined;
 
 		if (viewStateContext && viewStateContext.viewState.expanded && node.id && viewStateContext.viewState.expanded.indexOf(node.id) > -1) {
@@ -899,6 +927,14 @@ export class AsyncDataTree implements IDisposable
 		};
 	}
 
+	protected processChildren(children: T[]): T[] {
+		if (this.sorter) {
+			children.sort(this.sorter.compare.bind(this.sorter));
+		}
+
+		return children;
+	}
+
 	// view state
 
 	getViewState(): IAsyncDataTreeViewState {
@@ -994,6 +1030,12 @@ class CompressibleAsyncDataTreeRenderer i
 		}
 	}
 
+	disposeCompressedElements(node: ITreeNode>, TFilterData>, index: number, templateData: IDataTreeListTemplateData, height: number | undefined): void {
+		if (this.renderer.disposeCompressedElements) {
+			this.renderer.disposeCompressedElements(this.compressibleNodeMapperProvider().map(node) as ITreeNode, TFilterData>, index, templateData.templateData, height);
+		}
+	}
+
 	disposeTemplate(templateData: IDataTreeListTemplateData): void {
 		this.renderer.disposeTemplate(templateData.templateData);
 	}
@@ -1023,12 +1065,19 @@ function asCompressibleObjectTreeOptions(options?: IComp
 }
 
 export interface ICompressibleAsyncDataTreeOptions extends IAsyncDataTreeOptions {
+	readonly compressionEnabled?: boolean;
 	readonly keyboardNavigationLabelProvider?: ICompressibleKeyboardNavigationLabelProvider;
 }
 
+export interface ICompressibleAsyncDataTreeOptionsUpdate extends IAsyncDataTreeOptionsUpdate {
+	readonly compressionEnabled?: boolean;
+}
+
 export class CompressibleAsyncDataTree extends AsyncDataTree {
 
+	protected readonly tree!: CompressibleObjectTree, TFilterData>;
 	protected readonly compressibleNodeMapper: CompressibleAsyncDataTreeNodeMapper = new WeakMapper(node => new CompressibleAsyncDataTreeNodeWrapper(node));
+	private filter?: ITreeFilter;
 
 	constructor(
 		user: string,
@@ -1037,9 +1086,10 @@ export class CompressibleAsyncDataTree extends As
 		private compressionDelegate: ITreeCompressionDelegate,
 		renderers: ICompressibleTreeRenderer[],
 		dataSource: IAsyncDataSource,
-		options: IAsyncDataTreeOptions = {}
+		options: ICompressibleAsyncDataTreeOptions = {}
 	) {
 		super(user, container, virtualDelegate, renderers, dataSource, options);
+		this.filter = options.filter;
 	}
 
 	protected createTree(
@@ -1062,4 +1112,137 @@ export class CompressibleAsyncDataTree extends As
 			...super.asTreeElement(node, viewStateContext)
 		};
 	}
+
+	updateOptions(options: ICompressibleAsyncDataTreeOptionsUpdate = {}): void {
+		this.tree.updateOptions(options);
+	}
+
+	getViewState(): IAsyncDataTreeViewState {
+		if (!this.identityProvider) {
+			throw new TreeError(this.user, 'Can\'t get tree view state without an identity provider');
+		}
+
+		const getId = (element: T) => this.identityProvider!.getId(element).toString();
+		const focus = this.getFocus().map(getId);
+		const selection = this.getSelection().map(getId);
+
+		const expanded: string[] = [];
+		const root = this.tree.getCompressedTreeNode();
+		const queue = [root];
+
+		while (queue.length > 0) {
+			const node = queue.shift()!;
+
+			if (node !== root && node.collapsible && !node.collapsed) {
+				for (const asyncNode of node.element!.elements) {
+					expanded.push(getId(asyncNode.element as T));
+				}
+			}
+
+			queue.push(...node.children);
+		}
+
+		return { focus, selection, expanded, scrollTop: this.scrollTop };
+	}
+
+	protected render(node: IAsyncDataTreeNode, viewStateContext?: IAsyncDataTreeViewStateContext): void {
+		if (!this.identityProvider) {
+			return super.render(node, viewStateContext);
+		}
+
+		// Preserve traits across compressions. Hacky but does the trick.
+		// This is hard to fix properly since it requires rewriting the traits
+		// across trees and lists. Let's just keep it this way for now.
+		const getId = (element: T) => this.identityProvider!.getId(element).toString();
+		const getUncompressedIds = (nodes: IAsyncDataTreeNode[]): Set => {
+			const result = new Set();
+
+			for (const node of nodes) {
+				const compressedNode = this.tree.getCompressedTreeNode(node === this.root ? null : node);
+
+				if (!compressedNode.element) {
+					continue;
+				}
+
+				for (const node of compressedNode.element.elements) {
+					result.add(getId(node.element as T));
+				}
+			}
+
+			return result;
+		};
+
+		const oldSelection = getUncompressedIds(this.tree.getSelection() as IAsyncDataTreeNode[]);
+		const oldFocus = getUncompressedIds(this.tree.getFocus() as IAsyncDataTreeNode[]);
+
+		super.render(node, viewStateContext);
+
+		const selection = this.getSelection();
+		let didChangeSelection = false;
+
+		const focus = this.getFocus();
+		let didChangeFocus = false;
+
+		const visit = (node: ITreeNode> | null, TFilterData>) => {
+			const compressedNode = node.element;
+
+			if (compressedNode) {
+				for (let i = 0; i < compressedNode.elements.length; i++) {
+					const id = getId(compressedNode.elements[i].element as T);
+
+					if (oldSelection.has(id)) {
+						selection.push(compressedNode.elements[compressedNode.elements.length - 1].element as T);
+						didChangeSelection = true;
+					}
+
+					if (oldFocus.has(id)) {
+						focus.push(compressedNode.elements[compressedNode.elements.length - 1].element as T);
+						didChangeFocus = true;
+					}
+				}
+			}
+
+			node.children.forEach(visit);
+		};
+
+		visit(this.tree.getCompressedTreeNode(node === this.root ? null : node));
+
+		if (didChangeSelection) {
+			this.setSelection(selection);
+		}
+
+		if (didChangeFocus) {
+			this.setFocus(focus);
+		}
+	}
+
+	// For compressed async data trees, `TreeVisibility.Recurse` doesn't currently work
+	// and we have to filter everything beforehand
+	// Related to #85193 and #85835
+	protected processChildren(children: T[]): T[] {
+		if (this.filter) {
+			children = children.filter(e => {
+				const result = this.filter!.filter(e, TreeVisibility.Visible);
+				const visibility = getVisibility(result);
+
+				if (visibility === TreeVisibility.Recurse) {
+					throw new Error('Recursive tree visibility not supported in async data compressed trees');
+				}
+
+				return visibility === TreeVisibility.Visible;
+			});
+		}
+
+		return super.processChildren(children);
+	}
+}
+
+function getVisibility(filterResult: TreeFilterResult): TreeVisibility {
+	if (typeof filterResult === 'boolean') {
+		return filterResult ? TreeVisibility.Visible : TreeVisibility.Hidden;
+	} else if (isFilterResult(filterResult)) {
+		return getVisibleState(filterResult.visibility);
+	} else {
+		return getVisibleState(filterResult);
+	}
 }
diff --git a/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts b/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts
index 40c61feead..bc9347e5c2 100644
--- a/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts
+++ b/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts
@@ -11,7 +11,7 @@ import { IObjectTreeModelOptions, ObjectTreeModel, IObjectTreeModel } from 'vs/b
 
 // Exported only for test reasons, do not use directly
 export interface ICompressedTreeElement extends ITreeElement {
-	readonly children?: Iterator> | ICompressedTreeElement[];
+	readonly children?: ISequence>;
 	readonly incompressible?: boolean;
 }
 
@@ -100,16 +100,15 @@ export function decompress(element: ITreeElement>): IC
 
 function splice(treeElement: ICompressedTreeElement, element: T, children: Iterator>): ICompressedTreeElement {
 	if (treeElement.element === element) {
-		return { element, children };
+		return { ...treeElement, children };
 	}
 
-	return {
-		...treeElement,
-		children: Iterator.map(Iterator.from(treeElement.children), e => splice(e, element, children))
-	};
+	return { ...treeElement, children: Iterator.map(Iterator.from(treeElement.children), e => splice(e, element, children)) };
 }
 
-interface ICompressedObjectTreeModelOptions extends IObjectTreeModelOptions, TFilterData> { }
+interface ICompressedObjectTreeModelOptions extends IObjectTreeModelOptions, TFilterData> {
+	readonly compressionEnabled?: boolean;
+}
 
 // Exported only for test reasons, do not use directly
 export class CompressedObjectTreeModel, TFilterData extends NonNullable = void> implements ITreeModel | null, TFilterData, T | null> {
@@ -122,7 +121,7 @@ export class CompressedObjectTreeModel, TFilterData e
 
 	private model: ObjectTreeModel, TFilterData>;
 	private nodes = new Map>();
-	private enabled: boolean = true;
+	private enabled: boolean;
 
 	get size(): number { return this.nodes.size; }
 
@@ -132,13 +131,13 @@ export class CompressedObjectTreeModel, TFilterData e
 		options: ICompressedObjectTreeModelOptions = {}
 	) {
 		this.model = new ObjectTreeModel(user, list, options);
+		this.enabled = typeof options.compressionEnabled === 'undefined' ? true : options.compressionEnabled;
 	}
 
 	setChildren(
 		element: T | null,
 		children: ISequence> | undefined
 	): void {
-
 		if (element === null) {
 			const compressedChildren = Iterator.map(Iterator.from(children), this.enabled ? compress : noCompress);
 			this._setChildren(null, compressedChildren);
@@ -368,6 +367,7 @@ function mapOptions(compressedNodeUnwrapper: CompressedNodeUnwra
 }
 
 export interface ICompressibleObjectTreeModelOptions extends IObjectTreeModelOptions {
+	readonly compressionEnabled?: boolean;
 	readonly elementMapper?: ElementMapper;
 }
 
@@ -493,7 +493,7 @@ export class CompressibleObjectTreeModel, TFilterData
 		return this.model.resort(element, recursive);
 	}
 
-	getCompressedTreeNode(element: T): ITreeNode, TFilterData> {
-		return this.model.getNode(element) as ITreeNode, TFilterData>;
+	getCompressedTreeNode(location: T | null = null): ITreeNode | null, TFilterData> {
+		return this.model.getNode(location);
 	}
 }
diff --git a/src/vs/base/browser/ui/tree/dataTree.ts b/src/vs/base/browser/ui/tree/dataTree.ts
index 5b4aa9b168..766628925b 100644
--- a/src/vs/base/browser/ui/tree/dataTree.ts
+++ b/src/vs/base/browser/ui/tree/dataTree.ts
@@ -11,7 +11,7 @@ import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list
 import { Iterator } from 'vs/base/common/iterator';
 
 export interface IDataTreeOptions extends IAbstractTreeOptions {
-	sorter?: ITreeSorter;
+	readonly sorter?: ITreeSorter;
 }
 
 export interface IDataTreeViewState {
diff --git a/src/vs/base/browser/ui/tree/indexTreeModel.ts b/src/vs/base/browser/ui/tree/indexTreeModel.ts
index 2c4407fa26..61da572b86 100644
--- a/src/vs/base/browser/ui/tree/indexTreeModel.ts
+++ b/src/vs/base/browser/ui/tree/indexTreeModel.ts
@@ -442,6 +442,7 @@ export class IndexTreeModel, TFilterData = voi
 
 			if (visibility === TreeVisibility.Hidden) {
 				node.visible = false;
+				node.renderNodeCount = 0;
 				return false;
 			}
 
diff --git a/src/vs/base/browser/ui/tree/media/panelviewlet.css b/src/vs/base/browser/ui/tree/media/paneviewlet.css
similarity index 80%
rename from src/vs/base/browser/ui/tree/media/panelviewlet.css
rename to src/vs/base/browser/ui/tree/media/paneviewlet.css
index 3715ee6a80..740ab44eb2 100644
--- a/src/vs/base/browser/ui/tree/media/panelviewlet.css
+++ b/src/vs/base/browser/ui/tree/media/paneviewlet.css
@@ -3,11 +3,10 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
-.monaco-panel-view .panel > .panel-header h3.title {
+.monaco-pane-view .pane > .pane-header h3.title {
 	white-space: nowrap;
 	text-overflow: ellipsis;
 	overflow: hidden;
 	font-size: 11px;
-	-webkit-margin-before: 0;
-	-webkit-margin-after: 0;
+	margin: 0;
 }
diff --git a/src/vs/base/browser/ui/tree/media/tree.css b/src/vs/base/browser/ui/tree/media/tree.css
index d4dbaf2ea3..61cb32a82c 100644
--- a/src/vs/base/browser/ui/tree/media/tree.css
+++ b/src/vs/base/browser/ui/tree/media/tree.css
@@ -14,7 +14,7 @@
 	height: 100%;
 	position: absolute;
 	top: 0;
-	left: 18px;
+	left: 16px;
 	pointer-events: none;
 }
 
@@ -41,7 +41,7 @@
 .monaco-tl-twistie {
 	font-size: 10px;
 	text-align: right;
-	margin-right: 6px;
+	padding-right: 6px;
 	flex-shrink: 0;
 	width: 16px;
 	display: flex !important;
diff --git a/src/vs/base/browser/ui/tree/objectTree.ts b/src/vs/base/browser/ui/tree/objectTree.ts
index b5ed7a3b63..19930f9b00 100644
--- a/src/vs/base/browser/ui/tree/objectTree.ts
+++ b/src/vs/base/browser/ui/tree/objectTree.ts
@@ -4,7 +4,7 @@
  *--------------------------------------------------------------------------------------------*/
 
 import { ISequence } from 'vs/base/common/iterator';
-import { AbstractTree, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree';
+import { AbstractTree, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree';
 import { ISpliceable } from 'vs/base/common/sequence';
 import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer, ITreeSorter, ICollapseStateChangeEvent } from 'vs/base/browser/ui/tree/tree';
 import { ObjectTreeModel, IObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
@@ -56,7 +56,7 @@ export class ObjectTree, TFilterData = void> extends
 }
 
 interface ICompressedTreeNodeProvider {
-	getCompressedTreeNode(element: T): ITreeNode, TFilterData>;
+	getCompressedTreeNode(location: T | null): ITreeNode | null, TFilterData>;
 }
 
 export interface ICompressibleTreeRenderer extends ITreeRenderer {
@@ -69,7 +69,7 @@ interface CompressibleTemplateData {
 	readonly data: TTemplateData;
 }
 
-class CompressibleRenderer implements ITreeRenderer> {
+class CompressibleRenderer, TFilterData, TTemplateData> implements ITreeRenderer> {
 
 	readonly templateId: string;
 	readonly onDidChangeTwistieState: Event | undefined;
@@ -93,7 +93,7 @@ class CompressibleRenderer implements ITreeRender
 	}
 
 	renderElement(node: ITreeNode, index: number, templateData: CompressibleTemplateData, height: number | undefined): void {
-		const compressedTreeNode = this.compressedTreeNodeProvider.getCompressedTreeNode(node.element);
+		const compressedTreeNode = this.compressedTreeNodeProvider.getCompressedTreeNode(node.element) as ITreeNode, TFilterData>;
 
 		if (compressedTreeNode.element.elements.length === 1) {
 			templateData.compressedTreeNode = undefined;
@@ -132,6 +132,7 @@ export interface ICompressibleKeyboardNavigationLabelProvider extends IKeyboa
 }
 
 export interface ICompressibleObjectTreeOptions extends IObjectTreeOptions {
+	readonly compressionEnabled?: boolean;
 	readonly elementMapper?: ElementMapper;
 	readonly keyboardNavigationLabelProvider?: ICompressibleKeyboardNavigationLabelProvider;
 }
@@ -144,7 +145,7 @@ function asObjectTreeOptions(compressedTreeNodeProvider: () => I
 				let compressedTreeNode: ITreeNode, TFilterData>;
 
 				try {
-					compressedTreeNode = compressedTreeNodeProvider().getCompressedTreeNode(e);
+					compressedTreeNode = compressedTreeNodeProvider().getCompressedTreeNode(e) as ITreeNode, TFilterData>;
 				} catch {
 					return options.keyboardNavigationLabelProvider!.getKeyboardNavigationLabel(e);
 				}
@@ -159,6 +160,10 @@ function asObjectTreeOptions(compressedTreeNodeProvider: () => I
 	};
 }
 
+export interface ICompressibleObjectTreeOptionsUpdate extends IAbstractTreeOptionsUpdate {
+	readonly compressionEnabled?: boolean;
+}
+
 export class CompressibleObjectTree, TFilterData = void> extends ObjectTree implements ICompressedTreeNodeProvider {
 
 	protected model!: CompressibleObjectTreeModel;
@@ -171,7 +176,7 @@ export class CompressibleObjectTree, TFilterData = vo
 		options: ICompressibleObjectTreeOptions = {}
 	) {
 		const compressedTreeNodeProvider = () => this;
-		const compressibleRenderers = renderers.map(r => new CompressibleRenderer(compressedTreeNodeProvider, r));
+		const compressibleRenderers = renderers.map(r => new CompressibleRenderer(compressedTreeNodeProvider, r));
 		super(user, container, delegate, compressibleRenderers, asObjectTreeOptions(compressedTreeNodeProvider, options));
 	}
 
@@ -183,15 +188,15 @@ export class CompressibleObjectTree, TFilterData = vo
 		return new CompressibleObjectTreeModel(user, view, options);
 	}
 
-	isCompressionEnabled(): boolean {
-		return this.model.isCompressionEnabled();
+	updateOptions(optionsUpdate: ICompressibleObjectTreeOptionsUpdate = {}): void {
+		super.updateOptions(optionsUpdate);
+
+		if (typeof optionsUpdate.compressionEnabled !== 'undefined') {
+			this.model.setCompressionEnabled(optionsUpdate.compressionEnabled);
+		}
 	}
 
-	setCompressionEnabled(enabled: boolean): void {
-		this.model.setCompressionEnabled(enabled);
-	}
-
-	getCompressedTreeNode(element: T): ITreeNode, TFilterData> {
-		return this.model.getCompressedTreeNode(element)!;
+	getCompressedTreeNode(element: T | null = null): ITreeNode | null, TFilterData> {
+		return this.model.getCompressedTreeNode(element);
 	}
 }
diff --git a/src/vs/base/browser/ui/tree/objectTreeModel.ts b/src/vs/base/browser/ui/tree/objectTreeModel.ts
index 2906f9d7bd..268d6b4d50 100644
--- a/src/vs/base/browser/ui/tree/objectTreeModel.ts
+++ b/src/vs/base/browser/ui/tree/objectTreeModel.ts
@@ -257,7 +257,12 @@ export class ObjectTreeModel, TFilterData extends Non
 			throw new TreeError(this.user, `Invalid getParentNodeLocation call`);
 		}
 
-		const node = this.nodes.get(element)!;
+		const node = this.nodes.get(element);
+
+		if (!node) {
+			throw new TreeError(this.user, `Tree element not found: ${element}`);
+		}
+
 		const location = this.model.getNodeLocation(node);
 		const parentLocation = this.model.getParentNodeLocation(location);
 		const parent = this.model.getNode(parentLocation);
diff --git a/src/vs/base/browser/ui/widget.ts b/src/vs/base/browser/ui/widget.ts
index 37be854e99..4f8b9687b0 100644
--- a/src/vs/base/browser/ui/widget.ts
+++ b/src/vs/base/browser/ui/widget.ts
@@ -7,6 +7,7 @@ import * as dom from 'vs/base/browser/dom';
 import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
 import { IMouseEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent';
 import { Disposable } from 'vs/base/common/lifecycle';
+import { Gesture } from 'vs/base/browser/touch';
 
 export abstract class Widget extends Disposable {
 
@@ -49,4 +50,8 @@ export abstract class Widget extends Disposable {
 	protected onchange(domNode: HTMLElement, listener: (e: Event) => void): void {
 		this._register(dom.addDisposableListener(domNode, dom.EventType.CHANGE, listener));
 	}
+
+	protected ignoreGesture(domNode: HTMLElement): void {
+		Gesture.ignoreTarget(domNode);
+	}
 }
diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts
index 131df405be..0d0c483568 100644
--- a/src/vs/base/common/async.ts
+++ b/src/vs/base/common/async.ts
@@ -764,5 +764,5 @@ export async function retry(task: ITask>, delay: number, retries:
 		}
 	}
 
-	return Promise.reject(lastError);
+	throw lastError;
 }
diff --git a/src/vs/base/common/buffer.ts b/src/vs/base/common/buffer.ts
index 9d70184b4e..320613a3f6 100644
--- a/src/vs/base/common/buffer.ts
+++ b/src/vs/base/common/buffer.ts
@@ -3,8 +3,14 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
+import * as strings from 'vs/base/common/strings';
+import * as streams from 'vs/base/common/stream';
+
 declare var Buffer: any;
-export const hasBuffer = (typeof Buffer !== 'undefined');
+
+const hasBuffer = (typeof Buffer !== 'undefined');
+const hasTextEncoder = (typeof TextEncoder !== 'undefined');
+const hasTextDecoder = (typeof TextDecoder !== 'undefined');
 
 let textEncoder: TextEncoder | null;
 let textDecoder: TextDecoder | null;
@@ -31,11 +37,13 @@ export class VSBuffer {
 	static fromString(source: string): VSBuffer {
 		if (hasBuffer) {
 			return new VSBuffer(Buffer.from(source));
-		} else {
+		} else if (hasTextEncoder) {
 			if (!textEncoder) {
 				textEncoder = new TextEncoder();
 			}
 			return new VSBuffer(textEncoder.encode(source));
+		} else {
+			return new VSBuffer(strings.encodeUTF8(source));
 		}
 	}
 
@@ -69,11 +77,13 @@ export class VSBuffer {
 	toString(): string {
 		if (hasBuffer) {
 			return this.buffer.toString();
-		} else {
+		} else if (hasTextDecoder) {
 			if (!textDecoder) {
 				textDecoder = new TextDecoder();
 			}
 			return textDecoder.decode(this.buffer);
+		} else {
+			return strings.decodeUTF8(this.buffer);
 		}
 	}
 
@@ -132,335 +142,32 @@ function writeUInt8(destination: Uint8Array, value: number, offset: number): voi
 	destination[offset] = value;
 }
 
-export interface VSBufferReadable {
+export interface VSBufferReadable extends streams.Readable { }
 
-	/**
-	 * Read data from the underlying source. Will return
-	 * null to indicate that no more data can be read.
-	 */
-	read(): VSBuffer | null;
-}
+export interface VSBufferReadableStream extends streams.ReadableStream { }
 
-export interface ReadableStream {
+export interface VSBufferWriteableStream extends streams.WriteableStream { }
 
-	/**
-	 * The 'data' event is emitted whenever the stream is
-	 * relinquishing ownership of a chunk of data to a consumer.
-	 */
-	on(event: 'data', callback: (chunk: T) => void): void;
-
-	/**
-	 * Emitted when any error occurs.
-	 */
-	on(event: 'error', callback: (err: any) => void): void;
-
-	/**
-	 * The 'end' event is emitted when there is no more data
-	 * to be consumed from the stream. The 'end' event will
-	 * not be emitted unless the data is completely consumed.
-	 */
-	on(event: 'end', callback: () => void): void;
-
-	/**
-	 * Stops emitting any events until resume() is called.
-	 */
-	pause?(): void;
-
-	/**
-	 * Starts emitting events again after pause() was called.
-	 */
-	resume?(): void;
-
-	/**
-	 * Destroys the stream and stops emitting any event.
-	 */
-	destroy?(): void;
-}
-
-/**
- * A readable stream that sends data via VSBuffer.
- */
-export interface VSBufferReadableStream extends ReadableStream {
-	pause(): void;
-	resume(): void;
-	destroy(): void;
-}
-
-export function isVSBufferReadableStream(obj: any): obj is VSBufferReadableStream {
-	const candidate: VSBufferReadableStream = obj;
-
-	return candidate && [candidate.on, candidate.pause, candidate.resume, candidate.destroy].every(fn => typeof fn === 'function');
-}
-
-/**
- * Helper to fully read a VSBuffer readable into a single buffer.
- */
 export function readableToBuffer(readable: VSBufferReadable): VSBuffer {
-	const chunks: VSBuffer[] = [];
-
-	let chunk: VSBuffer | null;
-	while (chunk = readable.read()) {
-		chunks.push(chunk);
-	}
-
-	return VSBuffer.concat(chunks);
+	return streams.consumeReadable(readable, chunks => VSBuffer.concat(chunks));
 }
 
-/**
- * Helper to convert a buffer into a readable buffer.
- */
 export function bufferToReadable(buffer: VSBuffer): VSBufferReadable {
-	let done = false;
-
-	return {
-		read: () => {
-			if (done) {
-				return null;
-			}
-
-			done = true;
-
-			return buffer;
-		}
-	};
+	return streams.toReadable(buffer);
 }
 
-/**
- * Helper to fully read a VSBuffer stream into a single buffer.
- */
-export function streamToBuffer(stream: VSBufferReadableStream): Promise {
-	return new Promise((resolve, reject) => {
-		const chunks: VSBuffer[] = [];
-
-		stream.on('data', chunk => chunks.push(chunk));
-		stream.on('error', error => reject(error));
-		stream.on('end', () => resolve(VSBuffer.concat(chunks)));
-	});
+export function streamToBuffer(stream: streams.ReadableStream): Promise {
+	return streams.consumeStream(stream, chunks => VSBuffer.concat(chunks));
 }
 
-/**
- * Helper to create a VSBufferStream from an existing VSBuffer.
- */
-export function bufferToStream(buffer: VSBuffer): VSBufferReadableStream {
-	const stream = writeableBufferStream();
-
-	stream.end(buffer);
-
-	return stream;
+export function bufferToStream(buffer: VSBuffer): streams.ReadableStream {
+	return streams.toStream(buffer, chunks => VSBuffer.concat(chunks));
 }
 
-/**
- * Helper to create a VSBufferStream from a Uint8Array stream.
- */
-export function toVSBufferReadableStream(stream: ReadableStream): VSBufferReadableStream {
-	const vsbufferStream = writeableBufferStream();
-
-	stream.on('data', data => vsbufferStream.write(typeof data === 'string' ? VSBuffer.fromString(data) : VSBuffer.wrap(data)));
-	stream.on('end', () => vsbufferStream.end());
-	stream.on('error', error => vsbufferStream.error(error));
-
-	return vsbufferStream;
+export function streamToBufferReadableStream(stream: streams.ReadableStreamEvents): streams.ReadableStream {
+	return streams.transform(stream, { data: data => typeof data === 'string' ? VSBuffer.fromString(data) : VSBuffer.wrap(data) }, chunks => VSBuffer.concat(chunks));
 }
 
-/**
- * Helper to create a VSBufferStream that can be pushed
- * buffers to. Will only start to emit data when a listener
- * is added.
- */
-export function writeableBufferStream(): VSBufferWriteableStream {
-	return new VSBufferWriteableStreamImpl();
-}
-
-export interface VSBufferWriteableStream extends VSBufferReadableStream {
-	write(chunk: VSBuffer): void;
-	error(error: Error): void;
-	end(result?: VSBuffer | Error): void;
-}
-
-class VSBufferWriteableStreamImpl implements VSBufferWriteableStream {
-
-	private readonly state = {
-		flowing: false,
-		ended: false,
-		destroyed: false
-	};
-
-	private readonly buffer = {
-		data: [] as VSBuffer[],
-		error: [] as Error[]
-	};
-
-	private readonly listeners = {
-		data: [] as { (chunk: VSBuffer): void }[],
-		error: [] as { (error: Error): void }[],
-		end: [] as { (): void }[]
-	};
-
-	pause(): void {
-		if (this.state.destroyed) {
-			return;
-		}
-
-		this.state.flowing = false;
-	}
-
-	resume(): void {
-		if (this.state.destroyed) {
-			return;
-		}
-
-		if (!this.state.flowing) {
-			this.state.flowing = true;
-
-			// emit buffered events
-			this.flowData();
-			this.flowErrors();
-			this.flowEnd();
-		}
-	}
-
-	write(chunk: VSBuffer): void {
-		if (this.state.destroyed) {
-			return;
-		}
-
-		// flowing: directly send the data to listeners
-		if (this.state.flowing) {
-			this.listeners.data.forEach(listener => listener(chunk));
-		}
-
-		// not yet flowing: buffer data until flowing
-		else {
-			this.buffer.data.push(chunk);
-		}
-	}
-
-	error(error: Error): void {
-		if (this.state.destroyed) {
-			return;
-		}
-
-		// flowing: directly send the error to listeners
-		if (this.state.flowing) {
-			this.listeners.error.forEach(listener => listener(error));
-		}
-
-		// not yet flowing: buffer errors until flowing
-		else {
-			this.buffer.error.push(error);
-		}
-	}
-
-	end(result?: VSBuffer | Error): void {
-		if (this.state.destroyed) {
-			return;
-		}
-
-		// end with data or error if provided
-		if (result instanceof Error) {
-			this.error(result);
-		} else if (result) {
-			this.write(result);
-		}
-
-		// flowing: send end event to listeners
-		if (this.state.flowing) {
-			this.listeners.end.forEach(listener => listener());
-
-			this.destroy();
-		}
-
-		// not yet flowing: remember state
-		else {
-			this.state.ended = true;
-		}
-	}
-
-	on(event: 'data', callback: (chunk: VSBuffer) => void): void;
-	on(event: 'error', callback: (err: any) => void): void;
-	on(event: 'end', callback: () => void): void;
-	on(event: 'data' | 'error' | 'end', callback: (arg0?: any) => void): void {
-		if (this.state.destroyed) {
-			return;
-		}
-
-		switch (event) {
-			case 'data':
-				this.listeners.data.push(callback);
-
-				// switch into flowing mode as soon as the first 'data'
-				// listener is added and we are not yet in flowing mode
-				this.resume();
-
-				break;
-
-			case 'end':
-				this.listeners.end.push(callback);
-
-				// emit 'end' event directly if we are flowing
-				// and the end has already been reached
-				//
-				// finish() when it went through
-				if (this.state.flowing && this.flowEnd()) {
-					this.destroy();
-				}
-
-				break;
-
-			case 'error':
-				this.listeners.error.push(callback);
-
-				// emit buffered 'error' events unless done already
-				// now that we know that we have at least one listener
-				if (this.state.flowing) {
-					this.flowErrors();
-				}
-
-				break;
-		}
-	}
-
-	private flowData(): void {
-		if (this.buffer.data.length > 0) {
-			const fullDataBuffer = VSBuffer.concat(this.buffer.data);
-
-			this.listeners.data.forEach(listener => listener(fullDataBuffer));
-
-			this.buffer.data.length = 0;
-		}
-	}
-
-	private flowErrors(): void {
-		if (this.listeners.error.length > 0) {
-			for (const error of this.buffer.error) {
-				this.listeners.error.forEach(listener => listener(error));
-			}
-
-			this.buffer.error.length = 0;
-		}
-	}
-
-	private flowEnd(): boolean {
-		if (this.state.ended) {
-			this.listeners.end.forEach(listener => listener());
-
-			return this.listeners.end.length > 0;
-		}
-
-		return false;
-	}
-
-	destroy(): void {
-		if (!this.state.destroyed) {
-			this.state.destroyed = true;
-			this.state.ended = true;
-
-			this.buffer.data.length = 0;
-			this.buffer.error.length = 0;
-
-			this.listeners.data.length = 0;
-			this.listeners.error.length = 0;
-			this.listeners.end.length = 0;
-		}
-	}
+export function newWriteableBufferStream(): streams.WriteableStream {
+	return streams.newWriteableStream(chunks => VSBuffer.concat(chunks));
 }
diff --git a/src/vs/base/common/cache.ts b/src/vs/base/common/cache.ts
index bb43d54730..655db17510 100644
--- a/src/vs/base/common/cache.ts
+++ b/src/vs/base/common/cache.ts
@@ -22,7 +22,6 @@ export class Cache {
 
 		const cts = new CancellationTokenSource();
 		const promise = this.task(cts.token);
-		promise.finally(() => cts.dispose());
 
 		this.result = {
 			promise,
diff --git a/src/vs/base/common/color.ts b/src/vs/base/common/color.ts
index d5cf65fb2b..555c9bea07 100644
--- a/src/vs/base/common/color.ts
+++ b/src/vs/base/common/color.ts
@@ -399,6 +399,23 @@ export class Color {
 		return new Color(new RGBA(r, g, b, a));
 	}
 
+	makeOpaque(opaqueBackground: Color): Color {
+		if (this.isOpaque() || opaqueBackground.rgba.a !== 1) {
+			// only allow to blend onto a non-opaque color onto a opaque color
+			return this;
+		}
+
+		const { r, g, b, a } = this.rgba;
+
+		// https://stackoverflow.com/questions/12228548/finding-equivalent-color-with-opacity
+		return new Color(new RGBA(
+			opaqueBackground.rgba.r - a * (opaqueBackground.rgba.r - r),
+			opaqueBackground.rgba.g - a * (opaqueBackground.rgba.g - g),
+			opaqueBackground.rgba.b - a * (opaqueBackground.rgba.b - b),
+			1
+		));
+	}
+
 	flatten(...backgrounds: Color[]): Color {
 		const background = backgrounds.reduceRight((accumulator, color) => {
 			return Color._flatten(color, accumulator);
diff --git a/src/vs/base/common/errors.ts b/src/vs/base/common/errors.ts
index 4a04854bf8..9f04cd4c90 100644
--- a/src/vs/base/common/errors.ts
+++ b/src/vs/base/common/errors.ts
@@ -195,7 +195,6 @@ export function getErrorMessage(err: any): string {
 	return String(err);
 }
 
-
 export class NotImplementedError extends Error {
 	constructor(message?: string) {
 		super('NotImplemented');
diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts
index 0ae897bde6..c83d0bd2cc 100644
--- a/src/vs/base/common/event.ts
+++ b/src/vs/base/common/event.ts
@@ -7,6 +7,7 @@ import { onUnexpectedError } from 'vs/base/common/errors';
 import { once as onceFn } from 'vs/base/common/functional';
 import { Disposable, IDisposable, toDisposable, combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle';
 import { LinkedList } from 'vs/base/common/linkedList';
+import { CancellationToken } from 'vs/base/common/cancellation';
 
 /**
  * To an event a function with one or zero parameters
@@ -653,27 +654,39 @@ export interface IWaitUntil {
 
 export class AsyncEmitter extends Emitter {
 
-	private _asyncDeliveryQueue?: [Listener, T, Promise[]][];
+	private _asyncDeliveryQueue?: LinkedList<[Listener, Omit]>;
 
-	async fireAsync(eventFn: (thenables: Promise[], listener: Function) => T): Promise {
+	async fireAsync(data: Omit, token: CancellationToken, promiseJoin?: (p: Promise, listener: Function) => Promise): Promise {
 		if (!this._listeners) {
 			return;
 		}
 
-		// put all [listener,event]-pairs into delivery queue
-		// then emit all event. an inner/nested event might be
-		// the driver of this
 		if (!this._asyncDeliveryQueue) {
-			this._asyncDeliveryQueue = [];
+			this._asyncDeliveryQueue = new LinkedList();
 		}
 
 		for (let iter = this._listeners.iterator(), e = iter.next(); !e.done; e = iter.next()) {
-			const thenables: Promise[] = [];
-			this._asyncDeliveryQueue.push([e.value, eventFn(thenables, typeof e.value === 'function' ? e.value : e.value[0]), thenables]);
+			this._asyncDeliveryQueue.push([e.value, data]);
 		}
 
-		while (this._asyncDeliveryQueue.length > 0) {
-			const [listener, event, thenables] = this._asyncDeliveryQueue.shift()!;
+		while (this._asyncDeliveryQueue.size > 0 && !token.isCancellationRequested) {
+
+			const [listener, data] = this._asyncDeliveryQueue.shift()!;
+			const thenables: Promise[] = [];
+
+			const event = {
+				...data,
+				waitUntil: (p: Promise): void => {
+					if (Object.isFrozen(thenables)) {
+						throw new Error('waitUntil can NOT be called asynchronous');
+					}
+					if (promiseJoin) {
+						p = promiseJoin(p, typeof listener === 'function' ? listener : listener[0]);
+					}
+					thenables.push(p);
+				}
+			};
+
 			try {
 				if (typeof listener === 'function') {
 					listener.call(undefined, event);
@@ -688,7 +701,7 @@ export class AsyncEmitter extends Emitter {
 			// freeze thenables-collection to enforce sync-calls to
 			// wait until and then wait for all thenables to resolve
 			Object.freeze(thenables);
-			await Promise.all(thenables);
+			await Promise.all(thenables).catch(e => onUnexpectedError(e));
 		}
 	}
 }
diff --git a/src/vs/base/common/filters.ts b/src/vs/base/common/filters.ts
index ff688e0986..d247714d26 100644
--- a/src/vs/base/common/filters.ts
+++ b/src/vs/base/common/filters.ts
@@ -543,7 +543,7 @@ export function fuzzyScore(pattern: string, patternLow: string, patternStart: nu
 	const patternLen = pattern.length > _maxLen ? _maxLen : pattern.length;
 	const wordLen = word.length > _maxLen ? _maxLen : word.length;
 
-	if (patternStart >= patternLen || wordStart >= wordLen || patternLen > wordLen) {
+	if (patternStart >= patternLen || wordStart >= wordLen || (patternLen - patternStart) > (wordLen - wordStart)) {
 		return undefined;
 	}
 
diff --git a/src/vs/base/common/iterator.ts b/src/vs/base/common/iterator.ts
index f9bee211a7..0fca7c7cab 100644
--- a/src/vs/base/common/iterator.ts
+++ b/src/vs/base/common/iterator.ts
@@ -172,13 +172,28 @@ export module Iterator {
 			}
 		};
 	}
+
+	export function chain(iterator: Iterator): ChainableIterator {
+		return new ChainableIterator(iterator);
+	}
+}
+
+export class ChainableIterator implements Iterator {
+
+	constructor(private it: Iterator) { }
+
+	next(): IteratorResult { return this.it.next(); }
+	map(fn: (t: T) => R): ChainableIterator { return new ChainableIterator(Iterator.map(this.it, fn)); }
+	filter(fn: (t: T) => boolean): ChainableIterator { return new ChainableIterator(Iterator.filter(this.it, fn)); }
 }
 
 export type ISequence = Iterator | T[];
 
-export function getSequenceIterator(arg: Iterator | T[]): Iterator {
+export function getSequenceIterator(arg: ISequence | undefined): Iterator {
 	if (Array.isArray(arg)) {
 		return Iterator.fromArray(arg);
+	} else if (!arg) {
+		return Iterator.empty();
 	} else {
 		return arg;
 	}
@@ -271,7 +286,7 @@ export interface INavigator extends INextIterator {
 
 export class MappedNavigator extends MappedIterator implements INavigator {
 
-	constructor(protected navigator: INavigator, fn: (item: T) => R) {
+	constructor(protected navigator: INavigator, fn: (item: T | null) => R) {
 		super(navigator, fn);
 	}
 
diff --git a/src/vs/base/common/json.ts b/src/vs/base/common/json.ts
index 9da1277bc8..a7a4cde64b 100644
--- a/src/vs/base/common/json.ts
+++ b/src/vs/base/common/json.ts
@@ -137,6 +137,7 @@ export interface Location {
 export interface ParseOptions {
 	disallowComments?: boolean;
 	allowTrailingComma?: boolean;
+	allowEmptyContent?: boolean;
 }
 
 export namespace ParseOptions {
@@ -785,7 +786,7 @@ export function getLocation(text: string, position: number): Location {
 				if (position < offset) {
 					throw earlyReturnException;
 				}
-				setPreviousNode(value, offset, length, getLiteralNodeType(value));
+				setPreviousNode(value, offset, length, getNodeType(value));
 
 				if (position <= offset + length) {
 					throw earlyReturnException;
@@ -848,7 +849,7 @@ export function parse(text: string, errors: ParseError[] = [], options: ParseOpt
 	function onValue(value: any) {
 		if (Array.isArray(currentParent)) {
 			(currentParent).push(value);
-		} else if (currentProperty) {
+		} else if (currentProperty !== null) {
 			currentParent[currentProperty] = value;
 		}
 	}
@@ -927,7 +928,7 @@ export function parseTree(text: string, errors: ParseError[] = [], options: Pars
 			ensurePropertyComplete(offset + length);
 		},
 		onLiteralValue: (value: any, offset: number, length: number) => {
-			onValue({ type: getLiteralNodeType(value), offset, length, parent: currentParent, value });
+			onValue({ type: getNodeType(value), offset, length, parent: currentParent, value });
 			ensurePropertyComplete(offset + length);
 		},
 		onSeparator: (sep: string, offset: number, length: number) => {
@@ -1287,7 +1288,11 @@ export function visit(text: string, visitor: JSONVisitor, options: ParseOptions
 
 	scanNext();
 	if (_scanner.getToken() === SyntaxKind.EOF) {
-		return true;
+		if (options.allowEmptyContent) {
+			return true;
+		}
+		handleError(ParseErrorCode.ValueExpected, [], []);
+		return false;
 	}
 	if (!parseValue()) {
 		handleError(ParseErrorCode.ValueExpected, [], []);
@@ -1333,11 +1338,19 @@ export function stripComments(text: string, replaceCh?: string): string {
 	return parts.join('');
 }
 
-function getLiteralNodeType(value: any): NodeType {
+export function getNodeType(value: any): NodeType {
 	switch (typeof value) {
 		case 'boolean': return 'boolean';
 		case 'number': return 'number';
 		case 'string': return 'string';
+		case 'object': {
+			if (!value) {
+				return 'null';
+			} else if (Array.isArray(value)) {
+				return 'array';
+			}
+			return 'object';
+		}
 		default: return 'null';
 	}
 }
diff --git a/src/vs/base/common/jsonEdit.ts b/src/vs/base/common/jsonEdit.ts
index 1de95c55c0..a106d88dfc 100644
--- a/src/vs/base/common/jsonEdit.ts
+++ b/src/vs/base/common/jsonEdit.ts
@@ -84,40 +84,36 @@ export function setProperty(text: string, originalPath: JSONPath, value: any, fo
 			return withFormatting(text, edit, formattingOptions);
 		}
 	} else if (parent.type === 'array' && typeof lastSegment === 'number' && Array.isArray(parent.children)) {
-		const insertIndex = lastSegment;
-		if (insertIndex === -1) {
+		if (value !== undefined) {
 			// Insert
 			const newProperty = `${JSON.stringify(value)}`;
 			let edit: Edit;
-			if (parent.children.length === 0) {
-				edit = { offset: parent.offset + 1, length: 0, content: newProperty };
+			if (parent.children.length === 0 || lastSegment === 0) {
+				edit = { offset: parent.offset + 1, length: 0, content: parent.children.length === 0 ? newProperty : newProperty + ',' };
 			} else {
-				const previous = parent.children[parent.children.length - 1];
+				const index = lastSegment === -1 || lastSegment > parent.children.length ? parent.children.length : lastSegment;
+				const previous = parent.children[index - 1];
 				edit = { offset: previous.offset + previous.length, length: 0, content: ',' + newProperty };
 			}
 			return withFormatting(text, edit, formattingOptions);
 		} else {
-			if (value === undefined && parent.children.length >= 0) {
-				//Removal
-				const removalIndex = lastSegment;
-				const toRemove = parent.children[removalIndex];
-				let edit: Edit;
-				if (parent.children.length === 1) {
-					// only item
-					edit = { offset: parent.offset + 1, length: parent.length - 2, content: '' };
-				} else if (parent.children.length - 1 === removalIndex) {
-					// last item
-					const previous = parent.children[removalIndex - 1];
-					const offset = previous.offset + previous.length;
-					const parentEndOffset = parent.offset + parent.length;
-					edit = { offset, length: parentEndOffset - 2 - offset, content: '' };
-				} else {
-					edit = { offset: toRemove.offset, length: parent.children[removalIndex + 1].offset - toRemove.offset, content: '' };
-				}
-				return withFormatting(text, edit, formattingOptions);
+			//Removal
+			const removalIndex = lastSegment;
+			const toRemove = parent.children[removalIndex];
+			let edit: Edit;
+			if (parent.children.length === 1) {
+				// only item
+				edit = { offset: parent.offset + 1, length: parent.length - 2, content: '' };
+			} else if (parent.children.length - 1 === removalIndex) {
+				// last item
+				const previous = parent.children[removalIndex - 1];
+				const offset = previous.offset + previous.length;
+				const parentEndOffset = parent.offset + parent.length;
+				edit = { offset, length: parentEndOffset - 2 - offset, content: '' };
 			} else {
-				throw new Error('Array modification not supported yet');
+				edit = { offset: toRemove.offset, length: parent.children[removalIndex + 1].offset - toRemove.offset, content: '' };
 			}
+			return withFormatting(text, edit, formattingOptions);
 		}
 	} else {
 		throw new Error(`Can not add ${typeof lastSegment !== 'number' ? 'index' : 'property'} to parent of type ${parent.type}`);
diff --git a/src/vs/base/common/jsonSchema.ts b/src/vs/base/common/jsonSchema.ts
index 005215ce15..f91092e953 100644
--- a/src/vs/base/common/jsonSchema.ts
+++ b/src/vs/base/common/jsonSchema.ts
@@ -3,11 +3,13 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
+export type JSONSchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'null' | 'array' | 'object';
+
 export interface IJSONSchema {
 	id?: string;
 	$id?: string;
 	$schema?: string;
-	type?: string | string[];
+	type?: JSONSchemaType | JSONSchemaType[];
 	title?: string;
 	default?: any;
 	definitions?: IJSONSchemaMap;
diff --git a/src/vs/base/common/lazy.ts b/src/vs/base/common/lazy.ts
index ad3d0a6c6c..4ba28d5298 100644
--- a/src/vs/base/common/lazy.ts
+++ b/src/vs/base/common/lazy.ts
@@ -54,6 +54,11 @@ export class Lazy {
 		return this._value!;
 	}
 
+	/**
+	 * Get the wrapped value without forcing evaluation.
+	 */
+	get rawValue(): T | undefined { return this._value; }
+
 	/**
 	 * Create a new lazy value that is the result of applying `f` to the wrapped value.
 	 *
diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts
index e172e766e9..ec33e251e0 100644
--- a/src/vs/base/common/lifecycle.ts
+++ b/src/vs/base/common/lifecycle.ts
@@ -206,43 +206,6 @@ export class MutableDisposable implements IDisposable {
 	}
 }
 
-/**
- * Wrapper class that stores a disposable that is not currently "owned" by anyone.
- *
- * Example use cases:
- *
- * - Express that a function/method will take ownership of a disposable parameter.
- * - Express that a function returns a disposable that the caller must explicitly take ownership of.
- */
-export class UnownedDisposable extends Disposable {
-	private _hasBeenAcquired = false;
-	private _value?: T;
-
-	public constructor(value: T) {
-		super();
-		this._value = value;
-	}
-
-	public acquire(): T {
-		if (this._hasBeenAcquired) {
-			throw new Error('This disposable has already been acquired');
-		}
-		this._hasBeenAcquired = true;
-		const value = this._value!;
-		this._value = undefined;
-		return value;
-	}
-
-	public dispose() {
-		super.dispose();
-		if (!this._hasBeenAcquired) {
-			this._hasBeenAcquired = true;
-			this._value!.dispose();
-			this._value = undefined;
-		}
-	}
-}
-
 export interface IReference extends IDisposable {
 	readonly object: T;
 }
diff --git a/src/vs/base/common/mime.ts b/src/vs/base/common/mime.ts
index db69800686..a86aee3a97 100644
--- a/src/vs/base/common/mime.ts
+++ b/src/vs/base/common/mime.ts
@@ -262,7 +262,14 @@ export function suggestFilename(mode: string | undefined, prefix: string): strin
 		.filter(assoc => startsWith(assoc, '.'));
 
 	if (extensionsWithDotFirst.length > 0) {
-		return prefix + extensionsWithDotFirst[0];
+		const candidateExtension = extensionsWithDotFirst[0];
+		if (endsWith(prefix, candidateExtension)) {
+			// do not add the prefix if it already exists
+			// https://github.com/microsoft/vscode/issues/83603
+			return prefix;
+		}
+
+		return prefix + candidateExtension;
 	}
 
 	return extensions[0] || prefix;
diff --git a/src/vs/base/common/path.ts b/src/vs/base/common/path.ts
index ad915811d6..029422cedc 100644
--- a/src/vs/base/common/path.ts
+++ b/src/vs/base/common/path.ts
@@ -69,11 +69,11 @@ function validateString(value: string, name: string) {
 	}
 }
 
-function isPathSeparator(code: number) {
+function isPathSeparator(code: number | undefined) {
 	return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH;
 }
 
-function isPosixPathSeparator(code: number) {
+function isPosixPathSeparator(code: number | undefined) {
 	return code === CHAR_FORWARD_SLASH;
 }
 
diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts
index acb0b0d6e7..d94f1b6d4c 100644
--- a/src/vs/base/common/platform.ts
+++ b/src/vs/base/common/platform.ts
@@ -10,6 +10,7 @@ let _isMacintosh = false;
 let _isLinux = false;
 let _isNative = false;
 let _isWeb = false;
+let _isIOS = false;
 let _locale: string | undefined = undefined;
 let _language: string = LANGUAGE_DEFAULT;
 let _translationsConfigFile: string | undefined = undefined;
@@ -41,6 +42,7 @@ declare const global: any;
 interface INavigator {
 	userAgent: string;
 	language: string;
+	maxTouchPoints?: number;
 }
 declare const navigator: INavigator;
 declare const self: any;
@@ -52,6 +54,7 @@ if (typeof navigator === 'object' && !isElectronRenderer) {
 	_userAgent = navigator.userAgent;
 	_isWindows = _userAgent.indexOf('Windows') >= 0;
 	_isMacintosh = _userAgent.indexOf('Macintosh') >= 0;
+	_isIOS = _userAgent.indexOf('Macintosh') >= 0 && !!navigator.maxTouchPoints && navigator.maxTouchPoints > 0;
 	_isLinux = _userAgent.indexOf('Linux') >= 0;
 	_isWeb = true;
 	_locale = navigator.language;
@@ -106,6 +109,7 @@ export const isMacintosh = _isMacintosh;
 export const isLinux = _isLinux;
 export const isNative = _isNative;
 export const isWeb = _isWeb;
+export const isIOS = _isIOS;
 export const platform = _platform;
 export const userAgent = _userAgent;
 
@@ -189,7 +193,7 @@ export const setImmediate: ISetImmediate = (function defineSetImmediate() {
 				id: myId,
 				callback: callback
 			});
-			globals.postMessage({ vscodeSetImmediateId: myId });
+			globals.postMessage({ vscodeSetImmediateId: myId }, '*');
 		};
 	}
 	if (typeof process !== 'undefined' && typeof process.nextTick === 'function') {
diff --git a/src/vs/base/common/stream.ts b/src/vs/base/common/stream.ts
new file mode 100644
index 0000000000..0b7884b56e
--- /dev/null
+++ b/src/vs/base/common/stream.ts
@@ -0,0 +1,487 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+/**
+ * The payload that flows in readable stream events.
+ */
+export type ReadableStreamEventPayload = T | Error | 'end';
+
+export interface ReadableStreamEvents {
+
+	/**
+	 * The 'data' event is emitted whenever the stream is
+	 * relinquishing ownership of a chunk of data to a consumer.
+	 */
+	on(event: 'data', callback: (data: T) => void): void;
+
+	/**
+	 * Emitted when any error occurs.
+	 */
+	on(event: 'error', callback: (err: Error) => void): void;
+
+	/**
+	 * The 'end' event is emitted when there is no more data
+	 * to be consumed from the stream. The 'end' event will
+	 * not be emitted unless the data is completely consumed.
+	 */
+	on(event: 'end', callback: () => void): void;
+}
+
+/**
+ * A interface that emulates the API shape of a node.js readable
+ * stream for use in desktop and web environments.
+ */
+export interface ReadableStream extends ReadableStreamEvents {
+
+	/**
+	 * Stops emitting any events until resume() is called.
+	 */
+	pause(): void;
+
+	/**
+	 * Starts emitting events again after pause() was called.
+	 */
+	resume(): void;
+
+	/**
+	 * Destroys the stream and stops emitting any event.
+	 */
+	destroy(): void;
+}
+
+/**
+ * A interface that emulates the API shape of a node.js readable
+ * for use in desktop and web environments.
+ */
+export interface Readable {
+
+	/**
+	 * Read data from the underlying source. Will return
+	 * null to indicate that no more data can be read.
+	 */
+	read(): T | null;
+}
+
+/**
+ * A interface that emulates the API shape of a node.js writeable
+ * stream for use in desktop and web environments.
+ */
+export interface WriteableStream extends ReadableStream {
+
+	/**
+	 * Writing data to the stream will trigger the on('data')
+	 * event listener if the stream is flowing and buffer the
+	 * data otherwise until the stream is flowing.
+	 */
+	write(data: T): void;
+
+	/**
+	 * Signals an error to the consumer of the stream via the
+	 * on('error') handler if the stream is flowing.
+	 */
+	error(error: Error): void;
+
+	/**
+	 * Signals the end of the stream to the consumer. If the
+	 * result is not an error, will trigger the on('data') event
+	 * listener if the stream is flowing and buffer the data
+	 * otherwise until the stream is flowing.
+	 *
+	 * In case of an error, the on('error') event will be used
+	 * if the stream is flowing.
+	 */
+	end(result?: T | Error): void;
+}
+
+export function isReadableStream(obj: any): obj is ReadableStream {
+	const candidate: ReadableStream = obj;
+
+	return candidate && [candidate.on, candidate.pause, candidate.resume, candidate.destroy].every(fn => typeof fn === 'function');
+}
+
+export interface IReducer {
+	(data: T[]): T;
+}
+
+export interface IDataTransformer {
+	(data: Original): Transformed;
+}
+
+export interface IErrorTransformer {
+	(error: Error): Error;
+}
+
+export interface ITransformer {
+	data: IDataTransformer;
+	error?: IErrorTransformer;
+}
+
+export function newWriteableStream(reducer: IReducer): WriteableStream {
+	return new WriteableStreamImpl(reducer);
+}
+
+class WriteableStreamImpl implements WriteableStream {
+
+	private readonly state = {
+		flowing: false,
+		ended: false,
+		destroyed: false
+	};
+
+	private readonly buffer = {
+		data: [] as T[],
+		error: [] as Error[]
+	};
+
+	private readonly listeners = {
+		data: [] as { (data: T): void }[],
+		error: [] as { (error: Error): void }[],
+		end: [] as { (): void }[]
+	};
+
+	constructor(private reducer: IReducer) { }
+
+	pause(): void {
+		if (this.state.destroyed) {
+			return;
+		}
+
+		this.state.flowing = false;
+	}
+
+	resume(): void {
+		if (this.state.destroyed) {
+			return;
+		}
+
+		if (!this.state.flowing) {
+			this.state.flowing = true;
+
+			// emit buffered events
+			this.flowData();
+			this.flowErrors();
+			this.flowEnd();
+		}
+	}
+
+	write(data: T): void {
+		if (this.state.destroyed) {
+			return;
+		}
+
+		// flowing: directly send the data to listeners
+		if (this.state.flowing) {
+			this.listeners.data.forEach(listener => listener(data));
+		}
+
+		// not yet flowing: buffer data until flowing
+		else {
+			this.buffer.data.push(data);
+		}
+	}
+
+	error(error: Error): void {
+		if (this.state.destroyed) {
+			return;
+		}
+
+		// flowing: directly send the error to listeners
+		if (this.state.flowing) {
+			this.listeners.error.forEach(listener => listener(error));
+		}
+
+		// not yet flowing: buffer errors until flowing
+		else {
+			this.buffer.error.push(error);
+		}
+	}
+
+	end(result?: T | Error): void {
+		if (this.state.destroyed) {
+			return;
+		}
+
+		// end with data or error if provided
+		if (result instanceof Error) {
+			this.error(result);
+		} else if (result) {
+			this.write(result);
+		}
+
+		// flowing: send end event to listeners
+		if (this.state.flowing) {
+			this.listeners.end.forEach(listener => listener());
+
+			this.destroy();
+		}
+
+		// not yet flowing: remember state
+		else {
+			this.state.ended = true;
+		}
+	}
+
+	on(event: 'data', callback: (data: T) => void): void;
+	on(event: 'error', callback: (err: Error) => void): void;
+	on(event: 'end', callback: () => void): void;
+	on(event: 'data' | 'error' | 'end', callback: (arg0?: any) => void): void {
+		if (this.state.destroyed) {
+			return;
+		}
+
+		switch (event) {
+			case 'data':
+				this.listeners.data.push(callback);
+
+				// switch into flowing mode as soon as the first 'data'
+				// listener is added and we are not yet in flowing mode
+				this.resume();
+
+				break;
+
+			case 'end':
+				this.listeners.end.push(callback);
+
+				// emit 'end' event directly if we are flowing
+				// and the end has already been reached
+				//
+				// finish() when it went through
+				if (this.state.flowing && this.flowEnd()) {
+					this.destroy();
+				}
+
+				break;
+
+			case 'error':
+				this.listeners.error.push(callback);
+
+				// emit buffered 'error' events unless done already
+				// now that we know that we have at least one listener
+				if (this.state.flowing) {
+					this.flowErrors();
+				}
+
+				break;
+		}
+	}
+
+	private flowData(): void {
+		if (this.buffer.data.length > 0) {
+			const fullDataBuffer = this.reducer(this.buffer.data);
+
+			this.listeners.data.forEach(listener => listener(fullDataBuffer));
+
+			this.buffer.data.length = 0;
+		}
+	}
+
+	private flowErrors(): void {
+		if (this.listeners.error.length > 0) {
+			for (const error of this.buffer.error) {
+				this.listeners.error.forEach(listener => listener(error));
+			}
+
+			this.buffer.error.length = 0;
+		}
+	}
+
+	private flowEnd(): boolean {
+		if (this.state.ended) {
+			this.listeners.end.forEach(listener => listener());
+
+			return this.listeners.end.length > 0;
+		}
+
+		return false;
+	}
+
+	destroy(): void {
+		if (!this.state.destroyed) {
+			this.state.destroyed = true;
+			this.state.ended = true;
+
+			this.buffer.data.length = 0;
+			this.buffer.error.length = 0;
+
+			this.listeners.data.length = 0;
+			this.listeners.error.length = 0;
+			this.listeners.end.length = 0;
+		}
+	}
+}
+
+/**
+ * Helper to fully read a T readable into a T.
+ */
+export function consumeReadable(readable: Readable, reducer: IReducer): T {
+	const chunks: T[] = [];
+
+	let chunk: T | null;
+	while ((chunk = readable.read()) !== null) {
+		chunks.push(chunk);
+	}
+
+	return reducer(chunks);
+}
+
+/**
+ * Helper to read a T readable up to a maximum of chunks. If the limit is
+ * reached, will return a readable instead to ensure all data can still
+ * be read.
+ */
+export function consumeReadableWithLimit(readable: Readable, reducer: IReducer, maxChunks: number): T | Readable {
+	const chunks: T[] = [];
+
+	let chunk: T | null | undefined = undefined;
+	while ((chunk = readable.read()) !== null && chunks.length < maxChunks) {
+		chunks.push(chunk);
+	}
+
+	// If the last chunk is null, it means we reached the end of
+	// the readable and return all the data at once
+	if (chunk === null && chunks.length > 0) {
+		return reducer(chunks);
+	}
+
+	// Otherwise, we still have a chunk, it means we reached the maxChunks
+	// value and as such we return a new Readable that first returns
+	// the existing read chunks and then continues with reading from
+	// the underlying readable.
+	return {
+		read: () => {
+
+			// First consume chunks from our array
+			if (chunks.length > 0) {
+				return chunks.shift()!;
+			}
+
+			// Then ensure to return our last read chunk
+			if (typeof chunk !== 'undefined') {
+				const lastReadChunk = chunk;
+
+				// explicitly use undefined here to indicate that we consumed
+				// the chunk, which could have either been null or valued.
+				chunk = undefined;
+
+				return lastReadChunk;
+			}
+
+			// Finally delegate back to the Readable
+			return readable.read();
+		}
+	};
+}
+
+/**
+ * Helper to fully read a T stream into a T.
+ */
+export function consumeStream(stream: ReadableStream, reducer: IReducer): Promise {
+	return new Promise((resolve, reject) => {
+		const chunks: T[] = [];
+
+		stream.on('data', data => chunks.push(data));
+		stream.on('error', error => reject(error));
+		stream.on('end', () => resolve(reducer(chunks)));
+	});
+}
+
+/**
+ * Helper to read a T stream up to a maximum of chunks. If the limit is
+ * reached, will return a stream instead to ensure all data can still
+ * be read.
+ */
+export function consumeStreamWithLimit(stream: ReadableStream, reducer: IReducer, maxChunks: number): Promise> {
+	return new Promise((resolve, reject) => {
+		const chunks: T[] = [];
+
+		let wrapperStream: WriteableStream | undefined = undefined;
+
+		stream.on('data', data => {
+
+			// If we reach maxChunks, we start to return a stream
+			// and make sure that any data we have already read
+			// is in it as well
+			if (!wrapperStream && chunks.length === maxChunks) {
+				wrapperStream = newWriteableStream(reducer);
+				while (chunks.length) {
+					wrapperStream.write(chunks.shift()!);
+				}
+
+				wrapperStream.write(data);
+
+				return resolve(wrapperStream);
+			}
+
+			if (wrapperStream) {
+				wrapperStream.write(data);
+			} else {
+				chunks.push(data);
+			}
+		});
+
+		stream.on('error', error => {
+			if (wrapperStream) {
+				wrapperStream.error(error);
+			} else {
+				return reject(error);
+			}
+		});
+
+		stream.on('end', () => {
+			if (wrapperStream) {
+				while (chunks.length) {
+					wrapperStream.write(chunks.shift()!);
+				}
+
+				wrapperStream.end();
+			} else {
+				return resolve(reducer(chunks));
+			}
+		});
+	});
+}
+
+/**
+ * Helper to create a readable stream from an existing T.
+ */
+export function toStream(t: T, reducer: IReducer): ReadableStream {
+	const stream = newWriteableStream(reducer);
+
+	stream.end(t);
+
+	return stream;
+}
+
+/**
+ * Helper to convert a T into a Readable.
+ */
+export function toReadable(t: T): Readable {
+	let consumed = false;
+
+	return {
+		read: () => {
+			if (consumed) {
+				return null;
+			}
+
+			consumed = true;
+
+			return t;
+		}
+	};
+}
+
+/**
+ * Helper to transform a readable stream into another stream.
+ */
+export function transform(stream: ReadableStreamEvents, transformer: ITransformer, reducer: IReducer): ReadableStream {
+	const target = newWriteableStream(reducer);
+
+	stream.on('data', data => target.write(transformer.data(data)));
+	stream.on('end', () => target.end());
+	stream.on('error', error => target.error(transformer.error ? transformer.error(error) : error));
+
+	return target;
+}
diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts
index dad6301dc0..d7de8ec9e4 100644
--- a/src/vs/base/common/strings.ts
+++ b/src/vs/base/common/strings.ts
@@ -517,60 +517,80 @@ function getPrevCodePoint(str: string, offset: number): number {
 }
 
 export function nextCharLength(str: string, offset: number): number {
+	const graphemeBreakTree = GraphemeBreakTree.getInstance();
 	const initialOffset = offset;
 	const len = str.length;
 
-	let codePoint = getNextCodePoint(str, len, offset);
-	offset += (codePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
+	const initialCodePoint = getNextCodePoint(str, len, offset);
+	offset += (initialCodePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
 
+	let graphemeBreakType = graphemeBreakTree.getGraphemeBreakType(initialCodePoint);
 	while (offset < len) {
-		codePoint = getNextCodePoint(str, len, offset);
-		if (!isUnicodeMark(codePoint)) {
+		const nextCodePoint = getNextCodePoint(str, len, offset);
+		const nextGraphemeBreakType = graphemeBreakTree.getGraphemeBreakType(nextCodePoint);
+		if (breakBetweenGraphemeBreakType(graphemeBreakType, nextGraphemeBreakType)) {
 			break;
 		}
-		offset += (codePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
+		offset += (nextCodePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
+		graphemeBreakType = nextGraphemeBreakType;
 	}
 
 	return (offset - initialOffset);
 }
 
 export function prevCharLength(str: string, offset: number): number {
+	const graphemeBreakTree = GraphemeBreakTree.getInstance();
 	const initialOffset = offset;
 
-	let codePoint = getPrevCodePoint(str, offset);
-	offset -= (codePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
+	const initialCodePoint = getPrevCodePoint(str, offset);
+	offset -= (initialCodePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
 
-	while (offset > 0 && isUnicodeMark(codePoint)) {
-		codePoint = getPrevCodePoint(str, offset);
-		offset -= (codePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
+	let graphemeBreakType = graphemeBreakTree.getGraphemeBreakType(initialCodePoint);
+	while (offset > 0) {
+		const prevCodePoint = getPrevCodePoint(str, offset);
+		const prevGraphemeBreakType = graphemeBreakTree.getGraphemeBreakType(prevCodePoint);
+		if (breakBetweenGraphemeBreakType(prevGraphemeBreakType, graphemeBreakType)) {
+			break;
+		}
+		offset -= (prevCodePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
+		graphemeBreakType = prevGraphemeBreakType;
 	}
 
 	return (initialOffset - offset);
 }
 
 function _getCharContainingOffset(str: string, offset: number): [number, number] {
+	const graphemeBreakTree = GraphemeBreakTree.getInstance();
 	const len = str.length;
 	const initialOffset = offset;
 	const initialCodePoint = getNextCodePoint(str, len, offset);
+	const initialGraphemeBreakType = graphemeBreakTree.getGraphemeBreakType(initialCodePoint);
 	offset += (initialCodePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
 
 	// extend to the right
+	let graphemeBreakType = initialGraphemeBreakType;
 	while (offset < len) {
 		const nextCodePoint = getNextCodePoint(str, len, offset);
-		if (!isUnicodeMark(nextCodePoint)) {
+		const nextGraphemeBreakType = graphemeBreakTree.getGraphemeBreakType(nextCodePoint);
+		if (breakBetweenGraphemeBreakType(graphemeBreakType, nextGraphemeBreakType)) {
 			break;
 		}
 		offset += (nextCodePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
+		graphemeBreakType = nextGraphemeBreakType;
 	}
 	const endOffset = offset;
 
 	// extend to the left
 	offset = initialOffset;
-	let codePoint = initialCodePoint;
-
-	while (offset > 0 && isUnicodeMark(codePoint)) {
-		codePoint = getPrevCodePoint(str, offset);
-		offset -= (codePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
+	graphemeBreakType = initialGraphemeBreakType;
+	while (offset > 0) {
+		const prevCodePoint = getPrevCodePoint(str, offset);
+		const prevGraphemeBreakType = graphemeBreakTree.getGraphemeBreakType(prevCodePoint);
+		if (breakBetweenGraphemeBreakType(prevGraphemeBreakType, graphemeBreakType)) {
+			break;
+		}
+		offset -= (prevCodePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
+		graphemeBreakType = prevGraphemeBreakType;
 	}
 
 	return [offset, endOffset];
@@ -583,93 +603,117 @@ export function getCharContainingOffset(str: string, offset: number): [number, n
 	return _getCharContainingOffset(str, offset);
 }
 
-export function isUnicodeMark(codePoint: number): boolean {
-	return MarkClassifier.getInstance().isUnicodeMark(codePoint);
+/**
+ * A manual encoding of `str` to UTF8.
+ * Use only in environments which do not offer native conversion methods!
+ */
+export function encodeUTF8(str: string): Uint8Array {
+	const strLen = str.length;
+
+	// See https://en.wikipedia.org/wiki/UTF-8
+
+	// first loop to establish needed buffer size
+	let neededSize = 0;
+	let strOffset = 0;
+	while (strOffset < strLen) {
+		const codePoint = getNextCodePoint(str, strLen, strOffset);
+		strOffset += (codePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
+
+		if (codePoint < 0x0080) {
+			neededSize += 1;
+		} else if (codePoint < 0x0800) {
+			neededSize += 2;
+		} else if (codePoint < 0x10000) {
+			neededSize += 3;
+		} else {
+			neededSize += 4;
+		}
+	}
+
+	// second loop to actually encode
+	const arr = new Uint8Array(neededSize);
+	strOffset = 0;
+	let arrOffset = 0;
+	while (strOffset < strLen) {
+		const codePoint = getNextCodePoint(str, strLen, strOffset);
+		strOffset += (codePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
+
+		if (codePoint < 0x0080) {
+			arr[arrOffset++] = codePoint;
+		} else if (codePoint < 0x0800) {
+			arr[arrOffset++] = 0b11000000 | ((codePoint & 0b00000000000000000000011111000000) >>> 6);
+			arr[arrOffset++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
+		} else if (codePoint < 0x10000) {
+			arr[arrOffset++] = 0b11100000 | ((codePoint & 0b00000000000000001111000000000000) >>> 12);
+			arr[arrOffset++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6);
+			arr[arrOffset++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
+		} else {
+			arr[arrOffset++] = 0b11110000 | ((codePoint & 0b00000000000111000000000000000000) >>> 18);
+			arr[arrOffset++] = 0b10000000 | ((codePoint & 0b00000000000000111111000000000000) >>> 12);
+			arr[arrOffset++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6);
+			arr[arrOffset++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
+		}
+	}
+
+	return arr;
 }
 
-class MarkClassifier {
+/**
+ * A manual decoding of a UTF8 string.
+ * Use only in environments which do not offer native conversion methods!
+ */
+export function decodeUTF8(buffer: Uint8Array): string {
+	// https://en.wikipedia.org/wiki/UTF-8
 
-	private static _INSTANCE: MarkClassifier | null = null;
-
-	public static getInstance(): MarkClassifier {
-		if (!MarkClassifier._INSTANCE) {
-			MarkClassifier._INSTANCE = new MarkClassifier();
-		}
-		return MarkClassifier._INSTANCE;
-	}
-
-	private arr: Uint8Array;
-
-	constructor() {
-		// generated using https://github.com/alexandrudima/unicode-utils/blob/master/generate-mark-test.js
-		const ranges = [
-			0x0300, 0x036F, 0x0483, 0x0489, 0x0591, 0x05BD, 0x05BF, 0x05BF, 0x05C1, 0x05C2, 0x05C4, 0x05C5,
-			0x05C7, 0x05C7, 0x0610, 0x061A, 0x064B, 0x065F, 0x0670, 0x0670, 0x06D6, 0x06DC, 0x06DF, 0x06E4,
-			0x06E7, 0x06E8, 0x06EA, 0x06ED, 0x0711, 0x0711, 0x0730, 0x074A, 0x07A6, 0x07B0, 0x07EB, 0x07F3,
-			0x07FD, 0x07FD, 0x0816, 0x0819, 0x081B, 0x0823, 0x0825, 0x0827, 0x0829, 0x082D, 0x0859, 0x085B,
-			0x08D3, 0x08E1, 0x08E3, 0x0903, 0x093A, 0x093C, 0x093E, 0x094F, 0x0951, 0x0957, 0x0962, 0x0963,
-			0x0981, 0x0983, 0x09BC, 0x09BC, 0x09BE, 0x09CD, 0x09D7, 0x09D7, 0x09E2, 0x09E3, 0x09FE, 0x0A03,
-			0x0A3C, 0x0A51, 0x0A70, 0x0A71, 0x0A75, 0x0A75, 0x0A81, 0x0A83, 0x0ABC, 0x0ABC, 0x0ABE, 0x0ACD,
-			0x0AE2, 0x0AE3, 0x0AFA, 0x0B03, 0x0B3C, 0x0B3C, 0x0B3E, 0x0B57, 0x0B62, 0x0B63, 0x0B82, 0x0B82,
-			0x0BBE, 0x0BCD, 0x0BD7, 0x0BD7, 0x0C00, 0x0C04, 0x0C3E, 0x0C56, 0x0C62, 0x0C63, 0x0C81, 0x0C83,
-			0x0CBC, 0x0CBC, 0x0CBE, 0x0CD6, 0x0CE2, 0x0CE3, 0x0D00, 0x0D03, 0x0D3B, 0x0D3C, 0x0D3E, 0x0D4D,
-			0x0D57, 0x0D57, 0x0D62, 0x0D63, 0x0D81, 0x0D83, 0x0DCA, 0x0DDF, 0x0DF2, 0x0DF3, 0x0E31, 0x0E31,
-			0x0E34, 0x0E3A, 0x0E47, 0x0E4E, 0x0EB1, 0x0EB1, 0x0EB4, 0x0EBC, 0x0EC8, 0x0ECD, 0x0F18, 0x0F19,
-			0x0F35, 0x0F35, 0x0F37, 0x0F37, 0x0F39, 0x0F39, 0x0F3E, 0x0F3F, 0x0F71, 0x0F84, 0x0F86, 0x0F87,
-			0x0F8D, 0x0FBC, 0x0FC6, 0x0FC6, 0x102B, 0x103E, 0x1056, 0x1059, 0x105E, 0x1060, 0x1062, 0x1064,
-			0x1067, 0x106D, 0x1071, 0x1074, 0x1082, 0x108D, 0x108F, 0x108F, 0x109A, 0x109D, 0x135D, 0x135F,
-			0x1712, 0x1714, 0x1732, 0x1734, 0x1752, 0x1753, 0x1772, 0x1773, 0x17B4, 0x17D3, 0x17DD, 0x17DD,
-			0x180B, 0x180D, 0x1885, 0x1886, 0x18A9, 0x18A9, 0x1920, 0x193B, 0x1A17, 0x1A1B, 0x1A55, 0x1A7F,
-			0x1AB0, 0x1B04, 0x1B34, 0x1B44, 0x1B6B, 0x1B73, 0x1B80, 0x1B82, 0x1BA1, 0x1BAD, 0x1BE6, 0x1BF3,
-			0x1C24, 0x1C37, 0x1CD0, 0x1CD2, 0x1CD4, 0x1CE8, 0x1CED, 0x1CED, 0x1CF4, 0x1CF4, 0x1CF7, 0x1CF9,
-			0x1DC0, 0x1DFF, 0x20D0, 0x20F0, 0x2CEF, 0x2CF1, 0x2D7F, 0x2D7F, 0x2DE0, 0x2DFF, 0x302A, 0x302F,
-			0x3099, 0x309A, 0xA66F, 0xA672, 0xA674, 0xA67D, 0xA69E, 0xA69F, 0xA6F0, 0xA6F1, 0xA802, 0xA802,
-			0xA806, 0xA806, 0xA80B, 0xA80B, 0xA823, 0xA827, 0xA82C, 0xA82C, 0xA880, 0xA881, 0xA8B4, 0xA8C5,
-			0xA8E0, 0xA8F1, 0xA8FF, 0xA8FF, 0xA926, 0xA92D, 0xA947, 0xA953, 0xA980, 0xA983, 0xA9B3, 0xA9C0,
-			0xA9E5, 0xA9E5, 0xAA29, 0xAA36, 0xAA43, 0xAA43, 0xAA4C, 0xAA4D, 0xAA7B, 0xAA7D, 0xAAB0, 0xAAB0,
-			0xAAB2, 0xAAB4, 0xAAB7, 0xAAB8, 0xAABE, 0xAABF, 0xAAC1, 0xAAC1, 0xAAEB, 0xAAEF, 0xAAF5, 0xAAF6,
-			0xABE3, 0xABEA, 0xABEC, 0xABED, 0xFB1E, 0xFB1E, 0xFE00, 0xFE0F, 0xFE20, 0xFE2F, 0x101FD, 0x101FD,
-			0x102E0, 0x102E0, 0x10376, 0x1037A, 0x10A01, 0x10A0F, 0x10A38, 0x10A3F, 0x10AE5, 0x10AE6, 0x10D24, 0x10D27,
-			0x10EAB, 0x10EAC, 0x10F46, 0x10F50, 0x11000, 0x11002, 0x11038, 0x11046, 0x1107F, 0x11082, 0x110B0, 0x110BA,
-			0x11100, 0x11102, 0x11127, 0x11134, 0x11145, 0x11146, 0x11173, 0x11173, 0x11180, 0x11182, 0x111B3, 0x111C0,
-			0x111C9, 0x111CC, 0x111CE, 0x111CF, 0x1122C, 0x11237, 0x1123E, 0x1123E, 0x112DF, 0x112EA, 0x11300, 0x11303,
-			0x1133B, 0x1133C, 0x1133E, 0x1134D, 0x11357, 0x11357, 0x11362, 0x11374, 0x11435, 0x11446, 0x1145E, 0x1145E,
-			0x114B0, 0x114C3, 0x115AF, 0x115C0, 0x115DC, 0x115DD, 0x11630, 0x11640, 0x116AB, 0x116B7, 0x1171D, 0x1172B,
-			0x1182C, 0x1183A, 0x11930, 0x1193E, 0x11940, 0x11940, 0x11942, 0x11943, 0x119D1, 0x119E0, 0x119E4, 0x119E4,
-			0x11A01, 0x11A0A, 0x11A33, 0x11A39, 0x11A3B, 0x11A3E, 0x11A47, 0x11A47, 0x11A51, 0x11A5B, 0x11A8A, 0x11A99,
-			0x11C2F, 0x11C3F, 0x11C92, 0x11CB6, 0x11D31, 0x11D45, 0x11D47, 0x11D47, 0x11D8A, 0x11D97, 0x11EF3, 0x11EF6,
-			0x16AF0, 0x16AF4, 0x16B30, 0x16B36, 0x16F4F, 0x16F4F, 0x16F51, 0x16F92, 0x16FE4, 0x16FF1, 0x1BC9D, 0x1BC9E,
-			0x1D165, 0x1D169, 0x1D16D, 0x1D172, 0x1D17B, 0x1D182, 0x1D185, 0x1D18B, 0x1D1AA, 0x1D1AD, 0x1D242, 0x1D244,
-			0x1DA00, 0x1DA36, 0x1DA3B, 0x1DA6C, 0x1DA75, 0x1DA75, 0x1DA84, 0x1DA84, 0x1DA9B, 0x1E02A, 0x1E130, 0x1E136,
-			0x1E2EC, 0x1E2EF, 0x1E8D0, 0x1E8D6, 0x1E944, 0x1E94A, 0xE0100, 0xE01EF
-		];
-
-		const maxCodePoint = ranges[ranges.length - 1];
-		const arrLen = Math.ceil(maxCodePoint / 8);
-		const arr = new Uint8Array(arrLen);
-
-		for (let i = 0, len = ranges.length / 2; i < len; i++) {
-			const from = ranges[2 * i];
-			const to = ranges[2 * i + 1];
-
-			for (let j = from; j <= to; j++) {
-				const div8 = j >>> 3;
-				const mod8 = j & 7;
-				arr[div8] = arr[div8] | (1 << mod8);
-			}
+	const len = buffer.byteLength;
+	const result: string[] = [];
+	let offset = 0;
+	while (offset < len) {
+		const v0 = buffer[offset];
+		let codePoint: number;
+		if (v0 >= 0b11110000 && offset + 3 < len) {
+			// 4 bytes
+			codePoint = (
+				(((buffer[offset++] & 0b00000111) << 18) >>> 0)
+				| (((buffer[offset++] & 0b00111111) << 12) >>> 0)
+				| (((buffer[offset++] & 0b00111111) << 6) >>> 0)
+				| (((buffer[offset++] & 0b00111111) << 0) >>> 0)
+			);
+		} else if (v0 >= 0b11100000 && offset + 2 < len) {
+			// 3 bytes
+			codePoint = (
+				(((buffer[offset++] & 0b00001111) << 12) >>> 0)
+				| (((buffer[offset++] & 0b00111111) << 6) >>> 0)
+				| (((buffer[offset++] & 0b00111111) << 0) >>> 0)
+			);
+		} else if (v0 >= 0b11000000 && offset + 1 < len) {
+			// 2 bytes
+			codePoint = (
+				(((buffer[offset++] & 0b00011111) << 6) >>> 0)
+				| (((buffer[offset++] & 0b00111111) << 0) >>> 0)
+			);
+		} else {
+			// 1 byte
+			codePoint = buffer[offset++];
 		}
 
-		this.arr = arr;
+		if ((codePoint >= 0 && codePoint <= 0xD7FF) || (codePoint >= 0xE000 && codePoint <= 0xFFFF)) {
+			// Basic Multilingual Plane
+			result.push(String.fromCharCode(codePoint));
+		} else if (codePoint >= 0x010000 && codePoint <= 0x10FFFF) {
+			// Supplementary Planes
+			const uPrime = codePoint - 0x10000;
+			const w1 = 0xD800 + ((uPrime & 0b11111111110000000000) >>> 10);
+			const w2 = 0xDC00 + ((uPrime & 0b00000000001111111111) >>> 0);
+			result.push(String.fromCharCode(w1));
+			result.push(String.fromCharCode(w2));
+		} else {
+			// illegal code point
+			result.push(String.fromCharCode(0xFFFD));
+		}
 	}
 
-	public isUnicodeMark(codePoint: number): boolean {
-		const div8 = codePoint >>> 3;
-		const mod8 = codePoint & 7;
-		if (div8 >= this.arr.length) {
-			return false;
-		}
-		return (this.arr[div8] & (1 << mod8)) ? true : false;
-	}
+	return result.join('');
 }
 
 /**
@@ -925,3 +969,167 @@ export function singleLetterHash(n: number): string {
 
 	return String.fromCharCode(CharCode.A + n - LETTERS_CNT);
 }
+
+//#region Unicode Grapheme Break
+
+export function getGraphemeBreakType(codePoint: number): GraphemeBreakType {
+	const graphemeBreakTree = GraphemeBreakTree.getInstance();
+	return graphemeBreakTree.getGraphemeBreakType(codePoint);
+}
+
+export function breakBetweenGraphemeBreakType(breakTypeA: GraphemeBreakType, breakTypeB: GraphemeBreakType): boolean {
+	// http://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundary_Rules
+
+	// !!! Let's make the common case a bit faster
+	if (breakTypeA === GraphemeBreakType.Other) {
+		// see https://www.unicode.org/Public/13.0.0/ucd/auxiliary/GraphemeBreakTest-13.0.0d10.html#table
+		return (breakTypeB !== GraphemeBreakType.Extend && breakTypeB !== GraphemeBreakType.SpacingMark);
+	}
+
+	// Do not break between a CR and LF. Otherwise, break before and after controls.
+	// GB3                                        CR × LF
+	// GB4                       (Control | CR | LF) ÷
+	// GB5                                           ÷ (Control | CR | LF)
+	if (breakTypeA === GraphemeBreakType.CR) {
+		if (breakTypeB === GraphemeBreakType.LF) {
+			return false; // GB3
+		}
+	}
+	if (breakTypeA === GraphemeBreakType.Control || breakTypeA === GraphemeBreakType.CR || breakTypeA === GraphemeBreakType.LF) {
+		return true; // GB4
+	}
+	if (breakTypeB === GraphemeBreakType.Control || breakTypeB === GraphemeBreakType.CR || breakTypeB === GraphemeBreakType.LF) {
+		return true; // GB5
+	}
+
+	// Do not break Hangul syllable sequences.
+	// GB6                                         L × (L | V | LV | LVT)
+	// GB7                                  (LV | V) × (V | T)
+	// GB8                                 (LVT | T) × T
+	if (breakTypeA === GraphemeBreakType.L) {
+		if (breakTypeB === GraphemeBreakType.L || breakTypeB === GraphemeBreakType.V || breakTypeB === GraphemeBreakType.LV || breakTypeB === GraphemeBreakType.LVT) {
+			return false; // GB6
+		}
+	}
+	if (breakTypeA === GraphemeBreakType.LV || breakTypeA === GraphemeBreakType.V) {
+		if (breakTypeB === GraphemeBreakType.V || breakTypeB === GraphemeBreakType.T) {
+			return false; // GB7
+		}
+	}
+	if (breakTypeA === GraphemeBreakType.LVT || breakTypeA === GraphemeBreakType.T) {
+		if (breakTypeB === GraphemeBreakType.T) {
+			return false; // GB8
+		}
+	}
+
+	// Do not break before extending characters or ZWJ.
+	// GB9                                           × (Extend | ZWJ)
+	if (breakTypeB === GraphemeBreakType.Extend || breakTypeB === GraphemeBreakType.ZWJ) {
+		return false; // GB9
+	}
+
+	// The GB9a and GB9b rules only apply to extended grapheme clusters:
+	// Do not break before SpacingMarks, or after Prepend characters.
+	// GB9a                                          × SpacingMark
+	// GB9b                                  Prepend ×
+	if (breakTypeB === GraphemeBreakType.SpacingMark) {
+		return false; // GB9a
+	}
+	if (breakTypeA === GraphemeBreakType.Prepend) {
+		return false; // GB9b
+	}
+
+	// Do not break within emoji modifier sequences or emoji zwj sequences.
+	// GB11    \p{Extended_Pictographic} Extend* ZWJ × \p{Extended_Pictographic}
+	if (breakTypeA === GraphemeBreakType.ZWJ && breakTypeB === GraphemeBreakType.Extended_Pictographic) {
+		// Note: we are not implementing the rule entirely here to avoid introducing states
+		return false; // GB11
+	}
+
+	// GB12                          sot (RI RI)* RI × RI
+	// GB13                        [^RI] (RI RI)* RI × RI
+	if (breakTypeA === GraphemeBreakType.Regional_Indicator && breakTypeB === GraphemeBreakType.Regional_Indicator) {
+		// Note: we are not implementing the rule entirely here to avoid introducing states
+		return false; // GB12 & GB13
+	}
+
+	// GB999                                     Any ÷ Any
+	return true;
+}
+
+export const enum GraphemeBreakType {
+	Other = 0,
+	Prepend = 1,
+	CR = 2,
+	LF = 3,
+	Control = 4,
+	Extend = 5,
+	Regional_Indicator = 6,
+	SpacingMark = 7,
+	L = 8,
+	V = 9,
+	T = 10,
+	LV = 11,
+	LVT = 12,
+	ZWJ = 13,
+	Extended_Pictographic = 14
+}
+
+class GraphemeBreakTree {
+
+	private static _INSTANCE: GraphemeBreakTree | null = null;
+	public static getInstance(): GraphemeBreakTree {
+		if (!GraphemeBreakTree._INSTANCE) {
+			GraphemeBreakTree._INSTANCE = new GraphemeBreakTree();
+		}
+		return GraphemeBreakTree._INSTANCE;
+	}
+
+	private readonly _data: number[];
+
+	constructor() {
+		this._data = getGraphemeBreakRawData();
+	}
+
+	public getGraphemeBreakType(codePoint: number): GraphemeBreakType {
+		// !!! Let's make 7bit ASCII a bit faster: 0..31
+		if (codePoint < 32) {
+			if (codePoint === CharCode.LineFeed) {
+				return GraphemeBreakType.LF;
+			}
+			if (codePoint === CharCode.CarriageReturn) {
+				return GraphemeBreakType.CR;
+			}
+			return GraphemeBreakType.Control;
+		}
+		// !!! Let's make 7bit ASCII a bit faster: 32..126
+		if (codePoint < 127) {
+			return GraphemeBreakType.Other;
+		}
+
+		const data = this._data;
+		const nodeCount = data.length / 3;
+		let nodeIndex = 1;
+		while (nodeIndex <= nodeCount) {
+			if (codePoint < data[3 * nodeIndex]) {
+				// go left
+				nodeIndex = 2 * nodeIndex;
+			} else if (codePoint > data[3 * nodeIndex + 1]) {
+				// go right
+				nodeIndex = 2 * nodeIndex + 1;
+			} else {
+				// hit
+				return data[3 * nodeIndex + 2];
+			}
+		}
+
+		return GraphemeBreakType.Other;
+	}
+}
+
+function getGraphemeBreakRawData(): number[] {
+	// generated using https://github.com/alexandrudima/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]');
+}
+
+//#endregion
diff --git a/src/typings/windows-foreground-love.d.ts b/src/vs/base/common/styler.ts
similarity index 67%
rename from src/typings/windows-foreground-love.d.ts
rename to src/vs/base/common/styler.ts
index 2b09256584..d5e2b58df7 100644
--- a/src/typings/windows-foreground-love.d.ts
+++ b/src/vs/base/common/styler.ts
@@ -3,8 +3,10 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
-declare module 'windows-foreground-love' {
+import { Color } from 'vs/base/common/color';
 
-	export function allowSetForegroundWindow(pid?: number): boolean;
+export type styleFn = (colors: { [name: string]: Color | undefined }) => void;
 
-}
\ No newline at end of file
+export interface IThemable {
+	style: styleFn;
+}
diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts
index 02e4baf10c..24f4747bd4 100644
--- a/src/vs/base/common/types.ts
+++ b/src/vs/base/common/types.ts
@@ -93,6 +93,13 @@ export function isUndefinedOrNull(obj: any): obj is undefined | null {
 	return isUndefined(obj) || obj === null;
 }
 
+
+export function assertType(condition: any, type?: string): asserts condition {
+	if (!condition) {
+		throw new Error(type ? `Unexpected type, expected '${type}'` : 'Unexpected type');
+	}
+}
+
 /**
  * Asserts that the argument passed in is neither undefined nor null.
  */
diff --git a/src/vs/base/common/uri.ts b/src/vs/base/common/uri.ts
index 43019a9338..dc6ad3e4ab 100644
--- a/src/vs/base/common/uri.ts
+++ b/src/vs/base/common/uri.ts
@@ -10,10 +10,10 @@ const _schemePattern = /^\w[\w\d+.-]*$/;
 const _singleSlashStart = /^\//;
 const _doubleSlashStart = /^\/\//;
 
-function _validateUri(ret: URI): void {
+function _validateUri(ret: URI, _strict?: boolean): void {
 
 	// scheme, must be set
-	if (!ret.scheme) {
+	if (!ret.scheme && _strict) {
 		throw new Error(`[UriError]: Scheme is missing: {scheme: "", authority: "${ret.authority}", path: "${ret.path}", query: "${ret.query}", fragment: "${ret.fragment}"}`);
 	}
 
@@ -41,11 +41,13 @@ function _validateUri(ret: URI): void {
 	}
 }
 
-// graceful behaviour when scheme is missing: fallback to using 'file'-scheme
-function _schemeFix(scheme: string): string {
-	if (!scheme) {
-		console.trace('BAD uri lacks scheme, falling back to file-scheme.');
-		scheme = 'file';
+// for a while we allowed uris *without* schemes and this is the migration
+// for them, e.g. an uri without scheme and without strict-mode warns and falls
+// back to the file-scheme. that should cause the least carnage and still be a
+// clear warning
+function _schemeFix(scheme: string, _strict: boolean): string {
+	if (!scheme && !_strict) {
+		return 'file';
 	}
 	return scheme;
 }
@@ -138,7 +140,7 @@ export class URI implements UriComponents {
 	/**
 	 * @internal
 	 */
-	protected constructor(scheme: string, authority?: string, path?: string, query?: string, fragment?: string);
+	protected constructor(scheme: string, authority?: string, path?: string, query?: string, fragment?: string, _strict?: boolean);
 
 	/**
 	 * @internal
@@ -148,7 +150,7 @@ export class URI implements UriComponents {
 	/**
 	 * @internal
 	 */
-	protected constructor(schemeOrData: string | UriComponents, authority?: string, path?: string, query?: string, fragment?: string) {
+	protected constructor(schemeOrData: string | UriComponents, authority?: string, path?: string, query?: string, fragment?: string, _strict: boolean = false) {
 
 		if (typeof schemeOrData === 'object') {
 			this.scheme = schemeOrData.scheme || _empty;
@@ -160,13 +162,13 @@ export class URI implements UriComponents {
 			// that creates uri components.
 			// _validateUri(this);
 		} else {
-			this.scheme = _schemeFix(schemeOrData);
+			this.scheme = _schemeFix(schemeOrData, _strict);
 			this.authority = authority || _empty;
 			this.path = _referenceResolution(this.scheme, path || _empty);
 			this.query = query || _empty;
 			this.fragment = fragment || _empty;
 
-			_validateUri(this);
+			_validateUri(this, _strict);
 		}
 	}
 
@@ -205,7 +207,7 @@ export class URI implements UriComponents {
 
 	// ---- modify to new -------------------------
 
-	with(change: { scheme?: string; authority?: string | null; path?: string | null; query?: string | null; fragment?: string | null; }): URI {
+	with(change: { scheme?: string; authority?: string | null; path?: string | null; query?: string | null; fragment?: string | null }): URI {
 
 		if (!change) {
 			return this;
@@ -258,17 +260,18 @@ export class URI implements UriComponents {
 	 *
 	 * @param value A string which represents an URI (see `URI#toString`).
 	 */
-	static parse(value: string): URI {
+	static parse(value: string, _strict: boolean = false): URI {
 		const match = _regexp.exec(value);
 		if (!match) {
 			return new _URI(_empty, _empty, _empty, _empty, _empty);
 		}
 		return new _URI(
 			match[2] || _empty,
-			decodeURIComponent(match[4] || _empty),
-			decodeURIComponent(match[5] || _empty),
-			decodeURIComponent(match[7] || _empty),
-			decodeURIComponent(match[9] || _empty)
+			percentDecode(match[4] || _empty),
+			percentDecode(match[5] || _empty),
+			percentDecode(match[7] || _empty),
+			percentDecode(match[9] || _empty),
+			_strict
 		);
 	}
 
@@ -320,7 +323,7 @@ export class URI implements UriComponents {
 		return new _URI('file', authority, path, _empty, _empty);
 	}
 
-	static from(components: { scheme: string; authority?: string; path?: string; query?: string; fragment?: string; }): URI {
+	static from(components: { scheme: string; authority?: string; path?: string; query?: string; fragment?: string }): URI {
 		return new _URI(
 			components.scheme,
 			components.authority,
@@ -445,7 +448,7 @@ class _URI extends URI {
 }
 
 // reserved characters: https://tools.ietf.org/html/rfc3986#section-2.2
-const encodeTable: { [ch: number]: string; } = {
+const encodeTable: { [ch: number]: string } = {
 	[CharCode.Colon]: '%3A', // gen-delims
 	[CharCode.Slash]: '%2F',
 	[CharCode.QuestionMark]: '%3F',
@@ -646,3 +649,26 @@ function _asFormatted(uri: URI, skipEncoding: boolean): string {
 	}
 	return res;
 }
+
+// --- decode
+
+function decodeURIComponentGraceful(str: string): string {
+	try {
+		return decodeURIComponent(str);
+	} catch {
+		if (str.length > 3) {
+			return str.substr(0, 3) + decodeURIComponentGraceful(str.substr(3));
+		} else {
+			return str;
+		}
+	}
+}
+
+const _rEncodedAsHex = /(%[0-9A-Za-z][0-9A-Za-z])+/g;
+
+function percentDecode(str: string): string {
+	if (!str.match(_rEncodedAsHex)) {
+		return str;
+	}
+	return str.replace(_rEncodedAsHex, (match) => decodeURIComponentGraceful(match));
+}
diff --git a/src/vs/base/node/decoder.ts b/src/vs/base/node/decoder.ts
index b087eee4c8..4c910522f7 100644
--- a/src/vs/base/node/decoder.ts
+++ b/src/vs/base/node/decoder.ts
@@ -59,4 +59,4 @@ export class LineDecoder {
 	end(): string | null {
 		return this.remaining;
 	}
-}
\ No newline at end of file
+}
diff --git a/src/vs/base/node/encoding.ts b/src/vs/base/node/encoding.ts
index 68f08c8424..7aa3d01a1f 100644
--- a/src/vs/base/node/encoding.ts
+++ b/src/vs/base/node/encoding.ts
@@ -111,7 +111,7 @@ export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions
 				});
 			}
 
-			_final(callback: (error: Error | null) => void) {
+			_final(callback: () => void) {
 
 				// normal finish
 				if (this.decodeStream) {
@@ -144,7 +144,7 @@ export function decode(buffer: Buffer, encoding: string): string {
 }
 
 export function encode(content: string | Buffer, encoding: string, options?: { addBOM?: boolean }): Buffer {
-	return iconv.encode(content, toNodeEncoding(encoding), options);
+	return iconv.encode(content as string /* TODO report into upstream typings */, toNodeEncoding(encoding), options);
 }
 
 export function encodingExists(encoding: string): boolean {
@@ -167,7 +167,7 @@ function toNodeEncoding(enc: string | null): string {
 	return enc;
 }
 
-export function detectEncodingByBOMFromBuffer(buffer: Buffer | VSBuffer | null, bytesRead: number): string | null {
+export function detectEncodingByBOMFromBuffer(buffer: Buffer | VSBuffer | null, bytesRead: number): typeof UTF8_with_bom | typeof UTF16le | typeof UTF16be | null {
 	if (!buffer || bytesRead < UTF16be_BOM.length) {
 		return null;
 	}
@@ -193,33 +193,31 @@ export function detectEncodingByBOMFromBuffer(buffer: Buffer | VSBuffer | null,
 
 	// UTF-8
 	if (b0 === UTF8_BOM[0] && b1 === UTF8_BOM[1] && b2 === UTF8_BOM[2]) {
-		return UTF8;
+		return UTF8_with_bom;
 	}
 
 	return null;
 }
 
-const MINIMUM_THRESHOLD = 0.2;
-const IGNORE_ENCODINGS = ['ascii', 'utf-8', 'utf-16', 'utf-32'];
-
 /**
  * Guesses the encoding from buffer.
  */
 async function guessEncodingByBuffer(buffer: Buffer): Promise {
 	const jschardet = await import('jschardet');
 
-	jschardet.Constants.MINIMUM_THRESHOLD = MINIMUM_THRESHOLD;
-
 	const guessed = jschardet.detect(buffer);
 	if (!guessed || !guessed.encoding) {
 		return null;
 	}
 
-	const enc = guessed.encoding.toLowerCase();
-
-	// Ignore encodings that cannot guess correctly
-	// (http://chardet.readthedocs.io/en/latest/supported-encodings.html)
-	if (0 <= IGNORE_ENCODINGS.indexOf(enc)) {
+	// Ignore 'ascii' as guessed encoding because that
+	// is almost never what we want, rather fallback
+	// to the configured encoding then. Otherwise,
+	// opening a ascii-only file with auto guessing
+	// enabled will put the file into 'ascii' mode
+	// and thus typing any special characters is
+	// not possible anymore.
+	if (guessed.encoding.toLowerCase() === 'ascii') {
 		return null;
 	}
 
diff --git a/src/vs/base/node/languagePacks.js b/src/vs/base/node/languagePacks.js
index c63e26f401..82fdd4359a 100644
--- a/src/vs/base/node/languagePacks.js
+++ b/src/vs/base/node/languagePacks.js
@@ -50,8 +50,8 @@ function factory(nodeRequire, path, fs, perf) {
 	 * @param {string} dir
 	 * @returns {Promise}
 	 */
-	function mkdir(dir) {
-		return new Promise((c, e) => fs.mkdir(dir, err => (err && err.code !== 'EEXIST') ? e(err) : c(dir)));
+	function mkdirp(dir) {
+		return new Promise((c, e) => fs.mkdir(dir, { recursive: true }, err => (err && err.code !== 'EEXIST') ? e(err) : c(dir)));
 	}
 
 	/**
@@ -91,24 +91,6 @@ function factory(nodeRequire, path, fs, perf) {
 		});
 	}
 
-	/**
-	 * @param {string} dir
-	 * @returns {Promise}
-	 */
-	function mkdirp(dir) {
-		return mkdir(dir).then(null, err => {
-			if (err && err.code === 'ENOENT') {
-				const parent = path.dirname(dir);
-
-				if (parent !== dir) { // if not arrived at root
-					return mkdirp(parent).then(() => mkdir(dir));
-				}
-			}
-
-			throw err;
-		});
-	}
-
 	function readFile(file) {
 		return new Promise(function (resolve, reject) {
 			fs.readFile(file, 'utf8', function (err, data) {
diff --git a/src/vs/base/node/pfs.ts b/src/vs/base/node/pfs.ts
index b7a9b15343..3ec31d9a9e 100644
--- a/src/vs/base/node/pfs.ts
+++ b/src/vs/base/node/pfs.ts
@@ -3,7 +3,7 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
-import { join, dirname } from 'vs/base/common/path';
+import { join } from 'vs/base/common/path';
 import { Queue } from 'vs/base/common/async';
 import * as fs from 'fs';
 import * as os from 'os';
@@ -11,7 +11,6 @@ import * as platform from 'vs/base/common/platform';
 import { Event } from 'vs/base/common/event';
 import { endsWith } from 'vs/base/common/strings';
 import { promisify } from 'util';
-import { CancellationToken } from 'vs/base/common/cancellation';
 import { isRootOrDriveLetter } from 'vs/base/common/extpath';
 import { generateUuid } from 'vs/base/common/uuid';
 import { normalizeNFC } from 'vs/base/common/normalization';
@@ -398,7 +397,7 @@ function doWriteFileStreamAndFlush(path: string, reader: NodeJS.ReadableStream,
 // not in some cache.
 //
 // See https://github.com/nodejs/node/blob/v5.10.0/lib/fs.js#L1194
-function doWriteFileAndFlush(path: string, data: string | Buffer | Uint8Array, options: IEnsuredWriteFileOptions, callback: (error?: Error) => void): void {
+function doWriteFileAndFlush(path: string, data: string | Buffer | Uint8Array, options: IEnsuredWriteFileOptions, callback: (error: Error | null) => void): void {
 	if (options.encoding) {
 		data = encode(data instanceof Uint8Array ? Buffer.from(data) : data, options.encoding.charset, { addBOM: options.encoding.addBOM });
 	}
@@ -633,55 +632,8 @@ async function doCopyFile(source: string, target: string, mode: number): Promise
 	});
 }
 
-export async function mkdirp(path: string, mode?: number, token?: CancellationToken): Promise {
-	const mkdir = async () => {
-		try {
-			await promisify(fs.mkdir)(path, mode);
-		} catch (error) {
-
-			// ENOENT: a parent folder does not exist yet
-			if (error.code === 'ENOENT') {
-				return Promise.reject(error);
-			}
-
-			// Any other error: check if folder exists and
-			// return normally in that case if its a folder
-			try {
-				const fileStat = await stat(path);
-				if (!fileStat.isDirectory()) {
-					return Promise.reject(new Error(`'${path}' exists and is not a directory.`));
-				}
-			} catch (statError) {
-				throw error; // rethrow original error
-			}
-		}
-	};
-
-	// stop at root
-	if (path === dirname(path)) {
-		return Promise.resolve();
-	}
-
-	try {
-		await mkdir();
-	} catch (error) {
-
-		// Respect cancellation
-		if (token && token.isCancellationRequested) {
-			return Promise.resolve();
-		}
-
-		// ENOENT: a parent folder does not exist yet, continue
-		// to create the parent folder and then try again.
-		if (error.code === 'ENOENT') {
-			await mkdirp(dirname(path), mode);
-
-			return mkdir();
-		}
-
-		// Any other error
-		return Promise.reject(error);
-	}
+export async function mkdirp(path: string, mode?: number): Promise {
+	return promisify(fs.mkdir)(path, { mode, recursive: true });
 }
 
 // See https://github.com/Microsoft/vscode/issues/30180
diff --git a/src/vs/base/node/processes.ts b/src/vs/base/node/processes.ts
index 21f96596ec..3557b9f594 100644
--- a/src/vs/base/node/processes.ts
+++ b/src/vs/base/node/processes.ts
@@ -365,11 +365,11 @@ export class LineProcess extends AbstractProcess {
 	protected handleSpawn(childProcess: cp.ChildProcess, cc: ValueCallback, pp: ProgressCallback, ee: ErrorCallback, sync: boolean): void {
 		const stdoutLineDecoder = new LineDecoder();
 		const stderrLineDecoder = new LineDecoder();
-		childProcess.stdout.on('data', (data: Buffer) => {
+		childProcess.stdout!.on('data', (data: Buffer) => {
 			const lines = stdoutLineDecoder.write(data);
 			lines.forEach(line => pp({ line: line, source: Source.stdout }));
 		});
-		childProcess.stderr.on('data', (data: Buffer) => {
+		childProcess.stderr!.on('data', (data: Buffer) => {
 			const lines = stderrLineDecoder.write(data);
 			lines.forEach(line => pp({ line: line, source: Source.stderr }));
 		});
@@ -454,6 +454,14 @@ export namespace win32 {
 		if (paths === undefined || paths.length === 0) {
 			return path.join(cwd, command);
 		}
+
+		async function fileExists(path: string): Promise {
+			if (await promisify(fs.exists)(path)) {
+				return !((await promisify(fs.stat)(path)).isDirectory);
+			}
+			return false;
+		}
+
 		// We have a simple file name. We get the path variable from the env
 		// and try to find the executable on the path.
 		for (let pathEntry of paths) {
@@ -464,15 +472,15 @@ export namespace win32 {
 			} else {
 				fullPath = path.join(cwd, pathEntry, command);
 			}
-			if (await promisify(fs.exists)(fullPath)) {
+			if (await fileExists(fullPath)) {
 				return fullPath;
 			}
 			let withExtension = fullPath + '.com';
-			if (await promisify(fs.exists)(withExtension)) {
+			if (await fileExists(withExtension)) {
 				return withExtension;
 			}
 			withExtension = fullPath + '.exe';
-			if (await promisify(fs.exists)(withExtension)) {
+			if (await fileExists(withExtension)) {
 				return withExtension;
 			}
 		}
diff --git a/src/vs/base/node/watcher.ts b/src/vs/base/node/watcher.ts
index 5248c2e798..9205aec656 100644
--- a/src/vs/base/node/watcher.ts
+++ b/src/vs/base/node/watcher.ts
@@ -10,7 +10,7 @@ import { normalizeNFC } from 'vs/base/common/normalization';
 import { toDisposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
 import { exists, readdir } from 'vs/base/node/pfs';
 
-export function watchFile(path: string, onChange: (type: 'changed' | 'deleted', path: string) => void, onError: (error: string) => void): IDisposable {
+export function watchFile(path: string, onChange: (type: 'added' | 'changed' | 'deleted', path: string) => void, onError: (error: string) => void): IDisposable {
 	return doWatchNonRecursive({ path, isDirectory: false }, onChange, onError);
 }
 
@@ -189,4 +189,4 @@ function doWatchNonRecursive(file: { path: string, isDirectory: boolean }, onCha
 
 		watcherDisposables = dispose(watcherDisposables);
 	});
-}
\ No newline at end of file
+}
diff --git a/src/vs/base/node/zip.ts b/src/vs/base/node/zip.ts
index 320e2b2670..30724dc30f 100644
--- a/src/vs/base/node/zip.ts
+++ b/src/vs/base/node/zip.ts
@@ -86,7 +86,7 @@ function extractEntry(stream: Readable, fileName: string, mode: number, targetPa
 		}
 	});
 
-	return Promise.resolve(mkdirp(targetDirName, undefined, token)).then(() => new Promise((c, e) => {
+	return Promise.resolve(mkdirp(targetDirName)).then(() => new Promise((c, e) => {
 		if (token.isCancellationRequested) {
 			return;
 		}
@@ -149,7 +149,7 @@ function extractZip(zipfile: ZipFile, targetPath: string, options: IOptions, tok
 			// directory file names end with '/'
 			if (/\/$/.test(fileName)) {
 				const targetFileName = path.join(targetPath, fileName);
-				last = createCancelablePromise(token => mkdirp(targetFileName, undefined, token).then(() => readNextEntry(token)).then(undefined, e));
+				last = createCancelablePromise(token => mkdirp(targetFileName).then(() => readNextEntry(token)).then(undefined, e));
 				return;
 			}
 
@@ -163,7 +163,7 @@ function extractZip(zipfile: ZipFile, targetPath: string, options: IOptions, tok
 
 function openZip(zipFile: string, lazy: boolean = false): Promise {
 	return new Promise((resolve, reject) => {
-		_openZip(zipFile, lazy ? { lazyEntries: true } : undefined, (error?: Error, zipfile?: ZipFile) => {
+		_openZip(zipFile, lazy ? { lazyEntries: true } : undefined!, (error?: Error, zipfile?: ZipFile) => {
 			if (error) {
 				reject(toExtractError(error));
 			} else {
diff --git a/src/vs/base/parts/ipc/common/ipc.net.ts b/src/vs/base/parts/ipc/common/ipc.net.ts
index 552815306b..effadd30fb 100644
--- a/src/vs/base/parts/ipc/common/ipc.net.ts
+++ b/src/vs/base/parts/ipc/common/ipc.net.ts
@@ -137,7 +137,7 @@ export const enum ProtocolConstants {
 	/**
 	 * If there is a message that has been unacknowledged for 10 seconds, consider the connection closed...
 	 */
-	AcknowledgeTimeoutTime = 10000, // 10 seconds
+	AcknowledgeTimeoutTime = 20000, // 20 seconds
 	/**
 	 * Send at least a message every 5s for keep alive reasons.
 	 */
@@ -145,7 +145,7 @@ export const enum ProtocolConstants {
 	/**
 	 * If there is no message received for 10 seconds, consider the connection closed...
 	 */
-	KeepAliveTimeoutTime = 10000, // 10 seconds
+	KeepAliveTimeoutTime = 20000, // 20 seconds
 	/**
 	 * If there is no reconnection within this time-frame, consider the connection permanently closed...
 	 */
@@ -284,8 +284,8 @@ class ProtocolWriter {
 
 	public write(msg: ProtocolMessage) {
 		if (this._isDisposed) {
-			console.warn(`Cannot write message in a disposed ProtocolWriter`);
-			console.warn(msg);
+			// ignore: there could be left-over promises which complete and then
+			// decide to write a response, etc...
 			return;
 		}
 		msg.writtenTime = Date.now();
diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts
index c53a5cd494..51a8185c65 100644
--- a/src/vs/base/parts/ipc/common/ipc.ts
+++ b/src/vs/base/parts/ipc/common/ipc.ts
@@ -553,7 +553,7 @@ export class ChannelClient implements IChannelClient, IDisposable {
 			}
 		});
 
-		const handler: IHandler = (res: IRawEventFireResponse) => emitter.fire(res.data);
+		const handler: IHandler = (res: IRawResponse) => emitter.fire((res as IRawEventFireResponse).data);
 		this.handlers.set(id, handler);
 
 		return emitter.event;
diff --git a/src/vs/base/parts/ipc/node/ipc.net.ts b/src/vs/base/parts/ipc/node/ipc.net.ts
index 5a79572f41..1a96d81731 100644
--- a/src/vs/base/parts/ipc/node/ipc.net.ts
+++ b/src/vs/base/parts/ipc/node/ipc.net.ts
@@ -56,7 +56,7 @@ export class NodeSocket implements ISocket {
 		// anyways and nodejs is already doing that for us:
 		// > https://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback
 		// > However, the false return value is only advisory and the writable stream will unconditionally
-		// > accept and buffer chunk even if it has not not been allowed to drain.
+		// > accept and buffer chunk even if it has not been allowed to drain.
 		this.socket.write(buffer.buffer);
 	}
 
diff --git a/src/vs/base/parts/ipc/node/ipc.ts b/src/vs/base/parts/ipc/node/ipc.ts
index 5ee86d2a26..69678de891 100644
--- a/src/vs/base/parts/ipc/node/ipc.ts
+++ b/src/vs/base/parts/ipc/node/ipc.ts
@@ -92,7 +92,7 @@ export function createChannelSender(channel: IChannel, options?: IChannelSend
 	const disableMarshalling = options && options.disableMarshalling;
 
 	return new Proxy({}, {
-		get(_target, propKey, _receiver) {
+		get(_target: T, propKey: PropertyKey) {
 			if (typeof propKey === 'string') {
 
 				// Event
diff --git a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts
index 5bf24732ca..366c4915e2 100644
--- a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts
+++ b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts
@@ -19,13 +19,14 @@ import { OS } from 'vs/base/common/platform';
 import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
 import { IItemAccessor } from 'vs/base/parts/quickopen/common/quickOpenScorer';
 import { coalesce } from 'vs/base/common/arrays';
+import { IMatch } from 'vs/base/common/filters';
 
 export interface IContext {
 	event: any;
 	quickNavigateConfiguration: IQuickNavigateConfiguration;
 }
 
-export interface IHighlight {
+export interface IHighlight extends IMatch {
 	start: number;
 	end: number;
 }
@@ -461,7 +462,7 @@ class Renderer implements IRenderer {
 			options.title = entry.getTooltip();
 			options.descriptionTitle = entry.getDescriptionTooltip() || entry.getDescription(); // tooltip over description because it could overflow
 			options.descriptionMatches = descriptionHighlights || [];
-			data.label.setLabel(types.withNullAsUndefined(entry.getLabel()), entry.getDescription(), options);
+			data.label.setLabel(entry.getLabel() || '', entry.getDescription(), options);
 
 			// Meta
 			data.detail.set(entry.getDetail(), detailHighlights);
diff --git a/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts b/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts
index b65e1a39f5..6c0906bf94 100644
--- a/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts
+++ b/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts
@@ -23,6 +23,7 @@ import { ScrollbarVisibility } from 'vs/base/common/scrollable';
 import { Color } from 'vs/base/common/color';
 import { mixin } from 'vs/base/common/objects';
 import { StandardMouseEvent, IMouseEvent } from 'vs/base/browser/mouseEvent';
+import { IThemable } from 'vs/base/common/styler';
 
 export interface IQuickOpenCallbacks {
 	onOk: () => void;
@@ -92,7 +93,7 @@ const defaultStyles = {
 
 const DEFAULT_INPUT_ARIA_LABEL = nls.localize('quickOpenAriaLabel', "Quick picker. Type to narrow down results.");
 
-export class QuickOpenWidget extends Disposable implements IModelProvider {
+export class QuickOpenWidget extends Disposable implements IModelProvider, IThemable {
 
 	private static readonly MAX_WIDTH = 600;			// Max total width of quick open widget
 	private static readonly MAX_ITEMS_HEIGHT = 20 * 22;	// Max height of item list below input field
@@ -313,6 +314,16 @@ export class QuickOpenWidget extends Disposable implements IModelProvider {
 
 				this.navigateInTree(keyboardEvent.keyCode);
 			}
+
+			// Support to open item with Enter still even in quick nav mode
+			else if (keyboardEvent.keyCode === KeyCode.Enter) {
+				DOM.EventHelper.stop(e, true);
+
+				const focus = this.tree.getFocus();
+				if (focus) {
+					this.elementSelected(focus, e);
+				}
+			}
 		}));
 
 		this._register(DOM.addDisposableListener(this.treeContainer, DOM.EventType.KEY_UP, e => {
@@ -326,7 +337,7 @@ export class QuickOpenWidget extends Disposable implements IModelProvider {
 
 			// Select element when keys are pressed that signal it
 			const quickNavKeys = this.quickNavigateConfiguration.keybindings;
-			const wasTriggerKeyPressed = keyCode === KeyCode.Enter || quickNavKeys.some(k => {
+			const wasTriggerKeyPressed = quickNavKeys.some(k => {
 				const [firstPart, chordPart] = k.getParts();
 				if (chordPart) {
 					return false;
diff --git a/src/vs/base/parts/storage/test/node/storage.test.ts b/src/vs/base/parts/storage/test/node/storage.test.ts
index d656c14816..b244d1b415 100644
--- a/src/vs/base/parts/storage/test/node/storage.test.ts
+++ b/src/vs/base/parts/storage/test/node/storage.test.ts
@@ -294,7 +294,7 @@ suite('SQLite Storage Library', () => {
 		return set;
 	}
 
-	async function testDBBasics(path: string, logError?: (error: Error) => void) {
+	async function testDBBasics(path: string, logError?: (error: Error | string) => void) {
 		let options!: ISQLiteStorageDatabaseOptions;
 		if (logError) {
 			options = {
diff --git a/src/vs/base/parts/tree/browser/tree.css b/src/vs/base/parts/tree/browser/tree.css
index 405161811b..3f28e544ee 100644
--- a/src/vs/base/parts/tree/browser/tree.css
+++ b/src/vs/base/parts/tree/browser/tree.css
@@ -6,12 +6,9 @@
 	height: 100%;
 	width: 100%;
 	white-space: nowrap;
-	-webkit-user-select: none;
-	-khtml-user-select: none;
-	-moz-user-select: -moz-none;
-	-ms-user-select: none;
-	-o-user-select: none;
 	user-select: none;
+	-webkit-user-select: none;
+	-ms-user-select: none;
 	position: relative;
 }
 
@@ -32,10 +29,7 @@
 }
 
 .monaco-tree .monaco-tree-rows > .monaco-tree-row {
-	-moz-box-sizing:	border-box;
-	-o-box-sizing:		border-box;
-	-ms-box-sizing:		border-box;
-	box-sizing:			border-box;
+	box-sizing:	border-box;
 	cursor: pointer;
 	overflow: hidden;
 	width: 100%;
diff --git a/src/vs/base/parts/tree/browser/treeView.ts b/src/vs/base/parts/tree/browser/treeView.ts
index 77664bff71..c1e1efa294 100644
--- a/src/vs/base/parts/tree/browser/treeView.ts
+++ b/src/vs/base/parts/tree/browser/treeView.ts
@@ -265,7 +265,7 @@ export class ViewItem implements IViewItem {
 			}
 
 			if (this.context.horizontalScrolling) {
-				this.element.style.width = 'fit-content';
+				this.element.style.width = Browser.isFirefox ? '-moz-fit-content' : 'fit-content';
 			}
 
 			try {
@@ -289,7 +289,7 @@ export class ViewItem implements IViewItem {
 
 		const style = window.getComputedStyle(this.element);
 		const paddingLeft = parseFloat(style.paddingLeft!);
-		this.element.style.width = 'fit-content';
+		this.element.style.width = Browser.isFirefox ? '-moz-fit-content' : 'fit-content';
 		this.width = DOM.getContentWidth(this.element) + paddingLeft;
 		this.element.style.width = '';
 	}
diff --git a/src/vs/base/test/browser/ui/splitview/splitview.test.ts b/src/vs/base/test/browser/ui/splitview/splitview.test.ts
index bb68a9d97b..b78f502ea1 100644
--- a/src/vs/base/test/browser/ui/splitview/splitview.test.ts
+++ b/src/vs/base/test/browser/ui/splitview/splitview.test.ts
@@ -8,7 +8,7 @@ import { Emitter } from 'vs/base/common/event';
 import { SplitView, IView, Sizing, LayoutPriority } from 'vs/base/browser/ui/splitview/splitview';
 import { Sash, SashState } from 'vs/base/browser/ui/sash/sash';
 
-class TestView implements IView {
+class TestView implements IView {
 
 	private readonly _onDidChange = new Emitter();
 	readonly onDidChange = this._onDidChange.event;
@@ -43,7 +43,7 @@ class TestView implements IView {
 		assert(_minimumSize <= _maximumSize, 'splitview view minimum size must be <= maximum size');
 	}
 
-	layout(size: number, orthogonalSize: number | undefined): void {
+	layout(size: number, _offset: number, orthogonalSize: number | undefined): void {
 		this._size = size;
 		this._orthogonalSize = orthogonalSize;
 		this._onDidLayout.fire({ size, orthogonalSize });
@@ -527,11 +527,11 @@ suite('Splitview', () => {
 		view1.dispose();
 	});
 
-	test('orthogonal size propagates to views', () => {
+	test('context propagates to views', () => {
 		const view1 = new TestView(20, Number.POSITIVE_INFINITY);
 		const view2 = new TestView(20, Number.POSITIVE_INFINITY);
 		const view3 = new TestView(20, Number.POSITIVE_INFINITY, LayoutPriority.Low);
-		const splitview = new SplitView(container, { proportionalLayout: false });
+		const splitview = new SplitView(container, { proportionalLayout: false });
 		splitview.layout(200);
 
 		splitview.addView(view1, Sizing.Distribute);
diff --git a/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts b/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts
index 85fc0d82ac..4ed0de0f1a 100644
--- a/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts
+++ b/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts
@@ -12,19 +12,28 @@ import { timeout } from 'vs/base/common/async';
 
 interface Element {
 	id: string;
+	suffix?: string;
 	children?: Element[];
 }
 
-function find(elements: Element[] | undefined, id: string): Element {
-	while (elements) {
-		for (const element of elements) {
-			if (element.id === id) {
-				return element;
-			}
+function find(element: Element, id: string): Element | undefined {
+	if (element.id === id) {
+		return element;
+	}
+
+	if (!element.children) {
+		return undefined;
+	}
+
+	for (const child of element.children) {
+		const result = find(child, id);
+
+		if (result) {
+			return result;
 		}
 	}
 
-	throw new Error('element not found');
+	return undefined;
 }
 
 class Renderer implements ITreeRenderer {
@@ -33,7 +42,7 @@ class Renderer implements ITreeRenderer {
 		return container;
 	}
 	renderElement(element: ITreeNode, index: number, templateData: HTMLElement): void {
-		templateData.textContent = element.element.id;
+		templateData.textContent = element.element.id + (element.element.suffix || '');
 	}
 	disposeTemplate(templateData: HTMLElement): void {
 		// noop
@@ -65,7 +74,13 @@ class Model {
 	constructor(readonly root: Element) { }
 
 	get(id: string): Element {
-		return find(this.root.children, id);
+		const result = find(this.root, id);
+
+		if (!result) {
+			throw new Error('element not found');
+		}
+
+		return result;
 	}
 }
 
@@ -389,4 +404,36 @@ suite('AsyncDataTree', function () {
 		assert(!hasClass(twistie, 'collapsible'));
 		assert(!hasClass(twistie, 'collapsed'));
 	});
+
+	test('issues #84569, #82629 - rerender', async () => {
+		const container = document.createElement('div');
+		const model = new Model({
+			id: 'root',
+			children: [{
+				id: 'a',
+				children: [{
+					id: 'b',
+					suffix: '1'
+				}]
+			}]
+		});
+
+		const tree = new AsyncDataTree('test', container, new VirtualDelegate(), [new Renderer()], new DataSource(), { identityProvider: new IdentityProvider() });
+		tree.layout(200);
+
+		await tree.setInput(model.root);
+		await tree.expand(model.get('a'));
+		assert.deepEqual(Array.from(container.querySelectorAll('.monaco-list-row')).map(e => e.textContent), ['a', 'b1']);
+
+		const a = model.get('a');
+		const b = model.get('b');
+		a.children?.splice(0, 1, { id: 'b', suffix: '2' });
+
+		await Promise.all([
+			tree.updateChildren(a, true, true),
+			tree.updateChildren(b, true, true)
+		]);
+
+		assert.deepEqual(Array.from(container.querySelectorAll('.monaco-list-row')).map(e => e.textContent), ['a', 'b2']);
+	});
 });
diff --git a/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts b/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts
index 009f9ca9d6..bbfc063f20 100644
--- a/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts
+++ b/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts
@@ -724,4 +724,35 @@ suite('IndexTreeModel', function () {
 		model.refilter();
 		assert.deepEqual(toArray(list), ['platinum']);
 	});
+
+	test('explicit hidden nodes should have renderNodeCount == 0, issue #83211', function () {
+		const list: ITreeNode[] = [];
+		let query = new RegExp('');
+		const filter = new class implements ITreeFilter {
+			filter(element: string): boolean {
+				return query.test(element);
+			}
+		};
+
+		const model = new IndexTreeModel('test', toSpliceable(list), 'root', { filter });
+
+		model.splice([0], 0, [
+			{ element: 'a', children: [{ element: 'aa' }] },
+			{ element: 'b', children: [{ element: 'bb' }] }
+		]);
+
+		assert.deepEqual(toArray(list), ['a', 'aa', 'b', 'bb']);
+		assert.deepEqual(model.getListIndex([0]), 0);
+		assert.deepEqual(model.getListIndex([0, 0]), 1);
+		assert.deepEqual(model.getListIndex([1]), 2);
+		assert.deepEqual(model.getListIndex([1, 0]), 3);
+
+		query = /b/;
+		model.refilter();
+		assert.deepEqual(toArray(list), ['b', 'bb']);
+		assert.deepEqual(model.getListIndex([0]), -1);
+		assert.deepEqual(model.getListIndex([0, 0]), -1);
+		assert.deepEqual(model.getListIndex([1]), 0);
+		assert.deepEqual(model.getListIndex([1, 0]), 1);
+	});
 });
diff --git a/src/vs/base/test/browser/ui/tree/objectTree.test.ts b/src/vs/base/test/browser/ui/tree/objectTree.test.ts
index d748307051..3a5635c27d 100644
--- a/src/vs/base/test/browser/ui/tree/objectTree.test.ts
+++ b/src/vs/base/test/browser/ui/tree/objectTree.test.ts
@@ -348,8 +348,6 @@ suite('CompressibleObjectTree', function () {
 		const tree = new CompressibleObjectTree('test', container, new Delegate(), [new Renderer()]);
 		tree.layout(200);
 
-		assert.equal(tree.isCompressionEnabled(), true);
-
 		tree.setChildren(null, Iterator.fromArray([
 			{
 				element: 1, children: Iterator.fromArray([{
@@ -367,11 +365,11 @@ suite('CompressibleObjectTree', function () {
 		let rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
 		assert.deepEqual(rows, ['1/11/111', '1111', '1112', '1113']);
 
-		tree.setCompressionEnabled(false);
+		tree.updateOptions({ compressionEnabled: false });
 		rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
 		assert.deepEqual(rows, ['1', '11', '111', '1111', '1112', '1113']);
 
-		tree.setCompressionEnabled(true);
+		tree.updateOptions({ compressionEnabled: true });
 		rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
 		assert.deepEqual(rows, ['1/11/111', '1111', '1112', '1113']);
 	});
diff --git a/src/vs/base/test/common/event.test.ts b/src/vs/base/test/common/event.test.ts
index 7ae0a979d1..71f299f824 100644
--- a/src/vs/base/test/common/event.test.ts
+++ b/src/vs/base/test/common/event.test.ts
@@ -3,10 +3,11 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 import * as assert from 'assert';
-import { Event, Emitter, EventBufferer, EventMultiplexer, AsyncEmitter, IWaitUntil, PauseableEmitter } from 'vs/base/common/event';
+import { Event, Emitter, EventBufferer, EventMultiplexer, IWaitUntil, PauseableEmitter, AsyncEmitter } from 'vs/base/common/event';
 import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
 import * as Errors from 'vs/base/common/errors';
 import { timeout } from 'vs/base/common/async';
+import { CancellationToken } from 'vs/base/common/cancellation';
 
 namespace Samples {
 
@@ -174,7 +175,7 @@ suite('Event', function () {
 	test('Debounce Event', function (done: () => void) {
 		let doc = new Samples.Document3();
 
-		let onDocDidChange = Event.debounce(doc.onDidChange, (prev: string[], cur) => {
+		let onDocDidChange = Event.debounce(doc.onDidChange, (prev: string[] | undefined, cur) => {
 			if (!prev) {
 				prev = [cur];
 			} else if (prev.indexOf(cur) < 0) {
@@ -272,11 +273,7 @@ suite('AsyncEmitter', function () {
 			assert.equal(typeof e.waitUntil, 'function');
 		});
 
-		emitter.fireAsync(thenables => ({
-			foo: true,
-			bar: 1,
-			waitUntil(t: Promise) { thenables.push(t); }
-		}));
+		emitter.fireAsync({ foo: true, bar: 1, }, CancellationToken.None);
 		emitter.dispose();
 	});
 
@@ -303,12 +300,7 @@ suite('AsyncEmitter', function () {
 			}));
 		});
 
-		await emitter.fireAsync(thenables => ({
-			foo: true,
-			waitUntil(t) {
-				thenables.push(t);
-			}
-		}));
+		await emitter.fireAsync({ foo: true }, CancellationToken.None);
 		assert.equal(globalState, 2);
 	});
 
@@ -324,12 +316,7 @@ suite('AsyncEmitter', function () {
 		emitter.event(e => {
 			e.waitUntil(timeout(10).then(async _ => {
 				if (e.foo === 1) {
-					await emitter.fireAsync(thenables => ({
-						foo: 2,
-						waitUntil(t) {
-							thenables.push(t);
-						}
-					}));
+					await emitter.fireAsync({ foo: 2 }, CancellationToken.None);
 					assert.deepEqual(events, [1, 2]);
 					done = true;
 				}
@@ -342,14 +329,40 @@ suite('AsyncEmitter', function () {
 			e.waitUntil(timeout(7));
 		});
 
-		await emitter.fireAsync(thenables => ({
-			foo: 1,
-			waitUntil(t) {
-				thenables.push(t);
-			}
-		}));
+		await emitter.fireAsync({ foo: 1 }, CancellationToken.None);
 		assert.ok(done);
 	});
+
+	test('catch errors', async function () {
+		const origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler();
+		Errors.setUnexpectedErrorHandler(() => null);
+
+		interface E extends IWaitUntil {
+			foo: boolean;
+		}
+
+		let globalState = 0;
+		let emitter = new AsyncEmitter();
+
+		emitter.event(e => {
+			globalState += 1;
+			e.waitUntil(new Promise((_r, reject) => reject(new Error())));
+		});
+
+		emitter.event(e => {
+			globalState += 1;
+			e.waitUntil(timeout(10));
+		});
+
+		await emitter.fireAsync({ foo: true }, CancellationToken.None).then(() => {
+			assert.equal(globalState, 2);
+		}).catch(e => {
+			console.log(e);
+			assert.ok(false);
+		});
+
+		Errors.setUnexpectedErrorHandler(origErrorHandler);
+	});
 });
 
 suite('PausableEmitter', function () {
diff --git a/src/vs/base/test/common/filters.test.ts b/src/vs/base/test/common/filters.test.ts
index dbd841b1d7..aeb8623984 100644
--- a/src/vs/base/test/common/filters.test.ts
+++ b/src/vs/base/test/common/filters.test.ts
@@ -498,4 +498,14 @@ suite('Filters', () => {
 			fuzzyScore
 		);
 	});
+
+	test('"Go to Symbol" with the exact method name doesn\'t work as expected #84787', function () {
+		const match = fuzzyScore(':get', ':get', 1, 'get', 'get', 0, true);
+		assert.ok(Boolean(match));
+	});
+
+	test('Suggestion is not highlighted #85826', function () {
+		assertMatches('SemanticTokens', 'SemanticTokensEdits', '^S^e^m^a^n^t^i^c^T^o^k^e^n^sEdits', fuzzyScore);
+		assertMatches('SemanticTokens', 'SemanticTokensEdits', '^S^e^m^a^n^t^i^c^T^o^k^e^n^sEdits', fuzzyScoreGracefulAggressive);
+	});
 });
diff --git a/src/vs/base/test/common/jsonEdit.test.ts b/src/vs/base/test/common/jsonEdit.test.ts
index 7ace5de2fb..d61996802f 100644
--- a/src/vs/base/test/common/jsonEdit.test.ts
+++ b/src/vs/base/test/common/jsonEdit.test.ts
@@ -118,13 +118,43 @@ suite('JSON - edits', () => {
 		assertEdit(content, edits, '{\n  "x": "y"\n}');
 	});
 
-	test('insert item to empty array', () => {
+	test('insert item at 0', () => {
+		let content = '[\n  2,\n  3\n]';
+		let edits = setProperty(content, [0], 1, formatterOptions);
+		assertEdit(content, edits, '[\n  1,\n  2,\n  3\n]');
+	});
+
+	test('insert item at 0 in empty array', () => {
+		let content = '[\n]';
+		let edits = setProperty(content, [0], 1, formatterOptions);
+		assertEdit(content, edits, '[\n  1\n]');
+	});
+
+	test('insert item at an index', () => {
+		let content = '[\n  1,\n  3\n]';
+		let edits = setProperty(content, [1], 2, formatterOptions);
+		assertEdit(content, edits, '[\n  1,\n  2,\n  3\n]');
+	});
+
+	test('insert item at an index im empty array', () => {
+		let content = '[\n]';
+		let edits = setProperty(content, [1], 1, formatterOptions);
+		assertEdit(content, edits, '[\n  1\n]');
+	});
+
+	test('insert item at end index', () => {
+		let content = '[\n  1,\n  2\n]';
+		let edits = setProperty(content, [2], 3, formatterOptions);
+		assertEdit(content, edits, '[\n  1,\n  2,\n  3\n]');
+	});
+
+	test('insert item at end to empty array', () => {
 		let content = '[\n]';
 		let edits = setProperty(content, [-1], 'bar', formatterOptions);
 		assertEdit(content, edits, '[\n  "bar"\n]');
 	});
 
-	test('insert item', () => {
+	test('insert item at end', () => {
 		let content = '[\n  1,\n  2\n]';
 		let edits = setProperty(content, [-1], 'bar', formatterOptions);
 		assertEdit(content, edits, '[\n  1,\n  2,\n  "bar"\n]');
@@ -160,4 +190,4 @@ suite('JSON - edits', () => {
 		assertEdit(content, edits, '// This is a comment\n[\n  1,\n  "foo"\n]');
 	});
 
-});
\ No newline at end of file
+});
diff --git a/src/vs/base/test/common/stream.test.ts b/src/vs/base/test/common/stream.test.ts
new file mode 100644
index 0000000000..b08b8a6801
--- /dev/null
+++ b/src/vs/base/test/common/stream.test.ts
@@ -0,0 +1,176 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as assert from 'assert';
+import { isReadableStream, newWriteableStream, Readable, consumeReadable, consumeReadableWithLimit, consumeStream, ReadableStream, toStream, toReadable, transform, consumeStreamWithLimit } from 'vs/base/common/stream';
+
+suite('Stream', () => {
+
+	test('isReadableStream', () => {
+		assert.ok(!isReadableStream(Object.create(null)));
+		assert.ok(isReadableStream(newWriteableStream(d => d)));
+	});
+
+	test('WriteableStream', () => {
+		const stream = newWriteableStream(strings => strings.join());
+
+		let error = false;
+		stream.on('error', e => {
+			error = true;
+		});
+
+		let end = false;
+		stream.on('end', () => {
+			end = true;
+		});
+
+		stream.write('Hello');
+
+		const chunks: string[] = [];
+		stream.on('data', data => {
+			chunks.push(data);
+		});
+
+		assert.equal(chunks[0], 'Hello');
+
+		stream.write('World');
+		assert.equal(chunks[1], 'World');
+
+		assert.equal(error, false);
+		assert.equal(end, false);
+
+		stream.pause();
+		stream.write('1');
+		stream.write('2');
+		stream.write('3');
+
+		assert.equal(chunks.length, 2);
+
+		stream.resume();
+
+		assert.equal(chunks.length, 3);
+		assert.equal(chunks[2], '1,2,3');
+
+		stream.error(new Error());
+		assert.equal(error, true);
+
+		stream.end('Final Bit');
+		assert.equal(chunks.length, 4);
+		assert.equal(chunks[3], 'Final Bit');
+
+		stream.destroy();
+
+		stream.write('Unexpected');
+		assert.equal(chunks.length, 4);
+	});
+
+	test('consumeReadable', () => {
+		const readable = arrayToReadable(['1', '2', '3', '4', '5']);
+		const consumed = consumeReadable(readable, strings => strings.join());
+		assert.equal(consumed, '1,2,3,4,5');
+	});
+
+	test('consumeReadableWithLimit', () => {
+		for (let i = 0; i < 5; i++) {
+			const readable = arrayToReadable(['1', '2', '3', '4', '5']);
+
+			const consumedOrReadable = consumeReadableWithLimit(readable, strings => strings.join(), i);
+			if (typeof consumedOrReadable === 'string') {
+				assert.fail('Unexpected result');
+			} else {
+				const consumed = consumeReadable(consumedOrReadable, strings => strings.join());
+				assert.equal(consumed, '1,2,3,4,5');
+			}
+		}
+
+		let readable = arrayToReadable(['1', '2', '3', '4', '5']);
+		let consumedOrReadable = consumeReadableWithLimit(readable, strings => strings.join(), 5);
+		assert.equal(consumedOrReadable, '1,2,3,4,5');
+
+		readable = arrayToReadable(['1', '2', '3', '4', '5']);
+		consumedOrReadable = consumeReadableWithLimit(readable, strings => strings.join(), 6);
+		assert.equal(consumedOrReadable, '1,2,3,4,5');
+	});
+
+	function arrayToReadable(array: T[]): Readable {
+		return {
+			read: () => array.shift() || null
+		};
+	}
+
+	function readableToStream(readable: Readable): ReadableStream {
+		const stream = newWriteableStream(strings => strings.join());
+
+		// Simulate async behavior
+		setTimeout(() => {
+			let chunk: string | null = null;
+			while ((chunk = readable.read()) !== null) {
+				stream.write(chunk);
+			}
+
+			stream.end();
+		}, 0);
+
+		return stream;
+	}
+
+	test('consumeStream', async () => {
+		const stream = readableToStream(arrayToReadable(['1', '2', '3', '4', '5']));
+		const consumed = await consumeStream(stream, strings => strings.join());
+		assert.equal(consumed, '1,2,3,4,5');
+	});
+
+	test('consumeStreamWithLimit', async () => {
+		for (let i = 0; i < 5; i++) {
+			const readable = readableToStream(arrayToReadable(['1', '2', '3', '4', '5']));
+
+			const consumedOrStream = await consumeStreamWithLimit(readable, strings => strings.join(), i);
+			if (typeof consumedOrStream === 'string') {
+				assert.fail('Unexpected result');
+			} else {
+				const consumed = await consumeStream(consumedOrStream, strings => strings.join());
+				assert.equal(consumed, '1,2,3,4,5');
+			}
+		}
+
+		let stream = readableToStream(arrayToReadable(['1', '2', '3', '4', '5']));
+		let consumedOrStream = await consumeStreamWithLimit(stream, strings => strings.join(), 5);
+		assert.equal(consumedOrStream, '1,2,3,4,5');
+
+		stream = readableToStream(arrayToReadable(['1', '2', '3', '4', '5']));
+		consumedOrStream = await consumeStreamWithLimit(stream, strings => strings.join(), 6);
+		assert.equal(consumedOrStream, '1,2,3,4,5');
+	});
+
+	test('toStream', async () => {
+		const stream = toStream('1,2,3,4,5', strings => strings.join());
+		const consumed = await consumeStream(stream, strings => strings.join());
+		assert.equal(consumed, '1,2,3,4,5');
+	});
+
+	test('toReadable', async () => {
+		const readable = toReadable('1,2,3,4,5');
+		const consumed = await consumeReadable(readable, strings => strings.join());
+		assert.equal(consumed, '1,2,3,4,5');
+	});
+
+	test('transform', async () => {
+		const source = newWriteableStream(strings => strings.join());
+
+		const result = transform(source, { data: string => string + string }, strings => strings.join());
+
+		// Simulate async behavior
+		setTimeout(() => {
+			source.write('1');
+			source.write('2');
+			source.write('3');
+			source.write('4');
+			source.end('5');
+		}, 0);
+
+		const consumed = await consumeStream(result, strings => strings.join());
+		assert.equal(consumed, '11,22,33,44,55');
+	});
+});
diff --git a/src/vs/base/test/common/strings.test.ts b/src/vs/base/test/common/strings.test.ts
index 1a33054e5f..640e4732b1 100644
--- a/src/vs/base/test/common/strings.test.ts
+++ b/src/vs/base/test/common/strings.test.ts
@@ -458,4 +458,42 @@ suite('Strings', () => {
 		assert.equal(strings.removeAccents('ñice'), 'nice');
 		assert.equal(strings.removeAccents('ńice'), 'nice');
 	});
+
+	test('encodeUTF8', function () {
+		function assertEncodeUTF8(str: string, expected: number[]): void {
+			const actual = strings.encodeUTF8(str);
+			const actualArr: number[] = [];
+			for (let offset = 0; offset < actual.byteLength; offset++) {
+				actualArr[offset] = actual[offset];
+			}
+			assert.deepEqual(actualArr, expected);
+		}
+
+		function assertDecodeUTF8(data: number[], expected: string): void {
+			const actual = strings.decodeUTF8(new Uint8Array(data));
+			assert.deepEqual(actual, expected);
+		}
+
+		function assertEncodeDecodeUTF8(str: string, buff: number[]): void {
+			assertEncodeUTF8(str, buff);
+			assertDecodeUTF8(buff, str);
+		}
+
+		assertEncodeDecodeUTF8('\u0000', [0]);
+		assertEncodeDecodeUTF8('!', [33]);
+		assertEncodeDecodeUTF8('\u007F', [127]);
+		assertEncodeDecodeUTF8('\u0080', [194, 128]);
+		assertEncodeDecodeUTF8('Ɲ', [198, 157]);
+		assertEncodeDecodeUTF8('\u07FF', [223, 191]);
+		assertEncodeDecodeUTF8('\u0800', [224, 160, 128]);
+		assertEncodeDecodeUTF8('ஂ', [224, 174, 130]);
+		assertEncodeDecodeUTF8('\uffff', [239, 191, 191]);
+		assertEncodeDecodeUTF8('\u10000', [225, 128, 128, 48]);
+		assertEncodeDecodeUTF8('🧝', [240, 159, 167, 157]);
+
+	});
+
+	test('getGraphemeBreakType', () => {
+		assert.equal(strings.getGraphemeBreakType(0xBC1), strings.GraphemeBreakType.SpacingMark);
+	});
 });
diff --git a/src/vs/base/test/common/uri.test.ts b/src/vs/base/test/common/uri.test.ts
index a43e1cebfa..a85930d3ef 100644
--- a/src/vs/base/test/common/uri.test.ts
+++ b/src/vs/base/test/common/uri.test.ts
@@ -439,6 +439,10 @@ suite('URI', () => {
 		assert.equal(uri.path, uri2.path);
 	});
 
+	test('Unable to open \'%A0.txt\': URI malformed #76506', function () {
+		assert.equal(URI.parse('file://some/%.txt'), 'file://some/%25.txt');
+		assert.equal(URI.parse('file://some/%A0.txt'), 'file://some/%25A0.txt');
+	});
 
 	test('Links in markdown are broken if url contains encoded parameters #79474', function () {
 		this.skip();
diff --git a/src/vs/base/test/node/buffer.test.ts b/src/vs/base/test/node/buffer.test.ts
index 2cc4c5ee93..f7d465406e 100644
--- a/src/vs/base/test/node/buffer.test.ts
+++ b/src/vs/base/test/node/buffer.test.ts
@@ -4,7 +4,7 @@
  *--------------------------------------------------------------------------------------------*/
 
 import * as assert from 'assert';
-import { VSBuffer, bufferToReadable, readableToBuffer, bufferToStream, streamToBuffer, writeableBufferStream } from 'vs/base/common/buffer';
+import { VSBuffer, bufferToReadable, readableToBuffer, bufferToStream, streamToBuffer, newWriteableBufferStream } from 'vs/base/common/buffer';
 import { timeout } from 'vs/base/common/async';
 
 suite('Buffer', () => {
@@ -30,7 +30,7 @@ suite('Buffer', () => {
 	});
 
 	test('bufferWriteableStream - basics (no error)', async () => {
-		const stream = writeableBufferStream();
+		const stream = newWriteableBufferStream();
 
 		let chunks: VSBuffer[] = [];
 		stream.on('data', data => {
@@ -60,7 +60,7 @@ suite('Buffer', () => {
 	});
 
 	test('bufferWriteableStream - basics (error)', async () => {
-		const stream = writeableBufferStream();
+		const stream = newWriteableBufferStream();
 
 		let chunks: VSBuffer[] = [];
 		stream.on('data', data => {
@@ -89,7 +89,7 @@ suite('Buffer', () => {
 	});
 
 	test('bufferWriteableStream - buffers data when no listener', async () => {
-		const stream = writeableBufferStream();
+		const stream = newWriteableBufferStream();
 
 		await timeout(0);
 		stream.write(VSBuffer.fromString('Hello'));
@@ -118,7 +118,7 @@ suite('Buffer', () => {
 	});
 
 	test('bufferWriteableStream - buffers errors when no listener', async () => {
-		const stream = writeableBufferStream();
+		const stream = newWriteableBufferStream();
 
 		await timeout(0);
 		stream.write(VSBuffer.fromString('Hello'));
@@ -149,7 +149,7 @@ suite('Buffer', () => {
 	});
 
 	test('bufferWriteableStream - buffers end when no listener', async () => {
-		const stream = writeableBufferStream();
+		const stream = newWriteableBufferStream();
 
 		await timeout(0);
 		stream.write(VSBuffer.fromString('Hello'));
@@ -178,7 +178,7 @@ suite('Buffer', () => {
 	});
 
 	test('bufferWriteableStream - nothing happens after end()', async () => {
-		const stream = writeableBufferStream();
+		const stream = newWriteableBufferStream();
 
 		let chunks: VSBuffer[] = [];
 		stream.on('data', data => {
@@ -222,7 +222,7 @@ suite('Buffer', () => {
 	});
 
 	test('bufferWriteableStream - pause/resume (simple)', async () => {
-		const stream = writeableBufferStream();
+		const stream = newWriteableBufferStream();
 
 		let chunks: VSBuffer[] = [];
 		stream.on('data', data => {
@@ -259,7 +259,7 @@ suite('Buffer', () => {
 	});
 
 	test('bufferWriteableStream - pause/resume (pause after first write)', async () => {
-		const stream = writeableBufferStream();
+		const stream = newWriteableBufferStream();
 
 		let chunks: VSBuffer[] = [];
 		stream.on('data', data => {
@@ -299,7 +299,7 @@ suite('Buffer', () => {
 	});
 
 	test('bufferWriteableStream - pause/resume (error)', async () => {
-		const stream = writeableBufferStream();
+		const stream = newWriteableBufferStream();
 
 		let chunks: VSBuffer[] = [];
 		stream.on('data', data => {
@@ -336,7 +336,7 @@ suite('Buffer', () => {
 	});
 
 	test('bufferWriteableStream - destroy', async () => {
-		const stream = writeableBufferStream();
+		const stream = newWriteableBufferStream();
 
 		let chunks: VSBuffer[] = [];
 		stream.on('data', data => {
diff --git a/src/vs/base/test/node/encoding/encoding.test.ts b/src/vs/base/test/node/encoding/encoding.test.ts
index a6c0399e3f..9bceda47ec 100644
--- a/src/vs/base/test/node/encoding/encoding.test.ts
+++ b/src/vs/base/test/node/encoding/encoding.test.ts
@@ -9,7 +9,7 @@ import * as encoding from 'vs/base/node/encoding';
 import { Readable } from 'stream';
 import { getPathFromAmdModule } from 'vs/base/common/amd';
 
-export async function detectEncodingByBOM(file: string): Promise {
+export async function detectEncodingByBOM(file: string): Promise {
 	try {
 		const { buffer, bytesRead } = await readExactlyByFile(file, 3);
 
@@ -86,7 +86,7 @@ suite('Encoding', () => {
 		const file = getPathFromAmdModule(require, './fixtures/some_utf8.css');
 
 		const detectedEncoding = await detectEncodingByBOM(file);
-		assert.equal(detectedEncoding, 'utf8');
+		assert.equal(detectedEncoding, 'utf8bom');
 	});
 
 	test('detectBOM UTF-16 LE', async () => {
@@ -189,6 +189,20 @@ suite('Encoding', () => {
 		assert.equal(mimes.seemsBinary, false);
 	});
 
+	test('autoGuessEncoding (UTF8)', async function () {
+		const file = getPathFromAmdModule(require, './fixtures/some_file.css');
+		const buffer = await readExactlyByFile(file, 512 * 8);
+		const mimes = await encoding.detectEncodingFromBuffer(buffer, true);
+		assert.equal(mimes.encoding, 'utf8');
+	});
+
+	test('autoGuessEncoding (ASCII)', async function () {
+		const file = getPathFromAmdModule(require, './fixtures/some_ansi.css');
+		const buffer = await readExactlyByFile(file, 512 * 8);
+		const mimes = await encoding.detectEncodingFromBuffer(buffer, true);
+		assert.equal(mimes.encoding, null);
+	});
+
 	test('autoGuessEncoding (ShiftJIS)', async function () {
 		const file = getPathFromAmdModule(require, './fixtures/some.shiftjis.txt');
 		const buffer = await readExactlyByFile(file, 512 * 8);
diff --git a/src/vs/base/test/node/encoding/fixtures/some_file.css b/src/vs/base/test/node/encoding/fixtures/some_file.css
new file mode 100644
index 0000000000..35b4fcf5ab
--- /dev/null
+++ b/src/vs/base/test/node/encoding/fixtures/some_file.css
@@ -0,0 +1,42 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+/*----------------------------------------------------------
+The base color for this template is #5c87b2. If you'd like
+to use a different color start by replacing all instances of
+#5c87b2 with your new color.
+
+öäüßßß
+----------------------------------------------------------*/
+body
+{
+    background-color: #5c87b2;
+    font-size: .75em;
+    font-family: Segoe UI, Verdana, Helvetica, Sans-Serif;
+    margin: 8px;
+    padding: 0;
+    color: #696969;
+}
+
+h1, h2, h3, h4, h5, h6
+{
+    color: #000;
+    font-size: 40px;
+    margin: 0px;
+}
+
+textarea
+{
+   font-family: Consolas
+}
+
+#results
+{
+    margin-top: 2em;
+    margin-left: 2em;
+    color: black;
+    font-size: medium;
+}
+
diff --git a/src/vs/base/test/node/encoding/fixtures/some_utf16be.css b/src/vs/base/test/node/encoding/fixtures/some_utf16be.css
index 30c3b9d38b..8944381400 100644
Binary files a/src/vs/base/test/node/encoding/fixtures/some_utf16be.css and b/src/vs/base/test/node/encoding/fixtures/some_utf16be.css differ
diff --git a/src/vs/base/test/node/encoding/fixtures/some_utf16le.css b/src/vs/base/test/node/encoding/fixtures/some_utf16le.css
index aea04aa2cd..243daee1f0 100644
Binary files a/src/vs/base/test/node/encoding/fixtures/some_utf16le.css and b/src/vs/base/test/node/encoding/fixtures/some_utf16le.css differ
diff --git a/src/vs/base/test/node/encoding/fixtures/some_utf8.css b/src/vs/base/test/node/encoding/fixtures/some_utf8.css
index b7e5283202..35b4fcf5ab 100644
--- a/src/vs/base/test/node/encoding/fixtures/some_utf8.css
+++ b/src/vs/base/test/node/encoding/fixtures/some_utf8.css
@@ -7,6 +7,8 @@
 The base color for this template is #5c87b2. If you'd like
 to use a different color start by replacing all instances of
 #5c87b2 with your new color.
+
+öäüßßß
 ----------------------------------------------------------*/
 body
 {
@@ -25,12 +27,12 @@ h1, h2, h3, h4, h5, h6
     margin: 0px;
 }
 
-textarea 
+textarea
 {
    font-family: Consolas
 }
 
-#results 
+#results
 {
     margin-top: 2em;
     margin-left: 2em;
diff --git a/src/vs/base/test/node/glob.test.ts b/src/vs/base/test/node/glob.test.ts
index 33111c9c14..0570e9a21c 100644
--- a/src/vs/base/test/node/glob.test.ts
+++ b/src/vs/base/test/node/glob.test.ts
@@ -14,12 +14,12 @@ suite('Glob', () => {
 	// 	let patterns = [
 	// 		'{**/*.cs,**/*.json,**/*.csproj,**/*.sln}',
 	// 		'{**/*.cs,**/*.csproj,**/*.sln}',
-	// 		'{**/*.ts,**/*.tsx,**/*.js,**/*.jsx,**/*.es6,**/*.mjs}',
+	// 		'{**/*.ts,**/*.tsx,**/*.js,**/*.jsx,**/*.es6,**/*.mjs,**/*.cjs}',
 	// 		'**/*.go',
 	// 		'{**/*.ps,**/*.ps1}',
 	// 		'{**/*.c,**/*.cpp,**/*.h}',
 	// 		'{**/*.fsx,**/*.fsi,**/*.fs,**/*.ml,**/*.mli}',
-	// 		'{**/*.js,**/*.jsx,**/*.es6,**/*.mjs}',
+	// 		'{**/*.js,**/*.jsx,**/*.es6,**/*.mjs,**/*.cjs}',
 	// 		'{**/*.ts,**/*.tsx}',
 	// 		'{**/*.php}',
 	// 		'{**/*.php}',
@@ -1015,4 +1015,4 @@ suite('Glob', () => {
 			assertNoGlobMatch(p, '/DNXConsoleApp/foo/Program.cs');
 		}
 	});
-});
\ No newline at end of file
+});
diff --git a/src/vs/base/test/node/pfs/pfs.test.ts b/src/vs/base/test/node/pfs/pfs.test.ts
index ab9bf356b7..6961360841 100644
--- a/src/vs/base/test/node/pfs/pfs.test.ts
+++ b/src/vs/base/test/node/pfs/pfs.test.ts
@@ -12,7 +12,6 @@ import * as uuid from 'vs/base/common/uuid';
 import * as pfs from 'vs/base/node/pfs';
 import { timeout } from 'vs/base/common/async';
 import { getPathFromAmdModule } from 'vs/base/common/amd';
-import { CancellationTokenSource } from 'vs/base/common/cancellation';
 import { isWindows, isLinux } from 'vs/base/common/platform';
 import { canNormalize } from 'vs/base/common/normalization';
 import { VSBuffer } from 'vs/base/common/buffer';
@@ -50,7 +49,13 @@ function toReadable(value: string, throwError?: boolean): Readable {
 	});
 }
 
-suite('PFS', () => {
+suite('PFS', function () {
+
+	// Given issues such as https://github.com/microsoft/vscode/issues/84066
+	// we see random test failures when accessing the native file system. To
+	// diagnose further, we retry node.js file access tests up to 3 times to
+	// rule out any random disk issue.
+	this.retries(3);
 
 	test('writeFile', async () => {
 		const id = uuid.generateUuid();
@@ -253,7 +258,7 @@ suite('PFS', () => {
 		}
 		catch (error) {
 			assert.fail(error);
-			return Promise.reject(error);
+			throw error;
 		}
 	});
 
@@ -306,23 +311,6 @@ suite('PFS', () => {
 		return pfs.rimraf(parentDir, pfs.RimRafMode.MOVE);
 	});
 
-	test('mkdirp cancellation', async () => {
-		const id = uuid.generateUuid();
-		const parentDir = path.join(os.tmpdir(), 'vsctests', id);
-		const newDir = path.join(parentDir, 'pfs', id);
-
-		const source = new CancellationTokenSource();
-
-		const mkdirpPromise = pfs.mkdirp(newDir, 493, source.token);
-		source.cancel();
-
-		await mkdirpPromise;
-
-		assert.ok(!fs.existsSync(newDir));
-
-		return pfs.rimraf(parentDir, pfs.RimRafMode.MOVE);
-	});
-
 	test('readDirsInDir', async () => {
 		const id = uuid.generateUuid();
 		const parentDir = path.join(os.tmpdir(), 'vsctests', id);
@@ -525,7 +513,7 @@ suite('PFS', () => {
 		}
 
 		if (!expectedError || (expectedError).code !== 'EISDIR') {
-			return Promise.reject(new Error('Expected EISDIR error for writing to folder but got: ' + (expectedError ? (expectedError).code : 'no error')));
+			throw new Error('Expected EISDIR error for writing to folder but got: ' + (expectedError ? (expectedError).code : 'no error'));
 		}
 
 		// verify that the stream is still consumable (for https://github.com/Microsoft/vscode/issues/42542)
@@ -551,7 +539,7 @@ suite('PFS', () => {
 		}
 
 		if (!expectedError || expectedError.message !== readError) {
-			return Promise.reject(new Error('Expected error for writing to folder'));
+			throw new Error('Expected error for writing to folder');
 		}
 
 		await pfs.rimraf(parentDir);
@@ -582,7 +570,7 @@ suite('PFS', () => {
 		}
 
 		if (!expectedError || !((expectedError).code !== 'EACCES' || (expectedError).code !== 'EPERM')) {
-			return Promise.reject(new Error('Expected EACCES/EPERM error for writing to folder but got: ' + (expectedError ? (expectedError).code : 'no error')));
+			throw new Error('Expected EACCES/EPERM error for writing to folder but got: ' + (expectedError ? (expectedError).code : 'no error'));
 		}
 
 		await pfs.rimraf(parentDir);
@@ -609,7 +597,7 @@ suite('PFS', () => {
 		}
 
 		if (!expectedError) {
-			return Promise.reject(new Error('Expected error for writing to folder'));
+			throw new Error('Expected error for writing to folder');
 		}
 
 		await pfs.rimraf(parentDir);
diff --git a/src/vs/base/node/test/fixtures/extract.zip b/src/vs/base/test/node/zip/fixtures/extract.zip
similarity index 100%
rename from src/vs/base/node/test/fixtures/extract.zip
rename to src/vs/base/test/node/zip/fixtures/extract.zip
diff --git a/src/vs/base/node/test/zip.test.ts b/src/vs/base/test/node/zip/zip.test.ts
similarity index 100%
rename from src/vs/base/node/test/zip.test.ts
rename to src/vs/base/test/node/zip/zip.test.ts
diff --git a/src/vs/code/browser/workbench/workbench-dev.html b/src/vs/code/browser/workbench/workbench-dev.html
index 7ae960dbc9..af6317d035 100644
--- a/src/vs/code/browser/workbench/workbench-dev.html
+++ b/src/vs/code/browser/workbench/workbench-dev.html
@@ -32,6 +32,7 @@
 				'xterm': `${window.location.origin}/static/remote/web/node_modules/xterm/lib/xterm.js`,
 				'xterm-addon-search': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
 				'xterm-addon-web-links': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`,
+				'xterm-addon-webgl': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js`,
 				'semver-umd': `${window.location.origin}/static/remote/web/node_modules/semver-umd/lib/semver-umd.js`,
 			}
 		};
diff --git a/src/vs/code/browser/workbench/workbench.html b/src/vs/code/browser/workbench/workbench.html
index dbcb8e490f..8a6a9f54e6 100644
--- a/src/vs/code/browser/workbench/workbench.html
+++ b/src/vs/code/browser/workbench/workbench.html
@@ -36,6 +36,7 @@
 				'xterm': `${window.location.origin}/static/node_modules/xterm/lib/xterm.js`,
 				'xterm-addon-search': `${window.location.origin}/static/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
 				'xterm-addon-web-links': `${window.location.origin}/static/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`,
+				'xterm-addon-webgl': `${window.location.origin}/static/node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js`,
 				'semver-umd': `${window.location.origin}/static/node_modules/semver-umd/lib/semver-umd.js`,
 			}
 		};
diff --git a/src/vs/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts
index ac95d50d3f..4864b9255e 100644
--- a/src/vs/code/browser/workbench/workbench.ts
+++ b/src/vs/code/browser/workbench/workbench.ts
@@ -277,13 +277,22 @@ class WorkspaceProvider implements IWorkspaceProvider {
 
 (function () {
 
-	// Find config element in DOM
+	// Find config by checking for DOM
 	const configElement = document.getElementById('vscode-workbench-web-configuration');
 	const configElementAttribute = configElement ? configElement.getAttribute('data-settings') : undefined;
 	if (!configElement || !configElementAttribute) {
 		throw new Error('Missing web configuration element');
 	}
 
+	const config: IWorkbenchConstructionOptions & { folderUri?: UriComponents, workspaceUri?: UriComponents } = JSON.parse(configElementAttribute);
+
+	// Revive static extension locations
+	if (Array.isArray(config.staticExtensions)) {
+		config.staticExtensions.forEach(extension => {
+			extension.extensionLocation = URI.revive(extension.extensionLocation);
+		});
+	}
+
 	// Find workspace to open and payload
 	let foundWorkspace = false;
 	let workspace: IWorkspace;
@@ -319,27 +328,21 @@ class WorkspaceProvider implements IWorkspaceProvider {
 	});
 
 	// If no workspace is provided through the URL, check for config attribute from server
-	const options: IWorkbenchConstructionOptions & { folderUri?: UriComponents, workspaceUri?: UriComponents } = JSON.parse(configElementAttribute);
 	if (!foundWorkspace) {
-		if (options.folderUri) {
-			workspace = { folderUri: URI.revive(options.folderUri) };
-		} else if (options.workspaceUri) {
-			workspace = { workspaceUri: URI.revive(options.workspaceUri) };
+		if (config.folderUri) {
+			workspace = { folderUri: URI.revive(config.folderUri) };
+		} else if (config.workspaceUri) {
+			workspace = { workspaceUri: URI.revive(config.workspaceUri) };
 		} else {
 			workspace = undefined;
 		}
 	}
 
-	options.workspaceProvider = new WorkspaceProvider(workspace, payload);
-	options.urlCallbackProvider = new PollingURLCallbackProvider();
-	options.credentialsProvider = new LocalStorageCredentialsProvider();
-
-	if (Array.isArray(options.staticExtensions)) {
-		options.staticExtensions.forEach(extension => {
-			extension.extensionLocation = URI.revive(extension.extensionLocation);
-		});
-	}
-
 	// Finally create workbench
-	create(document.body, options);
+	create(document.body, {
+		...config,
+		workspaceProvider: new WorkspaceProvider(workspace, payload),
+		urlCallbackProvider: new PollingURLCallbackProvider(),
+		credentialsProvider: new LocalStorageCredentialsProvider()
+	});
 })();
diff --git a/src/vs/code/electron-browser/issue/issueReporterUtil.ts b/src/vs/code/common/issue/issueReporterUtil.ts
similarity index 100%
rename from src/vs/code/electron-browser/issue/issueReporterUtil.ts
rename to src/vs/code/common/issue/issueReporterUtil.ts
diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts
index ffb9fdac5f..381e7ecf8a 100644
--- a/src/vs/code/electron-browser/issue/issueReporterMain.ts
+++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts
@@ -35,7 +35,7 @@ import BaseHtml from 'vs/code/electron-browser/issue/issueReporterPage';
 import { LoggerChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc';
 import { ILogService, getLogLevel } from 'vs/platform/log/common/log';
 import { CodiconLabel } from 'vs/base/browser/ui/codiconLabel/codiconLabel';
-import { normalizeGitHubUrl } from 'vs/code/electron-browser/issue/issueReporterUtil';
+import { normalizeGitHubUrl } from 'vs/code/common/issue/issueReporterUtil';
 import { Button } from 'vs/base/browser/ui/button/button';
 import { SystemInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics';
 import { SpdLogService } from 'vs/platform/log/node/spdlogService';
@@ -345,8 +345,8 @@ export class IssueReporter extends Disposable {
 
 		const showInfoElements = document.getElementsByClassName('showInfo');
 		for (let i = 0; i < showInfoElements.length; i++) {
-			const showInfo = showInfoElements.item(i);
-			showInfo!.addEventListener('click', (e: MouseEvent) => {
+			const showInfo = showInfoElements.item(i)!;
+			(showInfo as HTMLAnchorElement).addEventListener('click', (e: MouseEvent) => {
 				e.preventDefault();
 				const label = (e.target);
 				if (label) {
@@ -432,9 +432,9 @@ export class IssueReporter extends Disposable {
 			sendWorkbenchCommand('workbench.action.reloadWindowWithExtensionsDisabled');
 		});
 
-		this.addEventListener('disableExtensions', 'keydown', (e: KeyboardEvent) => {
+		this.addEventListener('disableExtensions', 'keydown', (e: Event) => {
 			e.stopPropagation();
-			if (e.keyCode === 13 || e.keyCode === 32) {
+			if ((e as KeyboardEvent).keyCode === 13 || (e as KeyboardEvent).keyCode === 32) {
 				sendWorkbenchCommand('workbench.extensions.action.disableAll');
 				sendWorkbenchCommand('workbench.action.reloadWindow');
 			}
@@ -1120,16 +1120,6 @@ export class IssueReporter extends Disposable {
 		if (element) {
 			return element;
 		} else {
-			const error = new Error(`${elementId} not found.`);
-			this.logService.error(error);
-			type IssueReporterGetElementErrorClassification = {
-				message: { classification: 'CallstackOrException', purpose: 'PerformanceAndHealth' };
-			};
-			type IssueReporterGetElementErrorEvent = {
-				message: string;
-			};
-			this.telemetryService.publicLog2('issueReporterGetElementError', { message: error.message });
-
 			return undefined;
 		}
 	}
diff --git a/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts b/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts
index 678b54455e..bf6bdc8add 100644
--- a/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts
+++ b/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts
@@ -5,7 +5,7 @@
 
 import * as assert from 'assert';
 import { IssueReporterModel } from 'vs/code/electron-browser/issue/issueReporterModel';
-import { normalizeGitHubUrl } from 'vs/code/electron-browser/issue/issueReporterUtil';
+import { normalizeGitHubUrl } from 'vs/code/common/issue/issueReporterUtil';
 import { IssueType } from 'vs/platform/issue/node/issue';
 
 suite('IssueReporter', () => {
diff --git a/src/vs/code/electron-browser/processExplorer/media/processExplorer.css b/src/vs/code/electron-browser/processExplorer/media/processExplorer.css
index 3e3acaae00..2d6476c18d 100644
--- a/src/vs/code/electron-browser/processExplorer/media/processExplorer.css
+++ b/src/vs/code/electron-browser/processExplorer/media/processExplorer.css
@@ -29,8 +29,6 @@ body {
 	padding: 0;
 	height: 100%;
 	width: 100%;
-	-webkit-touch-callout: none;
-	-webkit-user-select: none;
 	user-select: none;
 	color: #cccccc;
 }
diff --git a/src/vs/code/electron-browser/proxy/auth.html b/src/vs/code/electron-browser/proxy/auth.html
index a6b932662a..d02876abb9 100644
--- a/src/vs/code/electron-browser/proxy/auth.html
+++ b/src/vs/code/electron-browser/proxy/auth.html
@@ -12,8 +12,6 @@
 			height: 100%;
 			width: 100%;
 			overflow: hidden;
-			-webkit-touch-callout: none;
-			-webkit-user-select: none;
 			user-select: none;
 		}
 
@@ -117,4 +115,4 @@
 
 
 
-
\ No newline at end of file
+
diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts
index 6b2be413a4..ad4689db44 100644
--- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts
+++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts
@@ -50,8 +50,8 @@ import { IFileService } from 'vs/platform/files/common/files';
 import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider';
 import { Schemas } from 'vs/base/common/network';
 import { IProductService } from 'vs/platform/product/common/productService';
-import { IUserDataSyncService, IUserDataSyncStoreService, ISettingsMergeService, registerConfiguration, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync';
-import { UserDataSyncService, UserDataAutoSync } from 'vs/platform/userDataSync/common/userDataSyncService';
+import { IUserDataSyncService, IUserDataSyncStoreService, ISettingsMergeService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync';
+import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
 import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
 import { UserDataSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc';
 import { SettingsMergeChannelClient } from 'vs/platform/userDataSync/common/settingsSyncIpc';
@@ -59,10 +59,12 @@ import { IElectronService } from 'vs/platform/electron/node/electron';
 import { LoggerService } from 'vs/platform/log/node/loggerService';
 import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog';
 import { IAuthTokenService } from 'vs/platform/auth/common/auth';
-import { AuthTokenService } from 'vs/platform/auth/common/authTokenService';
+import { AuthTokenService } from 'vs/platform/auth/electron-browser/authTokenService';
 import { AuthTokenChannel } from 'vs/platform/auth/common/authTokenIpc';
 import { ICredentialsService } from 'vs/platform/credentials/common/credentials';
 import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService';
+import { UserDataSyncUtilServiceClient } from 'vs/platform/userDataSync/common/keybindingsSyncIpc';
+import { UserDataAutoSync } from 'vs/platform/userDataSync/electron-browser/userDataAutoSync';
 
 export interface ISharedProcessConfiguration {
 	readonly machineId: string;
@@ -186,6 +188,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
 		services.set(IUserDataSyncLogService, new SyncDescriptor(UserDataSyncLogService));
 		const settingsMergeChannel = server.getChannel('settingsMerge', activeWindowRouter);
 		services.set(ISettingsMergeService, new SettingsMergeChannelClient(settingsMergeChannel));
+		services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(server.getChannel('userDataSyncUtil', activeWindowRouter)));
 		services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService));
 		services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService));
 		registerConfiguration();
diff --git a/src/vs/code/electron-browser/workbench/workbench.js b/src/vs/code/electron-browser/workbench/workbench.js
index e275ba1b6d..604183394d 100644
--- a/src/vs/code/electron-browser/workbench/workbench.js
+++ b/src/vs/code/electron-browser/workbench/workbench.js
@@ -33,17 +33,17 @@ bootstrapWindow.load([
 			return require('vs/workbench/electron-browser/desktop.main').main(configuration);
 		});
 	}, {
-		removeDeveloperKeybindingsAfterLoad: true,
-		canModifyDOM: function (windowConfig) {
-			showPartsSplash(windowConfig);
-		},
-		beforeLoaderConfig: function (windowConfig, loaderConfig) {
-			loaderConfig.recordStats = true;
-		},
-		beforeRequire: function () {
-			perf.mark('willLoadWorkbenchMain');
-		}
-	});
+	removeDeveloperKeybindingsAfterLoad: true,
+	canModifyDOM: function (windowConfig) {
+		showPartsSplash(windowConfig);
+	},
+	beforeLoaderConfig: function (windowConfig, loaderConfig) {
+		loaderConfig.recordStats = true;
+	},
+	beforeRequire: function () {
+		perf.mark('willLoadWorkbenchMain');
+	}
+});
 
 /**
  * @param {{
@@ -84,7 +84,7 @@ function showPartsSplash(configuration) {
 	style.className = 'initialShellColors';
 	document.head.appendChild(style);
 	document.body.className = baseTheme;
-	style.innerHTML = `body { background-color: ${shellBackground}; color: ${shellForeground}; }`;
+	style.innerHTML = `body { background-color: ${shellBackground}; color: ${shellForeground}; margin: 0; padding: 0; }`;
 
 	if (data && data.layoutInfo) {
 		// restore parts if possible (we might not always store layout info)
@@ -92,6 +92,18 @@ function showPartsSplash(configuration) {
 		const splash = document.createElement('div');
 		splash.id = id;
 
+		if (layoutInfo.windowBorder) {
+			splash.style.position = 'relative';
+			splash.style.height = 'calc(100vh - 2px)';
+			splash.style.width = 'calc(100vw - 2px)';
+			splash.style.border = '1px solid var(--window-border-color)';
+			splash.style.setProperty('--window-border-color', colorInfo.windowBorder);
+
+			if (layoutInfo.windowBorderRadius) {
+				splash.style.borderRadius = layoutInfo.windowBorderRadius;
+			}
+		}
+
 		// ensure there is enough space
 		layoutInfo.sideBarWidth = Math.min(layoutInfo.sideBarWidth, window.innerWidth - (layoutInfo.activityBarWidth + layoutInfo.editorPartMinWidth));
 
diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts
index 8958936e2d..c715f4d517 100644
--- a/src/vs/code/electron-main/app.ts
+++ b/src/vs/code/electron-main/app.ts
@@ -79,6 +79,7 @@ import { ISharedProcessMainService, SharedProcessMainService } from 'vs/platform
 import { assign } from 'vs/base/common/objects';
 import { IDialogMainService, DialogMainService } from 'vs/platform/dialogs/electron-main/dialogs';
 import { withNullAsUndefined } from 'vs/base/common/types';
+import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv';
 
 export class CodeApplication extends Disposable {
 
@@ -574,14 +575,15 @@ export class CodeApplication extends Disposable {
 		electronIpcServer.registerChannel('logger', loggerChannel);
 		sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel));
 
+		const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService);
+
 		// ExtensionHost Debug broadcast service
-		electronIpcServer.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ExtensionHostDebugBroadcastChannel());
+		electronIpcServer.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ElectronExtensionHostDebugBroadcastChannel(windowsMainService));
 
 		// Signal phase: ready (services set)
 		this.lifecycleMainService.phase = LifecycleMainPhase.Ready;
 
 		// Propagate to clients
-		const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService);
 		this.dialogMainService = accessor.get(IDialogMainService);
 
 		// Create a URL handler to open file URIs in the active window
@@ -637,7 +639,7 @@ export class CodeApplication extends Disposable {
 		// Watch Electron URLs and forward them to the UrlService
 		const args = this.environmentService.args;
 		const urls = args['open-url'] ? args._urls : [];
-		const urlListener = new ElectronURLListener(urls || [], urlService, windowsMainService);
+		const urlListener = new ElectronURLListener(urls || [], urlService, windowsMainService, this.environmentService);
 		this._register(urlListener);
 
 		// Open our first window
@@ -723,3 +725,28 @@ export class CodeApplication extends Disposable {
 		});
 	}
 }
+
+class ElectronExtensionHostDebugBroadcastChannel extends ExtensionHostDebugBroadcastChannel {
+
+	constructor(private windowsMainService: IWindowsMainService) {
+		super();
+	}
+
+	call(ctx: TContext, command: string, arg?: any): Promise {
+		if (command === 'openExtensionDevelopmentHostWindow') {
+			const env = arg[1];
+			const pargs = parseArgs(arg[0], OPTIONS);
+			const extDevPaths = pargs.extensionDevelopmentPath;
+			if (extDevPaths) {
+				this.windowsMainService.openExtensionDevelopmentHostWindow(extDevPaths, {
+					context: OpenContext.API,
+					cli: pargs,
+					userEnv: Object.keys(env).length > 0 ? env : undefined
+				});
+			}
+			return Promise.resolve();
+		} else {
+			return super.call(ctx, command, arg);
+		}
+	}
+}
diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts
index cc50acfc4c..79f9874681 100644
--- a/src/vs/code/electron-main/main.ts
+++ b/src/vs/code/electron-main/main.ts
@@ -8,8 +8,8 @@ import { app, dialog } from 'electron';
 import { assign } from 'vs/base/common/objects';
 import { isWindows, IProcessEnvironment, isMacintosh } from 'vs/base/common/platform';
 import product from 'vs/platform/product/common/product';
-import { parseMainProcessArgv } from 'vs/platform/environment/node/argvHelper';
-import { addArg, createWaitMarkerFile } from 'vs/platform/environment/node/argv';
+import { parseMainProcessArgv, addArg } from 'vs/platform/environment/node/argvHelper';
+import { createWaitMarkerFile } from 'vs/platform/environment/node/waitMarkerFile';
 import { mkdirp } from 'vs/base/node/pfs';
 import { validatePaths } from 'vs/code/node/paths';
 import { LifecycleMainService, ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
diff --git a/src/vs/code/electron-main/sharedProcess.ts b/src/vs/code/electron-main/sharedProcess.ts
index 0f5f7c3028..6ebf353f02 100644
--- a/src/vs/code/electron-main/sharedProcess.ts
+++ b/src/vs/code/electron-main/sharedProcess.ts
@@ -6,13 +6,14 @@
 import { assign } from 'vs/base/common/objects';
 import { memoize } from 'vs/base/common/decorators';
 import { IEnvironmentService } from 'vs/platform/environment/common/environment';
-import { BrowserWindow, ipcMain } from 'electron';
+import { BrowserWindow, ipcMain, WebContents, Event as ElectronEvent } from 'electron';
 import { ISharedProcess } from 'vs/platform/ipc/electron-main/sharedProcessMainService';
 import { Barrier } from 'vs/base/common/async';
 import { ILogService } from 'vs/platform/log/common/log';
 import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
 import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService';
 import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
+import { Event } from 'vs/base/common/event';
 
 export class SharedProcess implements ISharedProcess {
 
@@ -53,7 +54,7 @@ export class SharedProcess implements ISharedProcess {
 		this.window.loadURL(url);
 
 		// Prevent the window from dying
-		const onClose = (e: Event) => {
+		const onClose = (e: ElectronEvent) => {
 			this.logService.trace('SharedProcess#close prevented');
 
 			// We never allow to close the shared process unless we get explicitly disposed()
@@ -97,7 +98,8 @@ export class SharedProcess implements ISharedProcess {
 		});
 
 		return new Promise(c => {
-			ipcMain.once('handshake:hello', ({ sender }: { sender: any }) => {
+			const onHello = Event.once(Event.fromNodeEventEmitter(ipcMain, 'handshake:hello', ({ sender }: { sender: WebContents }) => sender));
+			disposables.add(onHello(sender => {
 				sender.send('handshake:hey there', {
 					sharedIPCHandle: this.environmentService.sharedIPCHandle,
 					args: this.environmentService.args,
@@ -106,7 +108,7 @@ export class SharedProcess implements ISharedProcess {
 
 				disposables.add(toDisposable(() => sender.send('handshake:goodbye')));
 				ipcMain.once('handshake:im ready', () => c(undefined));
-			});
+			}));
 		});
 	}
 
diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts
index 05b741cae2..e264267e50 100644
--- a/src/vs/code/electron-main/window.ts
+++ b/src/vs/code/electron-main/window.ts
@@ -31,6 +31,7 @@ import { IFileService } from 'vs/platform/files/common/files';
 import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
 import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs';
 import { mnemonicButtonLabel } from 'vs/base/common/labels';
+import { ThemeIcon } from 'vs/platform/theme/common/themeService';
 
 const RUN_TEXTMATE_IN_WORKER = false;
 
@@ -60,7 +61,7 @@ const enum WindowError {
 export class CodeWindow extends Disposable implements ICodeWindow {
 
 	private static readonly MIN_WIDTH = 600;
-	private static readonly MIN_HEIGHT = 600;
+	private static readonly MIN_HEIGHT = 270;
 
 	private static readonly MAX_URL_LENGTH = 2 * 1024 * 1024; // https://cs.chromium.org/chromium/src/url/url_constants.cc?l=32
 
@@ -438,15 +439,8 @@ export class CodeWindow extends Disposable implements ICodeWindow {
 
 		// Inject headers when requests are incoming
 		const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*'];
-		this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details, cb) => {
-			this.marketplaceHeadersPromise.then(headers => {
-				const requestHeaders = objects.assign(details.requestHeaders, headers) as { [key: string]: string | undefined };
-				if (!this.configurationService.getValue('extensions.disableExperimentalAzureSearch')) {
-					requestHeaders['Cookie'] = `${requestHeaders['Cookie'] ? requestHeaders['Cookie'] + ';' : ''}EnableExternalSearchForVSCode=true`;
-				}
-				cb({ cancel: false, requestHeaders });
-			});
-		});
+		this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details, cb) =>
+			this.marketplaceHeadersPromise.then(headers => cb({ cancel: false, requestHeaders: objects.assign(details.requestHeaders, headers) as { [key: string]: string | undefined } })));
 	}
 
 	private onWindowError(error: WindowError): void {
@@ -635,6 +629,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
 
 		// Set window ID
 		windowConfiguration.windowId = this._win.id;
+		windowConfiguration.sessionId = `window:${this._win.id}`;
 		windowConfiguration.logLevel = this.logService.getLevel();
 
 		// Set zoomlevel
@@ -657,7 +652,6 @@ export class CodeWindow extends Disposable implements ICodeWindow {
 
 		// Title style related
 		windowConfiguration.maximized = this._win.isMaximized();
-		windowConfiguration.frameless = this.hasHiddenTitleBarStyle && !isMacintosh;
 
 		// Dump Perf Counters
 		windowConfiguration.perfEntries = perf.exportEntries();
@@ -1096,8 +1090,8 @@ export class CodeWindow extends Disposable implements ICodeWindow {
 	private createTouchBarGroupSegments(items: ISerializableCommandAction[] = []): ITouchBarSegment[] {
 		const segments: ITouchBarSegment[] = items.map(item => {
 			let icon: NativeImage | undefined;
-			if (item.iconLocation && item.iconLocation.dark.scheme === 'file') {
-				icon = nativeImage.createFromPath(URI.revive(item.iconLocation.dark).fsPath);
+			if (item.icon && !ThemeIcon.isThemeIcon(item.icon) && item.icon?.dark?.scheme === 'file') {
+				icon = nativeImage.createFromPath(URI.revive(item.icon.dark).fsPath);
 				if (icon.isEmpty()) {
 					icon = undefined;
 				}
diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts
index 0ba64781a4..f523559052 100644
--- a/src/vs/code/node/cli.ts
+++ b/src/vs/code/node/cli.ts
@@ -6,8 +6,9 @@
 import * as os from 'os';
 import * as fs from 'fs';
 import { spawn, ChildProcess, SpawnOptions } from 'child_process';
-import { buildHelpMessage, buildVersionMessage, addArg, createWaitMarkerFile, OPTIONS } from 'vs/platform/environment/node/argv';
-import { parseCLIProcessArgv } from 'vs/platform/environment/node/argvHelper';
+import { buildHelpMessage, buildVersionMessage, OPTIONS } from 'vs/platform/environment/node/argv';
+import { parseCLIProcessArgv, addArg } from 'vs/platform/environment/node/argvHelper';
+import { createWaitMarkerFile } from 'vs/platform/environment/node/waitMarkerFile';
 import { ParsedArgs } from 'vs/platform/environment/common/environment';
 import product from 'vs/platform/product/common/product';
 import * as paths from 'vs/base/common/path';
@@ -134,8 +135,8 @@ export async function main(argv: string[]): Promise {
 			env['ELECTRON_ENABLE_LOGGING'] = '1';
 
 			processCallbacks.push(async child => {
-				child.stdout.on('data', (data: Buffer) => console.log(data.toString('utf8').trim()));
-				child.stderr.on('data', (data: Buffer) => console.log(data.toString('utf8').trim()));
+				child.stdout!.on('data', (data: Buffer) => console.log(data.toString('utf8').trim()));
+				child.stderr!.on('data', (data: Buffer) => console.log(data.toString('utf8').trim()));
 
 				await new Promise(c => child.once('exit', () => c()));
 			});
diff --git a/src/vs/code/node/paths.ts b/src/vs/code/node/paths.ts
index d308666733..b1084eb02d 100644
--- a/src/vs/code/node/paths.ts
+++ b/src/vs/code/node/paths.ts
@@ -25,9 +25,6 @@ export function validatePaths(args: ParsedArgs): ParsedArgs {
 		args._ = paths;
 	}
 
-	// Update environment
-	args.diff = args.diff && args._.length === 2;
-
 	return args;
 }
 
diff --git a/src/vs/code/test/electron-main/nativeHelpers.test.ts b/src/vs/code/test/electron-main/nativeHelpers.test.ts
index 2d7a1412c5..d35f0bb76b 100644
--- a/src/vs/code/test/electron-main/nativeHelpers.test.ts
+++ b/src/vs/code/test/electron-main/nativeHelpers.test.ts
@@ -28,7 +28,9 @@ suite('Windows Native Helpers', () => {
 	});
 
 	test('vscode-windows-ca-certs', async () => {
-		const windowsCerts = await import('vscode-windows-ca-certs');
+		const windowsCerts = await new Promise((resolve, reject) => {
+			require(['vscode-windows-ca-certs'], resolve, reject);
+		});
 		assert.ok(windowsCerts, 'Unable to load vscode-windows-ca-certs dependency.');
 	});
 
diff --git a/src/vs/code/test/node/argv.test.ts b/src/vs/code/test/node/argv.test.ts
index 0dee81f772..18155d59be 100644
--- a/src/vs/code/test/node/argv.test.ts
+++ b/src/vs/code/test/node/argv.test.ts
@@ -3,7 +3,8 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 import * as assert from 'assert';
-import { formatOptions, Option, addArg } from 'vs/platform/environment/node/argv';
+import { formatOptions, Option } from 'vs/platform/environment/node/argv';
+import { addArg } from 'vs/platform/environment/node/argvHelper';
 
 suite('formatOptions', () => {
 
diff --git a/src/vs/editor/browser/config/charWidthReader.ts b/src/vs/editor/browser/config/charWidthReader.ts
index 49900650f9..b45846e017 100644
--- a/src/vs/editor/browser/config/charWidthReader.ts
+++ b/src/vs/editor/browser/config/charWidthReader.ts
@@ -124,7 +124,7 @@ class DomCharWidthReader {
 
 	private static _render(testElement: HTMLElement, request: CharWidthRequest): void {
 		if (request.chr === ' ') {
-			let htmlString = ' ';
+			let htmlString = ' ';
 			// Repeat character 256 (2^8) times
 			for (let i = 0; i < 8; i++) {
 				htmlString += htmlString;
diff --git a/src/vs/editor/browser/controller/coreCommands.ts b/src/vs/editor/browser/controller/coreCommands.ts
index f9779cc326..41d5b451ae 100644
--- a/src/vs/editor/browser/controller/coreCommands.ts
+++ b/src/vs/editor/browser/controller/coreCommands.ts
@@ -1753,11 +1753,9 @@ registerCommand(new EditorOrNativeTextInputCommand({
 		kbExpr: null,
 		primary: KeyMod.CtrlCmd | KeyCode.KEY_A
 	},
-	menubarOpts: {
-		// {{SQL CARBON EDIT}} - Put this in the edit menu since we disabled the selection menu
-		menuId: MenuId.MenubarEditMenu,
-		group: '4_find_global',
-		// {{SQL CARBON EDIT}} - End
+	menuOpts: {
+		menuId: MenuId.MenubarEditMenu, // {{SQL CARBON EDIT}} - Put this in the edit menu since we disabled the selection menu
+		group: '4_find_global', // {{SQL CARBON EDIT}} - Put this in the edit menu since we disabled the selection menu
 		title: nls.localize({ key: 'miSelectAll', comment: ['&& denotes a mnemonic'] }, "&&Select All"),
 		order: 1
 	}
@@ -1773,7 +1771,7 @@ registerCommand(new EditorOrNativeTextInputCommand({
 		kbExpr: EditorContextKeys.textInputFocus,
 		primary: KeyMod.CtrlCmd | KeyCode.KEY_Z
 	},
-	menubarOpts: {
+	menuOpts: {
 		menuId: MenuId.MenubarEditMenu,
 		group: '1_do',
 		title: nls.localize({ key: 'miUndo', comment: ['&& denotes a mnemonic'] }, "&&Undo"),
@@ -1794,7 +1792,7 @@ registerCommand(new EditorOrNativeTextInputCommand({
 		secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z],
 		mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z }
 	},
-	menubarOpts: {
+	menuOpts: {
 		menuId: MenuId.MenubarEditMenu,
 		group: '1_do',
 		title: nls.localize({ key: 'miRedo', comment: ['&& denotes a mnemonic'] }, "&&Redo"),
diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts
index daf187e664..6781bbf8e4 100644
--- a/src/vs/editor/browser/controller/mouseHandler.ts
+++ b/src/vs/editor/browser/controller/mouseHandler.ts
@@ -17,18 +17,17 @@ import { IViewCursorRenderData } from 'vs/editor/browser/viewParts/viewCursors/v
 import { EditorZoom } from 'vs/editor/common/config/editorZoom';
 import { Position } from 'vs/editor/common/core/position';
 import { Selection } from 'vs/editor/common/core/selection';
-import { HorizontalRange } from 'vs/editor/common/view/renderingContext';
+import { HorizontalPosition } from 'vs/editor/common/view/renderingContext';
 import { ViewContext } from 'vs/editor/common/view/viewContext';
 import * as viewEvents from 'vs/editor/common/view/viewEvents';
 import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler';
 import { EditorOption } from 'vs/editor/common/config/editorOptions';
 
-
 /**
  * Merges mouse events when mouse move events are throttled
  */
-function createMouseMoveEventMerger(mouseTargetFactory: MouseTargetFactory | null) {
-	return function (lastEvent: EditorMouseEvent, currentEvent: EditorMouseEvent): EditorMouseEvent {
+export function createMouseMoveEventMerger(mouseTargetFactory: MouseTargetFactory | null) {
+	return function (lastEvent: EditorMouseEvent | null, currentEvent: EditorMouseEvent): EditorMouseEvent {
 		let targetIsWidget = false;
 		if (mouseTargetFactory) {
 			targetIsWidget = mouseTargetFactory.mouseTargetIsWidget(currentEvent);
@@ -59,7 +58,7 @@ export interface IPointerHandlerHelper {
 	 */
 	getPositionFromDOMInfo(spanNode: HTMLElement, offset: number): Position | null;
 
-	visibleRangeForPosition2(lineNumber: number, column: number): HorizontalRange | null;
+	visibleRangeForPosition(lineNumber: number, column: number): HorizontalPosition | null;
 	getLineWidth(lineNumber: number): number;
 }
 
@@ -72,8 +71,7 @@ export class MouseHandler extends ViewEventHandler {
 	protected viewHelper: IPointerHandlerHelper;
 	protected mouseTargetFactory: MouseTargetFactory;
 	private readonly _asyncFocus: RunOnceScheduler;
-
-	private readonly _mouseDownOperation: MouseDownOperation;
+	protected readonly _mouseDownOperation: MouseDownOperation;
 	private lastMouseLeaveTime: number;
 
 	constructor(context: ViewContext, viewController: ViewController, viewHelper: IPointerHandlerHelper) {
@@ -180,7 +178,7 @@ export class MouseHandler extends ViewEventHandler {
 		});
 	}
 
-	private _onMouseMove(e: EditorMouseEvent): void {
+	public _onMouseMove(e: EditorMouseEvent): void {
 		if (this._mouseDownOperation.isActive()) {
 			// In selection/drag operation
 			return;
@@ -197,7 +195,7 @@ export class MouseHandler extends ViewEventHandler {
 		});
 	}
 
-	private _onMouseLeave(e: EditorMouseEvent): void {
+	public _onMouseLeave(e: EditorMouseEvent): void {
 		this.lastMouseLeaveTime = (new Date()).getTime();
 		this.viewController.emitMouseLeave({
 			event: e,
diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts
index bbd0dcbbae..51d3080b2b 100644
--- a/src/vs/editor/browser/controller/mouseTarget.ts
+++ b/src/vs/editor/browser/controller/mouseTarget.ts
@@ -13,7 +13,7 @@ import { IViewCursorRenderData } from 'vs/editor/browser/viewParts/viewCursors/v
 import { EditorLayoutInfo, EditorOption } from 'vs/editor/common/config/editorOptions';
 import { Position } from 'vs/editor/common/core/position';
 import { Range as EditorRange } from 'vs/editor/common/core/range';
-import { HorizontalRange } from 'vs/editor/common/view/renderingContext';
+import { HorizontalPosition } from 'vs/editor/common/view/renderingContext';
 import { ViewContext } from 'vs/editor/common/view/viewContext';
 import { IViewModel } from 'vs/editor/common/viewModel/viewModel';
 import { CursorColumns } from 'vs/editor/common/controller/cursorCommon';
@@ -346,8 +346,8 @@ export class HitTestContext {
 		return this._viewHelper.getLineWidth(lineNumber);
 	}
 
-	public visibleRangeForPosition2(lineNumber: number, column: number): HorizontalRange | null {
-		return this._viewHelper.visibleRangeForPosition2(lineNumber, column);
+	public visibleRangeForPosition(lineNumber: number, column: number): HorizontalPosition | null {
+		return this._viewHelper.visibleRangeForPosition(lineNumber, column);
 	}
 
 	public getPositionFromDOMInfo(spanNode: HTMLElement, offset: number): Position | null {
@@ -743,7 +743,7 @@ export class MouseTargetFactory {
 			return request.fulfill(MouseTargetType.CONTENT_EMPTY, pos, undefined, detail);
 		}
 
-		const visibleRange = ctx.visibleRangeForPosition2(lineNumber, column);
+		const visibleRange = ctx.visibleRangeForPosition(lineNumber, column);
 
 		if (!visibleRange) {
 			return request.fulfill(MouseTargetType.UNKNOWN, pos);
@@ -761,14 +761,14 @@ export class MouseTargetFactory {
 		const points: OffsetColumn[] = [];
 		points.push({ offset: visibleRange.left, column: column });
 		if (column > 1) {
-			const visibleRange = ctx.visibleRangeForPosition2(lineNumber, column - 1);
+			const visibleRange = ctx.visibleRangeForPosition(lineNumber, column - 1);
 			if (visibleRange) {
 				points.push({ offset: visibleRange.left, column: column - 1 });
 			}
 		}
 		const lineMaxColumn = ctx.model.getLineMaxColumn(lineNumber);
 		if (column < lineMaxColumn) {
-			const visibleRange = ctx.visibleRangeForPosition2(lineNumber, column + 1);
+			const visibleRange = ctx.visibleRangeForPosition(lineNumber, column + 1);
 			if (visibleRange) {
 				points.push({ offset: visibleRange.left, column: column + 1 });
 			}
diff --git a/src/vs/editor/browser/controller/pointerHandler.ts b/src/vs/editor/browser/controller/pointerHandler.ts
index 7b87db0424..eb9f1de262 100644
--- a/src/vs/editor/browser/controller/pointerHandler.ts
+++ b/src/vs/editor/browser/controller/pointerHandler.ts
@@ -4,20 +4,22 @@
  *--------------------------------------------------------------------------------------------*/
 
 import * as dom from 'vs/base/browser/dom';
+import * as platform from 'vs/base/common/platform';
 import { EventType, Gesture, GestureEvent } from 'vs/base/browser/touch';
 import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
-import { IPointerHandlerHelper, MouseHandler } from 'vs/editor/browser/controller/mouseHandler';
+import { IPointerHandlerHelper, MouseHandler, createMouseMoveEventMerger } from 'vs/editor/browser/controller/mouseHandler';
 import { IMouseTarget } from 'vs/editor/browser/editorBrowser';
-import { EditorMouseEvent } from 'vs/editor/browser/editorDom';
+import { EditorMouseEvent, EditorPointerEventFactory } from 'vs/editor/browser/editorDom';
 import { ViewController } from 'vs/editor/browser/view/viewController';
 import { ViewContext } from 'vs/editor/common/view/viewContext';
+import { BrowserFeatures } from 'vs/base/browser/canIUse';
 
 interface IThrottledGestureEvent {
 	translationX: number;
 	translationY: number;
 }
 
-function gestureChangeEventMerger(lastEvent: IThrottledGestureEvent, currentEvent: MSGestureEvent): IThrottledGestureEvent {
+function gestureChangeEventMerger(lastEvent: IThrottledGestureEvent | null, currentEvent: MSGestureEvent): IThrottledGestureEvent {
 	const r = {
 		translationY: currentEvent.translationY,
 		translationX: currentEvent.translationX
@@ -52,7 +54,7 @@ class MsPointerHandler extends MouseHandler implements IDisposable {
 				const penGesture = new MSGesture();
 				touchGesture.target = this.viewHelper.linesContentDomNode;
 				penGesture.target = this.viewHelper.linesContentDomNode;
-				this.viewHelper.linesContentDomNode.addEventListener('MSPointerDown', (e: MSPointerEvent) => {
+				this.viewHelper.linesContentDomNode.addEventListener('MSPointerDown', (e: MSPointerEvent) => {
 					// Circumvent IE11 breaking change in e.pointerType & TypeScript's stale definitions
 					const pointerType = e.pointerType;
 					if (pointerType === ((e).MSPOINTER_TYPE_MOUSE || 'mouse')) {
@@ -66,7 +68,7 @@ class MsPointerHandler extends MouseHandler implements IDisposable {
 						penGesture.addPointer(e.pointerId);
 					}
 				});
-				this._register(dom.addDisposableThrottledListener(this.viewHelper.linesContentDomNode, 'MSGestureChange', (e) => this._onGestureChange(e), gestureChangeEventMerger));
+				this._register(dom.addDisposableThrottledListener(this.viewHelper.linesContentDomNode, 'MSGestureChange', (e) => this._onGestureChange(e), gestureChangeEventMerger));
 				this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, 'MSGestureTap', (e) => this._onCaptureGestureTap(e), true));
 			}
 		}, 100);
@@ -131,7 +133,7 @@ class StandardPointerHandler extends MouseHandler implements IDisposable {
 				const penGesture = new MSGesture();
 				touchGesture.target = this.viewHelper.linesContentDomNode;
 				penGesture.target = this.viewHelper.linesContentDomNode;
-				this.viewHelper.linesContentDomNode.addEventListener('pointerdown', (e: MSPointerEvent) => {
+				this.viewHelper.linesContentDomNode.addEventListener('pointerdown', (e: PointerEvent) => {
 					const pointerType = e.pointerType;
 					if (pointerType === 'mouse') {
 						this._lastPointerType = 'mouse';
@@ -144,7 +146,7 @@ class StandardPointerHandler extends MouseHandler implements IDisposable {
 						penGesture.addPointer(e.pointerId);
 					}
 				});
-				this._register(dom.addDisposableThrottledListener(this.viewHelper.linesContentDomNode, 'MSGestureChange', (e) => this._onGestureChange(e), gestureChangeEventMerger));
+				this._register(dom.addDisposableThrottledListener(this.viewHelper.linesContentDomNode, 'MSGestureChange', (e) => this._onGestureChange(e), gestureChangeEventMerger));
 				this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, 'MSGestureTap', (e) => this._onCaptureGestureTap(e), true));
 			}
 		}, 100);
@@ -185,6 +187,87 @@ class StandardPointerHandler extends MouseHandler implements IDisposable {
 	}
 }
 
+/**
+ * Currently only tested on iOS 13/ iPadOS.
+ */
+export class PointerEventHandler extends MouseHandler {
+	private _lastPointerType: string;
+	constructor(context: ViewContext, viewController: ViewController, viewHelper: IPointerHandlerHelper) {
+		super(context, viewController, viewHelper);
+
+		this._register(Gesture.addTarget(this.viewHelper.linesContentDomNode));
+		this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, EventType.Tap, (e) => this.onTap(e)));
+		this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, EventType.Change, (e) => this.onChange(e)));
+		this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, EventType.Contextmenu, (e: MouseEvent) => this._onContextMenu(new EditorMouseEvent(e, this.viewHelper.viewDomNode), false)));
+
+		this._lastPointerType = 'mouse';
+
+		this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, 'pointerdown', (e: any) => {
+			const pointerType = e.pointerType;
+			if (pointerType === 'mouse') {
+				this._lastPointerType = 'mouse';
+				return;
+			} else if (pointerType === 'touch') {
+				this._lastPointerType = 'touch';
+			} else {
+				this._lastPointerType = 'pen';
+			}
+		}));
+
+		// PonterEvents
+		const pointerEvents = new EditorPointerEventFactory(this.viewHelper.viewDomNode);
+
+		this._register(pointerEvents.onPointerMoveThrottled(this.viewHelper.viewDomNode,
+			(e) => this._onMouseMove(e),
+			createMouseMoveEventMerger(this.mouseTargetFactory), MouseHandler.MOUSE_MOVE_MINIMUM_TIME));
+		this._register(pointerEvents.onPointerUp(this.viewHelper.viewDomNode, (e) => this._onMouseUp(e)));
+		this._register(pointerEvents.onPointerLeave(this.viewHelper.viewDomNode, (e) => this._onMouseLeave(e)));
+		this._register(pointerEvents.onPointerDown(this.viewHelper.viewDomNode, (e) => this._onMouseDown(e)));
+	}
+
+	private onTap(event: GestureEvent): void {
+		if (!event.initialTarget || !this.viewHelper.linesContentDomNode.contains(event.initialTarget)) {
+			return;
+		}
+
+		event.preventDefault();
+		this.viewHelper.focusTextArea();
+		const target = this._createMouseTarget(new EditorMouseEvent(event, this.viewHelper.viewDomNode), false);
+
+		if (target.position) {
+			// this.viewController.moveTo(target.position);
+			this.viewController.dispatchMouse({
+				position: target.position,
+				mouseColumn: target.position.column,
+				startedOnLineNumbers: false,
+				mouseDownCount: event.tapCount,
+				inSelectionMode: false,
+				altKey: false,
+				ctrlKey: false,
+				metaKey: false,
+				shiftKey: false,
+
+				leftButton: false,
+				middleButton: false,
+			});
+		}
+	}
+
+	private onChange(e: GestureEvent): void {
+		if (this._lastPointerType === 'touch') {
+			this._context.viewLayout.deltaScrollNow(-e.translationX, -e.translationY);
+		}
+	}
+
+	public _onMouseDown(e: EditorMouseEvent): void {
+		if (e.target && this.viewHelper.linesContentDomNode.contains(e.target) && this._lastPointerType === 'touch') {
+			return;
+		}
+
+		super._onMouseDown(e);
+	}
+}
+
 class TouchHandler extends MouseHandler {
 
 	constructor(context: ViewContext, viewController: ViewController, viewHelper: IPointerHandlerHelper) {
@@ -221,6 +304,8 @@ export class PointerHandler extends Disposable {
 		super();
 		if (window.navigator.msPointerEnabled) {
 			this.handler = this._register(new MsPointerHandler(context, viewController, viewHelper));
+		} else if ((platform.isIOS && BrowserFeatures.pointerEvents)) {
+			this.handler = this._register(new PointerEventHandler(context, viewController, viewHelper));
 		} else if ((window).TouchEvent) {
 			this.handler = this._register(new TouchHandler(context, viewController, viewHelper));
 		} else if (window.navigator.pointerEnabled || (window).PointerEvent) {
diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts
index d0ee958956..c5f36bae59 100644
--- a/src/vs/editor/browser/controller/textAreaHandler.ts
+++ b/src/vs/editor/browser/controller/textAreaHandler.ts
@@ -25,13 +25,13 @@ import { Range } from 'vs/editor/common/core/range';
 import { Selection } from 'vs/editor/common/core/selection';
 import { ScrollType } from 'vs/editor/common/editorCommon';
 import { EndOfLinePreference } from 'vs/editor/common/model';
-import { HorizontalRange, RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext';
+import { RenderingContext, RestrictedRenderingContext, HorizontalPosition } from 'vs/editor/common/view/renderingContext';
 import { ViewContext } from 'vs/editor/common/view/viewContext';
 import * as viewEvents from 'vs/editor/common/view/viewEvents';
 import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
 
 export interface ITextAreaHandlerHelper {
-	visibleRangeForPositionRelativeToEditor(lineNumber: number, column: number): HorizontalRange | null;
+	visibleRangeForPositionRelativeToEditor(lineNumber: number, column: number): HorizontalPosition | null;
 }
 
 class VisibleTextAreaData {
@@ -62,6 +62,7 @@ export class TextAreaHandler extends ViewPart {
 	private _scrollTop: number;
 
 	private _accessibilitySupport: AccessibilitySupport;
+	private _accessibilityPageSize: number;
 	private _contentLeft: number;
 	private _contentWidth: number;
 	private _contentHeight: number;
@@ -92,6 +93,7 @@ export class TextAreaHandler extends ViewPart {
 		const layoutInfo = options.get(EditorOption.layoutInfo);
 
 		this._accessibilitySupport = options.get(EditorOption.accessibilitySupport);
+		this._accessibilityPageSize = options.get(EditorOption.accessibilityPageSize);
 		this._contentLeft = layoutInfo.contentLeft;
 		this._contentWidth = layoutInfo.contentWidth;
 		this._contentHeight = layoutInfo.contentHeight;
@@ -189,7 +191,7 @@ export class TextAreaHandler extends ViewPart {
 					return TextAreaState.EMPTY;
 				}
 
-				return PagedScreenReaderStrategy.fromEditorSelection(currentState, simpleModel, this._selections[0], this._accessibilitySupport === AccessibilitySupport.Unknown);
+				return PagedScreenReaderStrategy.fromEditorSelection(currentState, simpleModel, this._selections[0], this._accessibilityPageSize, this._accessibilitySupport === AccessibilitySupport.Unknown);
 			},
 
 			deduceModelPosition: (viewAnchorPosition: Position, deltaOffset: number, lineFeedCnt: number): Position => {
@@ -341,6 +343,7 @@ export class TextAreaHandler extends ViewPart {
 		const layoutInfo = options.get(EditorOption.layoutInfo);
 
 		this._accessibilitySupport = options.get(EditorOption.accessibilitySupport);
+		this._accessibilityPageSize = options.get(EditorOption.accessibilityPageSize);
 		this._contentLeft = layoutInfo.contentLeft;
 		this._contentWidth = layoutInfo.contentWidth;
 		this._contentHeight = layoutInfo.contentHeight;
@@ -406,9 +409,13 @@ export class TextAreaHandler extends ViewPart {
 		this._textAreaInput.focusTextArea();
 	}
 
+	public refreshFocusState() {
+		this._textAreaInput.refreshFocusState();
+	}
+
 	// --- end view API
 
-	private _primaryCursorVisibleRange: HorizontalRange | null = null;
+	private _primaryCursorVisibleRange: HorizontalPosition | null = null;
 
 	public prepareRender(ctx: RenderingContext): void {
 		const primaryCursorPosition = new Position(this._selections[0].positionLineNumber, this._selections[0].positionColumn);
@@ -427,8 +434,7 @@ export class TextAreaHandler extends ViewPart {
 				this._visibleTextArea.top - this._scrollTop,
 				this._contentLeft + this._visibleTextArea.left - this._scrollLeft,
 				this._visibleTextArea.width,
-				this._lineHeight,
-				true
+				this._lineHeight
 			);
 			return;
 		}
@@ -460,29 +466,22 @@ export class TextAreaHandler extends ViewPart {
 			// We will also make the fontSize and lineHeight the correct dimensions to help with the placement of these pickers
 			this._renderInsideEditor(
 				top, left,
-				canUseZeroSizeTextarea ? 0 : 1, this._lineHeight,
-				true
+				canUseZeroSizeTextarea ? 0 : 1, this._lineHeight
 			);
 			return;
 		}
 
 		this._renderInsideEditor(
 			top, left,
-			canUseZeroSizeTextarea ? 0 : 1, canUseZeroSizeTextarea ? 0 : 1,
-			false
+			canUseZeroSizeTextarea ? 0 : 1, canUseZeroSizeTextarea ? 0 : 1
 		);
 	}
 
-	private _renderInsideEditor(top: number, left: number, width: number, height: number, useEditorFont: boolean): void {
+	private _renderInsideEditor(top: number, left: number, width: number, height: number): void {
 		const ta = this.textArea;
 		const tac = this.textAreaCover;
 
-		if (useEditorFont) {
-			Configuration.applyFontInfo(ta, this._fontInfo);
-		} else {
-			ta.setFontSize(1);
-			ta.setLineHeight(this._fontInfo.lineHeight);
-		}
+		Configuration.applyFontInfo(ta, this._fontInfo);
 
 		ta.setTop(top);
 		ta.setLeft(left);
diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts
index b6098cec5f..818d2b2add 100644
--- a/src/vs/editor/browser/controller/textAreaInput.ts
+++ b/src/vs/editor/browser/controller/textAreaInput.ts
@@ -16,6 +16,7 @@ import * as strings from 'vs/base/common/strings';
 import { ITextAreaWrapper, ITypeData, TextAreaState } from 'vs/editor/browser/controller/textAreaState';
 import { Position } from 'vs/editor/common/core/position';
 import { Selection } from 'vs/editor/common/core/selection';
+import { BrowserFeatures } from 'vs/base/browser/canIUse';
 
 export interface ICompositionData {
 	data: string;
@@ -162,7 +163,7 @@ export class TextAreaInput extends Disposable {
 	private _isDoingComposition: boolean;
 	private _nextCommand: ReadFromTextArea;
 
-	constructor(host: ITextAreaInputHost, textArea: FastDomNode) {
+	constructor(host: ITextAreaInputHost, private textArea: FastDomNode) {
 		super();
 		this._host = host;
 		this._textArea = this._register(new TextAreaWrapper(textArea));
@@ -273,7 +274,11 @@ export class TextAreaInput extends Disposable {
 
 		this._register(dom.addDisposableListener(textArea.domNode, 'compositionend', (e: CompositionEvent) => {
 			this._lastTextAreaEvent = TextAreaInputEventType.compositionend;
-
+			// https://github.com/microsoft/monaco-editor/issues/1663
+			// On iOS 13.2, Chinese system IME randomly trigger an additional compositionend event with empty data
+			if (!this._isDoingComposition) {
+				return;
+			}
 			if (compositionDataInValid(e.locale)) {
 				// https://github.com/Microsoft/monaco-editor/issues/339
 				const [newState, typeInput] = deduceInputFromTextAreaValue(/*couldBeEmojiInput*/false, /*couldBeTypingAtOffset0*/false);
@@ -482,6 +487,14 @@ export class TextAreaInput extends Disposable {
 		return this._hasFocus;
 	}
 
+	public refreshFocusState(): void {
+		if (document.body.contains(this.textArea.domNode) && document.activeElement === this.textArea.domNode) {
+			this._setHasFocus(true);
+		} else {
+			this._setHasFocus(false);
+		}
+	}
+
 	private _setHasFocus(newHasFocus: boolean): void {
 		if (this._hasFocus === newHasFocus) {
 			// no change
@@ -533,7 +546,7 @@ export class TextAreaInput extends Disposable {
 	}
 
 	private _ensureClipboardGetsEditorSelection(e: ClipboardEvent): void {
-		const dataToCopy = this._host.getDataToCopy(ClipboardEventUtils.canUseTextData(e) && browser.hasClipboardSupport());
+		const dataToCopy = this._host.getDataToCopy(ClipboardEventUtils.canUseTextData(e) && BrowserFeatures.clipboard.richText);
 		const storedMetadata: ClipboardStoredMetadata = {
 			version: 1,
 			isFromEmptySelection: dataToCopy.isFromEmptySelection,
diff --git a/src/vs/editor/browser/controller/textAreaState.ts b/src/vs/editor/browser/controller/textAreaState.ts
index 2f7730da8a..76e07f80d8 100644
--- a/src/vs/editor/browser/controller/textAreaState.ts
+++ b/src/vs/editor/browser/controller/textAreaState.ts
@@ -226,26 +226,24 @@ export class TextAreaState {
 }
 
 export class PagedScreenReaderStrategy {
-	private static readonly _LINES_PER_PAGE = 10;
-
-	private static _getPageOfLine(lineNumber: number): number {
-		return Math.floor((lineNumber - 1) / PagedScreenReaderStrategy._LINES_PER_PAGE);
+	private static _getPageOfLine(lineNumber: number, linesPerPage: number): number {
+		return Math.floor((lineNumber - 1) / linesPerPage);
 	}
 
-	private static _getRangeForPage(page: number): Range {
-		const offset = page * PagedScreenReaderStrategy._LINES_PER_PAGE;
+	private static _getRangeForPage(page: number, linesPerPage: number): Range {
+		const offset = page * linesPerPage;
 		const startLineNumber = offset + 1;
-		const endLineNumber = offset + PagedScreenReaderStrategy._LINES_PER_PAGE;
+		const endLineNumber = offset + linesPerPage;
 		return new Range(startLineNumber, 1, endLineNumber + 1, 1);
 	}
 
-	public static fromEditorSelection(previousState: TextAreaState, model: ISimpleModel, selection: Range, trimLongText: boolean): TextAreaState {
+	public static fromEditorSelection(previousState: TextAreaState, model: ISimpleModel, selection: Range, linesPerPage: number, trimLongText: boolean): TextAreaState {
 
-		const selectionStartPage = PagedScreenReaderStrategy._getPageOfLine(selection.startLineNumber);
-		const selectionStartPageRange = PagedScreenReaderStrategy._getRangeForPage(selectionStartPage);
+		const selectionStartPage = PagedScreenReaderStrategy._getPageOfLine(selection.startLineNumber, linesPerPage);
+		const selectionStartPageRange = PagedScreenReaderStrategy._getRangeForPage(selectionStartPage, linesPerPage);
 
-		const selectionEndPage = PagedScreenReaderStrategy._getPageOfLine(selection.endLineNumber);
-		const selectionEndPageRange = PagedScreenReaderStrategy._getRangeForPage(selectionEndPage);
+		const selectionEndPage = PagedScreenReaderStrategy._getPageOfLine(selection.endLineNumber, linesPerPage);
+		const selectionEndPageRange = PagedScreenReaderStrategy._getRangeForPage(selectionEndPage, linesPerPage);
 
 		const pretextRange = selectionStartPageRange.intersectRanges(new Range(1, 1, selection.startLineNumber, selection.startColumn))!;
 		let pretext = model.getValueInRange(pretextRange, EndOfLinePreference.LF);
diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts
index 4d49958cfa..02b5da393f 100644
--- a/src/vs/editor/browser/editorBrowser.ts
+++ b/src/vs/editor/browser/editorBrowser.ts
@@ -6,7 +6,7 @@
 import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
 import { IMouseEvent, IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
 import { IDisposable } from 'vs/base/common/lifecycle';
-import { OverviewRulerPosition, ConfigurationChangedEvent, EditorLayoutInfo, IComputedEditorOptions, EditorOption, FindComputedEditorOptionValueById, IEditorOptions } from 'vs/editor/common/config/editorOptions';
+import { OverviewRulerPosition, ConfigurationChangedEvent, EditorLayoutInfo, IComputedEditorOptions, EditorOption, FindComputedEditorOptionValueById, IEditorOptions, IDiffEditorOptions } from 'vs/editor/common/config/editorOptions';
 import { ICursors } from 'vs/editor/common/controller/cursorCommon';
 import { ICursorPositionChangedEvent, ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
 import { IPosition, Position } from 'vs/editor/common/core/position';
@@ -16,7 +16,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon';
 import { IIdentifiedSingleEditOperation, IModelDecoration, IModelDeltaDecoration, ITextModel, ICursorStateComputer } from 'vs/editor/common/model';
 import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent } from 'vs/editor/common/model/textModelEvents';
 import { OverviewRulerZone } from 'vs/editor/common/view/overviewZoneManager';
-import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer';
+import { IEditorWhitespace } from 'vs/editor/common/viewLayout/linesLayout';
 import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
 import { IDiffComputationResult } from 'vs/editor/common/services/editorWorkerService';
 
@@ -938,6 +938,11 @@ export interface IDiffEditor extends editorCommon.IEditor {
 	 * If the diff computation is not finished or the model is missing, will return null.
 	 */
 	getDiffLineInformationForModified(lineNumber: number): IDiffLineInformation | null;
+
+	/**
+	 * Update the editor's options after the editor has been created.
+	 */
+	updateOptions(newOptions: IDiffEditorOptions): void;
 }
 
 /**
diff --git a/src/vs/editor/browser/editorDom.ts b/src/vs/editor/browser/editorDom.ts
index acbb8a7612..97104f762d 100644
--- a/src/vs/editor/browser/editorDom.ts
+++ b/src/vs/editor/browser/editorDom.ts
@@ -84,7 +84,7 @@ export class EditorMouseEvent extends StandardMouseEvent {
 }
 
 export interface EditorMouseEventMerger {
-	(lastEvent: EditorMouseEvent, currentEvent: EditorMouseEvent): EditorMouseEvent;
+	(lastEvent: EditorMouseEvent | null, currentEvent: EditorMouseEvent): EditorMouseEvent;
 }
 
 export class EditorMouseEventFactory {
@@ -124,17 +124,55 @@ export class EditorMouseEventFactory {
 	}
 
 	public onMouseMoveThrottled(target: HTMLElement, callback: (e: EditorMouseEvent) => void, merger: EditorMouseEventMerger, minimumTimeMs: number): IDisposable {
-		const myMerger: dom.IEventMerger = (lastEvent: EditorMouseEvent, currentEvent: MouseEvent): EditorMouseEvent => {
+		const myMerger: dom.IEventMerger = (lastEvent: EditorMouseEvent | null, currentEvent: MouseEvent): EditorMouseEvent => {
 			return merger(lastEvent, this._create(currentEvent));
 		};
 		return dom.addDisposableThrottledListener(target, 'mousemove', callback, myMerger, minimumTimeMs);
 	}
 }
 
+export class EditorPointerEventFactory {
+
+	private readonly _editorViewDomNode: HTMLElement;
+
+	constructor(editorViewDomNode: HTMLElement) {
+		this._editorViewDomNode = editorViewDomNode;
+	}
+
+	private _create(e: MouseEvent): EditorMouseEvent {
+		return new EditorMouseEvent(e, this._editorViewDomNode);
+	}
+
+	public onPointerUp(target: HTMLElement, callback: (e: EditorMouseEvent) => void): IDisposable {
+		return dom.addDisposableListener(target, 'pointerup', (e: MouseEvent) => {
+			callback(this._create(e));
+		});
+	}
+
+	public onPointerDown(target: HTMLElement, callback: (e: EditorMouseEvent) => void): IDisposable {
+		return dom.addDisposableListener(target, 'pointerdown', (e: MouseEvent) => {
+			callback(this._create(e));
+		});
+	}
+
+	public onPointerLeave(target: HTMLElement, callback: (e: EditorMouseEvent) => void): IDisposable {
+		return dom.addDisposableNonBubblingPointerOutListener(target, (e: MouseEvent) => {
+			callback(this._create(e));
+		});
+	}
+
+	public onPointerMoveThrottled(target: HTMLElement, callback: (e: EditorMouseEvent) => void, merger: EditorMouseEventMerger, minimumTimeMs: number): IDisposable {
+		const myMerger: dom.IEventMerger = (lastEvent: EditorMouseEvent | null, currentEvent: MouseEvent): EditorMouseEvent => {
+			return merger(lastEvent, this._create(currentEvent));
+		};
+		return dom.addDisposableThrottledListener(target, 'pointermove', callback, myMerger, minimumTimeMs);
+	}
+}
+
 export class GlobalEditorMouseMoveMonitor extends Disposable {
 
 	private readonly _editorViewDomNode: HTMLElement;
-	private readonly _globalMouseMoveMonitor: GlobalMouseMoveMonitor;
+	protected readonly _globalMouseMoveMonitor: GlobalMouseMoveMonitor;
 	private _keydownListener: IDisposable | null;
 
 	constructor(editorViewDomNode: HTMLElement) {
@@ -157,7 +195,7 @@ export class GlobalEditorMouseMoveMonitor extends Disposable {
 			this._globalMouseMoveMonitor.stopMonitoring(true);
 		}, true);
 
-		const myMerger: dom.IEventMerger = (lastEvent: EditorMouseEvent, currentEvent: MouseEvent): EditorMouseEvent => {
+		const myMerger: dom.IEventMerger = (lastEvent: EditorMouseEvent | null, currentEvent: MouseEvent): EditorMouseEvent => {
 			return merger(lastEvent, new EditorMouseEvent(currentEvent, this._editorViewDomNode));
 		};
 
diff --git a/src/vs/editor/browser/editorExtensions.ts b/src/vs/editor/browser/editorExtensions.ts
index 0c281a235c..b795204dbd 100644
--- a/src/vs/editor/browser/editorExtensions.ts
+++ b/src/vs/editor/browser/editorExtensions.ts
@@ -16,7 +16,7 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService';
 import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
 import { CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
 import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
-import { IConstructorSignature1, ServicesAccessor as InstantiationServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
+import { IConstructorSignature1, ServicesAccessor as InstantiationServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation';
 import { IKeybindings, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
 import { Registry } from 'vs/platform/registry/common/platform';
 import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
@@ -42,7 +42,7 @@ export interface ICommandKeybindingsOptions extends IKeybindings {
 	kbExpr?: ContextKeyExpr | null;
 	weight: number;
 }
-export interface ICommandMenubarOptions {
+export interface ICommandMenuOptions {
 	menuId: MenuId;
 	group: string;
 	order: number;
@@ -54,36 +54,29 @@ export interface ICommandOptions {
 	precondition: ContextKeyExpr | undefined;
 	kbOpts?: ICommandKeybindingsOptions;
 	description?: ICommandHandlerDescription;
-	menubarOpts?: ICommandMenubarOptions;
+	menuOpts?: ICommandMenuOptions | ICommandMenuOptions[];
 }
 export abstract class Command {
 	public readonly id: string;
 	public readonly precondition: ContextKeyExpr | undefined;
 	private readonly _kbOpts: ICommandKeybindingsOptions | undefined;
-	private readonly _menubarOpts: ICommandMenubarOptions | undefined;
+	private readonly _menuOpts: ICommandMenuOptions | ICommandMenuOptions[] | undefined;
 	private readonly _description: ICommandHandlerDescription | undefined;
 
 	constructor(opts: ICommandOptions) {
 		this.id = opts.id;
 		this.precondition = opts.precondition;
 		this._kbOpts = opts.kbOpts;
-		this._menubarOpts = opts.menubarOpts;
+		this._menuOpts = opts.menuOpts;
 		this._description = opts.description;
 	}
 
 	public register(): void {
 
-		if (this._menubarOpts) {
-			MenuRegistry.appendMenuItem(this._menubarOpts.menuId, {
-				group: this._menubarOpts.group,
-				command: {
-					id: this.id,
-					title: this._menubarOpts.title,
-					// precondition: this.precondition
-				},
-				when: this._menubarOpts.when,
-				order: this._menubarOpts.order
-			});
+		if (Array.isArray(this._menuOpts)) {
+			this._menuOpts.forEach(this._registerMenuItem, this);
+		} else if (this._menuOpts) {
+			this._registerMenuItem(this._menuOpts);
 		}
 
 		if (this._kbOpts) {
@@ -119,6 +112,19 @@ export abstract class Command {
 		}
 	}
 
+	private _registerMenuItem(item: ICommandMenuOptions): void {
+		MenuRegistry.appendMenuItem(item.menuId, {
+			group: item.group,
+			command: {
+				id: this.id,
+				title: item.title,
+				// precondition: this.precondition
+			},
+			when: item.when,
+			order: item.order
+		});
+	}
+
 	public abstract runCommand(accessor: ServicesAccessor, args: any): void | Promise;
 }
 
@@ -184,44 +190,59 @@ export abstract class EditorCommand extends Command {
 
 //#region EditorAction
 
-export interface IEditorCommandMenuOptions {
+export interface IEditorActionContextMenuOptions {
 	group: string;
 	order: number;
 	when?: ContextKeyExpr;
+	menuId?: MenuId;
 }
 export interface IActionOptions extends ICommandOptions {
 	label: string;
 	alias: string;
-	menuOpts?: IEditorCommandMenuOptions;
+	contextMenuOpts?: IEditorActionContextMenuOptions | IEditorActionContextMenuOptions[];
 }
+
 export abstract class EditorAction extends EditorCommand {
 
+	private static convertOptions(opts: IActionOptions): ICommandOptions {
+
+		let menuOpts: ICommandMenuOptions[];
+		if (Array.isArray(opts.menuOpts)) {
+			menuOpts = opts.menuOpts;
+		} else if (opts.menuOpts) {
+			menuOpts = [opts.menuOpts];
+		} else {
+			menuOpts = [];
+		}
+
+		function withDefaults(item: Partial): ICommandMenuOptions {
+			if (!item.menuId) {
+				item.menuId = MenuId.EditorContext;
+			}
+			if (!item.title) {
+				item.title = opts.label;
+			}
+			item.when = ContextKeyExpr.and(opts.precondition, item.when);
+			return item;
+		}
+
+		if (Array.isArray(opts.contextMenuOpts)) {
+			menuOpts.push(...opts.contextMenuOpts.map(withDefaults));
+		} else if (opts.contextMenuOpts) {
+			menuOpts.push(withDefaults(opts.contextMenuOpts));
+		}
+
+		opts.menuOpts = menuOpts;
+		return opts;
+	}
+
 	public readonly label: string;
 	public readonly alias: string;
-	private readonly menuOpts: IEditorCommandMenuOptions | undefined;
 
 	constructor(opts: IActionOptions) {
-		super(opts);
+		super(EditorAction.convertOptions(opts));
 		this.label = opts.label;
 		this.alias = opts.alias;
-		this.menuOpts = opts.menuOpts;
-	}
-
-	public register(): void {
-
-		if (this.menuOpts) {
-			MenuRegistry.appendMenuItem(MenuId.EditorContext, {
-				command: {
-					id: this.id,
-					title: this.label
-				},
-				when: ContextKeyExpr.and(this.precondition, this.menuOpts.when),
-				group: this.menuOpts.group,
-				order: this.menuOpts.order
-			});
-		}
-
-		super.register();
 	}
 
 	public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void | Promise {
@@ -303,11 +324,11 @@ export function registerInstantiatedEditorAction(editorAction: EditorAction): vo
 	EditorContributionRegistry.INSTANCE.registerEditorAction(editorAction);
 }
 
-export function registerEditorContribution(id: string, ctor: IEditorContributionCtor): void {
+export function registerEditorContribution(id: string, ctor: { new(editor: ICodeEditor, ...services: Services): IEditorContribution }): void {
 	EditorContributionRegistry.INSTANCE.registerEditorContribution(id, ctor);
 }
 
-export function registerDiffEditorContribution(id: string, ctor: IDiffEditorContributionCtor): void {
+export function registerDiffEditorContribution(id: string, ctor: { new(editor: IDiffEditor, ...services: Services): IEditorContribution }): void {
 	EditorContributionRegistry.INSTANCE.registerDiffEditorContribution(id, ctor);
 }
 
@@ -355,16 +376,16 @@ class EditorContributionRegistry {
 		this.editorCommands = Object.create(null);
 	}
 
-	public registerEditorContribution(id: string, ctor: IEditorContributionCtor): void {
-		this.editorContributions.push({ id, ctor });
+	public registerEditorContribution(id: string, ctor: { new(editor: ICodeEditor, ...services: Services): IEditorContribution }): void {
+		this.editorContributions.push({ id, ctor: ctor as IEditorContributionCtor });
 	}
 
 	public getEditorContributions(): IEditorContributionDescription[] {
 		return this.editorContributions.slice(0);
 	}
 
-	public registerDiffEditorContribution(id: string, ctor: IDiffEditorContributionCtor): void {
-		this.diffEditorContributions.push({ id, ctor });
+	public registerDiffEditorContribution(id: string, ctor: { new(editor: IDiffEditor, ...services: Services): IEditorContribution }): void {
+		this.diffEditorContributions.push({ id, ctor: ctor as IDiffEditorContributionCtor });
 	}
 
 	public getDiffEditorContributions(): IDiffEditorContributionDescription[] {
diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts
index 3b49459671..772e75ae42 100644
--- a/src/vs/editor/browser/services/openerService.ts
+++ b/src/vs/editor/browser/services/openerService.ts
@@ -4,19 +4,89 @@
  *--------------------------------------------------------------------------------------------*/
 
 import * as dom from 'vs/base/browser/dom';
-import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
+import { IDisposable } from 'vs/base/common/lifecycle';
 import { LinkedList } from 'vs/base/common/linkedList';
 import { parse } from 'vs/base/common/marshalling';
 import { Schemas } from 'vs/base/common/network';
-import * as resources from 'vs/base/common/resources';
-import { equalsIgnoreCase } from 'vs/base/common/strings';
+import { normalizePath } from 'vs/base/common/resources';
 import { URI } from 'vs/base/common/uri';
 import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
 import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
-import { IOpener, IOpenerService, IValidator, IExternalUriResolver, OpenOptions } from 'vs/platform/opener/common/opener';
+import { IOpener, IOpenerService, IValidator, IExternalUriResolver, OpenOptions, ResolveExternalUriOptions, IResolvedExternalUri, IExternalOpener, matchesScheme } from 'vs/platform/opener/common/opener';
 import { EditorOpenContext } from 'vs/platform/editor/common/editor';
 
-export class OpenerService extends Disposable implements IOpenerService {
+
+class CommandOpener implements IOpener {
+
+	constructor(@ICommandService private readonly _commandService: ICommandService) { }
+
+	async open(target: URI | string) {
+		if (!matchesScheme(target, Schemas.command)) {
+			return false;
+		}
+		// run command or bail out if command isn't known
+		if (typeof target === 'string') {
+			target = URI.parse(target);
+		}
+		if (!CommandsRegistry.getCommand(target.path)) {
+			throw new Error(`command '${target.path}' NOT known`);
+		}
+		// execute as command
+		let args: any = [];
+		try {
+			args = parse(decodeURIComponent(target.query));
+		} catch {
+			// ignore and retry
+			try {
+				args = parse(target.query);
+			} catch {
+				// ignore error
+			}
+		}
+		if (!Array.isArray(args)) {
+			args = [args];
+		}
+		await this._commandService.executeCommand(target.path, ...args);
+		return true;
+	}
+}
+
+class EditorOpener implements IOpener {
+
+	constructor(@ICodeEditorService private readonly _editorService: ICodeEditorService) { }
+
+	async open(target: URI | string, options: OpenOptions) {
+		if (typeof target === 'string') {
+			target = URI.parse(target);
+		}
+		let selection: { startLineNumber: number; startColumn: number; } | undefined = undefined;
+		const match = /^L?(\d+)(?:,(\d+))?/.exec(target.fragment);
+		if (match) {
+			// support file:///some/file.js#73,84
+			// support file:///some/file.js#L73
+			selection = {
+				startLineNumber: parseInt(match[1]),
+				startColumn: match[2] ? parseInt(match[2]) : 1
+			};
+			// remove fragment
+			target = target.with({ fragment: '' });
+		}
+
+		if (target.scheme === Schemas.file) {
+			target = normalizePath(target); // workaround for non-normalized paths (https://github.com/Microsoft/vscode/issues/12954)
+		}
+
+		await this._editorService.openCodeEditor(
+			{ resource: target, options: { selection, context: options?.fromUserGesture ? EditorOpenContext.USER : EditorOpenContext.API } },
+			this._editorService.getFocusedCodeEditor(),
+			options?.openToSide
+		);
+
+		return true;
+	}
+}
+
+export class OpenerService implements IOpenerService {
 
 	_serviceBrand: undefined;
 
@@ -24,58 +94,75 @@ export class OpenerService extends Disposable implements IOpenerService {
 	private readonly _validators = new LinkedList();
 	private readonly _resolvers = new LinkedList();
 
+	private _externalOpener: IExternalOpener;
+
 	constructor(
-		@ICodeEditorService private readonly _editorService: ICodeEditorService,
-		@ICommandService private readonly _commandService: ICommandService,
+		@ICodeEditorService editorService: ICodeEditorService,
+		@ICommandService commandService: ICommandService,
 	) {
-		super();
+		// Default external opener is going through window.open()
+		this._externalOpener = {
+			openExternal: href => {
+				dom.windowOpenNoOpener(href);
+				return Promise.resolve(true);
+			}
+		};
+
+		// Default opener: maito, http(s), command, and catch-all-editors
+		this._openers.push({
+			open: async (target: URI | string, options?: OpenOptions) => {
+				if (options?.openExternal || matchesScheme(target, Schemas.mailto) || matchesScheme(target, Schemas.http) || matchesScheme(target, Schemas.https)) {
+					// open externally
+					await this._doOpenExternal(target, options);
+					return true;
+				}
+				return false;
+			}
+		});
+		this._openers.push(new CommandOpener(commandService));
+		this._openers.push(new EditorOpener(editorService));
 	}
 
 	registerOpener(opener: IOpener): IDisposable {
-		const remove = this._openers.push(opener);
-
+		const remove = this._openers.unshift(opener);
 		return { dispose: remove };
 	}
 
 	registerValidator(validator: IValidator): IDisposable {
 		const remove = this._validators.push(validator);
-
 		return { dispose: remove };
 	}
 
 	registerExternalUriResolver(resolver: IExternalUriResolver): IDisposable {
 		const remove = this._resolvers.push(resolver);
-
 		return { dispose: remove };
 	}
 
-	async open(resource: URI, options?: OpenOptions): Promise {
+	setExternalOpener(externalOpener: IExternalOpener): void {
+		this._externalOpener = externalOpener;
+	}
 
-		// no scheme ?!?
-		if (!resource.scheme) {
-			return Promise.resolve(false);
-		}
+	async open(target: URI | string, options?: OpenOptions): Promise {
 
 		// check with contributed validators
 		for (const validator of this._validators.toArray()) {
-			if (!(await validator.shouldOpen(resource))) {
+			if (!(await validator.shouldOpen(target))) {
 				return false;
 			}
 		}
 
 		// check with contributed openers
 		for (const opener of this._openers.toArray()) {
-			const handled = await opener.open(resource, options);
+			const handled = await opener.open(target, options);
 			if (handled) {
 				return true;
 			}
 		}
 
-		// use default openers
-		return this._doOpen(resource, options);
+		return false;
 	}
 
-	async resolveExternalUri(resource: URI, options?: { readonly allowTunneling?: boolean }): Promise<{ resolved: URI, dispose(): void }> {
+	async resolveExternalUri(resource: URI, options?: ResolveExternalUriOptions): Promise {
 		for (const resolver of this._resolvers.toArray()) {
 			const result = await resolver.resolveExternalUri(resource, options);
 			if (result) {
@@ -86,72 +173,19 @@ export class OpenerService extends Disposable implements IOpenerService {
 		return { resolved: resource, dispose: () => { } };
 	}
 
-	private async _doOpen(resource: URI, options: OpenOptions | undefined): Promise {
-		const { scheme, path, query, fragment } = resource;
+	private async _doOpenExternal(resource: URI | string, options: OpenOptions | undefined): Promise {
 
-		if (equalsIgnoreCase(scheme, Schemas.mailto) || options?.openExternal) {
-			// open default mail application
-			return this._doOpenExternal(resource, options);
+		//todo@joh IExternalUriResolver should support `uri: URI | string`
+		const uri = typeof resource === 'string' ? URI.parse(resource) : resource;
+		const { resolved } = await this.resolveExternalUri(uri, options);
+
+		if (typeof resource === 'string' && uri.toString() === resolved.toString()) {
+			// open the url-string AS IS
+			return this._externalOpener.openExternal(resource);
+		} else {
+			// open URI using the toString(noEncode)+encodeURI-trick
+			return this._externalOpener.openExternal(encodeURI(resolved.toString(true)));
 		}
-
-		if (equalsIgnoreCase(scheme, Schemas.http) || equalsIgnoreCase(scheme, Schemas.https)) {
-			// open link in default browser
-			return this._doOpenExternal(resource, options);
-		}
-
-		if (equalsIgnoreCase(scheme, Schemas.command)) {
-			// run command or bail out if command isn't known
-			if (!CommandsRegistry.getCommand(path)) {
-				return Promise.reject(`command '${path}' NOT known`);
-			}
-			// execute as command
-			let args: any = [];
-			try {
-				args = parse(query);
-				if (!Array.isArray(args)) {
-					args = [args];
-				}
-			} catch (e) {
-				// ignore error
-			}
-
-			await this._commandService.executeCommand(path, ...args);
-
-			return true;
-		}
-
-		// finally open in editor
-		let selection: { startLineNumber: number; startColumn: number; } | undefined = undefined;
-		const match = /^L?(\d+)(?:,(\d+))?/.exec(fragment);
-		if (match) {
-			// support file:///some/file.js#73,84
-			// support file:///some/file.js#L73
-			selection = {
-				startLineNumber: parseInt(match[1]),
-				startColumn: match[2] ? parseInt(match[2]) : 1
-			};
-			// remove fragment
-			resource = resource.with({ fragment: '' });
-		}
-
-		if (resource.scheme === Schemas.file) {
-			resource = resources.normalizePath(resource); // workaround for non-normalized paths (https://github.com/Microsoft/vscode/issues/12954)
-		}
-
-		await this._editorService.openCodeEditor(
-			{ resource, options: { selection, context: options?.fromUserGesture ? EditorOpenContext.USER : EditorOpenContext.API } },
-			this._editorService.getFocusedCodeEditor(),
-			options?.openToSide
-		);
-
-		return true;
-	}
-
-	private async _doOpenExternal(resource: URI, options: OpenOptions | undefined): Promise {
-		const { resolved } = await this.resolveExternalUri(resource, options);
-		dom.windowOpenNoOpener(encodeURI(resolved.toString(true)));
-
-		return true;
 	}
 
 	dispose() {
diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts
index ae67d9315f..e5f64de83c 100644
--- a/src/vs/editor/browser/view/viewImpl.ts
+++ b/src/vs/editor/browser/view/viewImpl.ts
@@ -11,14 +11,13 @@ import { IDisposable } from 'vs/base/common/lifecycle';
 import { IPointerHandlerHelper } from 'vs/editor/browser/controller/mouseHandler';
 import { PointerHandler } from 'vs/editor/browser/controller/pointerHandler';
 import { ITextAreaHandlerHelper, TextAreaHandler } from 'vs/editor/browser/controller/textAreaHandler';
-import * as editorBrowser from 'vs/editor/browser/editorBrowser';
+import { IContentWidget, IContentWidgetPosition, IOverlayWidget, IOverlayWidgetPosition, IMouseTarget, IViewZoneChangeAccessor } from 'vs/editor/browser/editorBrowser';
 import { ICommandDelegate, ViewController } from 'vs/editor/browser/view/viewController';
 import { ViewOutgoingEvents } from 'vs/editor/browser/view/viewOutgoingEvents';
 import { ContentViewOverlays, MarginViewOverlays } from 'vs/editor/browser/view/viewOverlays';
 import { PartFingerprint, PartFingerprints, ViewPart } from 'vs/editor/browser/view/viewPart';
 import { ViewContentWidgets } from 'vs/editor/browser/viewParts/contentWidgets/contentWidgets';
-import { CurrentLineHighlightOverlay } from 'vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight';
-import { CurrentLineMarginHighlightOverlay } from 'vs/editor/browser/viewParts/currentLineMarginHighlight/currentLineMarginHighlight';
+import { CurrentLineHighlightOverlay, CurrentLineMarginHighlightOverlay } from 'vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight';
 import { DecorationsOverlay } from 'vs/editor/browser/viewParts/decorations/decorations';
 import { EditorScrollbar } from 'vs/editor/browser/viewParts/editorScrollbar/editorScrollbar';
 import { GlyphMarginOverlay } from 'vs/editor/browser/viewParts/glyphMargin/glyphMargin';
@@ -52,17 +51,15 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions';
 
 
 export interface IContentWidgetData {
-	widget: editorBrowser.IContentWidget;
-	position: editorBrowser.IContentWidgetPosition | null;
+	widget: IContentWidget;
+	position: IContentWidgetPosition | null;
 }
 
 export interface IOverlayWidgetData {
-	widget: editorBrowser.IOverlayWidget;
-	position: editorBrowser.IOverlayWidgetPosition | null;
+	widget: IOverlayWidget;
+	position: IOverlayWidgetPosition | null;
 }
 
-const invalidFunc = () => { throw new Error(`Invalid change accessor`); };
-
 export class View extends ViewEventHandler {
 
 	private readonly eventDispatcher: ViewEventDispatcher;
@@ -261,7 +258,7 @@ export class View extends ViewEventHandler {
 				return this.viewLines.getPositionFromDOMInfo(spanNode, offset);
 			},
 
-			visibleRangeForPosition2: (lineNumber: number, column: number) => {
+			visibleRangeForPosition: (lineNumber: number, column: number) => {
 				this._flushAccumulatedAndRenderNow();
 				return this.viewLines.visibleRangeForPosition(new Position(lineNumber, column));
 			},
@@ -349,7 +346,7 @@ export class View extends ViewEventHandler {
 		super.dispose();
 	}
 
-	private _renderOnce(callback: () => any): any {
+	private _renderOnce(callback: () => T): T {
 		const r = safeInvokeNoArg(callback);
 		this._scheduleRender();
 		return r;
@@ -459,7 +456,7 @@ export class View extends ViewEventHandler {
 		return visibleRange.left;
 	}
 
-	public getTargetAtClientPoint(clientX: number, clientY: number): editorBrowser.IMouseTarget | null {
+	public getTargetAtClientPoint(clientX: number, clientY: number): IMouseTarget | null {
 		return this.pointerHandler.getTargetAtClientPoint(clientX, clientY);
 	}
 
@@ -467,42 +464,15 @@ export class View extends ViewEventHandler {
 		return new OverviewRuler(this._context, cssClassName);
 	}
 
-	public change(callback: (changeAccessor: editorBrowser.IViewZoneChangeAccessor) => any): boolean {
-		let zonesHaveChanged = false;
-
-		this._renderOnce(() => {
-			const changeAccessor: editorBrowser.IViewZoneChangeAccessor = {
-				addZone: (zone: editorBrowser.IViewZone): string => {
-					zonesHaveChanged = true;
-					return this.viewZones.addZone(zone);
-				},
-				removeZone: (id: string): void => {
-					if (!id) {
-						return;
-					}
-					zonesHaveChanged = this.viewZones.removeZone(id) || zonesHaveChanged;
-				},
-				layoutZone: (id: string): void => {
-					if (!id) {
-						return;
-					}
-					zonesHaveChanged = this.viewZones.layoutZone(id) || zonesHaveChanged;
-				}
-			};
-
-			safeInvoke1Arg(callback, changeAccessor);
-
-			// Invalidate changeAccessor
-			changeAccessor.addZone = invalidFunc;
-			changeAccessor.removeZone = invalidFunc;
-			changeAccessor.layoutZone = invalidFunc;
-
+	public change(callback: (changeAccessor: IViewZoneChangeAccessor) => any): boolean {
+		return this._renderOnce(() => {
+			const zonesHaveChanged = this.viewZones.changeViewZones(callback);
 			if (zonesHaveChanged) {
 				this._context.viewLayout.onHeightMaybeChanged();
 				this._context.privateViewEventBus.emit(new viewEvents.ViewZonesChangedEvent());
 			}
+			return zonesHaveChanged;
 		});
-		return zonesHaveChanged;
 	}
 
 	public render(now: boolean, everything: boolean): void {
@@ -529,6 +499,10 @@ export class View extends ViewEventHandler {
 		return this._textAreaHandler.isFocused();
 	}
 
+	public refreshFocusState() {
+		this._textAreaHandler.refreshFocusState();
+	}
+
 	public addContentWidget(widgetData: IContentWidgetData): void {
 		this.contentWidgets.addWidget(widgetData.widget);
 		this.layoutContentWidget(widgetData);
@@ -579,10 +553,3 @@ function safeInvokeNoArg(func: Function): any {
 	}
 }
 
-function safeInvoke1Arg(func: Function, arg1: any): any {
-	try {
-		return func(arg1);
-	} catch (e) {
-		onUnexpectedError(e);
-	}
-}
diff --git a/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.css b/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.css
index 25966f4e5a..d427d435bf 100644
--- a/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.css
+++ b/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.css
@@ -9,4 +9,16 @@
 	left: 0;
 	top: 0;
 	box-sizing: border-box;
-}
\ No newline at end of file
+}
+
+.monaco-editor .margin-view-overlays .current-line {
+	display: block;
+	position: absolute;
+	left: 0;
+	top: 0;
+	box-sizing: border-box;
+}
+
+.monaco-editor .margin-view-overlays .current-line.current-line-margin.current-line-margin-both {
+	border-right: 0;
+}
diff --git a/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts b/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts
index 762d2cff11..7d613b77ae 100644
--- a/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts
+++ b/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts
@@ -9,18 +9,23 @@ import { editorLineHighlight, editorLineHighlightBorder } from 'vs/editor/common
 import { RenderingContext } from 'vs/editor/common/view/renderingContext';
 import { ViewContext } from 'vs/editor/common/view/viewContext';
 import * as viewEvents from 'vs/editor/common/view/viewEvents';
+import * as arrays from 'vs/base/common/arrays';
 import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
+import { Selection } from 'vs/editor/common/core/selection';
 import { EditorOption } from 'vs/editor/common/config/editorOptions';
 
+let isRenderedUsingBorder = true;
 
-export class CurrentLineHighlightOverlay extends DynamicViewOverlay {
+export abstract class AbstractLineHighlightOverlay extends DynamicViewOverlay {
 	private readonly _context: ViewContext;
-	private _lineHeight: number;
-	private _renderLineHighlight: 'none' | 'gutter' | 'line' | 'all';
-	private _contentWidth: number;
-	private _selectionIsEmpty: boolean;
-	private _primaryCursorLineNumber: number;
-	private _scrollWidth: number;
+	protected _lineHeight: number;
+	protected _renderLineHighlight: 'none' | 'gutter' | 'line' | 'all';
+	protected _contentLeft: number;
+	protected _contentWidth: number;
+	protected _selectionIsEmpty: boolean;
+	private _cursorLineNumbers: number[];
+	private _selections: Selection[];
+	private _renderData: string[] | null;
 
 	constructor(context: ViewContext) {
 		super();
@@ -28,15 +33,14 @@ export class CurrentLineHighlightOverlay extends DynamicViewOverlay {
 
 		const options = this._context.configuration.options;
 		const layoutInfo = options.get(EditorOption.layoutInfo);
-
 		this._lineHeight = options.get(EditorOption.lineHeight);
 		this._renderLineHighlight = options.get(EditorOption.renderLineHighlight);
+		this._contentLeft = layoutInfo.contentLeft;
 		this._contentWidth = layoutInfo.contentWidth;
-
 		this._selectionIsEmpty = true;
-		this._primaryCursorLineNumber = 1;
-		this._scrollWidth = 0;
-
+		this._cursorLineNumbers = [];
+		this._selections = [];
+		this._renderData = null;
 
 		this._context.addEventHandler(this);
 	}
@@ -46,33 +50,44 @@ export class CurrentLineHighlightOverlay extends DynamicViewOverlay {
 		super.dispose();
 	}
 
-	// --- begin event handlers
+	private _readFromSelections(): boolean {
+		let hasChanged = false;
 
+		// Only render the first selection when using border
+		const renderSelections = isRenderedUsingBorder ? this._selections.slice(0, 1) : this._selections;
+
+		const cursorsLineNumbers = renderSelections.map(s => s.positionLineNumber);
+		cursorsLineNumbers.sort();
+		if (!arrays.equals(this._cursorLineNumbers, cursorsLineNumbers)) {
+			this._cursorLineNumbers = cursorsLineNumbers;
+			hasChanged = true;
+		}
+
+		const selectionIsEmpty = renderSelections.every(s => s.isEmpty());
+		if (this._selectionIsEmpty !== selectionIsEmpty) {
+			this._selectionIsEmpty = selectionIsEmpty;
+			hasChanged = true;
+		}
+
+		return hasChanged;
+	}
+
+	// --- begin event handlers
+	public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean {
+		return this._readFromSelections();
+	}
 	public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
 		const options = this._context.configuration.options;
 		const layoutInfo = options.get(EditorOption.layoutInfo);
-
 		this._lineHeight = options.get(EditorOption.lineHeight);
 		this._renderLineHighlight = options.get(EditorOption.renderLineHighlight);
+		this._contentLeft = layoutInfo.contentLeft;
 		this._contentWidth = layoutInfo.contentWidth;
 		return true;
 	}
 	public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean {
-		let hasChanged = false;
-
-		const primaryCursorLineNumber = e.selections[0].positionLineNumber;
-		if (this._primaryCursorLineNumber !== primaryCursorLineNumber) {
-			this._primaryCursorLineNumber = primaryCursorLineNumber;
-			hasChanged = true;
-		}
-
-		const selectionIsEmpty = e.selections[0].isEmpty();
-		if (this._selectionIsEmpty !== selectionIsEmpty) {
-			this._selectionIsEmpty = selectionIsEmpty;
-			return true;
-		}
-
-		return hasChanged;
+		this._selections = e.selections;
+		return this._readFromSelections();
 	}
 	public onFlushed(e: viewEvents.ViewFlushedEvent): boolean {
 		return true;
@@ -84,7 +99,7 @@ export class CurrentLineHighlightOverlay extends DynamicViewOverlay {
 		return true;
 	}
 	public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
-		return e.scrollWidthChanged;
+		return e.scrollWidthChanged || e.scrollTopChanged;
 	}
 	public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
 		return true;
@@ -92,55 +107,99 @@ export class CurrentLineHighlightOverlay extends DynamicViewOverlay {
 	// --- end event handlers
 
 	public prepareRender(ctx: RenderingContext): void {
-		this._scrollWidth = ctx.scrollWidth;
+		if (!this._shouldRenderThis()) {
+			this._renderData = null;
+			return;
+		}
+		const renderedLine = this._renderOne(ctx);
+		const visibleStartLineNumber = ctx.visibleRange.startLineNumber;
+		const visibleEndLineNumber = ctx.visibleRange.endLineNumber;
+		const len = this._cursorLineNumbers.length;
+		let index = 0;
+		const renderData: string[] = [];
+		for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) {
+			const lineIndex = lineNumber - visibleStartLineNumber;
+			while (index < len && this._cursorLineNumbers[index] < lineNumber) {
+				index++;
+			}
+			if (index < len && this._cursorLineNumbers[index] === lineNumber) {
+				renderData[lineIndex] = renderedLine;
+			} else {
+				renderData[lineIndex] = '';
+			}
+		}
+		this._renderData = renderData;
 	}
 
 	public render(startLineNumber: number, lineNumber: number): string {
-		if (lineNumber === this._primaryCursorLineNumber) {
-			if (this._shouldShowCurrentLine()) {
-				const paintedInMargin = this._willRenderMarginCurrentLine();
-				const className = 'current-line' + (paintedInMargin ? ' current-line-both' : '');
-				return (
-					'
' - ); - } else { - return ''; - } + if (!this._renderData) { + return ''; } - return ''; + const lineIndex = lineNumber - startLineNumber; + if (lineIndex >= this._renderData.length) { + return ''; + } + return this._renderData[lineIndex]; } - private _shouldShowCurrentLine(): boolean { + protected abstract _shouldRenderThis(): boolean; + protected abstract _shouldRenderOther(): boolean; + protected abstract _renderOne(ctx: RenderingContext): string; +} + +export class CurrentLineHighlightOverlay extends AbstractLineHighlightOverlay { + + protected _renderOne(ctx: RenderingContext): string { + const className = 'current-line' + (this._shouldRenderOther() ? ' current-line-both' : ''); + return `
`; + } + protected _shouldRenderThis(): boolean { return ( (this._renderLineHighlight === 'line' || this._renderLineHighlight === 'all') && this._selectionIsEmpty ); } - - private _willRenderMarginCurrentLine(): boolean { + protected _shouldRenderOther(): boolean { return ( (this._renderLineHighlight === 'gutter' || this._renderLineHighlight === 'all') ); } } +export class CurrentLineMarginHighlightOverlay extends AbstractLineHighlightOverlay { + protected _renderOne(ctx: RenderingContext): string { + const className = 'current-line current-line-margin' + (this._shouldRenderOther() ? ' current-line-margin-both' : ''); + return `
`; + } + protected _shouldRenderThis(): boolean { + return ( + (this._renderLineHighlight === 'gutter' || this._renderLineHighlight === 'all') + ); + } + protected _shouldRenderOther(): boolean { + return ( + (this._renderLineHighlight === 'line' || this._renderLineHighlight === 'all') + && this._selectionIsEmpty + ); + } +} + registerThemingParticipant((theme, collector) => { + isRenderedUsingBorder = false; const lineHighlight = theme.getColor(editorLineHighlight); if (lineHighlight) { collector.addRule(`.monaco-editor .view-overlays .current-line { background-color: ${lineHighlight}; }`); + collector.addRule(`.monaco-editor .margin-view-overlays .current-line-margin { background-color: ${lineHighlight}; border: none; }`); } if (!lineHighlight || lineHighlight.isTransparent() || theme.defines(editorLineHighlightBorder)) { const lineHighlightBorder = theme.getColor(editorLineHighlightBorder); if (lineHighlightBorder) { + isRenderedUsingBorder = true; collector.addRule(`.monaco-editor .view-overlays .current-line { border: 2px solid ${lineHighlightBorder}; }`); + collector.addRule(`.monaco-editor .margin-view-overlays .current-line-margin { border: 2px solid ${lineHighlightBorder}; }`); if (theme.type === 'hc') { collector.addRule(`.monaco-editor .view-overlays .current-line { border-width: 1px; }`); + collector.addRule(`.monaco-editor .margin-view-overlays .current-line-margin { border-width: 1px; }`); } } } diff --git a/src/vs/editor/browser/viewParts/currentLineMarginHighlight/currentLineMarginHighlight.css b/src/vs/editor/browser/viewParts/currentLineMarginHighlight/currentLineMarginHighlight.css deleted file mode 100644 index 970c90a454..0000000000 --- a/src/vs/editor/browser/viewParts/currentLineMarginHighlight/currentLineMarginHighlight.css +++ /dev/null @@ -1,16 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-editor .margin-view-overlays .current-line { - display: block; - position: absolute; - left: 0; - top: 0; - box-sizing: border-box; -} - -.monaco-editor .margin-view-overlays .current-line.current-line-margin.current-line-margin-both { - border-right: 0; -} \ No newline at end of file diff --git a/src/vs/editor/browser/viewParts/currentLineMarginHighlight/currentLineMarginHighlight.ts b/src/vs/editor/browser/viewParts/currentLineMarginHighlight/currentLineMarginHighlight.ts deleted file mode 100644 index cd0aa2c9fa..0000000000 --- a/src/vs/editor/browser/viewParts/currentLineMarginHighlight/currentLineMarginHighlight.ts +++ /dev/null @@ -1,139 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import 'vs/css!./currentLineMarginHighlight'; -import { DynamicViewOverlay } from 'vs/editor/browser/view/dynamicViewOverlay'; -import { editorLineHighlight, editorLineHighlightBorder } from 'vs/editor/common/view/editorColorRegistry'; -import { RenderingContext } from 'vs/editor/common/view/renderingContext'; -import { ViewContext } from 'vs/editor/common/view/viewContext'; -import * as viewEvents from 'vs/editor/common/view/viewEvents'; -import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; - - -export class CurrentLineMarginHighlightOverlay extends DynamicViewOverlay { - private readonly _context: ViewContext; - private _lineHeight: number; - private _renderLineHighlight: 'none' | 'gutter' | 'line' | 'all'; - private _contentLeft: number; - private _selectionIsEmpty: boolean; - private _primaryCursorLineNumber: number; - - constructor(context: ViewContext) { - super(); - this._context = context; - - const options = this._context.configuration.options; - const layoutInfo = options.get(EditorOption.layoutInfo); - - this._lineHeight = options.get(EditorOption.lineHeight); - this._renderLineHighlight = options.get(EditorOption.renderLineHighlight); - this._contentLeft = layoutInfo.contentLeft; - - this._selectionIsEmpty = true; - this._primaryCursorLineNumber = 1; - - this._context.addEventHandler(this); - } - - public dispose(): void { - this._context.removeEventHandler(this); - super.dispose(); - } - - // --- begin event handlers - - public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { - const options = this._context.configuration.options; - const layoutInfo = options.get(EditorOption.layoutInfo); - - this._lineHeight = options.get(EditorOption.lineHeight); - this._renderLineHighlight = options.get(EditorOption.renderLineHighlight); - this._contentLeft = layoutInfo.contentLeft; - return true; - } - public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { - let hasChanged = false; - - const primaryCursorLineNumber = e.selections[0].positionLineNumber; - if (this._primaryCursorLineNumber !== primaryCursorLineNumber) { - this._primaryCursorLineNumber = primaryCursorLineNumber; - hasChanged = true; - } - - const selectionIsEmpty = e.selections[0].isEmpty(); - if (this._selectionIsEmpty !== selectionIsEmpty) { - this._selectionIsEmpty = selectionIsEmpty; - return true; - } - - return hasChanged; - } - public onFlushed(e: viewEvents.ViewFlushedEvent): boolean { - return true; - } - public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { - return true; - } - public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { - return true; - } - public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { - return true; - } - // --- end event handlers - - public prepareRender(ctx: RenderingContext): void { - } - - public render(startLineNumber: number, lineNumber: number): string { - if (lineNumber === this._primaryCursorLineNumber) { - let className = 'current-line'; - if (this._shouldShowCurrentLine()) { - const paintedInContent = this._willRenderContentCurrentLine(); - className = 'current-line current-line-margin' + (paintedInContent ? ' current-line-margin-both' : ''); - } - - return ( - '
' - ); - } - return ''; - } - - private _shouldShowCurrentLine(): boolean { - return ( - (this._renderLineHighlight === 'gutter' || this._renderLineHighlight === 'all') - ); - } - - private _willRenderContentCurrentLine(): boolean { - return ( - (this._renderLineHighlight === 'line' || this._renderLineHighlight === 'all') - && this._selectionIsEmpty - ); - } -} - -registerThemingParticipant((theme, collector) => { - const lineHighlight = theme.getColor(editorLineHighlight); - if (lineHighlight) { - collector.addRule(`.monaco-editor .margin-view-overlays .current-line-margin { background-color: ${lineHighlight}; border: none; }`); - } else { - const lineHighlightBorder = theme.getColor(editorLineHighlightBorder); - if (lineHighlightBorder) { - collector.addRule(`.monaco-editor .margin-view-overlays .current-line-margin { border: 2px solid ${lineHighlightBorder}; }`); - } - if (theme.type === 'hc') { - collector.addRule(`.monaco-editor .margin-view-overlays .current-line-margin { border-width: 1px; }`); - } - } -}); diff --git a/src/vs/editor/browser/viewParts/decorations/decorations.ts b/src/vs/editor/browser/viewParts/decorations/decorations.ts index 7baed2b6f0..dac0caeb53 100644 --- a/src/vs/editor/browser/viewParts/decorations/decorations.ts +++ b/src/vs/editor/browser/viewParts/decorations/decorations.ts @@ -195,6 +195,9 @@ export class DecorationsOverlay extends DynamicViewOverlay { for (let j = 0, lenJ = linesVisibleRanges.length; j < lenJ; j++) { const lineVisibleRanges = linesVisibleRanges[j]; + if (lineVisibleRanges.outsideRenderedLine) { + continue; + } const lineIndex = lineVisibleRanges.lineNumber - visibleStartLineNumber; if (showIfCollapsed && lineVisibleRanges.ranges.length === 1) { diff --git a/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts b/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts index 4b25021a9e..52ac1498bf 100644 --- a/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts +++ b/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts @@ -177,7 +177,7 @@ export class GlyphMarginOverlay extends DedupOverlay { output[lineIndex] = ''; } else { output[lineIndex] = ( - '
stopRenderingLineAfter && endColumn > stopRenderingLineAfter) { + if (stopRenderingLineAfter !== -1 && startColumn > stopRenderingLineAfter + 1 && endColumn > stopRenderingLineAfter + 1) { // This range is obviously not visible - return null; + outsideRenderedLine = true; } - if (stopRenderingLineAfter !== -1 && startColumn > stopRenderingLineAfter) { - startColumn = stopRenderingLineAfter; + if (stopRenderingLineAfter !== -1 && startColumn > stopRenderingLineAfter + 1) { + startColumn = stopRenderingLineAfter + 1; } - if (stopRenderingLineAfter !== -1 && endColumn > stopRenderingLineAfter) { - endColumn = stopRenderingLineAfter; + if (stopRenderingLineAfter !== -1 && endColumn > stopRenderingLineAfter + 1) { + endColumn = stopRenderingLineAfter + 1; } - return this._renderedViewLine.getVisibleRangesForRange(startColumn, endColumn, context); + const horizontalRanges = this._renderedViewLine.getVisibleRangesForRange(startColumn, endColumn, context); + if (horizontalRanges && horizontalRanges.length > 0) { + return new VisibleRanges(outsideRenderedLine, horizontalRanges); + } + + return null; } public getColumnOfNodeOffset(lineNumber: number, spanNode: HTMLElement, offset: number): number { @@ -401,7 +407,7 @@ class FastRenderedViewLine implements IRenderedViewLine { */ class RenderedViewLine implements IRenderedViewLine { - public domNode: FastDomNode; + public domNode: FastDomNode | null; public readonly input: RenderLineInput; protected readonly _characterMapping: CharacterMapping; @@ -414,7 +420,7 @@ class RenderedViewLine implements IRenderedViewLine { */ private readonly _pixelOffsetCache: Int32Array | null; - constructor(domNode: FastDomNode, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: ForeignElementType) { + constructor(domNode: FastDomNode | null, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: ForeignElementType) { this.domNode = domNode; this.input = renderLineInput; this._characterMapping = characterMapping; @@ -433,16 +439,19 @@ class RenderedViewLine implements IRenderedViewLine { // --- Reading from the DOM methods - protected _getReadingTarget(): HTMLElement { - return this.domNode.domNode.firstChild; + protected _getReadingTarget(myDomNode: FastDomNode): HTMLElement { + return myDomNode.domNode.firstChild; } /** * Width of the line in pixels */ public getWidth(): number { + if (!this.domNode) { + return 0; + } if (this._cachedWidth === -1) { - this._cachedWidth = this._getReadingTarget().offsetWidth; + this._cachedWidth = this._getReadingTarget(this.domNode).offsetWidth; } return this._cachedWidth; } @@ -458,14 +467,17 @@ class RenderedViewLine implements IRenderedViewLine { * Visible ranges for a model range */ public getVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null { + if (!this.domNode) { + return null; + } if (this._pixelOffsetCache !== null) { // the text is LTR - const startOffset = this._readPixelOffset(startColumn, context); + const startOffset = this._readPixelOffset(this.domNode, startColumn, context); if (startOffset === -1) { return null; } - const endOffset = this._readPixelOffset(endColumn, context); + const endOffset = this._readPixelOffset(this.domNode, endColumn, context); if (endOffset === -1) { return null; } @@ -473,23 +485,23 @@ class RenderedViewLine implements IRenderedViewLine { return [new HorizontalRange(startOffset, endOffset - startOffset)]; } - return this._readVisibleRangesForRange(startColumn, endColumn, context); + return this._readVisibleRangesForRange(this.domNode, startColumn, endColumn, context); } - protected _readVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null { + protected _readVisibleRangesForRange(domNode: FastDomNode, startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null { if (startColumn === endColumn) { - const pixelOffset = this._readPixelOffset(startColumn, context); + const pixelOffset = this._readPixelOffset(domNode, startColumn, context); if (pixelOffset === -1) { return null; } else { return [new HorizontalRange(pixelOffset, 0)]; } } else { - return this._readRawVisibleRangesForRange(startColumn, endColumn, context); + return this._readRawVisibleRangesForRange(domNode, startColumn, endColumn, context); } } - protected _readPixelOffset(column: number, context: DomReadingContext): number { + protected _readPixelOffset(domNode: FastDomNode, column: number, context: DomReadingContext): number { if (this._characterMapping.length === 0) { // This line has no content if (this._containsForeignElements === ForeignElementType.None) { @@ -514,18 +526,18 @@ class RenderedViewLine implements IRenderedViewLine { return cachedPixelOffset; } - const result = this._actualReadPixelOffset(column, context); + const result = this._actualReadPixelOffset(domNode, column, context); this._pixelOffsetCache[column] = result; return result; } - return this._actualReadPixelOffset(column, context); + return this._actualReadPixelOffset(domNode, column, context); } - private _actualReadPixelOffset(column: number, context: DomReadingContext): number { + private _actualReadPixelOffset(domNode: FastDomNode, column: number, context: DomReadingContext): number { if (this._characterMapping.length === 0) { // This line has no content - const r = RangeUtil.readHorizontalRanges(this._getReadingTarget(), 0, 0, 0, 0, context.clientRectDeltaLeft, context.endNode); + const r = RangeUtil.readHorizontalRanges(this._getReadingTarget(domNode), 0, 0, 0, 0, context.clientRectDeltaLeft, context.endNode); if (!r || r.length === 0) { return -1; } @@ -541,14 +553,14 @@ class RenderedViewLine implements IRenderedViewLine { const partIndex = CharacterMapping.getPartIndex(partData); const charOffsetInPart = CharacterMapping.getCharIndex(partData); - const r = RangeUtil.readHorizontalRanges(this._getReadingTarget(), partIndex, charOffsetInPart, partIndex, charOffsetInPart, context.clientRectDeltaLeft, context.endNode); + const r = RangeUtil.readHorizontalRanges(this._getReadingTarget(domNode), partIndex, charOffsetInPart, partIndex, charOffsetInPart, context.clientRectDeltaLeft, context.endNode); if (!r || r.length === 0) { return -1; } return r[0].left; } - private _readRawVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null { + private _readRawVisibleRangesForRange(domNode: FastDomNode, startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null { if (startColumn === 1 && endColumn === this._characterMapping.length) { // This branch helps IE with bidi text & gives a performance boost to other browsers when reading visible ranges for an entire line @@ -564,7 +576,7 @@ class RenderedViewLine implements IRenderedViewLine { const endPartIndex = CharacterMapping.getPartIndex(endPartData); const endCharOffsetInPart = CharacterMapping.getCharIndex(endPartData); - return RangeUtil.readHorizontalRanges(this._getReadingTarget(), startPartIndex, startCharOffsetInPart, endPartIndex, endCharOffsetInPart, context.clientRectDeltaLeft, context.endNode); + return RangeUtil.readHorizontalRanges(this._getReadingTarget(domNode), startPartIndex, startCharOffsetInPart, endPartIndex, endCharOffsetInPart, context.clientRectDeltaLeft, context.endNode); } /** @@ -585,8 +597,8 @@ class RenderedViewLine implements IRenderedViewLine { } class WebKitRenderedViewLine extends RenderedViewLine { - protected _readVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null { - const output = super._readVisibleRangesForRange(startColumn, endColumn, context); + protected _readVisibleRangesForRange(domNode: FastDomNode, startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null { + const output = super._readVisibleRangesForRange(domNode, startColumn, endColumn, context); if (!output || output.length === 0 || startColumn === endColumn || (startColumn === 1 && endColumn === this._characterMapping.length)) { return output; @@ -597,7 +609,7 @@ class WebKitRenderedViewLine extends RenderedViewLine { if (!this.input.containsRTL) { // This is an attempt to patch things up // Find position of last column - const endPixelOffset = this._readPixelOffset(endColumn, context); + const endPixelOffset = this._readPixelOffset(domNode, endColumn, context); if (endPixelOffset !== -1) { const lastRange = output[output.length - 1]; if (lastRange.left < endPixelOffset) { @@ -618,10 +630,10 @@ const createRenderedLine: (domNode: FastDomNode | null, renderLineI return createNormalRenderedLine; })(); -function createWebKitRenderedLine(domNode: FastDomNode, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: ForeignElementType): RenderedViewLine { +function createWebKitRenderedLine(domNode: FastDomNode | null, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: ForeignElementType): RenderedViewLine { return new WebKitRenderedViewLine(domNode, renderLineInput, characterMapping, containsRTL, containsForeignElements); } -function createNormalRenderedLine(domNode: FastDomNode, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: ForeignElementType): RenderedViewLine { +function createNormalRenderedLine(domNode: FastDomNode | null, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: ForeignElementType): RenderedViewLine { return new RenderedViewLine(domNode, renderLineInput, characterMapping, containsRTL, containsForeignElements); } diff --git a/src/vs/editor/browser/viewParts/lines/viewLines.css b/src/vs/editor/browser/viewParts/lines/viewLines.css index b43f182662..46c57aa0fa 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLines.css +++ b/src/vs/editor/browser/viewParts/lines/viewLines.css @@ -17,12 +17,9 @@ .monaco-editor.no-user-select .lines-content, .monaco-editor.no-user-select .view-line, .monaco-editor.no-user-select .view-lines { + user-select: none; -webkit-user-select: none; -ms-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -o-user-select: none; - user-select: none; } .monaco-editor .view-lines { @@ -45,4 +42,4 @@ float: none; min-height: inherit; margin-left: inherit; -}*/ \ No newline at end of file +}*/ diff --git a/src/vs/editor/browser/viewParts/lines/viewLines.ts b/src/vs/editor/browser/viewParts/lines/viewLines.ts index 5710667d8b..664e0d2575 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLines.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLines.ts @@ -12,9 +12,8 @@ import { PartFingerprint, PartFingerprints, ViewPart } from 'vs/editor/browser/v import { DomReadingContext, ViewLine, ViewLineOptions } from 'vs/editor/browser/viewParts/lines/viewLine'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { Selection } from 'vs/editor/common/core/selection'; import { ScrollType } from 'vs/editor/common/editorCommon'; -import { HorizontalRange, IViewLines, LineVisibleRanges } from 'vs/editor/common/view/renderingContext'; +import { IViewLines, LineVisibleRanges, VisibleRanges, HorizontalPosition } from 'vs/editor/common/view/renderingContext'; import { ViewContext } from 'vs/editor/common/view/viewContext'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { ViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; @@ -74,8 +73,8 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, private _typicalHalfwidthCharacterWidth: number; private _isViewportWrapping: boolean; private _revealHorizontalRightPadding: number; - private _selections: Selection[]; private _cursorSurroundingLines: number; + private _cursorSurroundingLinesStyle: 'default' | 'all'; private _canUseLayerHinting: boolean; private _viewLineOptions: ViewLineOptions; @@ -103,9 +102,9 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, this._isViewportWrapping = wrappingInfo.isViewportWrapping; this._revealHorizontalRightPadding = options.get(EditorOption.revealHorizontalRightPadding); this._cursorSurroundingLines = options.get(EditorOption.cursorSurroundingLines); + this._cursorSurroundingLinesStyle = options.get(EditorOption.cursorSurroundingLinesStyle); this._canUseLayerHinting = !options.get(EditorOption.disableLayerHinting); this._viewLineOptions = new ViewLineOptions(conf, this._context.theme.type); - this._selections = []; PartFingerprints.write(this.domNode, PartFingerprint.ViewLines); this.domNode.setClassName('view-lines'); @@ -156,6 +155,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, this._isViewportWrapping = wrappingInfo.isViewportWrapping; this._revealHorizontalRightPadding = options.get(EditorOption.revealHorizontalRightPadding); this._cursorSurroundingLines = options.get(EditorOption.cursorSurroundingLines); + this._cursorSurroundingLinesStyle = options.get(EditorOption.cursorSurroundingLinesStyle); this._canUseLayerHinting = !options.get(EditorOption.disableLayerHinting); Configuration.applyFontInfo(this.domNode, fontInfo); @@ -186,7 +186,6 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, return false; } public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { - this._selections = e.selections; const rendStartLineNumber = this._visibleLines.getStartLineNumber(); const rendEndLineNumber = this._visibleLines.getEndLineNumber(); let r = false; @@ -390,7 +389,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, const endColumn = lineNumber === range.endLineNumber ? range.endColumn : this._context.model.getLineMaxColumn(lineNumber); const visibleRangesForLine = this._visibleLines.getVisibleLine(lineNumber).getVisibleRangesForRange(startColumn, endColumn, domReadingContext); - if (!visibleRangesForLine || visibleRangesForLine.length === 0) { + if (!visibleRangesForLine) { continue; } @@ -399,11 +398,11 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, nextLineModelLineNumber = this._context.model.coordinatesConverter.convertViewPositionToModelPosition(new Position(lineNumber + 1, 1)).lineNumber; if (currentLineModelLineNumber !== nextLineModelLineNumber) { - visibleRangesForLine[visibleRangesForLine.length - 1].width += this._typicalHalfwidthCharacterWidth; + visibleRangesForLine.ranges[visibleRangesForLine.ranges.length - 1].width += this._typicalHalfwidthCharacterWidth; } } - visibleRanges[visibleRangesLen++] = new LineVisibleRanges(lineNumber, visibleRangesForLine); + visibleRanges[visibleRangesLen++] = new LineVisibleRanges(visibleRangesForLine.outsideRenderedLine, lineNumber, visibleRangesForLine.ranges); } if (visibleRangesLen === 0) { @@ -413,54 +412,26 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, return visibleRanges; } - private visibleRangesForRange2(_range: Range): HorizontalRange[] | null { - + private _visibleRangesForLineRange(lineNumber: number, startColumn: number, endColumn: number): VisibleRanges | null { if (this.shouldRender()) { // Cannot read from the DOM because it is dirty // i.e. the model & the dom are out of sync, so I'd be reading something stale return null; } - const range = Range.intersectRanges(_range, this._lastRenderedData.getCurrentVisibleRange()); - if (!range) { + if (lineNumber < this._visibleLines.getStartLineNumber() || lineNumber > this._visibleLines.getEndLineNumber()) { return null; } - let result: HorizontalRange[] = []; - const domReadingContext = new DomReadingContext(this.domNode.domNode, this._textRangeRestingSpot); - - const rendStartLineNumber = this._visibleLines.getStartLineNumber(); - const rendEndLineNumber = this._visibleLines.getEndLineNumber(); - for (let lineNumber = range.startLineNumber; lineNumber <= range.endLineNumber; lineNumber++) { - - if (lineNumber < rendStartLineNumber || lineNumber > rendEndLineNumber) { - continue; - } - - const startColumn = lineNumber === range.startLineNumber ? range.startColumn : 1; - const endColumn = lineNumber === range.endLineNumber ? range.endColumn : this._context.model.getLineMaxColumn(lineNumber); - const visibleRangesForLine = this._visibleLines.getVisibleLine(lineNumber).getVisibleRangesForRange(startColumn, endColumn, domReadingContext); - - if (!visibleRangesForLine || visibleRangesForLine.length === 0) { - continue; - } - - result = result.concat(visibleRangesForLine); - } - - if (result.length === 0) { - return null; - } - - return result; + return this._visibleLines.getVisibleLine(lineNumber).getVisibleRangesForRange(startColumn, endColumn, new DomReadingContext(this.domNode.domNode, this._textRangeRestingSpot)); } - public visibleRangeForPosition(position: Position): HorizontalRange | null { - const visibleRanges = this.visibleRangesForRange2(new Range(position.lineNumber, position.column, position.lineNumber, position.column)); + public visibleRangeForPosition(position: Position): HorizontalPosition | null { + const visibleRanges = this._visibleRangesForLineRange(position.lineNumber, position.column, position.column); if (!visibleRanges) { return null; } - return visibleRanges[0]; + return new HorizontalPosition(visibleRanges.outsideRenderedLine, visibleRanges.ranges[0].left); } // --- implementation @@ -573,6 +544,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, // (3) handle scrolling this._linesContent.setLayerHinting(this._canUseLayerHinting); + this._linesContent.setContain('strict'); const adjustedScrollTop = this._context.viewLayout.getCurrentScrollTop() - viewportData.bigNumbersDelta; this._linesContent.setTop(-adjustedScrollTop); this._linesContent.setLeft(-this._context.viewLayout.getCurrentScrollLeft()); @@ -599,10 +571,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, boxStartY = this._context.viewLayout.getVerticalOffsetForLineNumber(range.startLineNumber); boxEndY = this._context.viewLayout.getVerticalOffsetForLineNumber(range.endLineNumber) + this._lineHeight; - const shouldIgnoreScrollOff = source === 'mouse' && ( - this._selections.length > 1 // scroll off might trigger scrolling and mess up with multi cursor - || (this._selections.length > 0 && this._selections[0].isEmpty()) // we don't want to single click triggering selection - ); + const shouldIgnoreScrollOff = source === 'mouse' && this._cursorSurroundingLinesStyle === 'default'; if (!shouldIgnoreScrollOff) { const context = Math.min((viewportHeight / this._lineHeight) / 2, this._cursorSurroundingLines); @@ -617,7 +586,10 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, let newScrollTop: number; - if (verticalType === viewEvents.VerticalRevealType.Center || verticalType === viewEvents.VerticalRevealType.CenterIfOutsideViewport) { + if (boxEndY - boxStartY > viewportHeight) { + // the box is larger than the viewport ... scroll to its top + newScrollTop = boxStartY; + } else if (verticalType === viewEvents.VerticalRevealType.Center || verticalType === viewEvents.VerticalRevealType.CenterIfOutsideViewport) { if (verticalType === viewEvents.VerticalRevealType.CenterIfOutsideViewport && viewportStartY <= boxStartY && boxEndY <= viewportEndY) { // Box is already in the viewport... do nothing newScrollTop = viewportStartY; @@ -641,7 +613,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, const viewportStartX = viewport.left; const viewportEndX = viewportStartX + viewport.width; - const visibleRanges = this.visibleRangesForRange2(new Range(lineNumber, startColumn, lineNumber, endColumn)); + const visibleRanges = this._visibleRangesForLineRange(lineNumber, startColumn, endColumn); let boxStartX = Constants.MAX_SAFE_SMALL_INTEGER; let boxEndX = 0; @@ -653,7 +625,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, }; } - for (const visibleRange of visibleRanges) { + for (const visibleRange of visibleRanges.ranges) { if (visibleRange.left < boxStartX) { boxStartX = visibleRange.left; } diff --git a/src/vs/editor/browser/viewParts/margin/margin.ts b/src/vs/editor/browser/viewParts/margin/margin.ts index b55687e3b1..52e4677c32 100644 --- a/src/vs/editor/browser/viewParts/margin/margin.ts +++ b/src/vs/editor/browser/viewParts/margin/margin.ts @@ -78,6 +78,7 @@ export class Margin extends ViewPart { public render(ctx: RestrictedRenderingContext): void { this._domNode.setLayerHinting(this._canUseLayerHinting); + this._domNode.setContain('strict'); const adjustedScrollTop = ctx.scrollTop - ctx.bigNumbersDelta; this._domNode.setTop(-adjustedScrollTop); diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 974b15eb6d..d7f1c47b0c 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -13,7 +13,7 @@ import * as platform from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; import { ILine, RenderedLinesCollection } from 'vs/editor/browser/view/viewLayer'; import { PartFingerprint, PartFingerprints, ViewPart } from 'vs/editor/browser/view/viewPart'; -import { RenderMinimap, EditorOption } from 'vs/editor/common/config/editorOptions'; +import { RenderMinimap, EditorOption, MINIMAP_GUTTER_WIDTH } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; import { RGBA8 } from 'vs/editor/common/core/rgba'; import { IConfiguration, ScrollType } from 'vs/editor/common/editorCommon'; @@ -32,6 +32,8 @@ import { Selection } from 'vs/editor/common/core/selection'; import { Color } from 'vs/base/common/color'; import { GestureEvent, EventType, Gesture } from 'vs/base/browser/touch'; import { MinimapCharRendererFactory } from 'vs/editor/browser/viewParts/minimap/minimapCharRendererFactory'; +import { MinimapPosition } from 'vs/editor/common/model'; +import { once } from 'vs/base/common/functional'; function getMinimapLineHeight(renderMinimap: RenderMinimap, scale: number): number { if (renderMinimap === RenderMinimap.Text) { @@ -54,6 +56,8 @@ function getMinimapCharWidth(renderMinimap: RenderMinimap, scale: number): numbe */ const MOUSE_DRAG_RESET_DISTANCE = 140; +const GUTTER_DECORATION_WIDTH = 2; + class MinimapOptions { public readonly renderMinimap: RenderMinimap; @@ -70,7 +74,7 @@ class MinimapOptions { public readonly fontScale: number; - public readonly charRenderer: MinimapCharRenderer; + public readonly charRenderer: () => MinimapCharRenderer; /** * container dom node left position (in CSS px) @@ -114,7 +118,7 @@ class MinimapOptions { const minimapOpts = options.get(EditorOption.minimap); this.showSlider = minimapOpts.showSlider; this.fontScale = Math.round(minimapOpts.scale * pixelRatio); - this.charRenderer = MinimapCharRendererFactory.create(this.fontScale, fontInfo.fontFamily); + this.charRenderer = once(() => MinimapCharRendererFactory.create(this.fontScale, fontInfo.fontFamily)); this.pixelRatio = pixelRatio; this.typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth; this.lineHeight = options.get(EditorOption.lineHeight); @@ -500,6 +504,7 @@ export class Minimap extends ViewPart { this._slider.setPosition('absolute'); this._slider.setClassName('minimap-slider'); this._slider.setLayerHinting(true); + this._slider.setContain('strict'); this._domNode.appendChild(this._slider); this._sliderHorizontal = createFastDomNode(document.createElement('div')); @@ -815,9 +820,20 @@ export class Minimap extends ViewPart { continue; } + const decorationColor = (decoration.options.minimap).getColor(this._context.theme); for (let line = decoration.range.startLineNumber; line <= decoration.range.endLineNumber; line++) { - const decorationColor = (decoration.options.minimap).getColor(this._context.theme); - this.renderDecorationOnLine(canvasContext, lineOffsetMap, decoration.range, decorationColor, layout, line, lineHeight, lineHeight, tabSize, characterWidth); + switch (decoration.options.minimap.position) { + + case MinimapPosition.Inline: + this.renderDecorationOnLine(canvasContext, lineOffsetMap, decoration.range, decorationColor, layout, line, lineHeight, lineHeight, tabSize, characterWidth); + continue; + + case MinimapPosition.Gutter: + const y = (line - layout.startLineNumber) * lineHeight; + const x = 2; + this.renderDecoration(canvasContext, decorationColor, x, y, GUTTER_DECORATION_WIDTH, lineHeight); + continue; + } } } } @@ -835,12 +851,17 @@ export class Minimap extends ViewPart { charWidth: number): void { const y = (lineNumber - layout.startLineNumber) * lineHeight; + // Skip rendering the line if it's vertically outside our viewport + if (y + height < 0 || y > this._options.canvasOuterHeight) { + return; + } + // Cache line offset data so that it is only read once per line let lineIndexToXOffset = lineOffsetMap.get(lineNumber); const isFirstDecorationForLine = !lineIndexToXOffset; if (!lineIndexToXOffset) { const lineData = this._context.model.getLineContent(lineNumber); - lineIndexToXOffset = [0]; + lineIndexToXOffset = [MINIMAP_GUTTER_WIDTH]; for (let i = 1; i < lineData.length + 1; i++) { const charCode = lineData.charCodeAt(i - 1); const dx = charCode === CharCode.Tab @@ -856,7 +877,7 @@ export class Minimap extends ViewPart { } const { startColumn, endColumn, startLineNumber, endLineNumber } = decorationRange; - const x = startLineNumber === lineNumber ? lineIndexToXOffset[startColumn - 1] : 0; + const x = startLineNumber === lineNumber ? lineIndexToXOffset[startColumn - 1] : MINIMAP_GUTTER_WIDTH; const endColumnForLine = endLineNumber > lineNumber ? lineIndexToXOffset.length - 1 : endColumn - 1; @@ -875,7 +896,7 @@ export class Minimap extends ViewPart { private renderLineHighlight(canvasContext: CanvasRenderingContext2D, decorationColor: Color | undefined, y: number, height: number): void { canvasContext.fillStyle = decorationColor && decorationColor.transparent(0.5).toString() || ''; - canvasContext.fillRect(0, y, canvasContext.canvas.width, height); + canvasContext.fillRect(MINIMAP_GUTTER_WIDTH, y, canvasContext.canvas.width, height); } private renderDecoration(canvasContext: CanvasRenderingContext2D, decorationColor: Color | undefined, x: number, y: number, width: number, height: number) { @@ -885,6 +906,7 @@ export class Minimap extends ViewPart { private renderLines(layout: MinimapLayout): RenderData { const renderMinimap = this._options.renderMinimap; + const charRenderer = this._options.charRenderer(); const startLineNumber = layout.startLineNumber; const endLineNumber = layout.endLineNumber; const minimapLineHeight = getMinimapLineHeight(renderMinimap, this._options.fontScale); @@ -926,7 +948,7 @@ export class Minimap extends ViewPart { useLighterFont, renderMinimap, this._tokensColorTracker, - this._options.charRenderer, + charRenderer, dy, tabSize, lineInfo.data[lineIndex]!, @@ -1062,7 +1084,7 @@ export class Minimap extends ViewPart { const charWidth = getMinimapCharWidth(renderMinimap, fontScale); const maxDx = target.width - charWidth; - let dx = 0; + let dx = MINIMAP_GUTTER_WIDTH; let charIndex = 0; let tabsCharDelta = 0; @@ -1094,7 +1116,7 @@ export class Minimap extends ViewPart { if (renderMinimap === RenderMinimap.Blocks) { minimapCharRenderer.blockRenderChar(target, dx, dy, tokenColor, backgroundColor, useLighterFont); } else { // RenderMinimap.Text - minimapCharRenderer.renderChar(target, dx, dy, charCode, tokenColor, backgroundColor, useLighterFont); + minimapCharRenderer.renderChar(target, dx, dy, charCode, tokenColor, backgroundColor, fontScale, useLighterFont); } dx += charWidth; diff --git a/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts b/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts index 03ec73e22e..f4f5144e27 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts @@ -32,6 +32,7 @@ export class MinimapCharRenderer { chCode: number, color: RGBA8, backgroundColor: RGBA8, + fontScale: number, useLighterFont: boolean ): void { const charWidth = Constants.BASE_CHAR_WIDTH * this.scale; @@ -42,7 +43,7 @@ export class MinimapCharRenderer { } const charData = useLighterFont ? this.charDataLight : this.charDataNormal; - const charIndex = getCharIndex(chCode); + const charIndex = getCharIndex(chCode, fontScale); const destWidth = target.width * Constants.RGBA_CHANNELS_CNT; diff --git a/src/vs/editor/browser/viewParts/minimap/minimapCharRendererFactory.ts b/src/vs/editor/browser/viewParts/minimap/minimapCharRendererFactory.ts index 5b8a80917d..300d51789f 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimapCharRendererFactory.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimapCharRendererFactory.ts @@ -5,6 +5,7 @@ import { MinimapCharRenderer } from 'vs/editor/browser/viewParts/minimap/minimapCharRenderer'; import { allCharCodes } from 'vs/editor/browser/viewParts/minimap/minimapCharSheet'; +import { prebakedMiniMaps } from 'vs/editor/browser/viewParts/minimap/minimapPreBaked'; import { Constants } from './minimapCharSheet'; /** @@ -28,10 +29,16 @@ export class MinimapCharRendererFactory { return this.lastCreated; } - const factory = MinimapCharRendererFactory.createFromSampleData( - MinimapCharRendererFactory.createSampleData(fontFamily).data, - scale - ); + let factory: MinimapCharRenderer; + if (prebakedMiniMaps[scale]) { + factory = new MinimapCharRenderer(prebakedMiniMaps[scale](), scale); + } else { + factory = MinimapCharRendererFactory.createFromSampleData( + MinimapCharRendererFactory.createSampleData(fontFamily).data, + scale + ); + } + this.lastFontFamily = fontFamily; this.lastCreated = factory; return factory; diff --git a/src/vs/editor/browser/viewParts/minimap/minimapCharSheet.ts b/src/vs/editor/browser/viewParts/minimap/minimapCharSheet.ts index 5ca6c82254..dde22130ea 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimapCharSheet.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimapCharSheet.ts @@ -29,9 +29,13 @@ export const allCharCodes: ReadonlyArray = (() => { return v; })(); -export const getCharIndex = (chCode: number) => { +export const getCharIndex = (chCode: number, fontScale: number) => { chCode -= Constants.START_CH_CODE; if (chCode < 0 || chCode > Constants.CHAR_COUNT) { + if (fontScale <= 2) { + // for smaller scales, we can get away with using any ASCII character... + return (chCode + Constants.CHAR_COUNT) % Constants.CHAR_COUNT; + } return Constants.CHAR_COUNT - 1; // unknown symbol } diff --git a/src/vs/editor/browser/viewParts/minimap/minimapPreBaked.ts b/src/vs/editor/browser/viewParts/minimap/minimapPreBaked.ts new file mode 100644 index 0000000000..35f6be018b --- /dev/null +++ b/src/vs/editor/browser/viewParts/minimap/minimapPreBaked.ts @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { once } from 'vs/base/common/functional'; + +const charTable: { [hex: string]: number } = { + '0': 0, + '1': 1, + '2': 2, + '3': 3, + '4': 4, + '5': 5, + '6': 6, + '7': 7, + '8': 8, + '9': 9, + A: 10, + B: 11, + C: 12, + D: 13, + E: 14, + F: 15 +}; + +const decodeData = (str: string) => { + const output = new Uint8ClampedArray(str.length / 2); + for (let i = 0; i < str.length; i += 2) { + output[i >> 1] = (charTable[str[i]] << 4) | (charTable[str[i + 1]] & 0xF); + } + + return output; +}; + +/* +const encodeData = (data: Uint8ClampedArray, length: string) => { + const chars = '0123456789ABCDEF'; + let output = ''; + for (let i = 0; i < data.length; i++) { + output += chars[data[i] >> 4] + chars[data[i] & 0xf]; + } + return output; +}; +*/ + +/** + * Map of minimap scales to prebaked sample data at those scales. We don't + * sample much larger data, because then font family becomes visible, which + * is use-configurable. + */ +export const prebakedMiniMaps: { [scale: number]: () => Uint8ClampedArray } = { + 1: once(() => + decodeData( + '0000511D6300CF609C709645A78432005642574171487021003C451900274D35D762755E8B629C5BA856AF57BA649530C167D1512A272A3F6038604460398526BCA2A968DB6F8957C768BE5FBE2FB467CF5D8D5B795DC7625B5DFF50DE64C466DB2FC47CD860A65E9A2EB96CB54CE06DA763AB2EA26860524D3763536601005116008177A8705E53AB738E6A982F88BAA35B5F5B626D9C636B449B737E5B7B678598869A662F6B5B8542706C704C80736A607578685B70594A49715A4522E792' + ) + ), + 2: once(() => + decodeData( + '000000000000000055394F383D2800008B8B1F210002000081B1CBCBCC820000847AAF6B9AAF2119BE08B8881AD60000A44FD07DCCF107015338130C00000000385972265F390B406E2437634B4B48031B12B8A0847000001E15B29A402F0000000000004B33460B00007A752C2A0000000000004D3900000084394B82013400ABA5CFC7AD9C0302A45A3E5A98AB000089A43382D97900008BA54AA087A70A0248A6A7AE6DBE0000BF6F94987EA40A01A06DCFA7A7A9030496C32F77891D0000A99FB1A0AFA80603B29AB9CA75930D010C0948354D3900000C0948354F37460D0028BE673D8400000000AF9D7B6E00002B007AA8933400007AA642675C2700007984CFB9C3985B768772A8A6B7B20000CAAECAAFC4B700009F94A6009F840009D09F9BA4CA9C0000CC8FC76DC87F0000C991C472A2000000A894A48CA7B501079BA2C9C69BA20000B19A5D3FA89000005CA6009DA2960901B0A7F0669FB200009D009E00B7890000DAD0F5D092820000D294D4C48BD10000B5A7A4A3B1A50402CAB6CBA6A2000000B5A7A4A3B1A8044FCDADD19D9CB00000B7778F7B8AAE0803C9AB5D3F5D3F00009EA09EA0BAB006039EA0989A8C7900009B9EF4D6B7C00000A9A7816CACA80000ABAC84705D3F000096DA635CDC8C00006F486F266F263D4784006124097B00374F6D2D6D2D6D4A3A95872322000000030000000000008D8939130000000000002E22A5C9CBC70600AB25C0B5C9B400061A2DB04CA67001082AA6BEBEBFC606002321DACBC19E03087AA08B6768380000282FBAC0B8CA7A88AD25BBA5A29900004C396C5894A6000040485A6E356E9442A32CD17EADA70000B4237923628600003E2DE9C1D7B500002F25BBA5A2990000231DB6AFB4A804023025C0B5CAB588062B2CBDBEC0C706882435A75CA20000002326BD6A82A908048B4B9A5A668000002423A09CB4BB060025259C9D8A7900001C1FCAB2C7C700002A2A9387ABA200002626A4A47D6E9D14333163A0C87500004B6F9C2D643A257049364936493647358A34438355497F1A0000A24C1D590000D38DFFBDD4CD3126' + ) + ) +}; diff --git a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts index f9f47e7c8d..148cd44427 100644 --- a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts +++ b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts @@ -215,6 +215,7 @@ export class DecorationsOverviewRuler extends ViewPart { this._domNode.setClassName('decorationsOverviewRuler'); this._domNode.setPosition('absolute'); this._domNode.setLayerHinting(true); + this._domNode.setContain('strict'); this._domNode.setAttribute('aria-hidden', 'true'); this._updateSettings(false); diff --git a/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts b/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts index 5d12d2ac3a..b2bc2399b4 100644 --- a/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts +++ b/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts @@ -26,6 +26,7 @@ export class OverviewRuler extends ViewEventHandler implements IOverviewRuler { this._domNode.setClassName(cssClassName); this._domNode.setPosition('absolute'); this._domNode.setLayerHinting(true); + this._domNode.setContain('strict'); this._zoneManager = new OverviewZoneManager((lineNumber: number) => this._context.viewLayout.getVerticalOffsetForLineNumber(lineNumber)); this._zoneManager.setDOMWidth(0); diff --git a/src/vs/editor/browser/viewParts/selections/selections.ts b/src/vs/editor/browser/viewParts/selections/selections.ts index 91c8058cf8..a5b67cbe65 100644 --- a/src/vs/editor/browser/viewParts/selections/selections.ts +++ b/src/vs/editor/browser/viewParts/selections/selections.ts @@ -369,7 +369,7 @@ export class SelectionsOverlay extends DynamicViewOverlay { private _previousFrameVisibleRangesWithStyle: (LineVisibleRangesWithStyle[] | null)[] = []; public prepareRender(ctx: RenderingContext): void { - // Build HTML for inner corners separate from HTML for the the rest of selections, + // Build HTML for inner corners separate from HTML for the rest of selections, // as the inner corner HTML can interfere with that of other selections. // In final render, make sure to place the inner corner HTML before the rest of selection HTML. See issue #77777. const output: [string, string][] = []; diff --git a/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts b/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts index 3c01e05ca6..fc95177975 100644 --- a/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts +++ b/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts @@ -121,7 +121,7 @@ export class ViewCursor { if (this._cursorStyle === TextEditorCursorStyle.Line || this._cursorStyle === TextEditorCursorStyle.LineThin) { const visibleRange = ctx.visibleRangeForPosition(this._position); - if (!visibleRange) { + if (!visibleRange || visibleRange.outsideRenderedLine) { // Outside viewport return null; } @@ -151,13 +151,18 @@ export class ViewCursor { const lineContent = this._context.model.getLineContent(this._position.lineNumber); const nextCharLength = strings.nextCharLength(lineContent, this._position.column - 1); const visibleRangeForCharacter = ctx.linesVisibleRangesForRange(new Range(this._position.lineNumber, this._position.column, this._position.lineNumber, this._position.column + nextCharLength), false); - - if (!visibleRangeForCharacter || visibleRangeForCharacter.length === 0 || visibleRangeForCharacter[0].ranges.length === 0) { + if (!visibleRangeForCharacter || visibleRangeForCharacter.length === 0) { // Outside viewport return null; } - const range = visibleRangeForCharacter[0].ranges[0]; + const firstVisibleRangeForCharacter = visibleRangeForCharacter[0]; + if (firstVisibleRangeForCharacter.outsideRenderedLine || firstVisibleRangeForCharacter.ranges.length === 0) { + // Outside viewport + return null; + } + + const range = firstVisibleRangeForCharacter.ranges[0]; const width = range.width < 1 ? this._typicalHalfwidthCharacterWidth : range.width; let textContentClassName = ''; diff --git a/src/vs/editor/browser/viewParts/viewZones/viewZones.ts b/src/vs/editor/browser/viewParts/viewZones/viewZones.ts index feff29fc48..76664fd367 100644 --- a/src/vs/editor/browser/viewParts/viewZones/viewZones.ts +++ b/src/vs/editor/browser/viewParts/viewZones/viewZones.ts @@ -5,7 +5,7 @@ import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { IViewZone } from 'vs/editor/browser/editorBrowser'; +import { IViewZone, IViewZoneChangeAccessor } from 'vs/editor/browser/editorBrowser'; import { ViewPart } from 'vs/editor/browser/view/viewPart'; import { Position } from 'vs/editor/common/core/position'; import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext'; @@ -13,7 +13,7 @@ import { ViewContext } from 'vs/editor/common/view/viewContext'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { IViewWhitespaceViewportData } from 'vs/editor/common/viewModel/viewModel'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; - +import { IWhitespaceChangeAccessor, IEditorWhitespace } from 'vs/editor/common/viewLayout/linesLayout'; export interface IMyViewZone { whitespaceId: string; @@ -29,6 +29,8 @@ interface IComputedViewZoneProps { minWidthInPx: number; } +const invalidFunc = () => { throw new Error(`Invalid change accessor`); }; + export class ViewZones extends ViewPart { private _zones: { [id: string]: IMyViewZone; }; @@ -72,20 +74,29 @@ export class ViewZones extends ViewPart { // ---- begin view event handlers private _recomputeWhitespacesProps(): boolean { - let hadAChange = false; - - const keys = Object.keys(this._zones); - for (let i = 0, len = keys.length; i < len; i++) { - const id = keys[i]; - const zone = this._zones[id]; - const props = this._computeWhitespaceProps(zone.delegate); - if (this._context.viewLayout.changeWhitespace(id, props.afterViewLineNumber, props.heightInPx)) { - this._safeCallOnComputedHeight(zone.delegate, props.heightInPx); - hadAChange = true; - } + const whitespaces = this._context.viewLayout.getWhitespaces(); + const oldWhitespaces = new Map(); + for (const whitespace of whitespaces) { + oldWhitespaces.set(whitespace.id, whitespace); } + return this._context.viewLayout.changeWhitespace((whitespaceAccessor: IWhitespaceChangeAccessor) => { + let hadAChange = false; - return hadAChange; + const keys = Object.keys(this._zones); + for (let i = 0, len = keys.length; i < len; i++) { + const id = keys[i]; + const zone = this._zones[id]; + const props = this._computeWhitespaceProps(zone.delegate); + const oldWhitespace = oldWhitespaces.get(id); + if (oldWhitespace && (oldWhitespace.afterLineNumber !== props.afterViewLineNumber || oldWhitespace.height !== props.heightInPx)) { + whitespaceAccessor.changeOneWhitespace(id, props.afterViewLineNumber, props.heightInPx); + this._safeCallOnComputedHeight(zone.delegate, props.heightInPx); + hadAChange = true; + } + } + + return hadAChange; + }); } public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { @@ -138,7 +149,6 @@ export class ViewZones extends ViewPart { return 10000; } - private _computeWhitespaceProps(zone: IViewZone): IComputedViewZoneProps { if (zone.afterLineNumber === 0) { return { @@ -188,9 +198,44 @@ export class ViewZones extends ViewPart { }; } - public addZone(zone: IViewZone): string { + public changeViewZones(callback: (changeAccessor: IViewZoneChangeAccessor) => any): boolean { + + return this._context.viewLayout.changeWhitespace((whitespaceAccessor: IWhitespaceChangeAccessor) => { + let zonesHaveChanged = false; + + const changeAccessor: IViewZoneChangeAccessor = { + addZone: (zone: IViewZone): string => { + zonesHaveChanged = true; + return this._addZone(whitespaceAccessor, zone); + }, + removeZone: (id: string): void => { + if (!id) { + return; + } + zonesHaveChanged = this._removeZone(whitespaceAccessor, id) || zonesHaveChanged; + }, + layoutZone: (id: string): void => { + if (!id) { + return; + } + zonesHaveChanged = this._layoutZone(whitespaceAccessor, id) || zonesHaveChanged; + } + }; + + safeInvoke1Arg(callback, changeAccessor); + + // Invalidate changeAccessor + changeAccessor.addZone = invalidFunc; + changeAccessor.removeZone = invalidFunc; + changeAccessor.layoutZone = invalidFunc; + + return zonesHaveChanged; + }); + } + + private _addZone(whitespaceAccessor: IWhitespaceChangeAccessor, zone: IViewZone): string { const props = this._computeWhitespaceProps(zone); - const whitespaceId = this._context.viewLayout.addWhitespace(props.afterViewLineNumber, this._getZoneOrdinal(zone), props.heightInPx, props.minWidthInPx); + const whitespaceId = whitespaceAccessor.insertWhitespace(props.afterViewLineNumber, this._getZoneOrdinal(zone), props.heightInPx, props.minWidthInPx); const myZone: IMyViewZone = { whitespaceId: whitespaceId, @@ -224,11 +269,11 @@ export class ViewZones extends ViewPart { return myZone.whitespaceId; } - public removeZone(id: string): boolean { + private _removeZone(whitespaceAccessor: IWhitespaceChangeAccessor, id: string): boolean { if (this._zones.hasOwnProperty(id)) { const zone = this._zones[id]; delete this._zones[id]; - this._context.viewLayout.removeWhitespace(zone.whitespaceId); + whitespaceAccessor.removeWhitespace(zone.whitespaceId); zone.domNode.removeAttribute('monaco-visible-view-zone'); zone.domNode.removeAttribute('monaco-view-zone'); @@ -247,21 +292,20 @@ export class ViewZones extends ViewPart { return false; } - public layoutZone(id: string): boolean { - let changed = false; + private _layoutZone(whitespaceAccessor: IWhitespaceChangeAccessor, id: string): boolean { if (this._zones.hasOwnProperty(id)) { const zone = this._zones[id]; const props = this._computeWhitespaceProps(zone.delegate); // const newOrdinal = this._getZoneOrdinal(zone.delegate); - changed = this._context.viewLayout.changeWhitespace(zone.whitespaceId, props.afterViewLineNumber, props.heightInPx) || changed; + whitespaceAccessor.changeOneWhitespace(zone.whitespaceId, props.afterViewLineNumber, props.heightInPx); // TODO@Alex: change `newOrdinal` too - if (changed) { - this._safeCallOnComputedHeight(zone.delegate, props.heightInPx); - this.setShouldRender(); - } + this._safeCallOnComputedHeight(zone.delegate, props.heightInPx); + this.setShouldRender(); + + return true; } - return changed; + return false; } public shouldSuppressMouseDownOnViewZone(id: string): boolean { @@ -365,3 +409,11 @@ export class ViewZones extends ViewPart { } } } + +function safeInvoke1Arg(func: Function, arg1: any): any { + try { + return func(arg1); + } catch (e) { + onUnexpectedError(e); + } +} diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 92f0985852..101667d0e6 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/editor'; -import 'vs/css!./media/tokens'; import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -41,7 +40,7 @@ import * as modes from 'vs/editor/common/modes'; import { editorUnnecessaryCodeBorder, editorUnnecessaryCodeOpacity } from 'vs/editor/common/view/editorColorRegistry'; import { editorErrorBorder, editorErrorForeground, editorHintBorder, editorHintForeground, editorInfoBorder, editorInfoForeground, editorWarningBorder, editorWarningForeground, editorForeground } from 'vs/platform/theme/common/colorRegistry'; import { VerticalRevealType } from 'vs/editor/common/view/viewEvents'; -import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer'; +import { IEditorWhitespace } from 'vs/editor/common/viewLayout/linesLayout'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -870,9 +869,11 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } public onVisible(): void { + this._modelData?.view.refreshFocusState(); } public onHide(): void { + this._modelData?.view.refreshFocusState(); } public getContribution(id: string): T { @@ -1371,6 +1372,9 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE const e2: ICursorSelectionChangedEvent = { selection: e.selections[0], secondarySelections: e.selections.slice(1), + modelVersionId: e.modelVersionId, + oldSelections: e.oldSelections, + oldModelVersionId: e.oldModelVersionId, source: e.source, reason: e.reason }; diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 5db54a7631..5febc3cf15 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -32,7 +32,7 @@ import { IDiffComputationResult, IEditorWorkerService } from 'vs/editor/common/s import { OverviewRulerZone } from 'vs/editor/common/view/overviewZoneManager'; import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations'; import { RenderLineInput, renderViewLine } from 'vs/editor/common/viewLayout/viewLineRenderer'; -import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer'; +import { IEditorWhitespace } from 'vs/editor/common/viewLayout/linesLayout'; import { InlineDecoration, InlineDecorationType, ViewLineRenderingData } from 'vs/editor/common/viewModel/viewModel'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -1394,12 +1394,16 @@ abstract class ViewZonesComputer { private readonly lineChanges: editorCommon.ILineChange[]; private readonly originalForeignVZ: IEditorWhitespace[]; + private readonly originalLineHeight: number; private readonly modifiedForeignVZ: IEditorWhitespace[]; + private readonly modifiedLineHeight: number; - constructor(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], modifiedForeignVZ: IEditorWhitespace[]) { + constructor(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], originalLineHeight: number, modifiedForeignVZ: IEditorWhitespace[], modifiedLineHeight: number) { this.lineChanges = lineChanges; this.originalForeignVZ = originalForeignVZ; + this.originalLineHeight = originalLineHeight; this.modifiedForeignVZ = modifiedForeignVZ; + this.modifiedLineHeight = modifiedLineHeight; } public getViewZones(): IEditorsZones { @@ -1474,7 +1478,7 @@ abstract class ViewZonesComputer { stepOriginal.push({ afterLineNumber: viewZoneLineNumber, - heightInLines: modifiedForeignVZ.current.heightInLines, + heightInLines: modifiedForeignVZ.current.height / this.modifiedLineHeight, domNode: null, marginDomNode: marginDomNode }); @@ -1491,7 +1495,7 @@ abstract class ViewZonesComputer { } stepModified.push({ afterLineNumber: viewZoneLineNumber, - heightInLines: originalForeignVZ.current.heightInLines, + heightInLines: originalForeignVZ.current.height / this.originalLineHeight, domNode: null }); originalForeignVZ.advance(); @@ -1750,7 +1754,7 @@ class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffE } protected _getViewZones(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], modifiedForeignVZ: IEditorWhitespace[], originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor): IEditorsZones { - let c = new SideBySideViewZonesComputer(lineChanges, originalForeignVZ, modifiedForeignVZ); + let c = new SideBySideViewZonesComputer(lineChanges, originalForeignVZ, originalEditor.getOption(EditorOption.lineHeight), modifiedForeignVZ, modifiedEditor.getOption(EditorOption.lineHeight)); return c.getViewZones(); } @@ -1877,8 +1881,8 @@ class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffE class SideBySideViewZonesComputer extends ViewZonesComputer { - constructor(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], modifiedForeignVZ: IEditorWhitespace[]) { - super(lineChanges, originalForeignVZ, modifiedForeignVZ); + constructor(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], originalLineHeight: number, modifiedForeignVZ: IEditorWhitespace[], modifiedLineHeight: number) { + super(lineChanges, originalForeignVZ, originalLineHeight, modifiedForeignVZ, modifiedLineHeight); } protected _createOriginalMarginDomNodeForModifiedForeignViewZoneInAddedRegion(): HTMLDivElement | null { @@ -2038,7 +2042,7 @@ class InlineViewZonesComputer extends ViewZonesComputer { private readonly renderIndicators: boolean; constructor(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], modifiedForeignVZ: IEditorWhitespace[], originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor, renderIndicators: boolean) { - super(lineChanges, originalForeignVZ, modifiedForeignVZ); + super(lineChanges, originalForeignVZ, originalEditor.getOption(EditorOption.lineHeight), modifiedForeignVZ, modifiedEditor.getOption(EditorOption.lineHeight)); this.originalModel = originalEditor.getModel()!; this.modifiedEditorOptions = modifiedEditor.getOptions(); this.modifiedEditorTabSize = modifiedEditor.getModel()!.getOptions().tabSize; diff --git a/src/vs/editor/browser/widget/diffNavigator.ts b/src/vs/editor/browser/widget/diffNavigator.ts index 5e956dba0f..da6788e23a 100644 --- a/src/vs/editor/browser/widget/diffNavigator.ts +++ b/src/vs/editor/browser/widget/diffNavigator.ts @@ -30,10 +30,17 @@ const defaultOptions: Options = { alwaysRevealFirst: true }; +export interface IDiffNavigator { + canNavigate(): boolean; + next(): void; + previous(): void; + dispose(): void; +} + /** * Create a new diff navigator for the provided diff editor. */ -export class DiffNavigator extends Disposable { +export class DiffNavigator extends Disposable implements IDiffNavigator { private readonly _editor: IDiffEditor; private readonly _options: Options; diff --git a/src/vs/editor/browser/widget/diffReview.ts b/src/vs/editor/browser/widget/diffReview.ts index a3082b9cde..6d7a9c20fc 100644 --- a/src/vs/editor/browser/widget/diffReview.ts +++ b/src/vs/editor/browser/widget/diffReview.ts @@ -695,7 +695,7 @@ export class DiffReview extends Disposable { if (originalLine !== 0) { originalLineNumber.appendChild(document.createTextNode(String(originalLine))); } else { - originalLineNumber.innerHTML = ' '; + originalLineNumber.innerHTML = ' '; } cell.appendChild(originalLineNumber); @@ -707,13 +707,13 @@ export class DiffReview extends Disposable { if (modifiedLine !== 0) { modifiedLineNumber.appendChild(document.createTextNode(String(modifiedLine))); } else { - modifiedLineNumber.innerHTML = ' '; + modifiedLineNumber.innerHTML = ' '; } cell.appendChild(modifiedLineNumber); const spacer = document.createElement('span'); spacer.className = spacerClassName; - spacer.innerHTML = '  '; + spacer.innerHTML = '  '; cell.appendChild(spacer); let lineContent: string; diff --git a/src/vs/editor/browser/widget/media/diffReview.css b/src/vs/editor/browser/widget/media/diffReview.css index f96044361e..1c4a1a7b0f 100644 --- a/src/vs/editor/browser/widget/media/diffReview.css +++ b/src/vs/editor/browser/widget/media/diffReview.css @@ -10,12 +10,9 @@ .monaco-diff-editor .diff-review { position: absolute; + user-select: none; -webkit-user-select: none; -ms-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -o-user-select: none; - user-select: none; } .monaco-diff-editor .diff-review-summary { @@ -67,4 +64,4 @@ .monaco-diff-editor.hc-black .action-label.icon.close-diff-review, .monaco-diff-editor.vs-dark .action-label.icon.close-diff-review { background: url('close-dark.svg') center center no-repeat; -} \ No newline at end of file +} diff --git a/src/vs/editor/common/commands/shiftCommand.ts b/src/vs/editor/common/commands/shiftCommand.ts index da4e75c846..413bc86f12 100644 --- a/src/vs/editor/common/commands/shiftCommand.ts +++ b/src/vs/editor/common/commands/shiftCommand.ts @@ -11,6 +11,7 @@ import { Selection, SelectionDirection } from 'vs/editor/common/core/selection'; import { ICommand, ICursorStateComputerData, IEditOperationBuilder } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; +import { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions'; export interface IShiftCommandOpts { isUnshift: boolean; @@ -18,6 +19,7 @@ export interface IShiftCommandOpts { indentSize: number; insertSpaces: boolean; useTabStops: boolean; + autoIndent: EditorAutoIndentStrategy; } const repeatCache: { [str: string]: string[]; } = Object.create(null); @@ -137,7 +139,7 @@ export class ShiftCommand implements ICommand { // The current line is "miss-aligned", so let's see if this is expected... // This can only happen when it has trailing commas in the indent if (model.isCheapToTokenize(lineNumber - 1)) { - let enterAction = LanguageConfigurationRegistry.getRawEnterActionAtPosition(model, lineNumber - 1, model.getLineMaxColumn(lineNumber - 1)); + let enterAction = LanguageConfigurationRegistry.getEnterAction(this._opts.autoIndent, model, new Range(lineNumber - 1, model.getLineMaxColumn(lineNumber - 1), lineNumber - 1, model.getLineMaxColumn(lineNumber - 1))); if (enterAction) { extraSpaces = previousLineExtraSpaces; if (enterAction.appendText) { diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index cb5d69d5cf..bc1a4ebf60 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -15,6 +15,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationRegistry, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; +import { forEach } from 'vs/base/common/collections'; /** * Control what pressing Tab does. @@ -197,6 +198,43 @@ function migrateOptions(options: IEditorOptions): void { options.tabCompletion = 'onlySnippets'; } + const suggest = options.suggest; + if (suggest && typeof (suggest).filteredTypes === 'object' && (suggest).filteredTypes) { + const mapping: Record = {}; + mapping['method'] = 'showMethods'; + mapping['function'] = 'showFunctions'; + mapping['constructor'] = 'showConstructors'; + mapping['field'] = 'showFields'; + mapping['variable'] = 'showVariables'; + mapping['class'] = 'showClasses'; + mapping['struct'] = 'showStructs'; + mapping['interface'] = 'showInterfaces'; + mapping['module'] = 'showModules'; + mapping['property'] = 'showProperties'; + mapping['event'] = 'showEvents'; + mapping['operator'] = 'showOperators'; + mapping['unit'] = 'showUnits'; + mapping['value'] = 'showValues'; + mapping['constant'] = 'showConstants'; + mapping['enum'] = 'showEnums'; + mapping['enumMember'] = 'showEnumMembers'; + mapping['keyword'] = 'showKeywords'; + mapping['text'] = 'showWords'; + mapping['color'] = 'showColors'; + mapping['file'] = 'showFiles'; + mapping['reference'] = 'showReferences'; + mapping['folder'] = 'showFolders'; + mapping['typeParameter'] = 'showTypeParameters'; + mapping['snippet'] = 'showSnippets'; + forEach(mapping, entry => { + const value = (suggest).filteredTypes[entry.key]; + if (value === false) { + (suggest)[entry.value] = value; + } + }); + // delete (suggest).filteredTypes; + } + const hover = options.hover; if (hover === true) { options.hover = { @@ -218,6 +256,13 @@ function migrateOptions(options: IEditorOptions): void { enabled: false }; } + + const autoIndent = options.autoIndent; + if (autoIndent === true) { + options.autoIndent = 'full'; + } else if (autoIndent === false) { + options.autoIndent = 'advanced'; + } } function deepCloneAndMigrateOptions(_options: IEditorOptions): IEditorOptions { @@ -304,18 +349,6 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed return EditorConfiguration2.computeOptions(this._validatedOptions, env); } - private static _primitiveArrayEquals(a: any[], b: any[]): boolean { - if (a.length !== b.length) { - return false; - } - for (let i = 0; i < a.length; i++) { - if (a[i] !== b[i]) { - return false; - } - } - return true; - } - private static _subsetEquals(base: { [key: string]: any }, subset: { [key: string]: any }): boolean { for (const key in subset) { if (hasOwnProperty.call(subset, key)) { @@ -326,7 +359,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed continue; } if (Array.isArray(baseValue) && Array.isArray(subsetValue)) { - if (!this._primitiveArrayEquals(baseValue, subsetValue)) { + if (!arrays.equals(baseValue, subsetValue)) { return false; } continue; @@ -387,14 +420,18 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed } -const configurationRegistry = Registry.as(Extensions.Configuration); -const editorConfiguration: IConfigurationNode = { +export const editorConfigurationBaseNode = Object.freeze({ id: 'editor', order: 5, type: 'object', title: nls.localize('editorConfigurationTitle', "Editor"), overridable: true, scope: ConfigurationScope.RESOURCE, +}); + +const configurationRegistry = Registry.as(Extensions.Configuration); +const editorConfiguration: IConfigurationNode = { + ...editorConfigurationBaseNode, properties: { 'editor.tabSize': { type: 'number', @@ -451,29 +488,6 @@ const editorConfiguration: IConfigurationNode = { default: 20_000, description: nls.localize('maxTokenizationLineLength', "Lines above this length will not be tokenized for performance reasons") }, - 'editor.codeActionsOnSave': { - type: 'object', - properties: { - 'source.organizeImports': { - type: 'boolean', - description: nls.localize('codeActionsOnSave.organizeImports', "Controls whether organize imports action should be run on file save.") - }, - 'source.fixAll': { - type: 'boolean', - description: nls.localize('codeActionsOnSave.fixAll', "Controls whether auto fix action should be run on file save.") - } - }, - 'additionalProperties': { - type: 'boolean' - }, - default: {}, - description: nls.localize('codeActionsOnSave', "Code action kinds to be run on save.") - }, - 'editor.codeActionsOnSaveTimeout': { - type: 'number', - default: 750, - description: nls.localize('codeActionsOnSaveTimeout', "Timeout in milliseconds after which the code actions that are run on save are cancelled.") - }, 'diffEditor.maxComputationTime': { type: 'number', default: 5000, diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index a7d8622ea7..4958674ce9 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -10,9 +10,9 @@ import { FontInfo } from 'vs/editor/common/config/fontInfo'; import { Constants } from 'vs/base/common/uint'; import { USUAL_WORD_SEPARATORS } from 'vs/editor/common/model/wordHelper'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; -import { isObject } from 'vs/base/common/types'; import { IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; import { IDimension } from 'vs/editor/common/editorCommon'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; //#region typed options @@ -31,6 +31,18 @@ export type EditorAutoSurroundStrategy = 'languageDefined' | 'quotes' | 'bracket */ export type EditorAutoClosingOvertypeStrategy = 'always' | 'auto' | 'never'; +/** + * Configuration options for auto indentation in the editor + * @internal + */ +export const enum EditorAutoIndentStrategy { + None = 0, + Keep = 1, + Brackets = 2, + Advanced = 3, + Full = 4 +} + /** * Configuration options for the editor. */ @@ -71,6 +83,12 @@ export interface IEditorOptions { * Defaults to 0. */ cursorSurroundingLines?: number; + /** + * Controls when `cursorSurroundingLines` should be enforced + * Defaults to `default`, `cursorSurroundingLines` is not enforced when cursor position is changed + * by mouse. + */ + cursorSurroundingLinesStyle?: 'default' | 'all'; /** * Render last line number when the file ends with a newline. * Defaults to true. @@ -137,7 +155,7 @@ export interface IEditorOptions { fixedOverflowWidgets?: boolean; /** * The number of vertical lanes the overview ruler should render. - * Defaults to 2. + * Defaults to 3. */ overviewRulerLanes?: number; /** @@ -180,8 +198,8 @@ export interface IEditorOptions { */ fontLigatures?: boolean | string; /** - * Disable the use of `will-change` for the editor margin and lines layers. - * The usage of `will-change` acts as a hint for browsers to create an extra layer. + * Disable the use of `transform: translate3d(0px, 0px, 0px)` for the editor margin and lines layers. + * The usage of `transform: translate3d(0px, 0px, 0px)` acts as a hint for browsers to create an extra layer. * Defaults to false. */ disableLayerHinting?: boolean; @@ -313,6 +331,10 @@ export interface IEditorOptions { * Defaults to 'auto'. It is best to leave this to 'auto'. */ accessibilitySupport?: 'auto' | 'off' | 'on'; + /** + * Controls the number of lines in the editor that can be read out by a screen reader + */ + accessibilityPageSize?: number; /** * Suggest options. */ @@ -358,7 +380,7 @@ export interface IEditorOptions { * Enable auto indentation adjustment. * Defaults to false. */ - autoIndent?: boolean; + autoIndent?: 'none' | 'keep' | 'brackets' | 'advanced' | 'full'; /** * Enable format on type. * Defaults to false. @@ -524,6 +546,12 @@ export interface IEditorConstructionOptions extends IEditorOptions { dimension?: IDimension; } +/** + * @internal + * The width of the minimap gutter, in pixels. + */ +export const MINIMAP_GUTTER_WIDTH = 8; + /** * Configuration options for the diff editor. */ @@ -909,6 +937,20 @@ class EditorEnumOption extends Bas //#endregion +//#region autoIndent + +function _autoIndentFromString(autoIndent: 'none' | 'keep' | 'brackets' | 'advanced' | 'full'): EditorAutoIndentStrategy { + switch (autoIndent) { + case 'none': return EditorAutoIndentStrategy.None; + case 'keep': return EditorAutoIndentStrategy.Keep; + case 'brackets': return EditorAutoIndentStrategy.Brackets; + case 'advanced': return EditorAutoIndentStrategy.Advanced; + case 'full': return EditorAutoIndentStrategy.Full; + } +} + +//#endregion + //#region accessibilitySupport class EditorAccessibilitySupport extends BaseEditorOption { @@ -1109,9 +1151,9 @@ export interface IEditorFindOptions { */ seedSearchStringFromSelection?: boolean; /** - * Controls if Find in Selection flag is turned on when multiple lines of text are selected in the editor. + * Controls if Find in Selection flag is turned on in the editor. */ - autoFindInSelection?: boolean; + autoFindInSelection?: 'never' | 'always' | 'multiline'; /* * Controls whether the Find Widget should add extra lines on top of the editor. */ @@ -1130,7 +1172,7 @@ class EditorFind extends BaseEditorOption constructor() { const defaults: EditorFindOptions = { seedSearchStringFromSelection: true, - autoFindInSelection: false, + autoFindInSelection: 'never', globalFindClipboard: false, addExtraSpaceOnTop: true }; @@ -1143,8 +1185,14 @@ class EditorFind extends BaseEditorOption description: nls.localize('find.seedSearchStringFromSelection', "Controls whether the search string in the Find Widget is seeded from the editor selection.") }, 'editor.find.autoFindInSelection': { - type: 'boolean', + type: 'string', + enum: ['never', 'always', 'multiline'], default: defaults.autoFindInSelection, + enumDescriptions: [ + nls.localize('editor.find.autoFindInSelection.never', 'Never turn on Find in selection automatically (default)'), + nls.localize('editor.find.autoFindInSelection.always', 'Always turn on Find in selection automatically'), + nls.localize('editor.find.autoFindInSelection.multiline', 'Turn on Find in selection automatically when multiple lines of content are selected.') + ], description: nls.localize('find.autoFindInSelection', "Controls whether the find operation is carried out on selected text or the entire file in the editor.") }, 'editor.find.globalFindClipboard': { @@ -1169,7 +1217,9 @@ class EditorFind extends BaseEditorOption const input = _input as IEditorFindOptions; return { seedSearchStringFromSelection: EditorBooleanOption.boolean(input.seedSearchStringFromSelection, this.defaultValue.seedSearchStringFromSelection), - autoFindInSelection: EditorBooleanOption.boolean(input.autoFindInSelection, this.defaultValue.autoFindInSelection), + autoFindInSelection: typeof _input.autoFindInSelection === 'boolean' + ? (_input.autoFindInSelection ? 'always' : 'never') + : EditorStringEnumOption.stringSet<'never' | 'always' | 'multiline'>(input.autoFindInSelection, this.defaultValue.autoFindInSelection, ['never', 'always', 'multiline']), globalFindClipboard: EditorBooleanOption.boolean(input.globalFindClipboard, this.defaultValue.globalFindClipboard), addExtraSpaceOnTop: EditorBooleanOption.boolean(input.addExtraSpaceOnTop, this.defaultValue.addExtraSpaceOnTop) }; @@ -1202,6 +1252,7 @@ export class EditorFontLigatures extends BaseEditorOption { EditorOption.fontSize, 'fontSize', EDITOR_FONT_DEFAULTS.fontSize, { type: 'number', + minimum: 6, + maximum: 100, default: EDITOR_FONT_DEFAULTS.fontSize, description: nls.localize('fontSize', "Controls the font size in pixels.") } @@ -1264,7 +1317,7 @@ class EditorFontSize extends SimpleEditorOption { if (r === 0) { return EDITOR_FONT_DEFAULTS.fontSize; } - return EditorFloatOption.clamp(r, 8, 100); + return EditorFloatOption.clamp(r, 6, 100); } public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, value: number): number { // The final fontSize respects the editor zoom level. @@ -1277,14 +1330,26 @@ class EditorFontSize extends SimpleEditorOption { //#region gotoLocation +export type GoToLocationValues = 'peek' | 'gotoAndPeek' | 'goto'; + /** * Configuration options for go to location */ export interface IGotoLocationOptions { - /** - * Control how goto-command work when having multiple results. - */ - multiple?: 'peek' | 'gotoAndPeek' | 'goto'; + + multiple?: GoToLocationValues; + + multipleDefinitions?: GoToLocationValues; + multipleTypeDefinitions?: GoToLocationValues; + multipleDeclarations?: GoToLocationValues; + multipleImplementations?: GoToLocationValues; + multipleReferences?: GoToLocationValues; + + alternativeDefinitionCommand?: string; + alternativeTypeDefinitionCommand?: string; + alternativeDeclarationCommand?: string; + alternativeImplementationCommand?: string; + alternativeReferenceCommand?: string; } export type GoToLocationOptions = Readonly>; @@ -1292,20 +1357,79 @@ export type GoToLocationOptions = Readonly>; class EditorGoToLocation extends BaseEditorOption { constructor() { - const defaults: GoToLocationOptions = { multiple: 'peek' }; + const defaults: GoToLocationOptions = { + multiple: 'peek', + multipleDefinitions: 'peek', + multipleTypeDefinitions: 'peek', + multipleDeclarations: 'peek', + multipleImplementations: 'peek', + multipleReferences: 'peek', + alternativeDefinitionCommand: 'editor.action.goToReferences', + alternativeTypeDefinitionCommand: 'editor.action.goToReferences', + alternativeDeclarationCommand: 'editor.action.goToReferences', + alternativeImplementationCommand: '', + alternativeReferenceCommand: '', + }; + const jsonSubset: IJSONSchema = { + type: 'string', + enum: ['peek', 'gotoAndPeek', 'goto'], + default: defaults.multiple, + enumDescriptions: [ + nls.localize('editor.gotoLocation.multiple.peek', 'Show peek view of the results (default)'), + nls.localize('editor.gotoLocation.multiple.gotoAndPeek', 'Go to the primary result and show a peek view'), + nls.localize('editor.gotoLocation.multiple.goto', 'Go to the primary result and enable peek-less navigation to others') + ] + }; super( EditorOption.gotoLocation, 'gotoLocation', defaults, { 'editor.gotoLocation.multiple': { - description: nls.localize('editor.gotoLocation.multiple', "Controls the behavior of 'Go To' commands, like Go To Definition, when multiple target locations exist."), + deprecationMessage: nls.localize('editor.gotoLocation.multiple.deprecated', "This setting is deprecated, please use separate settings like 'editor.editor.gotoLocation.multipleDefinitions' or 'editor.editor.gotoLocation.multipleImplementations' instead."), + }, + 'editor.gotoLocation.multipleDefinitions': { + description: nls.localize('editor.editor.gotoLocation.multipleDefinitions', "Controls the behavior the 'Go to Definition'-command when multiple target locations exist."), + ...jsonSubset, + }, + 'editor.gotoLocation.multipleTypeDefinitions': { + description: nls.localize('editor.editor.gotoLocation.multipleTypeDefinitions', "Controls the behavior the 'Go to Type Definition'-command when multiple target locations exist."), + ...jsonSubset, + }, + 'editor.gotoLocation.multipleDeclarations': { + description: nls.localize('editor.editor.gotoLocation.multipleDeclarations', "Controls the behavior the 'Go to Declaration'-command when multiple target locations exist."), + ...jsonSubset, + }, + 'editor.gotoLocation.multipleImplementations': { + description: nls.localize('editor.editor.gotoLocation.multipleImplemenattions', "Controls the behavior the 'Go to Implementations'-command when multiple target locations exist."), + ...jsonSubset, + }, + 'editor.gotoLocation.multipleReferences': { + description: nls.localize('editor.editor.gotoLocation.multipleReferences', "Controls the behavior the 'Go to References'-command when multiple target locations exist."), + ...jsonSubset, + }, + 'editor.gotoLocation.alternativeDefinitionCommand': { type: 'string', - enum: ['peek', 'gotoAndPeek', 'goto'], - default: defaults.multiple, - enumDescriptions: [ - nls.localize('editor.gotoLocation.multiple.peek', 'Show peek view of the results (default)'), - nls.localize('editor.gotoLocation.multiple.gotoAndPeek', 'Go to the primary result and show a peek view'), - nls.localize('editor.gotoLocation.multiple.goto', 'Go to the primary result and enable peek-less navigation to others') - ] + default: defaults.alternativeDefinitionCommand, + description: nls.localize('alternativeDefinitionCommand', "Alternative command id that is being executed when the result of 'Go to Definition' is the current location.") + }, + 'editor.gotoLocation.alternativeTypeDefinitionCommand': { + type: 'string', + default: defaults.alternativeTypeDefinitionCommand, + description: nls.localize('alternativeTypeDefinitionCommand', "Alternative command id that is being executed when the result of 'Go to Type Definition' is the current location.") + }, + 'editor.gotoLocation.alternativeDeclarationCommand': { + type: 'string', + default: defaults.alternativeDeclarationCommand, + description: nls.localize('alternativeDeclarationCommand', "Alternative command id that is being executed when the result of 'Go to Declaration' is the current location.") + }, + 'editor.gotoLocation.alternativeImplementationCommand': { + type: 'string', + default: defaults.alternativeImplementationCommand, + description: nls.localize('alternativeImplementationCommand', "Alternative command id that is being executed when the result of 'Go to Implementation' is the current location.") + }, + 'editor.gotoLocation.alternativeReferenceCommand': { + type: 'string', + default: defaults.alternativeReferenceCommand, + description: nls.localize('alternativeReferenceCommand', "Alternative command id that is being executed when the result of 'Go to Reference' is the current location.") }, } ); @@ -1317,7 +1441,17 @@ class EditorGoToLocation extends BaseEditorOption(input.multiple, this.defaultValue.multiple, ['peek', 'gotoAndPeek', 'goto']) + multiple: EditorStringEnumOption.stringSet(input.multiple, this.defaultValue.multiple!, ['peek', 'gotoAndPeek', 'goto']), + multipleDefinitions: input.multipleDefinitions ?? EditorStringEnumOption.stringSet(input.multipleDefinitions, 'peek', ['peek', 'gotoAndPeek', 'goto']), + multipleTypeDefinitions: input.multipleTypeDefinitions ?? EditorStringEnumOption.stringSet(input.multipleTypeDefinitions, 'peek', ['peek', 'gotoAndPeek', 'goto']), + multipleDeclarations: input.multipleDeclarations ?? EditorStringEnumOption.stringSet(input.multipleDeclarations, 'peek', ['peek', 'gotoAndPeek', 'goto']), + multipleImplementations: input.multipleImplementations ?? EditorStringEnumOption.stringSet(input.multipleImplementations, 'peek', ['peek', 'gotoAndPeek', 'goto']), + multipleReferences: input.multipleReferences ?? EditorStringEnumOption.stringSet(input.multipleReferences, 'peek', ['peek', 'gotoAndPeek', 'goto']), + alternativeDefinitionCommand: EditorStringOption.string(input.alternativeDefinitionCommand, this.defaultValue.alternativeDefinitionCommand), + alternativeTypeDefinitionCommand: EditorStringOption.string(input.alternativeTypeDefinitionCommand, this.defaultValue.alternativeTypeDefinitionCommand), + alternativeDeclarationCommand: EditorStringOption.string(input.alternativeDeclarationCommand, this.defaultValue.alternativeDeclarationCommand), + alternativeImplementationCommand: EditorStringOption.string(input.alternativeImplementationCommand, this.defaultValue.alternativeImplementationCommand), + alternativeReferenceCommand: EditorStringOption.string(input.alternativeReferenceCommand, this.defaultValue.alternativeReferenceCommand), }; } } @@ -1646,7 +1780,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption minimapMaxColumn) { minimapWidth = Math.floor(minimapMaxColumn * minimapCharWidth); @@ -2295,7 +2429,11 @@ export interface ISuggestOptions { /** * Overwrite word ends on accept. Default to false. */ - overwriteOnAccept?: boolean; + insertMode?: 'insert' | 'replace'; + /** + * Show a highlight when suggestion replaces or keep text after the cursor. Defaults to false. + */ + insertHighlight?: boolean; /** * Enable graceful matching. Defaults to true. */ @@ -2321,9 +2459,105 @@ export interface ISuggestOptions { */ maxVisibleSuggestions?: number; /** - * Names of suggestion types to filter. + * Show method-suggestions. */ - filteredTypes?: Record; + showMethods?: boolean; + /** + * Show function-suggestions. + */ + showFunctions?: boolean; + /** + * Show constructor-suggestions. + */ + showConstructors?: boolean; + /** + * Show field-suggestions. + */ + showFields?: boolean; + /** + * Show variable-suggestions. + */ + showVariables?: boolean; + /** + * Show class-suggestions. + */ + showClasses?: boolean; + /** + * Show struct-suggestions. + */ + showStructs?: boolean; + /** + * Show interface-suggestions. + */ + showInterfaces?: boolean; + /** + * Show module-suggestions. + */ + showModules?: boolean; + /** + * Show property-suggestions. + */ + showProperties?: boolean; + /** + * Show event-suggestions. + */ + showEvents?: boolean; + /** + * Show operator-suggestions. + */ + showOperators?: boolean; + /** + * Show unit-suggestions. + */ + showUnits?: boolean; + /** + * Show value-suggestions. + */ + showValues?: boolean; + /** + * Show constant-suggestions. + */ + showConstants?: boolean; + /** + * Show enum-suggestions. + */ + showEnums?: boolean; + /** + * Show enumMember-suggestions. + */ + showEnumMembers?: boolean; + /** + * Show keyword-suggestions. + */ + showKeywords?: boolean; + /** + * Show text-suggestions. + */ + showWords?: boolean; + /** + * Show color-suggestions. + */ + showColors?: boolean; + /** + * Show file-suggestions. + */ + showFiles?: boolean; + /** + * Show reference-suggestions. + */ + showReferences?: boolean; + /** + * Show folder-suggestions. + */ + showFolders?: boolean; + /** + * Show typeParameter-suggestions. + */ + showTypeParameters?: boolean; + /** + * Show snippet-suggestions. + */ + showSnippets?: boolean; } export type InternalSuggestOptions = Readonly>; @@ -2332,22 +2566,57 @@ class EditorSuggest extends BaseEditorOption !newCursorState.modelState.equals(oldState.cursorState[i].modelState)) ) { - this._onDidChange.fire(new CursorStateChangedEvent(selections, source || 'keyboard', reason)); + const oldSelections = oldState ? oldState.cursorState.map(s => s.modelState.selection) : null; + const oldModelVersionId = oldState ? oldState.modelVersionId : 0; + this._onDidChange.fire(new CursorStateChangedEvent(selections, newState.modelVersionId, oldSelections, oldModelVersionId, source || 'keyboard', reason)); } return true; diff --git a/src/vs/editor/common/controller/cursorCommon.ts b/src/vs/editor/common/controller/cursorCommon.ts index 7b40ac0ced..df52c1bf20 100644 --- a/src/vs/editor/common/controller/cursorCommon.ts +++ b/src/vs/editor/common/controller/cursorCommon.ts @@ -6,7 +6,7 @@ import { CharCode } from 'vs/base/common/charCode'; import { onUnexpectedError } from 'vs/base/common/errors'; import * as strings from 'vs/base/common/strings'; -import { EditorAutoClosingStrategy, EditorAutoSurroundStrategy, ConfigurationChangedEvent, EditorAutoClosingOvertypeStrategy, EditorOption } from 'vs/editor/common/config/editorOptions'; +import { EditorAutoClosingStrategy, EditorAutoSurroundStrategy, ConfigurationChangedEvent, EditorAutoClosingOvertypeStrategy, EditorOption, EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions'; import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; @@ -103,7 +103,7 @@ export class CursorConfiguration { public readonly autoClosingQuotes: EditorAutoClosingStrategy; public readonly autoClosingOvertype: EditorAutoClosingOvertypeStrategy; public readonly autoSurround: EditorAutoSurroundStrategy; - public readonly autoIndent: boolean; + public readonly autoIndent: EditorAutoIndentStrategy; public readonly autoClosingPairsOpen2: Map; public readonly autoClosingPairsClose2: Map; public readonly surroundingPairs: CharacterMap; @@ -523,12 +523,15 @@ export class CursorColumns { if (codePoint === CharCode.Tab) { result = CursorColumns.nextRenderTabStop(result, tabSize); } else { + let graphemeBreakType = strings.getGraphemeBreakType(codePoint); while (i < endOffset) { const nextCodePoint = strings.getNextCodePoint(lineContent, endOffset, i); - if (!strings.isUnicodeMark(nextCodePoint)) { + const nextGraphemeBreakType = strings.getGraphemeBreakType(nextCodePoint); + if (strings.breakBetweenGraphemeBreakType(graphemeBreakType, nextGraphemeBreakType)) { break; } i += (nextCodePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1); + graphemeBreakType = nextGraphemeBreakType; } if (strings.isFullWidthCharacter(codePoint) || strings.isEmojiImprecise(codePoint)) { result = result + 2; @@ -582,12 +585,15 @@ export class CursorColumns { if (codePoint === CharCode.Tab) { afterVisibleColumn = CursorColumns.nextRenderTabStop(beforeVisibleColumn, tabSize); } else { + let graphemeBreakType = strings.getGraphemeBreakType(codePoint); while (i < lineLength) { const nextCodePoint = strings.getNextCodePoint(lineContent, lineLength, i); - if (!strings.isUnicodeMark(nextCodePoint)) { + const nextGraphemeBreakType = strings.getGraphemeBreakType(nextCodePoint); + if (strings.breakBetweenGraphemeBreakType(graphemeBreakType, nextGraphemeBreakType)) { break; } i += (nextCodePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1); + graphemeBreakType = nextGraphemeBreakType; } if (strings.isFullWidthCharacter(codePoint) || strings.isEmojiImprecise(codePoint)) { afterVisibleColumn = beforeVisibleColumn + 2; diff --git a/src/vs/editor/common/controller/cursorEvents.ts b/src/vs/editor/common/controller/cursorEvents.ts index 6ebd1e4a28..562ecb5409 100644 --- a/src/vs/editor/common/controller/cursorEvents.ts +++ b/src/vs/editor/common/controller/cursorEvents.ts @@ -72,6 +72,18 @@ export interface ICursorSelectionChangedEvent { * The secondary selections. */ readonly secondarySelections: Selection[]; + /** + * The model version id. + */ + readonly modelVersionId: number; + /** + * The old selections. + */ + readonly oldSelections: Selection[] | null; + /** + * The model version id the that `oldSelections` refer to. + */ + readonly oldModelVersionId: number; /** * Source of the call that caused the event. */ diff --git a/src/vs/editor/common/controller/cursorTypeOperations.ts b/src/vs/editor/common/controller/cursorTypeOperations.ts index 8461aeb721..fe3dfd7ac0 100644 --- a/src/vs/editor/common/controller/cursorTypeOperations.ts +++ b/src/vs/editor/common/controller/cursorTypeOperations.ts @@ -19,6 +19,7 @@ import { ITextModel } from 'vs/editor/common/model'; import { EnterAction, IndentAction, StandardAutoClosingPairConditional } from 'vs/editor/common/modes/languageConfiguration'; 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'; export class TypeOperations { @@ -34,7 +35,8 @@ export class TypeOperations { tabSize: config.tabSize, indentSize: config.indentSize, insertSpaces: config.insertSpaces, - useTabStops: config.useTabStops + useTabStops: config.useTabStops, + autoIndent: config.autoIndent }); } return commands; @@ -48,7 +50,8 @@ export class TypeOperations { tabSize: config.tabSize, indentSize: config.indentSize, insertSpaces: config.insertSpaces, - useTabStops: config.useTabStops + useTabStops: config.useTabStops, + autoIndent: config.autoIndent }); } return commands; @@ -149,15 +152,15 @@ export class TypeOperations { let action: IndentAction | EnterAction | null = null; let indentation: string = ''; - let expectedIndentAction = config.autoIndent ? LanguageConfigurationRegistry.getInheritIndentForLine(model, lineNumber, false) : null; + const expectedIndentAction = LanguageConfigurationRegistry.getInheritIndentForLine(config.autoIndent, model, lineNumber, false); if (expectedIndentAction) { action = expectedIndentAction.action; indentation = expectedIndentAction.indentation; } else if (lineNumber > 1) { let lastLineNumber: number; for (lastLineNumber = lineNumber - 1; lastLineNumber >= 1; lastLineNumber--) { - let lineText = model.getLineContent(lastLineNumber); - let nonWhitespaceIdx = strings.lastNonWhitespaceIndex(lineText); + const lineText = model.getLineContent(lastLineNumber); + const nonWhitespaceIdx = strings.lastNonWhitespaceIndex(lineText); if (nonWhitespaceIdx >= 0) { break; } @@ -168,14 +171,10 @@ export class TypeOperations { return null; } - let maxColumn = model.getLineMaxColumn(lastLineNumber); - let expectedEnterAction = LanguageConfigurationRegistry.getEnterAction(model, new Range(lastLineNumber, maxColumn, lastLineNumber, maxColumn)); + const maxColumn = model.getLineMaxColumn(lastLineNumber); + const expectedEnterAction = LanguageConfigurationRegistry.getEnterAction(config.autoIndent, model, new Range(lastLineNumber, maxColumn, lastLineNumber, maxColumn)); if (expectedEnterAction) { - indentation = expectedEnterAction.indentation; - action = expectedEnterAction.enterAction; - if (action) { - indentation += action.appendText; - } + indentation = expectedEnterAction.indentation + expectedEnterAction.appendText; } } @@ -251,7 +250,8 @@ export class TypeOperations { tabSize: config.tabSize, indentSize: config.indentSize, insertSpaces: config.insertSpaces, - useTabStops: config.useTabStops + useTabStops: config.useTabStops, + autoIndent: config.autoIndent }); } } @@ -289,104 +289,97 @@ export class TypeOperations { } private static _enter(config: CursorConfiguration, model: ITextModel, keepPosition: boolean, range: Range): ICommand { - if (!model.isCheapToTokenize(range.getStartPosition().lineNumber)) { + if (config.autoIndent === EditorAutoIndentStrategy.None) { + return TypeOperations._typeCommand(range, '\n', keepPosition); + } + if (!model.isCheapToTokenize(range.getStartPosition().lineNumber) || config.autoIndent === EditorAutoIndentStrategy.Keep) { let lineText = model.getLineContent(range.startLineNumber); let indentation = strings.getLeadingWhitespace(lineText).substring(0, range.startColumn - 1); return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(indentation), keepPosition); } - let r = LanguageConfigurationRegistry.getEnterAction(model, range); + const r = LanguageConfigurationRegistry.getEnterAction(config.autoIndent, model, range); if (r) { - let enterAction = r.enterAction; - let indentation = r.indentation; - - if (enterAction.indentAction === IndentAction.None) { + if (r.indentAction === IndentAction.None) { // Nothing special - return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(indentation + enterAction.appendText), keepPosition); + return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(r.indentation + r.appendText), keepPosition); - } else if (enterAction.indentAction === IndentAction.Indent) { + } else if (r.indentAction === IndentAction.Indent) { // Indent once - return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(indentation + enterAction.appendText), keepPosition); + return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(r.indentation + r.appendText), keepPosition); - } else if (enterAction.indentAction === IndentAction.IndentOutdent) { + } else if (r.indentAction === IndentAction.IndentOutdent) { // Ultra special - let normalIndent = config.normalizeIndentation(indentation); - let increasedIndent = config.normalizeIndentation(indentation + enterAction.appendText); + const normalIndent = config.normalizeIndentation(r.indentation); + const increasedIndent = config.normalizeIndentation(r.indentation + r.appendText); - let typeText = '\n' + increasedIndent + '\n' + normalIndent; + const typeText = '\n' + increasedIndent + '\n' + normalIndent; if (keepPosition) { return new ReplaceCommandWithoutChangingPosition(range, typeText, true); } else { return new ReplaceCommandWithOffsetCursorState(range, typeText, -1, increasedIndent.length - normalIndent.length, true); } - } else if (enterAction.indentAction === IndentAction.Outdent) { - let actualIndentation = TypeOperations.unshiftIndent(config, indentation); - return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(actualIndentation + enterAction.appendText), keepPosition); + } else if (r.indentAction === IndentAction.Outdent) { + const actualIndentation = TypeOperations.unshiftIndent(config, r.indentation); + return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(actualIndentation + r.appendText), keepPosition); } } - // no enter rules applied, we should check indentation rules then. - if (!config.autoIndent) { - // Nothing special - let lineText = model.getLineContent(range.startLineNumber); - let indentation = strings.getLeadingWhitespace(lineText).substring(0, range.startColumn - 1); - return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(indentation), keepPosition); - } + const lineText = model.getLineContent(range.startLineNumber); + const indentation = strings.getLeadingWhitespace(lineText).substring(0, range.startColumn - 1); - let ir = LanguageConfigurationRegistry.getIndentForEnter(model, range, { - unshiftIndent: (indent) => { - return TypeOperations.unshiftIndent(config, indent); - }, - shiftIndent: (indent) => { - return TypeOperations.shiftIndent(config, indent); - }, - normalizeIndentation: (indent) => { - return config.normalizeIndentation(indent); - } - }, config.autoIndent); - - let lineText = model.getLineContent(range.startLineNumber); - let indentation = strings.getLeadingWhitespace(lineText).substring(0, range.startColumn - 1); - - if (ir) { - let oldEndViewColumn = CursorColumns.visibleColumnFromColumn2(config, model, range.getEndPosition()); - let oldEndColumn = range.endColumn; - - let beforeText = '\n'; - if (indentation !== config.normalizeIndentation(ir.beforeEnter)) { - beforeText = config.normalizeIndentation(ir.beforeEnter) + lineText.substring(indentation.length, range.startColumn - 1) + '\n'; - range = new Range(range.startLineNumber, 1, range.endLineNumber, range.endColumn); - } - - let newLineContent = model.getLineContent(range.endLineNumber); - let firstNonWhitespace = strings.firstNonWhitespaceIndex(newLineContent); - if (firstNonWhitespace >= 0) { - range = range.setEndPosition(range.endLineNumber, Math.max(range.endColumn, firstNonWhitespace + 1)); - } else { - range = range.setEndPosition(range.endLineNumber, model.getLineMaxColumn(range.endLineNumber)); - } - - if (keepPosition) { - return new ReplaceCommandWithoutChangingPosition(range, beforeText + config.normalizeIndentation(ir.afterEnter), true); - } else { - let offset = 0; - if (oldEndColumn <= firstNonWhitespace + 1) { - if (!config.insertSpaces) { - oldEndViewColumn = Math.ceil(oldEndViewColumn / config.indentSize); - } - offset = Math.min(oldEndViewColumn + 1 - config.normalizeIndentation(ir.afterEnter).length - 1, 0); + if (config.autoIndent >= EditorAutoIndentStrategy.Full) { + const ir = LanguageConfigurationRegistry.getIndentForEnter(config.autoIndent, model, range, { + unshiftIndent: (indent) => { + return TypeOperations.unshiftIndent(config, indent); + }, + shiftIndent: (indent) => { + return TypeOperations.shiftIndent(config, indent); + }, + normalizeIndentation: (indent) => { + return config.normalizeIndentation(indent); } - return new ReplaceCommandWithOffsetCursorState(range, beforeText + config.normalizeIndentation(ir.afterEnter), 0, offset, true); - } + }); - } else { - return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(indentation), keepPosition); + if (ir) { + let oldEndViewColumn = CursorColumns.visibleColumnFromColumn2(config, model, range.getEndPosition()); + const oldEndColumn = range.endColumn; + + let beforeText = '\n'; + if (indentation !== config.normalizeIndentation(ir.beforeEnter)) { + beforeText = config.normalizeIndentation(ir.beforeEnter) + lineText.substring(indentation.length, range.startColumn - 1) + '\n'; + range = new Range(range.startLineNumber, 1, range.endLineNumber, range.endColumn); + } + + const newLineContent = model.getLineContent(range.endLineNumber); + const firstNonWhitespace = strings.firstNonWhitespaceIndex(newLineContent); + if (firstNonWhitespace >= 0) { + range = range.setEndPosition(range.endLineNumber, Math.max(range.endColumn, firstNonWhitespace + 1)); + } else { + range = range.setEndPosition(range.endLineNumber, model.getLineMaxColumn(range.endLineNumber)); + } + + if (keepPosition) { + return new ReplaceCommandWithoutChangingPosition(range, beforeText + config.normalizeIndentation(ir.afterEnter), true); + } else { + let offset = 0; + if (oldEndColumn <= firstNonWhitespace + 1) { + if (!config.insertSpaces) { + oldEndViewColumn = Math.ceil(oldEndViewColumn / config.indentSize); + } + offset = Math.min(oldEndViewColumn + 1 - config.normalizeIndentation(ir.afterEnter).length - 1, 0); + } + return new ReplaceCommandWithOffsetCursorState(range, beforeText + config.normalizeIndentation(ir.afterEnter), 0, offset, true); + } + } } + + return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(indentation), keepPosition); } private static _isAutoIndentType(config: CursorConfiguration, model: ITextModel, selections: Selection[]): boolean { - if (!config.autoIndent) { + if (config.autoIndent < EditorAutoIndentStrategy.Full) { return false; } @@ -400,8 +393,8 @@ export class TypeOperations { } private static _runAutoIndentType(config: CursorConfiguration, model: ITextModel, range: Range, ch: string): ICommand | null { - let currentIndentation = LanguageConfigurationRegistry.getIndentationAtPosition(model, range.startLineNumber, range.startColumn); - let actualIndentation = LanguageConfigurationRegistry.getIndentActionForType(model, range, ch, { + const currentIndentation = LanguageConfigurationRegistry.getIndentationAtPosition(model, range.startLineNumber, range.startColumn); + const actualIndentation = LanguageConfigurationRegistry.getIndentActionForType(config.autoIndent, model, range, ch, { shiftIndent: (indentation) => { return TypeOperations.shiftIndent(config, indentation); }, @@ -415,7 +408,7 @@ export class TypeOperations { } if (actualIndentation !== config.normalizeIndentation(currentIndentation)) { - let firstNonWhitespace = model.getLineFirstNonWhitespaceColumn(range.startLineNumber); + const firstNonWhitespace = model.getLineFirstNonWhitespaceColumn(range.startLineNumber); if (firstNonWhitespace === 0) { return TypeOperations._typeCommand( new Range(range.startLineNumber, 0, range.endLineNumber, range.endColumn), @@ -459,9 +452,10 @@ export class TypeOperations { return false; } - // Do not over-type after a backslash + // Do not over-type quotes after a backslash + const chIsQuote = isQuote(ch); const beforeCharacter = position.column > 2 ? lineText.charCodeAt(position.column - 2) : CharCode.Null; - if (beforeCharacter === CharCode.Backslash) { + if (beforeCharacter === CharCode.Backslash && chIsQuote) { return false; } diff --git a/src/vs/editor/common/controller/cursorWordOperations.ts b/src/vs/editor/common/controller/cursorWordOperations.ts index d3ddf0fe9a..32fdbf2028 100644 --- a/src/vs/editor/common/controller/cursorWordOperations.ts +++ b/src/vs/editor/common/controller/cursorWordOperations.ts @@ -289,17 +289,26 @@ export class WordOperations { column = model.getLineMaxColumn(lineNumber); } } else if (wordNavigationType === WordNavigationType.WordAccessibility) { + if (movedDown) { + // If we move to the next line, pretend that the cursor is right before the first character. + // This is needed when the first word starts right at the first character - and in order not to miss it, + // we need to start before. + column = 0; + } while ( nextWordOnLine - && nextWordOnLine.wordType === WordType.Separator + && (nextWordOnLine.wordType === WordType.Separator + || nextWordOnLine.start + 1 <= column + ) ) { // Skip over a word made up of one single separator + // Also skip over word if it begins before current cursor position to ascertain we're moving forward at least 1 character. nextWordOnLine = WordOperations._findNextWordOnLine(wordSeparators, model, new Position(lineNumber, nextWordOnLine.end + 1)); } if (nextWordOnLine) { - column = nextWordOnLine.end + 1; + column = nextWordOnLine.start + 1; } else { column = model.getLineMaxColumn(lineNumber); } diff --git a/src/vs/editor/common/core/lineTokens.ts b/src/vs/editor/common/core/lineTokens.ts index 8100e22068..816b865e90 100644 --- a/src/vs/editor/common/core/lineTokens.ts +++ b/src/vs/editor/common/core/lineTokens.ts @@ -67,6 +67,11 @@ export class LineTokens implements IViewLineTokens { return 0; } + public getMetadata(tokenIndex: number): number { + const metadata = this._tokens[(tokenIndex << 1) + 1]; + return metadata; + } + public getLanguageId(tokenIndex: number): LanguageId { const metadata = this._tokens[(tokenIndex << 1) + 1]; return TokenMetadata.getLanguageId(metadata); @@ -132,8 +137,8 @@ export class LineTokens implements IViewLineTokens { while (low < high) { - let mid = low + Math.floor((high - low) / 2); - let endOffset = tokens[(mid << 1)]; + const mid = low + Math.floor((high - low) / 2); + const endOffset = tokens[(mid << 1)]; if (endOffset === desiredIndex) { return mid + 1; diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index 6f6bc288be..21e4f4c8e2 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -14,7 +14,7 @@ import { IModelContentChange, IModelContentChangedEvent, IModelDecorationsChange import { SearchData } from 'vs/editor/common/model/textModelSearch'; import { LanguageId, LanguageIdentifier, FormattingOptions } from 'vs/editor/common/modes'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; -import { MultilineTokens } from 'vs/editor/common/model/tokensStore'; +import { MultilineTokens, MultilineTokens2 } from 'vs/editor/common/model/tokensStore'; /** * Vertical Lane in the overview ruler of the editor. @@ -30,7 +30,8 @@ export enum OverviewRulerLane { * Position in the minimap to render the decoration. */ export enum MinimapPosition { - Inline = 1 + Inline = 1, + Gutter = 2 } export interface IDecorationOptions { @@ -791,6 +792,11 @@ export interface ITextModel { */ setTokens(tokens: MultilineTokens[]): void; + /** + * @internal + */ + setSemanticTokens(tokens: MultilineTokens2[] | null): void; + /** * Flush all tokenization state. * @internal @@ -1192,6 +1198,13 @@ export interface ITextBufferFactory { getFirstLineText(lengthLimit: number): string; } +/** + * @internal + */ +export const enum ModelConstants { + FIRST_LINE_DETECTION_LENGTH_LIMIT = 1000 +} + /** * @internal */ diff --git a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts index 525b5565ca..764ca730fe 100644 --- a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts +++ b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts @@ -577,23 +577,34 @@ export class PieceTreeBase { let m: RegExpExecArray | null; // Reset regex to search from the beginning - searcher.reset(start); let ret: BufferCursor = { line: 0, column: 0 }; + let searchText: string; + let offsetInBuffer: (offset: number) => number; + + if (searcher._wordSeparators) { + searchText = buffer.buffer.substring(start, end); + offsetInBuffer = (offset: number) => offset + start; + searcher.reset(-1); + } else { + searchText = buffer.buffer; + offsetInBuffer = (offset: number) => offset; + searcher.reset(start); + } do { - m = searcher.next(buffer.buffer); + m = searcher.next(searchText); if (m) { - if (m.index >= end) { + if (offsetInBuffer(m.index) >= end) { return resultLen; } - this.positionInBuffer(node, m.index - startOffsetInBuffer, ret); + this.positionInBuffer(node, offsetInBuffer(m.index) - startOffsetInBuffer, ret); let lineFeedCnt = this.getLineFeedCnt(node.piece.bufferIndex, startCursor, ret); let retStartColumn = ret.line === startCursor.line ? ret.column - startCursor.column + startColumn : ret.column + 1; let retEndColumn = retStartColumn + m[0].length; result[resultLen++] = createFindMatch(new Range(startLineNumber + lineFeedCnt, retStartColumn, startLineNumber + lineFeedCnt, retEndColumn), m, captureMatches); - if (m.index + m[0].length >= end) { + if (offsetInBuffer(m.index) + m[0].length >= end) { return resultLen; } if (resultLen >= limitResultCount) { diff --git a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder.ts b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder.ts index 744b6f2f30..3654c267d3 100644 --- a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder.ts +++ b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder.ts @@ -57,7 +57,7 @@ export class PieceTreeTextBufferFactory implements ITextBufferFactory { } public getFirstLineText(lengthLimit: number): string { - return this._chunks[0].buffer.substr(0, 100).split(/\r\n|\r|\n/)[0]; + return this._chunks[0].buffer.substr(0, lengthLimit).split(/\r\n|\r|\n/)[0]; } } diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 04a368d187..3e6b13803f 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -32,7 +32,7 @@ import { BracketsUtils, RichEditBracket, RichEditBrackets } from 'vs/editor/comm import { ITheme, ThemeColor } from 'vs/platform/theme/common/themeService'; import { withUndefinedAsNull } from 'vs/base/common/types'; import { VSBufferReadableStream, VSBuffer } from 'vs/base/common/buffer'; -import { TokensStore, MultilineTokens, countEOL } from 'vs/editor/common/model/tokensStore'; +import { TokensStore, MultilineTokens, countEOL, MultilineTokens2, TokensStore2 } from 'vs/editor/common/model/tokensStore'; import { Color } from 'vs/base/common/color'; function createTextBufferBuilder() { @@ -276,6 +276,7 @@ export class TextModel extends Disposable implements model.ITextModel { private _languageIdentifier: LanguageIdentifier; private readonly _languageRegistryListener: IDisposable; private readonly _tokens: TokensStore; + private readonly _tokens2: TokensStore2; private readonly _tokenization: TextModelTokenization; //#endregion @@ -339,6 +340,7 @@ export class TextModel extends Disposable implements model.ITextModel { this._trimAutoWhitespaceLines = null; this._tokens = new TokensStore(); + this._tokens2 = new TokensStore2(); this._tokenization = new TextModelTokenization(this); } @@ -414,6 +416,7 @@ export class TextModel extends Disposable implements model.ITextModel { // Flush all tokens this._tokens.flush(); + this._tokens2.flush(); // Destroy all my decorations this._decorations = Object.create(null); @@ -1262,8 +1265,9 @@ export class TextModel extends Disposable implements model.ITextModel { let lineCount = oldLineCount; for (let i = 0, len = contentChanges.length; i < len; i++) { const change = contentChanges[i]; - const [eolCount, firstLineLength] = countEOL(change.text); + const [eolCount, firstLineLength, lastLineLength] = countEOL(change.text); this._tokens.acceptEdit(change.range, eolCount, firstLineLength); + this._tokens2.acceptEdit(change.range, eolCount, firstLineLength, lastLineLength, change.text.length > 0 ? change.text.charCodeAt(0) : CharCode.Null); this._onDidChangeDecorations.fire(); this._decorationsTree.acceptReplace(change.rangeOffset, change.rangeLength, change.text.length, change.forceMoveMarkers); @@ -1717,6 +1721,15 @@ export class TextModel extends Disposable implements model.ITextModel { }); } + public setSemanticTokens(tokens: MultilineTokens2[] | null): void { + this._tokens2.set(tokens); + + this._emitModelTokensChangedEvent({ + tokenizationSupportChanged: false, + ranges: [{ fromLineNumber: 1, toLineNumber: this.getLineCount() }] + }); + } + public tokenizeViewport(startLineNumber: number, endLineNumber: number): void { startLineNumber = Math.max(1, startLineNumber); endLineNumber = Math.min(this._buffer.getLineCount(), endLineNumber); @@ -1734,6 +1747,15 @@ export class TextModel extends Disposable implements model.ITextModel { }); } + public clearSemanticTokens(): void { + this._tokens2.flush(); + + this._emitModelTokensChangedEvent({ + tokenizationSupportChanged: false, + ranges: [{ fromLineNumber: 1, toLineNumber: this.getLineCount() }] + }); + } + private _emitModelTokensChangedEvent(e: IModelTokensChangedEvent): void { if (!this._isDisposing) { this._onDidChangeTokens.fire(e); @@ -1772,7 +1794,8 @@ export class TextModel extends Disposable implements model.ITextModel { private _getLineTokens(lineNumber: number): LineTokens { const lineText = this.getLineContent(lineNumber); - return this._tokens.getTokens(this._languageIdentifier.id, lineNumber - 1, lineText); + const syntacticTokens = this._tokens.getTokens(this._languageIdentifier.id, lineNumber - 1, lineText); + return this._tokens2.addSemanticTokens(lineNumber, syntacticTokens); } public getLanguageIdentifier(): LanguageIdentifier { diff --git a/src/vs/editor/common/model/textModelSearch.ts b/src/vs/editor/common/model/textModelSearch.ts index b8d0685b31..37ff0d86fb 100644 --- a/src/vs/editor/common/model/textModelSearch.ts +++ b/src/vs/editor/common/model/textModelSearch.ts @@ -510,7 +510,7 @@ export function isValidMatch(wordSeparators: WordCharacterClassifier, text: stri } export class Searcher { - private readonly _wordSeparators: WordCharacterClassifier | null; + public readonly _wordSeparators: WordCharacterClassifier | null; private readonly _searchRegex: RegExp; private _prevMatchStartIndex: number; private _prevMatchLength: number; diff --git a/src/vs/editor/common/model/tokensStore.ts b/src/vs/editor/common/model/tokensStore.ts index 589faf6a17..5e9cb8f86f 100644 --- a/src/vs/editor/common/model/tokensStore.ts +++ b/src/vs/editor/common/model/tokensStore.ts @@ -11,9 +11,10 @@ import { ColorId, FontStyle, LanguageId, MetadataConsts, StandardTokenType, Toke import { writeUInt32BE, readUInt32BE } from 'vs/base/common/buffer'; import { CharCode } from 'vs/base/common/charCode'; -export function countEOL(text: string): [number, number] { +export function countEOL(text: string): [number, number, number] { let eolCount = 0; let firstLineLength = 0; + let lastLineStart = 0; for (let i = 0, len = text.length; i < len; i++) { const chr = text.charCodeAt(i); @@ -28,17 +29,19 @@ export function countEOL(text: string): [number, number] { } else { // \r... case } + lastLineStart = i + 1; } else if (chr === CharCode.LineFeed) { if (eolCount === 0) { firstLineLength = i; } eolCount++; + lastLineStart = i + 1; } } if (eolCount === 0) { firstLineLength = text.length; } - return [eolCount, firstLineLength]; + return [eolCount, firstLineLength, text.length - lastLineStart]; } function getDefaultMetadata(topLevelLanguageId: LanguageId): number { @@ -109,6 +112,453 @@ export class MultilineTokensBuilder { } } +export interface IEncodedTokens { + getTokenCount(): number; + getDeltaLine(tokenIndex: number): number; + getMaxDeltaLine(): number; + getStartCharacter(tokenIndex: number): number; + getEndCharacter(tokenIndex: number): number; + getMetadata(tokenIndex: number): number; + + clear(): void; + acceptDeleteRange(startDeltaLine: number, startCharacter: number, endDeltaLine: number, endCharacter: number): void; + acceptInsertText(deltaLine: number, character: number, eolCount: number, firstLineLength: number, lastLineLength: number, firstCharCode: number): void; +} + +export class SparseEncodedTokens implements IEncodedTokens { + /** + * The encoding of tokens is: + * 4*i deltaLine (from `startLineNumber`) + * 4*i+1 startCharacter (from the line start) + * 4*i+2 endCharacter (from the line start) + * 4*i+3 metadata + */ + private _tokens: Uint32Array; + private _tokenCount: number; + + constructor(tokens: Uint32Array) { + this._tokens = tokens; + this._tokenCount = tokens.length / 4; + } + + public getMaxDeltaLine(): number { + const tokenCount = this.getTokenCount(); + if (tokenCount === 0) { + return -1; + } + return this.getDeltaLine(tokenCount - 1); + } + + public getTokenCount(): number { + return this._tokenCount; + } + + public getDeltaLine(tokenIndex: number): number { + return this._tokens[4 * tokenIndex]; + } + + public getStartCharacter(tokenIndex: number): number { + return this._tokens[4 * tokenIndex + 1]; + } + + public getEndCharacter(tokenIndex: number): number { + return this._tokens[4 * tokenIndex + 2]; + } + + public getMetadata(tokenIndex: number): number { + return this._tokens[4 * tokenIndex + 3]; + } + + public clear(): void { + this._tokenCount = 0; + } + + public acceptDeleteRange(startDeltaLine: number, startCharacter: number, endDeltaLine: number, endCharacter: number): void { + // This is a bit complex, here are the cases I used to think about this: + // + // 1. The token starts before the deletion range + // 1a. The token is completely before the deletion range + // ----------- + // xxxxxxxxxxx + // 1b. The token starts before, the deletion range ends after the token + // ----------- + // xxxxxxxxxxx + // 1c. The token starts before, the deletion range ends precisely with the token + // --------------- + // xxxxxxxx + // 1d. The token starts before, the deletion range is inside the token + // --------------- + // xxxxx + // + // 2. The token starts at the same position with the deletion range + // 2a. The token starts at the same position, and ends inside the deletion range + // ------- + // xxxxxxxxxxx + // 2b. The token starts at the same position, and ends at the same position as the deletion range + // ---------- + // xxxxxxxxxx + // 2c. The token starts at the same position, and ends after the deletion range + // ------------- + // xxxxxxx + // + // 3. The token starts inside the deletion range + // 3a. The token is inside the deletion range + // ------- + // xxxxxxxxxxxxx + // 3b. The token starts inside the deletion range, and ends at the same position as the deletion range + // ---------- + // xxxxxxxxxxxxx + // 3c. The token starts inside the deletion range, and ends after the deletion range + // ------------ + // xxxxxxxxxxx + // + // 4. The token starts after the deletion range + // ----------- + // xxxxxxxx + // + const tokens = this._tokens; + const tokenCount = this._tokenCount; + const deletedLineCount = (endDeltaLine - startDeltaLine); + let newTokenCount = 0; + let hasDeletedTokens = false; + for (let i = 0; i < tokenCount; i++) { + const srcOffset = 4 * i; + let tokenDeltaLine = tokens[srcOffset]; + let tokenStartCharacter = tokens[srcOffset + 1]; + let tokenEndCharacter = tokens[srcOffset + 2]; + const tokenMetadata = tokens[srcOffset + 3]; + + if (tokenDeltaLine < startDeltaLine || (tokenDeltaLine === startDeltaLine && tokenEndCharacter <= startCharacter)) { + // 1a. The token is completely before the deletion range + // => nothing to do + newTokenCount++; + continue; + } else if (tokenDeltaLine === startDeltaLine && tokenStartCharacter < startCharacter) { + // 1b, 1c, 1d + // => the token survives, but it needs to shrink + if (tokenDeltaLine === endDeltaLine && tokenEndCharacter > endCharacter) { + // 1d. The token starts before, the deletion range is inside the token + // => the token shrinks by the deletion character count + tokenEndCharacter -= (endCharacter - startCharacter); + } else { + // 1b. The token starts before, the deletion range ends after the token + // 1c. The token starts before, the deletion range ends precisely with the token + // => the token shrinks its ending to the deletion start + tokenEndCharacter = startCharacter; + } + } else if (tokenDeltaLine === startDeltaLine && tokenStartCharacter === startCharacter) { + // 2a, 2b, 2c + if (tokenDeltaLine === endDeltaLine && tokenEndCharacter > endCharacter) { + // 2c. The token starts at the same position, and ends after the deletion range + // => the token shrinks by the deletion character count + tokenEndCharacter -= (endCharacter - startCharacter); + } else { + // 2a. The token starts at the same position, and ends inside the deletion range + // 2b. The token starts at the same position, and ends at the same position as the deletion range + // => the token is deleted + hasDeletedTokens = true; + continue; + } + } else if (tokenDeltaLine < endDeltaLine || (tokenDeltaLine === endDeltaLine && tokenStartCharacter < endCharacter)) { + // 3a, 3b, 3c + if (tokenDeltaLine === endDeltaLine && tokenEndCharacter > endCharacter) { + // 3c. The token starts inside the deletion range, and ends after the deletion range + // => the token moves left and shrinks + if (tokenDeltaLine === startDeltaLine) { + // the deletion started on the same line as the token + // => the token moves left and shrinks + tokenStartCharacter = startCharacter; + tokenEndCharacter = tokenStartCharacter + (tokenEndCharacter - endCharacter); + } else { + // the deletion started on a line above the token + // => the token moves to the beginning of the line + tokenStartCharacter = 0; + tokenEndCharacter = tokenStartCharacter + (tokenEndCharacter - endCharacter); + } + } else { + // 3a. The token is inside the deletion range + // 3b. The token starts inside the deletion range, and ends at the same position as the deletion range + // => the token is deleted + hasDeletedTokens = true; + continue; + } + } else if (tokenDeltaLine > endDeltaLine) { + // 4. (partial) The token starts after the deletion range, on a line below... + if (deletedLineCount === 0 && !hasDeletedTokens) { + // early stop, there is no need to walk all the tokens and do nothing... + newTokenCount = tokenCount; + break; + } + tokenDeltaLine -= deletedLineCount; + } else if (tokenDeltaLine === endDeltaLine && tokenStartCharacter >= endCharacter) { + // 4. (continued) The token starts after the deletion range, on the last line where a deletion occurs + tokenDeltaLine -= deletedLineCount; + tokenStartCharacter -= endCharacter; + tokenEndCharacter -= endCharacter; + } else { + throw new Error(`Not possible!`); + } + + const destOffset = 4 * newTokenCount; + tokens[destOffset] = tokenDeltaLine; + tokens[destOffset + 1] = tokenStartCharacter; + tokens[destOffset + 2] = tokenEndCharacter; + tokens[destOffset + 3] = tokenMetadata; + newTokenCount++; + } + + this._tokenCount = newTokenCount; + } + + public acceptInsertText(deltaLine: number, character: number, eolCount: number, firstLineLength: number, lastLineLength: number, firstCharCode: number): void { + // Here are the cases I used to think about this: + // + // 1. The token is completely before the insertion point + // ----------- | + // 2. The token ends precisely at the insertion point + // -----------| + // 3. The token contains the insertion point + // -----|------ + // 4. The token starts precisely at the insertion point + // |----------- + // 5. The token is completely after the insertion point + // | ----------- + // + const isInsertingPreciselyOneWordCharacter = ( + eolCount === 0 + && firstLineLength === 1 + && ( + (firstCharCode >= CharCode.Digit0 && firstCharCode <= CharCode.Digit9) + || (firstCharCode >= CharCode.A && firstCharCode <= CharCode.Z) + || (firstCharCode >= CharCode.a && firstCharCode <= CharCode.z) + ) + ); + const tokens = this._tokens; + const tokenCount = this._tokenCount; + for (let i = 0; i < tokenCount; i++) { + const offset = 4 * i; + let tokenDeltaLine = tokens[offset]; + let tokenStartCharacter = tokens[offset + 1]; + let tokenEndCharacter = tokens[offset + 2]; + + if (tokenDeltaLine < deltaLine || (tokenDeltaLine === deltaLine && tokenEndCharacter < character)) { + // 1. The token is completely before the insertion point + // => nothing to do + continue; + } else if (tokenDeltaLine === deltaLine && tokenEndCharacter === character) { + // 2. The token ends precisely at the insertion point + // => expand the end character only if inserting precisely one character that is a word character + if (isInsertingPreciselyOneWordCharacter) { + tokenEndCharacter += 1; + } else { + continue; + } + } else if (tokenDeltaLine === deltaLine && tokenStartCharacter < character && character < tokenEndCharacter) { + // 3. The token contains the insertion point + if (eolCount === 0) { + // => just expand the end character + tokenEndCharacter += firstLineLength; + } else { + // => cut off the token + tokenEndCharacter = character; + } + } else { + // 4. or 5. + if (tokenDeltaLine === deltaLine && tokenStartCharacter === character) { + // 4. The token starts precisely at the insertion point + // => grow the token (by keeping its start constant) only if inserting precisely one character that is a word character + // => otherwise behave as in case 5. + if (isInsertingPreciselyOneWordCharacter) { + continue; + } + } + // => the token must move and keep its size constant + tokenDeltaLine += eolCount; + if (tokenDeltaLine === deltaLine) { + // this token is on the line where the insertion is taking place + if (eolCount === 0) { + tokenStartCharacter += firstLineLength; + tokenEndCharacter += firstLineLength; + } else { + const tokenLength = tokenEndCharacter - tokenStartCharacter; + tokenStartCharacter = lastLineLength + (tokenStartCharacter - character); + tokenEndCharacter = tokenStartCharacter + tokenLength; + } + } + } + + tokens[offset] = tokenDeltaLine; + tokens[offset + 1] = tokenStartCharacter; + tokens[offset + 2] = tokenEndCharacter; + } + } +} + +export class LineTokens2 { + + private readonly _actual: IEncodedTokens; + private readonly _startTokenIndex: number; + private readonly _endTokenIndex: number; + + constructor(actual: IEncodedTokens, startTokenIndex: number, endTokenIndex: number) { + this._actual = actual; + this._startTokenIndex = startTokenIndex; + this._endTokenIndex = endTokenIndex; + } + + public getCount(): number { + return this._endTokenIndex - this._startTokenIndex + 1; + } + + public getStartCharacter(tokenIndex: number): number { + return this._actual.getStartCharacter(this._startTokenIndex + tokenIndex); + } + + public getEndCharacter(tokenIndex: number): number { + return this._actual.getEndCharacter(this._startTokenIndex + tokenIndex); + } + + public getMetadata(tokenIndex: number): number { + return this._actual.getMetadata(this._startTokenIndex + tokenIndex); + } +} + +export class MultilineTokens2 { + + public startLineNumber: number; + public endLineNumber: number; + public tokens: IEncodedTokens; + + constructor(startLineNumber: number, tokens: IEncodedTokens) { + this.startLineNumber = startLineNumber; + this.tokens = tokens; + this.endLineNumber = this.startLineNumber + this.tokens.getMaxDeltaLine(); + } + + private _updateEndLineNumber(): void { + this.endLineNumber = this.startLineNumber + this.tokens.getMaxDeltaLine(); + } + + public getLineTokens(lineNumber: number): LineTokens2 | null { + if (this.startLineNumber <= lineNumber && lineNumber <= this.endLineNumber) { + const findResult = MultilineTokens2._findTokensWithLine(this.tokens, lineNumber - this.startLineNumber); + if (findResult) { + const [startTokenIndex, endTokenIndex] = findResult; + return new LineTokens2(this.tokens, startTokenIndex, endTokenIndex); + } + } + return null; + } + + private static _findTokensWithLine(tokens: IEncodedTokens, deltaLine: number): [number, number] | null { + let low = 0; + let high = tokens.getTokenCount() - 1; + + while (low < high) { + const mid = low + Math.floor((high - low) / 2); + const midDeltaLine = tokens.getDeltaLine(mid); + + if (midDeltaLine < deltaLine) { + low = mid + 1; + } else if (midDeltaLine > deltaLine) { + high = mid - 1; + } else { + let min = mid; + while (min > low && tokens.getDeltaLine(min - 1) === deltaLine) { + min--; + } + let max = mid; + while (max < high && tokens.getDeltaLine(max + 1) === deltaLine) { + max++; + } + return [min, max]; + } + } + + if (tokens.getDeltaLine(low) === deltaLine) { + return [low, low]; + } + + return null; + } + + public applyEdit(range: IRange, text: string): void { + const [eolCount, firstLineLength, lastLineLength] = countEOL(text); + this.acceptEdit(range, eolCount, firstLineLength, lastLineLength, text.length > 0 ? text.charCodeAt(0) : CharCode.Null); + } + + public acceptEdit(range: IRange, eolCount: number, firstLineLength: number, lastLineLength: number, firstCharCode: number): void { + this._acceptDeleteRange(range); + this._acceptInsertText(new Position(range.startLineNumber, range.startColumn), eolCount, firstLineLength, lastLineLength, firstCharCode); + this._updateEndLineNumber(); + } + + private _acceptDeleteRange(range: IRange): void { + if (range.startLineNumber === range.endLineNumber && range.startColumn === range.endColumn) { + // Nothing to delete + return; + } + + const firstLineIndex = range.startLineNumber - this.startLineNumber; + const lastLineIndex = range.endLineNumber - this.startLineNumber; + + if (lastLineIndex < 0) { + // this deletion occurs entirely before this block, so we only need to adjust line numbers + const deletedLinesCount = lastLineIndex - firstLineIndex; + this.startLineNumber -= deletedLinesCount; + return; + } + + const tokenMaxDeltaLine = this.tokens.getMaxDeltaLine(); + + if (firstLineIndex >= tokenMaxDeltaLine + 1) { + // this deletion occurs entirely after this block, so there is nothing to do + return; + } + + if (firstLineIndex < 0 && lastLineIndex >= tokenMaxDeltaLine + 1) { + // this deletion completely encompasses this block + this.startLineNumber = 0; + this.tokens.clear(); + return; + } + + if (firstLineIndex < 0) { + const deletedBefore = -firstLineIndex; + this.startLineNumber -= deletedBefore; + + this.tokens.acceptDeleteRange(0, 0, lastLineIndex, range.endColumn - 1); + } else { + this.tokens.acceptDeleteRange(firstLineIndex, range.startColumn - 1, lastLineIndex, range.endColumn - 1); + } + } + + private _acceptInsertText(position: Position, eolCount: number, firstLineLength: number, lastLineLength: number, firstCharCode: number): void { + + if (eolCount === 0 && firstLineLength === 0) { + // Nothing to insert + return; + } + + const lineIndex = position.lineNumber - this.startLineNumber; + + if (lineIndex < 0) { + // this insertion occurs before this block, so we only need to adjust line numbers + this.startLineNumber += eolCount; + return; + } + + const tokenMaxDeltaLine = this.tokens.getMaxDeltaLine(); + + if (lineIndex >= tokenMaxDeltaLine + 1) { + // this insertion occurs after this block, so there is nothing to do + return; + } + + this.tokens.acceptInsertText(lineIndex, position.column - 1, eolCount, firstLineLength, lastLineLength, firstCharCode); + } +} + export class MultilineTokens { public startLineNumber: number; @@ -193,6 +643,7 @@ export class MultilineTokens { // this deletion completely encompasses this block this.startLineNumber = 0; this.tokens = []; + return; } if (firstLineIndex === lastLineIndex) { @@ -289,6 +740,120 @@ function toUint32Array(arr: Uint32Array | ArrayBuffer): Uint32Array { } } +export class TokensStore2 { + + private _pieces: MultilineTokens2[]; + + constructor() { + this._pieces = []; + } + + public flush(): void { + this._pieces = []; + } + + public set(pieces: MultilineTokens2[] | null) { + this._pieces = pieces || []; + } + + public addSemanticTokens(lineNumber: number, aTokens: LineTokens): LineTokens { + const pieces = this._pieces; + + if (pieces.length === 0) { + return aTokens; + } + + const pieceIndex = TokensStore2._findFirstPieceWithLine(pieces, lineNumber); + const bTokens = this._pieces[pieceIndex].getLineTokens(lineNumber); + + if (!bTokens) { + return aTokens; + } + + const aLen = aTokens.getCount(); + const bLen = bTokens.getCount(); + + let aIndex = 0; + let result: number[] = [], resultLen = 0; + for (let bIndex = 0; bIndex < bLen; bIndex++) { + const bStartCharacter = bTokens.getStartCharacter(bIndex); + const bEndCharacter = bTokens.getEndCharacter(bIndex); + const bMetadata = bTokens.getMetadata(bIndex); + + // push any token from `a` that is before `b` + while (aIndex < aLen && aTokens.getEndOffset(aIndex) <= bStartCharacter) { + result[resultLen++] = aTokens.getEndOffset(aIndex); + result[resultLen++] = aTokens.getMetadata(aIndex); + aIndex++; + } + + // push the token from `a` if it intersects the token from `b` + if (aIndex < aLen && aTokens.getStartOffset(aIndex) < bStartCharacter) { + result[resultLen++] = bStartCharacter; + result[resultLen++] = aTokens.getMetadata(aIndex); + } + + // skip any tokens from `a` that are contained inside `b` + while (aIndex < aLen && aTokens.getEndOffset(aIndex) <= bEndCharacter) { + aIndex++; + } + + const aMetadata = aTokens.getMetadata(aIndex - 1 > 0 ? aIndex - 1 : aIndex); + const languageId = TokenMetadata.getLanguageId(aMetadata); + const tokenType = TokenMetadata.getTokenType(aMetadata); + + // push the token from `b` + result[resultLen++] = bEndCharacter; + result[resultLen++] = ( + (bMetadata & MetadataConsts.LANG_TTYPE_CMPL) + | ((languageId << MetadataConsts.LANGUAGEID_OFFSET) >>> 0) + | ((tokenType << MetadataConsts.TOKEN_TYPE_OFFSET) >>> 0) + ); + } + + // push the remaining tokens from `a` + while (aIndex < aLen) { + result[resultLen++] = aTokens.getEndOffset(aIndex); + result[resultLen++] = aTokens.getMetadata(aIndex); + aIndex++; + } + + return new LineTokens(new Uint32Array(result), aTokens.getLineContent()); + } + + private static _findFirstPieceWithLine(pieces: MultilineTokens2[], lineNumber: number): number { + let low = 0; + let high = pieces.length - 1; + + while (low < high) { + let mid = low + Math.floor((high - low) / 2); + + if (pieces[mid].endLineNumber < lineNumber) { + low = mid + 1; + } else if (pieces[mid].startLineNumber > lineNumber) { + high = mid - 1; + } else { + while (mid > low && pieces[mid - 1].startLineNumber <= lineNumber && lineNumber <= pieces[mid - 1].endLineNumber) { + mid--; + } + return mid; + } + } + + return low; + } + + //#region Editing + + public acceptEdit(range: IRange, eolCount: number, firstLineLength: number, lastLineLength: number, firstCharCode: number): void { + for (const piece of this._pieces) { + piece.acceptEdit(range, eolCount, firstLineLength, lastLineLength, firstCharCode); + } + } + + //#endregion +} + export class TokensStore { private _lineTokens: (Uint32Array | ArrayBuffer | null)[]; private _len: number; diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index eebbf31657..c05b63fc23 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -19,7 +19,6 @@ import { LanguageFeatureRegistry } from 'vs/editor/common/modes/languageFeatureR import { TokenizationRegistryImpl } from 'vs/editor/common/modes/tokenizationRegistry'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IMarkerData } from 'vs/platform/markers/common/markers'; -import { keys } from 'vs/base/common/map'; /** * Open ended enum at runtime @@ -126,6 +125,8 @@ export const enum MetadataConsts { FOREGROUND_MASK = 0b00000000011111111100000000000000, BACKGROUND_MASK = 0b11111111100000000000000000000000, + LANG_TTYPE_CMPL = 0b11111111111111111111100000000000, + LANGUAGEID_OFFSET = 0, TOKEN_TYPE_OFFSET = 8, FONT_STYLE_OFFSET = 11, @@ -453,7 +454,7 @@ export interface CompletionItem { * *Note:* The range must be a [single line](#Range.isSingleLine) and it must * [contain](#Range.contains) the position at which completion has been [requested](#CompletionItemProvider.provideCompletionItems). */ - range: IRange; + range: IRange | { insert: IRange, replace: IRange }; /** * An optional set of characters that when pressed while this completion is active will accept it first and * then type that character. *Note* that all commit characters should have `length=1` and that superfluous @@ -547,6 +548,7 @@ export interface CodeAction { diagnostics?: IMarkerData[]; kind?: string; isPreferred?: boolean; + disabled?: string; } /** @@ -938,12 +940,6 @@ export namespace SymbolKinds { export function fromString(value: string): SymbolKind | undefined { return byName.get(value); } - /** - * @internal - */ - export function names(): readonly string[] { - return keys(byName); - } /** * @internal */ @@ -1451,14 +1447,6 @@ export interface IWebviewPanelOptions { readonly retainContextWhenHidden?: boolean; } -/** - * @internal - */ -export const enum WebviewContentState { - Readonly = 1, - Unchanged = 2, - Dirty = 3, -} export interface CodeLens { range: IRange; @@ -1477,6 +1465,33 @@ export interface CodeLensProvider { resolveCodeLens?(model: model.ITextModel, codeLens: CodeLens, token: CancellationToken): ProviderResult; } +export interface SemanticTokensLegend { + readonly tokenTypes: string[]; + readonly tokenModifiers: string[]; +} + +export interface SemanticTokens { + readonly resultId?: string; + readonly data: Uint32Array; +} + +export interface SemanticTokensEdit { + readonly start: number; + readonly deleteCount: number; + readonly data?: Uint32Array; +} + +export interface SemanticTokensEdits { + readonly resultId?: string; + readonly edits: SemanticTokensEdit[]; +} + +export interface SemanticTokensProvider { + getLegend(): SemanticTokensLegend; + provideSemanticTokens(model: model.ITextModel, lastResultId: string | null, ranges: Range[] | null, token: CancellationToken): ProviderResult; + releaseSemanticTokens(resultId: string | undefined): void; +} + // --- feature registries ------ /** @@ -1579,6 +1594,11 @@ export const SelectionRangeRegistry = new LanguageFeatureRegistry(); +/** + * @internal + */ +export const SemanticTokensProviderRegistry = new LanguageFeatureRegistry(); + /** * @internal */ diff --git a/src/vs/editor/common/modes/languageConfiguration.ts b/src/vs/editor/common/modes/languageConfiguration.ts index ce8479a254..c2aac2feee 100644 --- a/src/vs/editor/common/modes/languageConfiguration.ts +++ b/src/vs/editor/common/modes/languageConfiguration.ts @@ -228,6 +228,28 @@ export interface EnterAction { removeText?: number; } +/** + * @internal + */ +export interface CompleteEnterAction { + /** + * Describe what to do with the indentation. + */ + indentAction: IndentAction; + /** + * Describes text to be appended after the new line and after the indentation. + */ + appendText: string; + /** + * Describes the number of characters to remove from the new line's indentation. + */ + removeText: number; + /** + * The line's indentation minus removeText + */ + indentation: string; +} + /** * @internal */ diff --git a/src/vs/editor/common/modes/languageConfigurationRegistry.ts b/src/vs/editor/common/modes/languageConfigurationRegistry.ts index 513189706e..477fb1f41e 100644 --- a/src/vs/editor/common/modes/languageConfigurationRegistry.ts +++ b/src/vs/editor/common/modes/languageConfigurationRegistry.ts @@ -3,7 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import * as strings from 'vs/base/common/strings'; @@ -12,13 +11,14 @@ 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 { LanguageId, LanguageIdentifier } from 'vs/editor/common/modes'; -import { EnterAction, FoldingRules, IAutoClosingPair, IndentAction, IndentationRule, LanguageConfiguration, StandardAutoClosingPairConditional } from 'vs/editor/common/modes/languageConfiguration'; -import { createScopedLineTokens } from 'vs/editor/common/modes/supports'; +import { EnterAction, FoldingRules, IAutoClosingPair, IndentAction, IndentationRule, LanguageConfiguration, StandardAutoClosingPairConditional, CompleteEnterAction } 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'; import { IndentConsts, IndentRulesSupport } from 'vs/editor/common/modes/supports/indentRules'; -import { IOnEnterSupportOptions, OnEnterSupport } from 'vs/editor/common/modes/supports/onEnter'; +import { OnEnterSupport } from 'vs/editor/common/modes/supports/onEnter'; import { RichEditBrackets } from 'vs/editor/common/modes/supports/richEditBrackets'; +import { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions'; /** * Interface used to support insertion of mode specific comments. @@ -48,11 +48,11 @@ export class RichEditSupport { private readonly _languageIdentifier: LanguageIdentifier; private _brackets: RichEditBrackets | null; private _electricCharacter: BracketElectricCharacterSupport | null; + private readonly _onEnterSupport: OnEnterSupport | null; public readonly comments: ICommentsConfiguration | null; public readonly characterPair: CharacterPairSupport; public readonly wordDefinition: RegExp; - public readonly onEnter: OnEnterSupport | null; public readonly indentRulesSupport: IndentRulesSupport | null; public readonly indentationRules: IndentationRule | undefined; public readonly foldingRules: FoldingRules; @@ -70,8 +70,7 @@ export class RichEditSupport { this._conf = RichEditSupport._mergeConf(prev, rawConf); - this.onEnter = RichEditSupport._handleOnEnter(this._conf); - + this._onEnterSupport = (this._conf.brackets || this._conf.indentationRules || this._conf.onEnterRules ? new OnEnterSupport(this._conf) : null); this.comments = RichEditSupport._handleComments(this._conf); this.characterPair = new CharacterPairSupport(this._conf); @@ -102,6 +101,13 @@ export class RichEditSupport { return this._electricCharacter; } + public onEnter(autoIndent: EditorAutoIndentStrategy, oneLineAboveText: string, beforeEnterText: string, afterEnterText: string): EnterAction | null { + if (!this._onEnterSupport) { + return null; + } + return this._onEnterSupport.onEnter(autoIndent, oneLineAboveText, beforeEnterText, afterEnterText); + } + private static _mergeConf(prev: LanguageConfiguration | null, current: LanguageConfiguration): LanguageConfiguration { return { comments: (prev ? current.comments || prev.comments : current.comments), @@ -117,29 +123,6 @@ export class RichEditSupport { }; } - private static _handleOnEnter(conf: LanguageConfiguration): OnEnterSupport | null { - // on enter - let onEnter: IOnEnterSupportOptions = {}; - let empty = true; - - if (conf.brackets) { - empty = false; - onEnter.brackets = conf.brackets; - } - if (conf.indentationRules) { - empty = false; - } - if (conf.onEnterRules) { - empty = false; - onEnter.regExpRules = conf.onEnterRules; - } - - if (!empty) { - return new OnEnterSupport(onEnter); - } - return null; - } - private static _handleComments(conf: LanguageConfiguration): ICommentsConfiguration | null { let commentRule = conf.comments; if (!commentRule) { @@ -351,8 +334,12 @@ export class LanguageConfigurationRegistryImpl { * * This function only return the inherited indent based on above lines, it doesn't check whether current line should decrease or not. */ - public getInheritIndentForLine(model: IVirtualModel, lineNumber: number, honorIntentialIndent: boolean = true): { indentation: string; action: IndentAction | null; line?: number; } | null { - let indentRulesSupport = this.getIndentRulesSupport(model.getLanguageIdentifier().id); + public getInheritIndentForLine(autoIndent: EditorAutoIndentStrategy, model: IVirtualModel, lineNumber: number, honorIntentialIndent: boolean = true): { indentation: string; action: IndentAction | null; line?: number; } | null { + if (autoIndent < EditorAutoIndentStrategy.Full) { + return null; + } + + const indentRulesSupport = this.getIndentRulesSupport(model.getLanguageIdentifier().id); if (!indentRulesSupport) { return null; } @@ -364,7 +351,7 @@ export class LanguageConfigurationRegistryImpl { }; } - let precedingUnIgnoredLine = this.getPrecedingValidLine(model, lineNumber, indentRulesSupport); + const precedingUnIgnoredLine = this.getPrecedingValidLine(model, lineNumber, indentRulesSupport); if (precedingUnIgnoredLine < 0) { return null; } else if (precedingUnIgnoredLine < 1) { @@ -374,8 +361,7 @@ export class LanguageConfigurationRegistryImpl { }; } - let precedingUnIgnoredLineContent = model.getLineContent(precedingUnIgnoredLine); - + const precedingUnIgnoredLineContent = model.getLineContent(precedingUnIgnoredLine); if (indentRulesSupport.shouldIncrease(precedingUnIgnoredLineContent) || indentRulesSupport.shouldIndentNextLine(precedingUnIgnoredLineContent)) { return { indentation: strings.getLeadingWhitespace(precedingUnIgnoredLineContent), @@ -402,9 +388,9 @@ export class LanguageConfigurationRegistryImpl { }; } - let previousLine = precedingUnIgnoredLine - 1; + const previousLine = precedingUnIgnoredLine - 1; - let previousLineIndentMetadata = indentRulesSupport.getIndentMetadata(model.getLineContent(previousLine)); + const previousLineIndentMetadata = indentRulesSupport.getIndentMetadata(model.getLineContent(previousLine)); if (!(previousLineIndentMetadata & (IndentConsts.INCREASE_MASK | IndentConsts.DECREASE_MASK)) && (previousLineIndentMetadata & IndentConsts.INDENT_NEXTLINE_MASK)) { let stopLine = 0; @@ -432,7 +418,7 @@ export class LanguageConfigurationRegistryImpl { } else { // search from precedingUnIgnoredLine until we find one whose indent is not temporary for (let i = precedingUnIgnoredLine; i > 0; i--) { - let lineContent = model.getLineContent(i); + const lineContent = model.getLineContent(i); if (indentRulesSupport.shouldIncrease(lineContent)) { return { indentation: strings.getLeadingWhitespace(lineContent), @@ -472,27 +458,28 @@ export class LanguageConfigurationRegistryImpl { } } - public getGoodIndentForLine(virtualModel: IVirtualModel, languageId: LanguageId, lineNumber: number, indentConverter: IIndentConverter): string | null { - let indentRulesSupport = this.getIndentRulesSupport(languageId); + public getGoodIndentForLine(autoIndent: EditorAutoIndentStrategy, virtualModel: IVirtualModel, languageId: LanguageId, lineNumber: number, indentConverter: IIndentConverter): string | null { + if (autoIndent < EditorAutoIndentStrategy.Full) { + return null; + } + + const richEditSupport = this._getRichEditSupport(languageId); + if (!richEditSupport) { + return null; + } + + const indentRulesSupport = this.getIndentRulesSupport(languageId); if (!indentRulesSupport) { return null; } - let indent = this.getInheritIndentForLine(virtualModel, lineNumber); - let lineContent = virtualModel.getLineContent(lineNumber); + const indent = this.getInheritIndentForLine(autoIndent, virtualModel, lineNumber); + const lineContent = virtualModel.getLineContent(lineNumber); if (indent) { - let inheritLine = indent.line; + const inheritLine = indent.line; if (inheritLine !== undefined) { - let onEnterSupport = this._getOnEnterSupport(languageId); - let enterResult: EnterAction | null = null; - try { - if (onEnterSupport) { - enterResult = onEnterSupport.onEnter('', virtualModel.getLineContent(inheritLine), ''); - } - } catch (e) { - onUnexpectedError(e); - } + const enterResult = richEditSupport.onEnter(autoIndent, '', virtualModel.getLineContent(inheritLine), ''); if (enterResult) { let indentation = strings.getLeadingWhitespace(virtualModel.getLineContent(inheritLine)); @@ -539,16 +526,17 @@ export class LanguageConfigurationRegistryImpl { return null; } - public getIndentForEnter(model: ITextModel, range: Range, indentConverter: IIndentConverter, autoIndent: boolean): { beforeEnter: string, afterEnter: string } | null { + public getIndentForEnter(autoIndent: EditorAutoIndentStrategy, model: ITextModel, range: Range, indentConverter: IIndentConverter): { beforeEnter: string, afterEnter: string } | null { + if (autoIndent < EditorAutoIndentStrategy.Full) { + return null; + } model.forceTokenization(range.startLineNumber); - let lineTokens = model.getLineTokens(range.startLineNumber); - - let beforeEnterText; - let afterEnterText; - let scopedLineTokens = createScopedLineTokens(lineTokens, range.startColumn - 1); - let scopedLineText = scopedLineTokens.getLineContent(); + const lineTokens = model.getLineTokens(range.startLineNumber); + const scopedLineTokens = createScopedLineTokens(lineTokens, range.startColumn - 1); + const scopedLineText = scopedLineTokens.getLineContent(); let embeddedLanguage = false; + let beforeEnterText: string; if (scopedLineTokens.firstCharOffset > 0 && lineTokens.getLanguageId(0) !== scopedLineTokens.languageId) { // we are in the embeded language content embeddedLanguage = true; // if embeddedLanguage is true, then we don't touch the indentation of current line @@ -557,6 +545,7 @@ export class LanguageConfigurationRegistryImpl { beforeEnterText = lineTokens.getLineContent().substring(0, range.startColumn - 1); } + let afterEnterText: string; if (range.isEmpty()) { afterEnterText = scopedLineText.substr(range.startColumn - 1 - scopedLineTokens.firstCharOffset); } else { @@ -564,31 +553,15 @@ export class LanguageConfigurationRegistryImpl { afterEnterText = endScopedLineTokens.getLineContent().substr(range.endColumn - 1 - scopedLineTokens.firstCharOffset); } - let indentRulesSupport = this.getIndentRulesSupport(scopedLineTokens.languageId); - + const indentRulesSupport = this.getIndentRulesSupport(scopedLineTokens.languageId); if (!indentRulesSupport) { return null; } - let beforeEnterResult = beforeEnterText; - let beforeEnterIndent = strings.getLeadingWhitespace(beforeEnterText); + const beforeEnterResult = beforeEnterText; + const beforeEnterIndent = strings.getLeadingWhitespace(beforeEnterText); - if (!autoIndent && !embeddedLanguage) { - let beforeEnterIndentAction = this.getInheritIndentForLine(model, range.startLineNumber); - - if (indentRulesSupport.shouldDecrease(beforeEnterText)) { - if (beforeEnterIndentAction) { - beforeEnterIndent = beforeEnterIndentAction.indentation; - if (beforeEnterIndentAction.action !== IndentAction.Indent) { - beforeEnterIndent = indentConverter.unshiftIndent(beforeEnterIndent); - } - } - } - - beforeEnterResult = beforeEnterIndent + strings.ltrim(strings.ltrim(beforeEnterText, ' '), '\t'); - } - - let virtualModel: IVirtualModel = { + const virtualModel: IVirtualModel = { getLineTokens: (lineNumber: number) => { return model.getLineTokens(lineNumber); }, @@ -607,10 +580,10 @@ export class LanguageConfigurationRegistryImpl { } }; - let currentLineIndent = strings.getLeadingWhitespace(lineTokens.getLineContent()); - let afterEnterAction = this.getInheritIndentForLine(virtualModel, range.startLineNumber + 1); + const currentLineIndent = strings.getLeadingWhitespace(lineTokens.getLineContent()); + const afterEnterAction = this.getInheritIndentForLine(autoIndent, virtualModel, range.startLineNumber + 1); if (!afterEnterAction) { - let beforeEnter = embeddedLanguage ? currentLineIndent : beforeEnterIndent; + const beforeEnter = embeddedLanguage ? currentLineIndent : beforeEnterIndent; return { beforeEnter: beforeEnter, afterEnter: beforeEnter @@ -637,18 +610,21 @@ export class LanguageConfigurationRegistryImpl { * We should always allow intentional indentation. It means, if users change the indentation of `lineNumber` and the content of * this line doesn't match decreaseIndentPattern, we should not adjust the indentation. */ - public getIndentActionForType(model: ITextModel, range: Range, ch: string, indentConverter: IIndentConverter): string | null { - let scopedLineTokens = this.getScopedLineTokens(model, range.startLineNumber, range.startColumn); - let indentRulesSupport = this.getIndentRulesSupport(scopedLineTokens.languageId); + public getIndentActionForType(autoIndent: EditorAutoIndentStrategy, model: ITextModel, range: Range, ch: string, indentConverter: IIndentConverter): string | null { + if (autoIndent < EditorAutoIndentStrategy.Full) { + return null; + } + const scopedLineTokens = this.getScopedLineTokens(model, range.startLineNumber, range.startColumn); + const indentRulesSupport = this.getIndentRulesSupport(scopedLineTokens.languageId); if (!indentRulesSupport) { return null; } - let scopedLineText = scopedLineTokens.getLineContent(); - let beforeTypeText = scopedLineText.substr(0, range.startColumn - 1 - scopedLineTokens.firstCharOffset); - let afterTypeText; + const scopedLineText = scopedLineTokens.getLineContent(); + const beforeTypeText = scopedLineText.substr(0, range.startColumn - 1 - scopedLineTokens.firstCharOffset); // selection support + let afterTypeText: string; if (range.isEmpty()) { afterTypeText = scopedLineText.substr(range.startColumn - 1 - scopedLineTokens.firstCharOffset); } else { @@ -661,13 +637,12 @@ export class LanguageConfigurationRegistryImpl { if (!indentRulesSupport.shouldDecrease(beforeTypeText + afterTypeText) && indentRulesSupport.shouldDecrease(beforeTypeText + ch + afterTypeText)) { // after typing `ch`, the content matches decreaseIndentPattern, we should adjust the indent to a good manner. // 1. Get inherited indent action - let r = this.getInheritIndentForLine(model, range.startLineNumber, false); + const r = this.getInheritIndentForLine(autoIndent, model, range.startLineNumber, false); if (!r) { return null; } let indentation = r.indentation; - if (r.action !== IndentAction.Indent) { indentation = indentConverter.unshiftIndent(indentation); } @@ -679,15 +654,13 @@ export class LanguageConfigurationRegistryImpl { } public getIndentMetadata(model: ITextModel, lineNumber: number): number | null { - let indentRulesSupport = this.getIndentRulesSupport(model.getLanguageIdentifier().id); + const indentRulesSupport = this.getIndentRulesSupport(model.getLanguageIdentifier().id); if (!indentRulesSupport) { return null; } - if (lineNumber < 1 || lineNumber > model.getLineCount()) { return null; } - return indentRulesSupport.getIndentMetadata(model.getLineContent(lineNumber)); } @@ -695,34 +668,18 @@ export class LanguageConfigurationRegistryImpl { // begin onEnter - private _getOnEnterSupport(languageId: LanguageId): OnEnterSupport | null { - let value = this._getRichEditSupport(languageId); - if (!value) { - return null; - } - return value.onEnter || null; - } - - public getRawEnterActionAtPosition(model: ITextModel, lineNumber: number, column: number): EnterAction | null { - let r = this.getEnterAction(model, new Range(lineNumber, column, lineNumber, column)); - - return r ? r.enterAction : null; - } - - public getEnterAction(model: ITextModel, range: Range): { enterAction: EnterAction; indentation: string; } | null { - let indentation = this.getIndentationAtPosition(model, range.startLineNumber, range.startColumn); - - let scopedLineTokens = this.getScopedLineTokens(model, range.startLineNumber, range.startColumn); - let onEnterSupport = this._getOnEnterSupport(scopedLineTokens.languageId); - if (!onEnterSupport) { + public getEnterAction(autoIndent: EditorAutoIndentStrategy, model: ITextModel, range: Range): CompleteEnterAction | null { + const scopedLineTokens = this.getScopedLineTokens(model, range.startLineNumber, range.startColumn); + const richEditSupport = this._getRichEditSupport(scopedLineTokens.languageId); + if (!richEditSupport) { return null; } - let scopedLineText = scopedLineTokens.getLineContent(); - let beforeEnterText = scopedLineText.substr(0, range.startColumn - 1 - scopedLineTokens.firstCharOffset); - let afterEnterText; + const scopedLineText = scopedLineTokens.getLineContent(); + const beforeEnterText = scopedLineText.substr(0, range.startColumn - 1 - scopedLineTokens.firstCharOffset); // selection support + let afterEnterText: string; if (range.isEmpty()) { afterEnterText = scopedLineText.substr(range.startColumn - 1 - scopedLineTokens.firstCharOffset); } else { @@ -730,73 +687,70 @@ export class LanguageConfigurationRegistryImpl { afterEnterText = endScopedLineTokens.getLineContent().substr(range.endColumn - 1 - scopedLineTokens.firstCharOffset); } - let lineNumber = range.startLineNumber; let oneLineAboveText = ''; - - if (lineNumber > 1 && scopedLineTokens.firstCharOffset === 0) { + if (range.startLineNumber > 1 && scopedLineTokens.firstCharOffset === 0) { // This is not the first line and the entire line belongs to this mode - let oneLineAboveScopedLineTokens = this.getScopedLineTokens(model, lineNumber - 1); + const oneLineAboveScopedLineTokens = this.getScopedLineTokens(model, range.startLineNumber - 1); if (oneLineAboveScopedLineTokens.languageId === scopedLineTokens.languageId) { // The line above ends with text belonging to the same mode oneLineAboveText = oneLineAboveScopedLineTokens.getLineContent(); } } - let enterResult: EnterAction | null = null; - try { - enterResult = onEnterSupport.onEnter(oneLineAboveText, beforeEnterText, afterEnterText); - } catch (e) { - onUnexpectedError(e); - } - + const enterResult = richEditSupport.onEnter(autoIndent, oneLineAboveText, beforeEnterText, afterEnterText); if (!enterResult) { return null; - } else { - // Here we add `\t` to appendText first because enterAction is leveraging appendText and removeText to change indentation. - if (!enterResult.appendText) { - if ( - (enterResult.indentAction === IndentAction.Indent) || - (enterResult.indentAction === IndentAction.IndentOutdent) - ) { - enterResult.appendText = '\t'; - } else { - enterResult.appendText = ''; - } + } + + const indentAction = enterResult.indentAction; + let appendText = enterResult.appendText; + const removeText = enterResult.removeText || 0; + + // Here we add `\t` to appendText first because enterAction is leveraging appendText and removeText to change indentation. + if (!appendText) { + if ( + (indentAction === IndentAction.Indent) || + (indentAction === IndentAction.IndentOutdent) + ) { + appendText = '\t'; + } else { + appendText = ''; } } - if (enterResult.removeText) { - indentation = indentation.substring(0, indentation.length - enterResult.removeText); + let indentation = this.getIndentationAtPosition(model, range.startLineNumber, range.startColumn); + if (removeText) { + indentation = indentation.substring(0, indentation.length - removeText); } return { - enterAction: enterResult, - indentation: indentation, + indentAction: indentAction, + appendText: appendText, + removeText: removeText, + indentation: indentation }; } public getIndentationAtPosition(model: ITextModel, lineNumber: number, column: number): string { - let lineText = model.getLineContent(lineNumber); + const lineText = model.getLineContent(lineNumber); let indentation = strings.getLeadingWhitespace(lineText); if (indentation.length > column - 1) { indentation = indentation.substring(0, column - 1); } - return indentation; } - private getScopedLineTokens(model: ITextModel, lineNumber: number, columnNumber?: number) { + private getScopedLineTokens(model: ITextModel, lineNumber: number, columnNumber?: number): ScopedLineTokens { model.forceTokenization(lineNumber); - let lineTokens = model.getLineTokens(lineNumber); - let column = (typeof columnNumber === 'undefined' ? model.getLineMaxColumn(lineNumber) - 1 : columnNumber - 1); - let scopedLineTokens = createScopedLineTokens(lineTokens, column); - return scopedLineTokens; + const lineTokens = model.getLineTokens(lineNumber); + const column = (typeof columnNumber === 'undefined' ? model.getLineMaxColumn(lineNumber) - 1 : columnNumber - 1); + return createScopedLineTokens(lineTokens, column); } // end onEnter public getBracketsSupport(languageId: LanguageId): RichEditBrackets | null { - let value = this._getRichEditSupport(languageId); + const value = this._getRichEditSupport(languageId); if (!value) { return null; } diff --git a/src/vs/editor/common/modes/linkComputer.ts b/src/vs/editor/common/modes/linkComputer.ts index c3a62b262b..6c427272d7 100644 --- a/src/vs/editor/common/modes/linkComputer.ts +++ b/src/vs/editor/common/modes/linkComputer.ts @@ -264,6 +264,10 @@ export class LinkComputer { case CharCode.BackTick: chClass = (linkBeginChCode === CharCode.SingleQuote || linkBeginChCode === CharCode.DoubleQuote) ? CharacterClass.None : CharacterClass.ForceTermination; break; + case CharCode.Asterisk: + // `*` terminates a link if the link began with `*` + chClass = (linkBeginChCode === CharCode.Asterisk) ? CharacterClass.ForceTermination : CharacterClass.None; + break; default: chClass = classifier.get(chCode); } diff --git a/src/vs/editor/common/modes/supports/onEnter.ts b/src/vs/editor/common/modes/supports/onEnter.ts index 358b7c8553..d317c77a52 100644 --- a/src/vs/editor/common/modes/supports/onEnter.ts +++ b/src/vs/editor/common/modes/supports/onEnter.ts @@ -6,10 +6,11 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import * as strings from 'vs/base/common/strings'; import { CharacterPair, EnterAction, IndentAction, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration'; +import { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions'; export interface IOnEnterSupportOptions { brackets?: CharacterPair[]; - regExpRules?: OnEnterRule[]; + onEnterRules?: OnEnterRule[]; } interface IProcessedBracketPair { @@ -24,7 +25,7 @@ export class OnEnterSupport { private readonly _brackets: IProcessedBracketPair[]; private readonly _regExpRules: OnEnterRule[]; - constructor(opts?: IOnEnterSupportOptions) { + constructor(opts: IOnEnterSupportOptions) { opts = opts || {}; opts.brackets = opts.brackets || [ ['(', ')'], @@ -45,49 +46,54 @@ export class OnEnterSupport { }); } }); - this._regExpRules = opts.regExpRules || []; + this._regExpRules = opts.onEnterRules || []; } - public onEnter(oneLineAboveText: string, beforeEnterText: string, afterEnterText: string): EnterAction | null { + public onEnter(autoIndent: EditorAutoIndentStrategy, oneLineAboveText: string, beforeEnterText: string, afterEnterText: string): EnterAction | null { // (1): `regExpRules` - for (let i = 0, len = this._regExpRules.length; i < len; i++) { - let rule = this._regExpRules[i]; - const regResult = [{ - reg: rule.beforeText, - text: beforeEnterText - }, { - reg: rule.afterText, - text: afterEnterText - }, { - reg: rule.oneLineAboveText, - text: oneLineAboveText - }].every((obj): boolean => { - return obj.reg ? obj.reg.test(obj.text) : true; - }); + if (autoIndent >= EditorAutoIndentStrategy.Advanced) { + for (let i = 0, len = this._regExpRules.length; i < len; i++) { + let rule = this._regExpRules[i]; + const regResult = [{ + reg: rule.beforeText, + text: beforeEnterText + }, { + reg: rule.afterText, + text: afterEnterText + }, { + reg: rule.oneLineAboveText, + text: oneLineAboveText + }].every((obj): boolean => { + return obj.reg ? obj.reg.test(obj.text) : true; + }); - if (regResult) { - return rule.action; + if (regResult) { + return rule.action; + } } } - // (2): Special indent-outdent - if (beforeEnterText.length > 0 && afterEnterText.length > 0) { - for (let i = 0, len = this._brackets.length; i < len; i++) { - let bracket = this._brackets[i]; - if (bracket.openRegExp.test(beforeEnterText) && bracket.closeRegExp.test(afterEnterText)) { - return { indentAction: IndentAction.IndentOutdent }; + if (autoIndent >= EditorAutoIndentStrategy.Brackets) { + if (beforeEnterText.length > 0 && afterEnterText.length > 0) { + for (let i = 0, len = this._brackets.length; i < len; i++) { + let bracket = this._brackets[i]; + if (bracket.openRegExp.test(beforeEnterText) && bracket.closeRegExp.test(afterEnterText)) { + return { indentAction: IndentAction.IndentOutdent }; + } } } } // (4): Open bracket based logic - if (beforeEnterText.length > 0) { - for (let i = 0, len = this._brackets.length; i < len; i++) { - let bracket = this._brackets[i]; - if (bracket.openRegExp.test(beforeEnterText)) { - return { indentAction: IndentAction.Indent }; + if (autoIndent >= EditorAutoIndentStrategy.Brackets) { + if (beforeEnterText.length > 0) { + for (let i = 0, len = this._brackets.length; i < len; i++) { + let bracket = this._brackets[i]; + if (bracket.openRegExp.test(beforeEnterText)) { + return { indentAction: IndentAction.Indent }; + } } } } diff --git a/src/vs/editor/common/modes/textToHtmlTokenizer.ts b/src/vs/editor/common/modes/textToHtmlTokenizer.ts index f9988db015..d45497ec20 100644 --- a/src/vs/editor/common/modes/textToHtmlTokenizer.ts +++ b/src/vs/editor/common/modes/textToHtmlTokenizer.ts @@ -46,7 +46,7 @@ export function tokenizeLineToHTML(text: string, viewLineTokens: IViewLineTokens let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize; tabsCharDelta += insertSpacesCount - 1; while (insertSpacesCount > 0) { - partContent += useNbsp ? ' ' : ' '; + partContent += useNbsp ? ' ' : ' '; insertSpacesCount--; } break; @@ -78,7 +78,7 @@ export function tokenizeLineToHTML(text: string, viewLineTokens: IViewLineTokens break; case CharCode.Space: - partContent += useNbsp ? ' ' : ' '; + partContent += useNbsp ? ' ' : ' '; break; default: diff --git a/src/vs/editor/common/services/editorSimpleWorker.ts b/src/vs/editor/common/services/editorSimpleWorker.ts index 94e101d93c..1a4eb9d349 100644 --- a/src/vs/editor/common/services/editorSimpleWorker.ts +++ b/src/vs/editor/common/services/editorSimpleWorker.ts @@ -17,7 +17,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import { EndOfLineSequence, IWordAtPosition } from 'vs/editor/common/model'; import { IModelChangedEvent, MirrorTextModel as BaseMirrorModel } from 'vs/editor/common/model/mirrorTextModel'; import { ensureValidWordDefinition, getWordAtText } from 'vs/editor/common/model/wordHelper'; -import { CompletionItem, CompletionItemKind, CompletionList, IInplaceReplaceSupportResult, ILink, TextEdit } from 'vs/editor/common/modes'; +import { IInplaceReplaceSupportResult, ILink, TextEdit } from 'vs/editor/common/modes'; import { ILinkComputerTarget, computeLinks } from 'vs/editor/common/modes/linkComputer'; import { BasicInplaceReplace } from 'vs/editor/common/modes/supports/inplaceReplaceSupport'; import { IDiffComputationResult } from 'vs/editor/common/services/editorWorkerService'; @@ -529,44 +529,38 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { private static readonly _suggestionsLimit = 10000; - public async textualSuggest(modelUrl: string, position: IPosition, wordDef: string, wordDefFlags: string): Promise { + public async textualSuggest(modelUrl: string, position: IPosition, wordDef: string, wordDefFlags: string): Promise { const model = this._getModel(modelUrl); if (!model) { return null; } - const seen: Record = Object.create(null); - const suggestions: CompletionItem[] = []; + + const words: string[] = []; + const seen = new Set(); const wordDefRegExp = new RegExp(wordDef, wordDefFlags); - const wordUntil = model.getWordUntilPosition(position, wordDefRegExp); const wordAt = model.getWordAtPosition(position, wordDefRegExp); if (wordAt) { - seen[model.getValueInRange(wordAt)] = true; + seen.add(model.getValueInRange(wordAt)); } for ( let iter = model.createWordIterator(wordDefRegExp), e = iter.next(); - !e.done && suggestions.length <= EditorSimpleWorker._suggestionsLimit; + !e.done && seen.size <= EditorSimpleWorker._suggestionsLimit; e = iter.next() ) { const word = e.value; - if (seen[word]) { + if (seen.has(word)) { continue; } - seen[word] = true; + seen.add(word); if (!isNaN(Number(word))) { continue; } - - suggestions.push({ - kind: CompletionItemKind.Text, - label: word, - insertText: word, - range: { startLineNumber: position.lineNumber, startColumn: wordUntil.startColumn, endLineNumber: position.lineNumber, endColumn: wordUntil.endColumn } - }); + words.push(word); } - return { suggestions }; + return words; } diff --git a/src/vs/editor/common/services/editorWorkerServiceImpl.ts b/src/vs/editor/common/services/editorWorkerServiceImpl.ts index bd362844ab..3fb21fdc2f 100644 --- a/src/vs/editor/common/services/editorWorkerServiceImpl.ts +++ b/src/vs/editor/common/services/editorWorkerServiceImpl.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { SimpleWorkerClient, logOnceWebWorkerWarning, IWorkerClient } from 'vs/base/common/worker/simpleWorker'; import { DefaultWorkerFactory } from 'vs/base/worker/defaultWorkerFactory'; import { IPosition, Position } from 'vs/editor/common/core/position'; -import { IRange } from 'vs/editor/common/core/range'; +import { IRange, Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; @@ -144,7 +144,7 @@ class WordBasedCompletionItemProvider implements modes.CompletionItemProvider { this._modelService = modelService; } - provideCompletionItems(model: ITextModel, position: Position): Promise | undefined { + async provideCompletionItems(model: ITextModel, position: Position): Promise { const { wordBasedSuggestions } = this._configurationService.getValue<{ wordBasedSuggestions?: boolean }>(model.uri, position, 'editor'); if (!wordBasedSuggestions) { return undefined; @@ -152,7 +152,27 @@ class WordBasedCompletionItemProvider implements modes.CompletionItemProvider { if (!canSyncModel(this._modelService, model.uri)) { return undefined; // File too large } - return this._workerManager.withWorker().then(client => client.textualSuggest(model.uri, position)); + + const word = model.getWordAtPosition(position); + const replace = !word ? Range.fromPositions(position) : new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn); + const insert = replace.setEndPosition(position.lineNumber, position.column); + + const client = await this._workerManager.withWorker(); + const words = await client.textualSuggest(model.uri, position); + if (!words) { + return undefined; + } + + return { + suggestions: words.map((word): modes.CompletionItem => { + return { + kind: modes.CompletionItemKind.Text, + label: word, + insertText: word, + range: { insert, replace } + }; + }) + }; } } @@ -433,7 +453,7 @@ export class EditorWorkerClient extends Disposable { }); } - public textualSuggest(resource: URI, position: IPosition): Promise { + public textualSuggest(resource: URI, position: IPosition): Promise { return this._withSyncedResources([resource]).then(proxy => { let model = this._modelService.getModel(resource); if (!model) { diff --git a/src/vs/editor/common/services/markerDecorationsServiceImpl.ts b/src/vs/editor/common/services/markerDecorationsServiceImpl.ts index a6517e1f55..625ebc08ca 100644 --- a/src/vs/editor/common/services/markerDecorationsServiceImpl.ts +++ b/src/vs/editor/common/services/markerDecorationsServiceImpl.ts @@ -6,7 +6,7 @@ import { IMarkerService, IMarker, MarkerSeverity, MarkerTag } from 'vs/platform/markers/common/markers'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { IModelDeltaDecoration, ITextModel, IModelDecorationOptions, TrackedRangeStickiness, OverviewRulerLane, IModelDecoration } from 'vs/editor/common/model'; +import { IModelDeltaDecoration, ITextModel, IModelDecorationOptions, TrackedRangeStickiness, OverviewRulerLane, IModelDecoration, MinimapPosition, IModelDecorationMinimapOptions } from 'vs/editor/common/model'; import { ClassName } from 'vs/editor/common/model/intervalTree'; import { themeColorFromId, ThemeColor } from 'vs/platform/theme/common/themeService'; import { overviewRulerWarning, overviewRulerInfo, overviewRulerError } from 'vs/editor/common/view/editorColorRegistry'; @@ -17,6 +17,7 @@ import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDeco import { Schemas } from 'vs/base/common/network'; import { Emitter, Event } from 'vs/base/common/event'; import { withUndefinedAsNull } from 'vs/base/common/types'; +import { minimapWarning, minimapError } from 'vs/platform/theme/common/colorRegistry'; function MODEL_ID(resource: URI): string { return resource.toString(); @@ -189,6 +190,7 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor let color: ThemeColor | undefined = undefined; let zIndex: number; let inlineClassName: string | undefined = undefined; + let minimap: IModelDecorationMinimapOptions | undefined; switch (marker.severity) { case MarkerSeverity.Hint: @@ -203,6 +205,10 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor className = ClassName.EditorWarningDecoration; color = themeColorFromId(overviewRulerWarning); zIndex = 20; + minimap = { + color: themeColorFromId(minimapWarning), + position: MinimapPosition.Inline + }; break; case MarkerSeverity.Info: className = ClassName.EditorInfoDecoration; @@ -214,6 +220,10 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor className = ClassName.EditorErrorDecoration; color = themeColorFromId(overviewRulerError); zIndex = 30; + minimap = { + color: themeColorFromId(minimapError), + position: MinimapPosition.Inline + }; break; } @@ -234,6 +244,7 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor color, position: OverviewRulerLane.Right }, + minimap, zIndex, inlineClassName, }; diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index 772ed30880..ef023724ae 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -6,19 +6,24 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; +import * as errors from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/config/editorOptions'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Range } from 'vs/editor/common/core/range'; import { DefaultEndOfLine, EndOfLinePreference, EndOfLineSequence, IIdentifiedSingleEditOperation, ITextBuffer, ITextBufferFactory, ITextModel, ITextModelCreationOptions } from 'vs/editor/common/model'; import { TextModel, createTextBuffer } from 'vs/editor/common/model/textModel'; -import { IModelLanguageChangedEvent } from 'vs/editor/common/model/textModelEvents'; -import { LanguageIdentifier } from 'vs/editor/common/modes'; +import { IModelLanguageChangedEvent, IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; +import { LanguageIdentifier, SemanticTokensProviderRegistry, SemanticTokensProvider, SemanticTokensLegend, SemanticTokens, SemanticTokensEdits } from 'vs/editor/common/modes'; import { PLAINTEXT_LANGUAGE_IDENTIFIER } from 'vs/editor/common/modes/modesRegistry'; import { ILanguageSelection } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { RunOnceScheduler } from 'vs/base/common/async'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { SparseEncodedTokens, MultilineTokens2 } from 'vs/editor/common/model/tokensStore'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; function MODEL_ID(resource: URI): string { return resource.toString(); @@ -114,7 +119,8 @@ export class ModelServiceImpl extends Disposable implements IModelService { constructor( @IConfigurationService configurationService: IConfigurationService, - @ITextResourcePropertiesService resourcePropertiesService: ITextResourcePropertiesService + @ITextResourcePropertiesService resourcePropertiesService: ITextResourcePropertiesService, + @IThemeService themeService: IThemeService ) { super(); this._configurationService = configurationService; @@ -124,6 +130,8 @@ export class ModelServiceImpl extends Disposable implements IModelService { this._configurationServiceSubscription = this._configurationService.onDidChangeConfiguration(e => this._updateModelOptions()); this._updateModelOptions(); + + this._register(new SemanticColoringFeature(this, themeService)); } private static _readModelOptions(config: IRawConfig, isForSimpleWidget: boolean): ITextModelCreationOptions { @@ -430,3 +438,467 @@ export class ModelServiceImpl extends Disposable implements IModelService { export interface ILineSequence { getLineContent(lineNumber: number): string; } + +class SemanticColoringFeature extends Disposable { + private _watchers: Record; + private _semanticStyling: SemanticStyling; + + constructor(modelService: IModelService, themeService: IThemeService) { + super(); + this._watchers = Object.create(null); + this._semanticStyling = this._register(new SemanticStyling(themeService)); + this._register(modelService.onModelAdded((model) => { + this._watchers[model.uri.toString()] = new ModelSemanticColoring(model, themeService, this._semanticStyling); + })); + this._register(modelService.onModelRemoved((model) => { + this._watchers[model.uri.toString()].dispose(); + delete this._watchers[model.uri.toString()]; + })); + } +} + +class SemanticStyling extends Disposable { + + private _caches: WeakMap; + + constructor( + private readonly _themeService: IThemeService + ) { + super(); + this._caches = new WeakMap(); + if (this._themeService) { + // workaround for tests which use undefined... :/ + this._register(this._themeService.onThemeChange(() => { + this._caches = new WeakMap(); + })); + } + } + + public get(provider: SemanticTokensProvider): SemanticColoringProviderStyling { + if (!this._caches.has(provider)) { + this._caches.set(provider, new SemanticColoringProviderStyling(provider.getLegend(), this._themeService)); + } + return this._caches.get(provider)!; + } +} + +const enum Constants { + NO_STYLING = 0b01111111111111111111111111111111 +} + +class HashTableEntry { + public readonly tokenTypeIndex: number; + public readonly tokenModifierSet: number; + public readonly metadata: number; + public next: HashTableEntry | null; + + constructor(tokenTypeIndex: number, tokenModifierSet: number, metadata: number) { + this.tokenTypeIndex = tokenTypeIndex; + this.tokenModifierSet = tokenModifierSet; + this.metadata = metadata; + this.next = null; + } +} + +class HashTable { + + private static _SIZES = [3, 7, 13, 31, 61, 127, 251, 509, 1021, 2039, 4093, 8191, 16381, 32749, 65521, 131071, 262139, 524287, 1048573, 2097143]; + + private _elementsCount: number; + private _currentLengthIndex: number; + private _currentLength: number; + private _growCount: number; + private _elements: (HashTableEntry | null)[]; + + constructor() { + this._elementsCount = 0; + this._currentLengthIndex = 0; + this._currentLength = HashTable._SIZES[this._currentLengthIndex]; + this._growCount = Math.round(this._currentLengthIndex + 1 < HashTable._SIZES.length ? 2 / 3 * this._currentLength : 0); + this._elements = []; + HashTable._nullOutEntries(this._elements, this._currentLength); + } + + private static _nullOutEntries(entries: (HashTableEntry | null)[], length: number): void { + for (let i = 0; i < length; i++) { + entries[i] = null; + } + } + + private _hashFunc(tokenTypeIndex: number, tokenModifierSet: number): number { + return ((((tokenTypeIndex << 5) - tokenTypeIndex) + tokenModifierSet) | 0) % this._currentLength; // tokenTypeIndex * 31 + tokenModifierSet, keep as int32 + } + + public get(tokenTypeIndex: number, tokenModifierSet: number): HashTableEntry | null { + const hash = this._hashFunc(tokenTypeIndex, tokenModifierSet); + + let p = this._elements[hash]; + while (p) { + if (p.tokenTypeIndex === tokenTypeIndex && p.tokenModifierSet === tokenModifierSet) { + return p; + } + p = p.next; + } + + return null; + } + + public add(tokenTypeIndex: number, tokenModifierSet: number, metadata: number): void { + this._elementsCount++; + if (this._growCount !== 0 && this._elementsCount >= this._growCount) { + // expand! + const oldElements = this._elements; + + this._currentLengthIndex++; + this._currentLength = HashTable._SIZES[this._currentLengthIndex]; + this._growCount = Math.round(this._currentLengthIndex + 1 < HashTable._SIZES.length ? 2 / 3 * this._currentLength : 0); + this._elements = []; + HashTable._nullOutEntries(this._elements, this._currentLength); + + for (const first of oldElements) { + let p = first; + while (p) { + const oldNext = p.next; + p.next = null; + this._add(p); + p = oldNext; + } + } + } + this._add(new HashTableEntry(tokenTypeIndex, tokenModifierSet, metadata)); + } + + private _add(element: HashTableEntry): void { + const hash = this._hashFunc(element.tokenTypeIndex, element.tokenModifierSet); + element.next = this._elements[hash]; + this._elements[hash] = element; + } +} + +class SemanticColoringProviderStyling { + + private readonly _hashTable: HashTable; + + constructor( + private readonly _legend: SemanticTokensLegend, + private readonly _themeService: IThemeService + ) { + this._hashTable = new HashTable(); + } + + public getMetadata(tokenTypeIndex: number, tokenModifierSet: number): number { + const entry = this._hashTable.get(tokenTypeIndex, tokenModifierSet); + if (entry) { + return entry.metadata; + } + + const tokenType = this._legend.tokenTypes[tokenTypeIndex]; + const tokenModifiers: string[] = []; + for (let modifierIndex = 0; tokenModifierSet !== 0 && modifierIndex < this._legend.tokenModifiers.length; modifierIndex++) { + if (tokenModifierSet & 1) { + tokenModifiers.push(this._legend.tokenModifiers[modifierIndex]); + } + tokenModifierSet = tokenModifierSet >> 1; + } + + let metadata = this._themeService.getTheme().getTokenStyleMetadata(tokenType, tokenModifiers); + if (typeof metadata === 'undefined') { + metadata = Constants.NO_STYLING; + } + + this._hashTable.add(tokenTypeIndex, tokenModifierSet, metadata); + return metadata; + } +} + +const enum SemanticColoringConstants { + /** + * Let's aim at having 8KB buffers if possible... + * So that would be 8192 / (5 * 4) = 409.6 tokens per area + */ + DesiredTokensPerArea = 400, + + /** + * Try to keep the total number of areas under 1024 if possible, + * simply compensate by having more tokens per area... + */ + DesiredMaxAreas = 1024, +} + +class SemanticTokensResponse { + constructor( + private readonly _provider: SemanticTokensProvider, + public readonly resultId: string | undefined, + public readonly data: Uint32Array + ) { } + + public dispose(): void { + this._provider.releaseSemanticTokens(this.resultId); + } +} + +class ModelSemanticColoring extends Disposable { + + private _isDisposed: boolean; + private readonly _model: ITextModel; + private readonly _semanticStyling: SemanticStyling; + private readonly _fetchSemanticTokens: RunOnceScheduler; + private _currentResponse: SemanticTokensResponse | null; + private _currentRequestCancellationTokenSource: CancellationTokenSource | null; + + constructor(model: ITextModel, themeService: IThemeService, stylingProvider: SemanticStyling) { + super(); + + this._isDisposed = false; + this._model = model; + this._semanticStyling = stylingProvider; + this._fetchSemanticTokens = this._register(new RunOnceScheduler(() => this._fetchSemanticTokensNow(), 500)); + this._currentResponse = null; + this._currentRequestCancellationTokenSource = null; + + this._register(this._model.onDidChangeContent(e => this._fetchSemanticTokens.schedule())); + this._register(SemanticTokensProviderRegistry.onDidChange(e => this._fetchSemanticTokens.schedule())); + if (themeService) { + // workaround for tests which use undefined... :/ + this._register(themeService.onThemeChange(_ => { + // clear out existing tokens + this._setSemanticTokens(null, null, null, []); + this._fetchSemanticTokens.schedule(); + })); + } + this._fetchSemanticTokens.schedule(0); + } + + public dispose(): void { + this._isDisposed = true; + if (this._currentResponse) { + this._currentResponse.dispose(); + this._currentResponse = null; + } + if (this._currentRequestCancellationTokenSource) { + this._currentRequestCancellationTokenSource.cancel(); + this._currentRequestCancellationTokenSource = null; + } + super.dispose(); + } + + private _fetchSemanticTokensNow(): void { + if (this._currentRequestCancellationTokenSource) { + // there is already a request running, let it finish... + return; + } + const provider = this._getSemanticColoringProvider(); + if (!provider) { + return; + } + this._currentRequestCancellationTokenSource = new CancellationTokenSource(); + + const pendingChanges: IModelContentChangedEvent[] = []; + const contentChangeListener = this._model.onDidChangeContent((e) => { + pendingChanges.push(e); + }); + + const styling = this._semanticStyling.get(provider); + + const lastResultId = this._currentResponse ? this._currentResponse.resultId || null : null; + const request = Promise.resolve(provider.provideSemanticTokens(this._model, lastResultId, null, this._currentRequestCancellationTokenSource.token)); + + request.then((res) => { + this._currentRequestCancellationTokenSource = null; + contentChangeListener.dispose(); + this._setSemanticTokens(provider, res || null, styling, pendingChanges); + }, (err) => { + errors.onUnexpectedError(err); + this._currentRequestCancellationTokenSource = null; + contentChangeListener.dispose(); + this._setSemanticTokens(provider, null, styling, pendingChanges); + }); + } + + private static _isSemanticTokens(v: SemanticTokens | SemanticTokensEdits): v is SemanticTokens { + return v && !!((v).data); + } + + private static _isSemanticTokensEdits(v: SemanticTokens | SemanticTokensEdits): v is SemanticTokensEdits { + return v && Array.isArray((v).edits); + } + + private static _copy(src: Uint32Array, srcOffset: number, dest: Uint32Array, destOffset: number, length: number): void { + for (let i = 0; i < length; i++) { + dest[destOffset + i] = src[srcOffset + i]; + } + } + + private _setSemanticTokens(provider: SemanticTokensProvider | null, tokens: SemanticTokens | SemanticTokensEdits | null, styling: SemanticColoringProviderStyling | null, pendingChanges: IModelContentChangedEvent[]): void { + const currentResponse = this._currentResponse; + if (this._currentResponse) { + this._currentResponse.dispose(); + this._currentResponse = null; + } + if (this._isDisposed) { + // disposed! + if (provider && tokens) { + provider.releaseSemanticTokens(tokens.resultId); + } + return; + } + if (!provider || !tokens || !styling) { + this._model.setSemanticTokens(null); + return; + } + + if (ModelSemanticColoring._isSemanticTokensEdits(tokens)) { + if (!currentResponse) { + // not possible! + this._model.setSemanticTokens(null); + return; + } + if (tokens.edits.length === 0) { + // nothing to do! + tokens = { + resultId: tokens.resultId, + data: currentResponse.data + }; + } else { + let deltaLength = 0; + for (const edit of tokens.edits) { + deltaLength += (edit.data ? edit.data.length : 0) - edit.deleteCount; + } + + const srcData = currentResponse.data; + const destData = new Uint32Array(srcData.length + deltaLength); + + let srcLastStart = srcData.length; + let destLastStart = destData.length; + for (let i = tokens.edits.length - 1; i >= 0; i--) { + const edit = tokens.edits[i]; + + const copyCount = srcLastStart - (edit.start + edit.deleteCount); + if (copyCount > 0) { + ModelSemanticColoring._copy(srcData, srcLastStart - copyCount, destData, destLastStart - copyCount, copyCount); + destLastStart -= copyCount; + } + + if (edit.data) { + ModelSemanticColoring._copy(edit.data, 0, destData, destLastStart - edit.data.length, edit.data.length); + destLastStart -= edit.data.length; + } + + srcLastStart = edit.start; + } + + if (srcLastStart > 0) { + ModelSemanticColoring._copy(srcData, 0, destData, 0, srcLastStart); + } + + tokens = { + resultId: tokens.resultId, + data: destData + }; + } + } + + if (ModelSemanticColoring._isSemanticTokens(tokens)) { + + this._currentResponse = new SemanticTokensResponse(provider, tokens.resultId, tokens.data); + + const srcData = tokens.data; + const tokenCount = (tokens.data.length / 5) | 0; + const tokensPerArea = Math.max(Math.ceil(tokenCount / SemanticColoringConstants.DesiredMaxAreas), SemanticColoringConstants.DesiredTokensPerArea); + + const result: MultilineTokens2[] = []; + + let tokenIndex = 0; + let lastLineNumber = 1; + let lastStartCharacter = 0; + while (tokenIndex < tokenCount) { + const tokenStartIndex = tokenIndex; + let tokenEndIndex = Math.min(tokenStartIndex + tokensPerArea, tokenCount); + + // Keep tokens on the same line in the same area... + if (tokenEndIndex < tokenCount) { + + let smallTokenEndIndex = tokenEndIndex; + while (smallTokenEndIndex - 1 > tokenStartIndex && srcData[5 * smallTokenEndIndex] === 0) { + smallTokenEndIndex--; + } + + if (smallTokenEndIndex - 1 === tokenStartIndex) { + // there are so many tokens on this line that our area would be empty, we must now go right + let bigTokenEndIndex = tokenEndIndex; + while (bigTokenEndIndex + 1 < tokenCount && srcData[5 * bigTokenEndIndex] === 0) { + bigTokenEndIndex++; + } + tokenEndIndex = bigTokenEndIndex; + } else { + tokenEndIndex = smallTokenEndIndex; + } + } + + let destData = new Uint32Array((tokenEndIndex - tokenStartIndex) * 4); + let destOffset = 0; + let areaLine = 0; + while (tokenIndex < tokenEndIndex) { + const srcOffset = 5 * tokenIndex; + const deltaLine = srcData[srcOffset]; + const deltaCharacter = srcData[srcOffset + 1]; + const lineNumber = lastLineNumber + deltaLine; + const startCharacter = (deltaLine === 0 ? lastStartCharacter + deltaCharacter : deltaCharacter); + const length = srcData[srcOffset + 2]; + const tokenTypeIndex = srcData[srcOffset + 3]; + const tokenModifierSet = srcData[srcOffset + 4]; + const metadata = styling.getMetadata(tokenTypeIndex, tokenModifierSet); + + if (metadata !== Constants.NO_STYLING) { + if (areaLine === 0) { + areaLine = lineNumber; + } + destData[destOffset] = lineNumber - areaLine; + destData[destOffset + 1] = startCharacter; + destData[destOffset + 2] = startCharacter + length; + destData[destOffset + 3] = metadata; + destOffset += 4; + } + + lastLineNumber = lineNumber; + lastStartCharacter = startCharacter; + tokenIndex++; + } + + if (destOffset !== destData.length) { + destData = destData.subarray(0, destOffset); + } + + const tokens = new MultilineTokens2(areaLine, new SparseEncodedTokens(destData)); + result.push(tokens); + } + + // Adjust incoming semantic tokens + if (pendingChanges.length > 0) { + // More changes occurred while the request was running + // We need to: + // 1. Adjust incoming semantic tokens + // 2. Request them again + for (const change of pendingChanges) { + for (const area of result) { + for (const singleChange of change.changes) { + area.applyEdit(singleChange.range, singleChange.text); + } + } + } + + this._fetchSemanticTokens.schedule(); + } + + this._model.setSemanticTokens(result); + return; + } + + this._model.setSemanticTokens(null); + } + + private _getSemanticColoringProvider(): SemanticTokensProvider | null { + const result = SemanticTokensProviderRegistry.ordered(this._model); + return (result.length > 0 ? result[0] : null); + } +} diff --git a/src/vs/editor/common/services/resolverService.ts b/src/vs/editor/common/services/resolverService.ts index 92e21e8984..588ec89aba 100644 --- a/src/vs/editor/common/services/resolverService.ts +++ b/src/vs/editor/common/services/resolverService.ts @@ -52,6 +52,9 @@ export interface ITextEditorModel extends IEditorModel { createSnapshot(this: IResolvedTextEditorModel): ITextSnapshot; createSnapshot(this: ITextEditorModel): ITextSnapshot | null; + /** + * Signals if this model is readonly or not. + */ isReadonly(): boolean; } diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index 5f771526d3..28c9a1302e 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -233,7 +233,8 @@ export enum OverviewRulerLane { * Position in the minimap to render the decoration. */ export enum MinimapPosition { - Inline = 1 + Inline = 1, + Gutter = 2 } /** diff --git a/src/vs/editor/common/view/editorColorRegistry.ts b/src/vs/editor/common/view/editorColorRegistry.ts index ab355a699b..b3db749997 100644 --- a/src/vs/editor/common/view/editorColorRegistry.ts +++ b/src/vs/editor/common/view/editorColorRegistry.ts @@ -15,6 +15,8 @@ export const editorLineHighlight = registerColor('editor.lineHighlightBackground export const editorLineHighlightBorder = registerColor('editor.lineHighlightBorder', { dark: '#282828', light: '#eeeeee', hc: '#f38518' }, nls.localize('lineHighlightBorderBox', 'Background color for the border around the line at the cursor position.')); export const editorRangeHighlight = registerColor('editor.rangeHighlightBackground', { dark: '#ffffff0b', light: '#fdff0033', hc: null }, nls.localize('rangeHighlight', 'Background color of highlighted ranges, like by quick open and find features. The color must not be opaque so as not to hide underlying decorations.'), true); export const editorRangeHighlightBorder = registerColor('editor.rangeHighlightBorder', { dark: null, light: null, hc: activeContrastBorder }, nls.localize('rangeHighlightBorder', 'Background color of the border around highlighted ranges.'), true); +export const editorSymbolHighlight = registerColor('editor.symbolHighlightBackground', { dark: editorRangeHighlight, light: editorRangeHighlight, hc: null }, nls.localize('symbolHighlight', 'Background color of highlighted symbol, like for go to definition or go next/previous symbol. The color must not be opaque so as not to hide underlying decorations.'), true); +export const editorSymbolHighlightBorder = registerColor('editor.symbolHighlightBorder', { dark: null, light: null, hc: activeContrastBorder }, nls.localize('symbolHighlightBorder', 'Background color of the border around highlighted symbols.'), true); export const editorCursorForeground = registerColor('editorCursor.foreground', { dark: '#AEAFAD', light: Color.black, hc: Color.white }, nls.localize('caret', 'Color of the editor cursor.')); export const editorCursorBackground = registerColor('editorCursor.background', null, nls.localize('editorCursorBackground', 'The background color of the editor cursor. Allows customizing the color of a character overlapped by a block cursor.')); @@ -73,6 +75,16 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.monaco-editor .rangeHighlight { border: 1px ${theme.type === 'hc' ? 'dotted' : 'solid'} ${rangeHighlightBorder}; }`); } + const symbolHighlight = theme.getColor(editorSymbolHighlight); + if (symbolHighlight) { + collector.addRule(`.monaco-editor .symbolHighlight { background-color: ${symbolHighlight}; }`); + } + + const symbolHighlightBorder = theme.getColor(editorSymbolHighlightBorder); + if (symbolHighlightBorder) { + collector.addRule(`.monaco-editor .symbolHighlight { border: 1px ${theme.type === 'hc' ? 'dotted' : 'solid'} ${symbolHighlightBorder}; }`); + } + const invisibles = theme.getColor(editorWhitespaces); if (invisibles) { collector.addRule(`.vs-whitespace { color: ${invisibles} !important; }`); diff --git a/src/vs/editor/common/view/renderingContext.ts b/src/vs/editor/common/view/renderingContext.ts index c5aaa36030..a28b7d0b12 100644 --- a/src/vs/editor/common/view/renderingContext.ts +++ b/src/vs/editor/common/view/renderingContext.ts @@ -10,7 +10,7 @@ import { IViewLayout, ViewModelDecoration } from 'vs/editor/common/viewModel/vie export interface IViewLines { linesVisibleRangesForRange(range: Range, includeNewLines: boolean): LineVisibleRanges[] | null; - visibleRangeForPosition(position: Position): HorizontalRange | null; + visibleRangeForPosition(position: Position): HorizontalPosition | null; } export abstract class RestrictedRenderingContext { @@ -77,26 +77,20 @@ export class RenderingContext extends RestrictedRenderingContext { return this._viewLines.linesVisibleRangesForRange(range, includeNewLines); } - public visibleRangeForPosition(position: Position): HorizontalRange | null { + public visibleRangeForPosition(position: Position): HorizontalPosition | null { return this._viewLines.visibleRangeForPosition(position); } } export class LineVisibleRanges { - _lineVisibleRangesBrand: void; - - public lineNumber: number; - public ranges: HorizontalRange[]; - - constructor(lineNumber: number, ranges: HorizontalRange[]) { - this.lineNumber = lineNumber; - this.ranges = ranges; - } + constructor( + public readonly outsideRenderedLine: boolean, + public readonly lineNumber: number, + public readonly ranges: HorizontalRange[] + ) { } } export class HorizontalRange { - _horizontalRangeBrand: void; - public left: number; public width: number; @@ -109,3 +103,21 @@ export class HorizontalRange { return `[${this.left},${this.width}]`; } } + +export class HorizontalPosition { + public outsideRenderedLine: boolean; + public left: number; + + constructor(outsideRenderedLine: boolean, left: number) { + this.outsideRenderedLine = outsideRenderedLine; + this.left = Math.round(left); + } +} + +export class VisibleRanges { + constructor( + public readonly outsideRenderedLine: boolean, + public readonly ranges: HorizontalRange[] + ) { + } +} diff --git a/src/vs/editor/common/viewLayout/linesLayout.ts b/src/vs/editor/common/viewLayout/linesLayout.ts index 673c178331..be71fcb116 100644 --- a/src/vs/editor/common/viewLayout/linesLayout.ts +++ b/src/vs/editor/common/viewLayout/linesLayout.ts @@ -4,44 +4,157 @@ *--------------------------------------------------------------------------------------------*/ import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; -import { IEditorWhitespace, WhitespaceComputer } from 'vs/editor/common/viewLayout/whitespaceComputer'; import { IViewWhitespaceViewportData } from 'vs/editor/common/viewModel/viewModel'; +import * as strings from 'vs/base/common/strings'; + +export interface IEditorWhitespace { + readonly id: string; + readonly afterLineNumber: number; + readonly height: number; +} + +/** + * An accessor that allows for whtiespace to be added, removed or changed in bulk. + */ +export interface IWhitespaceChangeAccessor { + insertWhitespace(afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): string; + changeOneWhitespace(id: string, newAfterLineNumber: number, newHeight: number): void; + removeWhitespace(id: string): void; +} + +interface IPendingChange { id: string; newAfterLineNumber: number; newHeight: number; } +interface IPendingRemove { id: string; } + +class PendingChanges { + private _hasPending: boolean; + private _inserts: EditorWhitespace[]; + private _changes: IPendingChange[]; + private _removes: IPendingRemove[]; + + constructor() { + this._hasPending = false; + this._inserts = []; + this._changes = []; + this._removes = []; + } + + public insert(x: EditorWhitespace): void { + this._hasPending = true; + this._inserts.push(x); + } + + public change(x: IPendingChange): void { + this._hasPending = true; + this._changes.push(x); + } + + public remove(x: IPendingRemove): void { + this._hasPending = true; + this._removes.push(x); + } + + public mustCommit(): boolean { + return this._hasPending; + } + + public commit(linesLayout: LinesLayout): void { + if (!this._hasPending) { + return; + } + + const inserts = this._inserts; + const changes = this._changes; + const removes = this._removes; + + this._hasPending = false; + this._inserts = []; + this._changes = []; + this._removes = []; + + linesLayout._commitPendingChanges(inserts, changes, removes); + } +} + +export class EditorWhitespace implements IEditorWhitespace { + public id: string; + public afterLineNumber: number; + public ordinal: number; + public height: number; + public minWidth: number; + public prefixSum: number; + + constructor(id: string, afterLineNumber: number, ordinal: number, height: number, minWidth: number) { + this.id = id; + this.afterLineNumber = afterLineNumber; + this.ordinal = ordinal; + this.height = height; + this.minWidth = minWidth; + this.prefixSum = 0; + } +} /** * Layouting of objects that take vertical space (by having a height) and push down other objects. * * These objects are basically either text (lines) or spaces between those lines (whitespaces). * This provides commodity operations for working with lines that contain whitespace that pushes lines lower (vertically). - * This is written with no knowledge of an editor in mind. */ export class LinesLayout { - /** - * Keep track of the total number of lines. - * This is useful for doing binary searches or for doing hit-testing. - */ - private _lineCount: number; + private static INSTANCE_COUNT = 0; - /** - * The height of a line in pixels. - */ + private readonly _instanceId: string; + private readonly _pendingChanges: PendingChanges; + private _lastWhitespaceId: number; + private _arr: EditorWhitespace[]; + private _prefixSumValidIndex: number; + private _minWidth: number; + private _lineCount: number; private _lineHeight: number; - /** - * Contains whitespace information in pixels - */ - private readonly _whitespaces: WhitespaceComputer; - constructor(lineCount: number, lineHeight: number) { + this._instanceId = strings.singleLetterHash(++LinesLayout.INSTANCE_COUNT); + this._pendingChanges = new PendingChanges(); + this._lastWhitespaceId = 0; + this._arr = []; + this._prefixSumValidIndex = -1; + this._minWidth = -1; /* marker for not being computed */ this._lineCount = lineCount; this._lineHeight = lineHeight; - this._whitespaces = new WhitespaceComputer(); + } + + /** + * Find the insertion index for a new value inside a sorted array of values. + * If the value is already present in the sorted array, the insertion index will be after the already existing value. + */ + public static findInsertionIndex(arr: EditorWhitespace[], afterLineNumber: number, ordinal: number): number { + let low = 0; + let high = arr.length; + + while (low < high) { + const mid = ((low + high) >>> 1); + + if (afterLineNumber === arr[mid].afterLineNumber) { + if (ordinal < arr[mid].ordinal) { + high = mid; + } else { + low = mid + 1; + } + } else if (afterLineNumber < arr[mid].afterLineNumber) { + high = mid; + } else { + low = mid + 1; + } + } + + return low; } /** * Change the height of a line in pixels. */ public setLineHeight(lineHeight: number): void { + this._checkPendingChanges(); this._lineHeight = lineHeight; } @@ -51,37 +164,153 @@ export class LinesLayout { * @param lineCount New number of lines. */ public onFlushed(lineCount: number): void { + this._checkPendingChanges(); this._lineCount = lineCount; } - /** - * Insert a new whitespace of a certain height after a line number. - * The whitespace has a "sticky" characteristic. - * Irrespective of edits above or below `afterLineNumber`, the whitespace will follow the initial line. - * - * @param afterLineNumber The conceptual position of this whitespace. The whitespace will follow this line as best as possible even when deleting/inserting lines above/below. - * @param heightInPx The height of the whitespace, in pixels. - * @return An id that can be used later to mutate or delete the whitespace - */ - public insertWhitespace(afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): string { - return this._whitespaces.insertWhitespace(afterLineNumber, ordinal, heightInPx, minWidth); + public changeWhitespace(callback: (accessor: IWhitespaceChangeAccessor) => T): T { + try { + const accessor = { + insertWhitespace: (afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): string => { + afterLineNumber = afterLineNumber | 0; + ordinal = ordinal | 0; + heightInPx = heightInPx | 0; + minWidth = minWidth | 0; + + const id = this._instanceId + (++this._lastWhitespaceId); + this._pendingChanges.insert(new EditorWhitespace(id, afterLineNumber, ordinal, heightInPx, minWidth)); + return id; + }, + changeOneWhitespace: (id: string, newAfterLineNumber: number, newHeight: number): void => { + newAfterLineNumber = newAfterLineNumber | 0; + newHeight = newHeight | 0; + + this._pendingChanges.change({ id, newAfterLineNumber, newHeight }); + }, + removeWhitespace: (id: string): void => { + this._pendingChanges.remove({ id }); + } + }; + return callback(accessor); + } finally { + this._pendingChanges.commit(this); + } } - /** - * Change properties associated with a certain whitespace. - */ - public changeWhitespace(id: string, newAfterLineNumber: number, newHeight: number): boolean { - return this._whitespaces.changeWhitespace(id, newAfterLineNumber, newHeight); + public _commitPendingChanges(inserts: EditorWhitespace[], changes: IPendingChange[], removes: IPendingRemove[]): void { + if (inserts.length > 0 || removes.length > 0) { + this._minWidth = -1; /* marker for not being computed */ + } + + if (inserts.length + changes.length + removes.length <= 1) { + // when only one thing happened, handle it "delicately" + for (const insert of inserts) { + this._insertWhitespace(insert); + } + for (const change of changes) { + this._changeOneWhitespace(change.id, change.newAfterLineNumber, change.newHeight); + } + for (const remove of removes) { + const index = this._findWhitespaceIndex(remove.id); + if (index === -1) { + continue; + } + this._removeWhitespace(index); + } + return; + } + + // simply rebuild the entire datastructure + + const toRemove = new Set(); + for (const remove of removes) { + toRemove.add(remove.id); + } + + const toChange = new Map(); + for (const change of changes) { + toChange.set(change.id, change); + } + + const applyRemoveAndChange = (whitespaces: EditorWhitespace[]): EditorWhitespace[] => { + let result: EditorWhitespace[] = []; + for (const whitespace of whitespaces) { + if (toRemove.has(whitespace.id)) { + continue; + } + if (toChange.has(whitespace.id)) { + const change = toChange.get(whitespace.id)!; + whitespace.afterLineNumber = change.newAfterLineNumber; + whitespace.height = change.newHeight; + } + result.push(whitespace); + } + return result; + }; + + const result = applyRemoveAndChange(this._arr).concat(applyRemoveAndChange(inserts)); + result.sort((a, b) => { + if (a.afterLineNumber === b.afterLineNumber) { + return a.ordinal - b.ordinal; + } + return a.afterLineNumber - b.afterLineNumber; + }); + + this._arr = result; + this._prefixSumValidIndex = -1; } - /** - * Remove an existing whitespace. - * - * @param id The whitespace to remove - * @return Returns true if the whitespace is found and it is removed. - */ - public removeWhitespace(id: string): boolean { - return this._whitespaces.removeWhitespace(id); + private _checkPendingChanges(): void { + if (this._pendingChanges.mustCommit()) { + console.warn(`Commiting pending changes before change accessor leaves due to read access.`); + this._pendingChanges.commit(this); + } + } + + private _insertWhitespace(whitespace: EditorWhitespace): void { + const insertIndex = LinesLayout.findInsertionIndex(this._arr, whitespace.afterLineNumber, whitespace.ordinal); + this._arr.splice(insertIndex, 0, whitespace); + this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, insertIndex - 1); + } + + private _findWhitespaceIndex(id: string): number { + const arr = this._arr; + for (let i = 0, len = arr.length; i < len; i++) { + if (arr[i].id === id) { + return i; + } + } + return -1; + } + + private _changeOneWhitespace(id: string, newAfterLineNumber: number, newHeight: number): void { + const index = this._findWhitespaceIndex(id); + if (index === -1) { + return; + } + if (this._arr[index].height !== newHeight) { + this._arr[index].height = newHeight; + this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, index - 1); + } + if (this._arr[index].afterLineNumber !== newAfterLineNumber) { + // `afterLineNumber` changed for this whitespace + + // Record old whitespace + const whitespace = this._arr[index]; + + // Since changing `afterLineNumber` can trigger a reordering, we're gonna remove this whitespace + this._removeWhitespace(index); + + whitespace.afterLineNumber = newAfterLineNumber; + + // And add it again + this._insertWhitespace(whitespace); + } + } + + private _removeWhitespace(removeIndex: number): void { + this._arr.splice(removeIndex, 1); + this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, removeIndex - 1); } /** @@ -91,8 +320,24 @@ export class LinesLayout { * @param toLineNumber The line number at which the deletion ended, inclusive */ public onLinesDeleted(fromLineNumber: number, toLineNumber: number): void { + this._checkPendingChanges(); + fromLineNumber = fromLineNumber | 0; + toLineNumber = toLineNumber | 0; + this._lineCount -= (toLineNumber - fromLineNumber + 1); - this._whitespaces.onLinesDeleted(fromLineNumber, toLineNumber); + for (let i = 0, len = this._arr.length; i < len; i++) { + const afterLineNumber = this._arr[i].afterLineNumber; + + if (fromLineNumber <= afterLineNumber && afterLineNumber <= toLineNumber) { + // The line this whitespace was after has been deleted + // => move whitespace to before first deleted line + this._arr[i].afterLineNumber = fromLineNumber - 1; + } else if (afterLineNumber > toLineNumber) { + // The line this whitespace was after has been moved up + // => move whitespace up + this._arr[i].afterLineNumber -= (toLineNumber - fromLineNumber + 1); + } + } } /** @@ -102,8 +347,53 @@ export class LinesLayout { * @param toLineNumber The line number at which the insertion ended, inclusive. */ public onLinesInserted(fromLineNumber: number, toLineNumber: number): void { + this._checkPendingChanges(); + fromLineNumber = fromLineNumber | 0; + toLineNumber = toLineNumber | 0; + this._lineCount += (toLineNumber - fromLineNumber + 1); - this._whitespaces.onLinesInserted(fromLineNumber, toLineNumber); + for (let i = 0, len = this._arr.length; i < len; i++) { + const afterLineNumber = this._arr[i].afterLineNumber; + + if (fromLineNumber <= afterLineNumber) { + this._arr[i].afterLineNumber += (toLineNumber - fromLineNumber + 1); + } + } + } + + /** + * Get the sum of all the whitespaces. + */ + public getWhitespacesTotalHeight(): number { + this._checkPendingChanges(); + if (this._arr.length === 0) { + return 0; + } + return this.getWhitespacesAccumulatedHeight(this._arr.length - 1); + } + + /** + * Return the sum of the heights of the whitespaces at [0..index]. + * This includes the whitespace at `index`. + * + * @param index The index of the whitespace. + * @return The sum of the heights of all whitespaces before the one at `index`, including the one at `index`. + */ + public getWhitespacesAccumulatedHeight(index: number): number { + this._checkPendingChanges(); + index = index | 0; + + let startIndex = Math.max(0, this._prefixSumValidIndex + 1); + if (startIndex === 0) { + this._arr[0].prefixSum = this._arr[0].height; + startIndex++; + } + + for (let i = startIndex; i <= index; i++) { + this._arr[i].prefixSum = this._arr[i - 1].prefixSum + this._arr[i].height; + } + this._prefixSumValidIndex = Math.max(this._prefixSumValidIndex, index); + return this._arr[index].prefixSum; } /** @@ -112,11 +402,81 @@ export class LinesLayout { * @return The sum of heights for all objects. */ public getLinesTotalHeight(): number { - let linesHeight = this._lineHeight * this._lineCount; - let whitespacesHeight = this._whitespaces.getTotalHeight(); + this._checkPendingChanges(); + const linesHeight = this._lineHeight * this._lineCount; + const whitespacesHeight = this.getWhitespacesTotalHeight(); return linesHeight + whitespacesHeight; } + /** + * Returns the accumulated height of whitespaces before the given line number. + * + * @param lineNumber The line number + */ + public getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber: number): number { + this._checkPendingChanges(); + lineNumber = lineNumber | 0; + + const lastWhitespaceBeforeLineNumber = this._findLastWhitespaceBeforeLineNumber(lineNumber); + + if (lastWhitespaceBeforeLineNumber === -1) { + return 0; + } + + return this.getWhitespacesAccumulatedHeight(lastWhitespaceBeforeLineNumber); + } + + private _findLastWhitespaceBeforeLineNumber(lineNumber: number): number { + lineNumber = lineNumber | 0; + + // Find the whitespace before line number + const arr = this._arr; + let low = 0; + let high = arr.length - 1; + + while (low <= high) { + const delta = (high - low) | 0; + const halfDelta = (delta / 2) | 0; + const mid = (low + halfDelta) | 0; + + if (arr[mid].afterLineNumber < lineNumber) { + if (mid + 1 >= arr.length || arr[mid + 1].afterLineNumber >= lineNumber) { + return mid; + } else { + low = (mid + 1) | 0; + } + } else { + high = (mid - 1) | 0; + } + } + + return -1; + } + + private _findFirstWhitespaceAfterLineNumber(lineNumber: number): number { + lineNumber = lineNumber | 0; + + const lastWhitespaceBeforeLineNumber = this._findLastWhitespaceBeforeLineNumber(lineNumber); + const firstWhitespaceAfterLineNumber = lastWhitespaceBeforeLineNumber + 1; + + if (firstWhitespaceAfterLineNumber < this._arr.length) { + return firstWhitespaceAfterLineNumber; + } + + return -1; + } + + /** + * Find the index of the first whitespace which has `afterLineNumber` >= `lineNumber`. + * @return The index of the first whitespace with `afterLineNumber` >= `lineNumber` or -1 if no whitespace is found. + */ + public getFirstWhitespaceIndexAfterLineNumber(lineNumber: number): number { + this._checkPendingChanges(); + lineNumber = lineNumber | 0; + + return this._findFirstWhitespaceAfterLineNumber(lineNumber); + } + /** * Get the vertical offset (the sum of heights for all objects above) a certain line number. * @@ -124,6 +484,7 @@ export class LinesLayout { * @return The sum of heights for all objects above `lineNumber`. */ public getVerticalOffsetForLineNumber(lineNumber: number): number { + this._checkPendingChanges(); lineNumber = lineNumber | 0; let previousLinesHeight: number; @@ -133,36 +494,40 @@ export class LinesLayout { previousLinesHeight = 0; } - let previousWhitespacesHeight = this._whitespaces.getAccumulatedHeightBeforeLineNumber(lineNumber); + const previousWhitespacesHeight = this.getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber); return previousLinesHeight + previousWhitespacesHeight; } - /** - * Returns the accumulated height of whitespaces before the given line number. - * - * @param lineNumber The line number - */ - public getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber: number): number { - return this._whitespaces.getAccumulatedHeightBeforeLineNumber(lineNumber); - } - /** * Returns if there is any whitespace in the document. */ public hasWhitespace(): boolean { - return this._whitespaces.getCount() > 0; + this._checkPendingChanges(); + return this.getWhitespacesCount() > 0; } + /** + * The maximum min width for all whitespaces. + */ public getWhitespaceMinWidth(): number { - return this._whitespaces.getMinWidth(); + this._checkPendingChanges(); + if (this._minWidth === -1) { + let minWidth = 0; + for (let i = 0, len = this._arr.length; i < len; i++) { + minWidth = Math.max(minWidth, this._arr[i].minWidth); + } + this._minWidth = minWidth; + } + return this._minWidth; } /** * Check if `verticalOffset` is below all lines. */ public isAfterLines(verticalOffset: number): boolean { - let totalHeight = this.getLinesTotalHeight(); + this._checkPendingChanges(); + const totalHeight = this.getLinesTotalHeight(); return verticalOffset > totalHeight; } @@ -175,6 +540,7 @@ export class LinesLayout { * @return The line number at or after vertical offset `verticalOffset`. */ public getLineNumberAtOrAfterVerticalOffset(verticalOffset: number): number { + this._checkPendingChanges(); verticalOffset = verticalOffset | 0; if (verticalOffset < 0) { @@ -187,9 +553,9 @@ export class LinesLayout { let maxLineNumber = linesCount; while (minLineNumber < maxLineNumber) { - let midLineNumber = ((minLineNumber + maxLineNumber) / 2) | 0; + const midLineNumber = ((minLineNumber + maxLineNumber) / 2) | 0; - let midLineNumberVerticalOffset = this.getVerticalOffsetForLineNumber(midLineNumber) | 0; + const midLineNumberVerticalOffset = this.getVerticalOffsetForLineNumber(midLineNumber) | 0; if (verticalOffset >= midLineNumberVerticalOffset + lineHeight) { // vertical offset is after mid line number @@ -218,6 +584,7 @@ export class LinesLayout { * @return A structure describing the lines positioned between `verticalOffset1` and `verticalOffset2`. */ public getLinesViewportData(verticalOffset1: number, verticalOffset2: number): IPartialViewLinesViewportData { + this._checkPendingChanges(); verticalOffset1 = verticalOffset1 | 0; verticalOffset2 = verticalOffset2 | 0; const lineHeight = this._lineHeight; @@ -230,8 +597,8 @@ export class LinesLayout { let endLineNumber = this._lineCount | 0; // Also keep track of what whitespace we've got - let whitespaceIndex = this._whitespaces.getFirstWhitespaceIndexAfterLineNumber(startLineNumber) | 0; - const whitespaceCount = this._whitespaces.getCount() | 0; + let whitespaceIndex = this.getFirstWhitespaceIndexAfterLineNumber(startLineNumber) | 0; + const whitespaceCount = this.getWhitespacesCount() | 0; let currentWhitespaceHeight: number; let currentWhitespaceAfterLineNumber: number; @@ -240,8 +607,8 @@ export class LinesLayout { currentWhitespaceAfterLineNumber = endLineNumber + 1; currentWhitespaceHeight = 0; } else { - currentWhitespaceAfterLineNumber = this._whitespaces.getAfterLineNumberForWhitespaceIndex(whitespaceIndex) | 0; - currentWhitespaceHeight = this._whitespaces.getHeightForWhitespaceIndex(whitespaceIndex) | 0; + currentWhitespaceAfterLineNumber = this.getAfterLineNumberForWhitespaceIndex(whitespaceIndex) | 0; + currentWhitespaceHeight = this.getHeightForWhitespaceIndex(whitespaceIndex) | 0; } let currentVerticalOffset = startLineNumberVerticalOffset; @@ -258,7 +625,7 @@ export class LinesLayout { currentLineRelativeOffset -= bigNumbersDelta; } - let linesOffsets: number[] = []; + const linesOffsets: number[] = []; const verticalCenter = verticalOffset1 + (verticalOffset2 - verticalOffset1) / 2; let centeredLineNumber = -1; @@ -267,8 +634,8 @@ export class LinesLayout { for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) { if (centeredLineNumber === -1) { - let currentLineTop = currentVerticalOffset; - let currentLineBottom = currentVerticalOffset + lineHeight; + const currentLineTop = currentVerticalOffset; + const currentLineBottom = currentVerticalOffset + lineHeight; if ((currentLineTop <= verticalCenter && verticalCenter < currentLineBottom) || currentLineTop > verticalCenter) { centeredLineNumber = lineNumber; } @@ -291,8 +658,8 @@ export class LinesLayout { if (whitespaceIndex >= whitespaceCount) { currentWhitespaceAfterLineNumber = endLineNumber + 1; } else { - currentWhitespaceAfterLineNumber = this._whitespaces.getAfterLineNumberForWhitespaceIndex(whitespaceIndex) | 0; - currentWhitespaceHeight = this._whitespaces.getHeightForWhitespaceIndex(whitespaceIndex) | 0; + currentWhitespaceAfterLineNumber = this.getAfterLineNumberForWhitespaceIndex(whitespaceIndex) | 0; + currentWhitespaceHeight = this.getHeightForWhitespaceIndex(whitespaceIndex) | 0; } } @@ -335,9 +702,10 @@ export class LinesLayout { } public getVerticalOffsetForWhitespaceIndex(whitespaceIndex: number): number { + this._checkPendingChanges(); whitespaceIndex = whitespaceIndex | 0; - let afterLineNumber = this._whitespaces.getAfterLineNumberForWhitespaceIndex(whitespaceIndex); + const afterLineNumber = this.getAfterLineNumberForWhitespaceIndex(whitespaceIndex); let previousLinesHeight: number; if (afterLineNumber >= 1) { @@ -348,7 +716,7 @@ export class LinesLayout { let previousWhitespacesHeight: number; if (whitespaceIndex > 0) { - previousWhitespacesHeight = this._whitespaces.getAccumulatedHeight(whitespaceIndex - 1); + previousWhitespacesHeight = this.getWhitespacesAccumulatedHeight(whitespaceIndex - 1); } else { previousWhitespacesHeight = 0; } @@ -356,30 +724,28 @@ export class LinesLayout { } public getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset: number): number { + this._checkPendingChanges(); verticalOffset = verticalOffset | 0; - let midWhitespaceIndex: number, - minWhitespaceIndex = 0, - maxWhitespaceIndex = this._whitespaces.getCount() - 1, - midWhitespaceVerticalOffset: number, - midWhitespaceHeight: number; + let minWhitespaceIndex = 0; + let maxWhitespaceIndex = this.getWhitespacesCount() - 1; if (maxWhitespaceIndex < 0) { return -1; } // Special case: nothing to be found - let maxWhitespaceVerticalOffset = this.getVerticalOffsetForWhitespaceIndex(maxWhitespaceIndex); - let maxWhitespaceHeight = this._whitespaces.getHeightForWhitespaceIndex(maxWhitespaceIndex); + const maxWhitespaceVerticalOffset = this.getVerticalOffsetForWhitespaceIndex(maxWhitespaceIndex); + const maxWhitespaceHeight = this.getHeightForWhitespaceIndex(maxWhitespaceIndex); if (verticalOffset >= maxWhitespaceVerticalOffset + maxWhitespaceHeight) { return -1; } while (minWhitespaceIndex < maxWhitespaceIndex) { - midWhitespaceIndex = Math.floor((minWhitespaceIndex + maxWhitespaceIndex) / 2); + const midWhitespaceIndex = Math.floor((minWhitespaceIndex + maxWhitespaceIndex) / 2); - midWhitespaceVerticalOffset = this.getVerticalOffsetForWhitespaceIndex(midWhitespaceIndex); - midWhitespaceHeight = this._whitespaces.getHeightForWhitespaceIndex(midWhitespaceIndex); + const midWhitespaceVerticalOffset = this.getVerticalOffsetForWhitespaceIndex(midWhitespaceIndex); + const midWhitespaceHeight = this.getHeightForWhitespaceIndex(midWhitespaceIndex); if (verticalOffset >= midWhitespaceVerticalOffset + midWhitespaceHeight) { // vertical offset is after whitespace @@ -402,27 +768,28 @@ export class LinesLayout { * @return Precisely the whitespace that is layouted at `verticaloffset` or null. */ public getWhitespaceAtVerticalOffset(verticalOffset: number): IViewWhitespaceViewportData | null { + this._checkPendingChanges(); verticalOffset = verticalOffset | 0; - let candidateIndex = this.getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset); + const candidateIndex = this.getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset); if (candidateIndex < 0) { return null; } - if (candidateIndex >= this._whitespaces.getCount()) { + if (candidateIndex >= this.getWhitespacesCount()) { return null; } - let candidateTop = this.getVerticalOffsetForWhitespaceIndex(candidateIndex); + const candidateTop = this.getVerticalOffsetForWhitespaceIndex(candidateIndex); if (candidateTop > verticalOffset) { return null; } - let candidateHeight = this._whitespaces.getHeightForWhitespaceIndex(candidateIndex); - let candidateId = this._whitespaces.getIdForWhitespaceIndex(candidateIndex); - let candidateAfterLineNumber = this._whitespaces.getAfterLineNumberForWhitespaceIndex(candidateIndex); + const candidateHeight = this.getHeightForWhitespaceIndex(candidateIndex); + const candidateId = this.getIdForWhitespaceIndex(candidateIndex); + const candidateAfterLineNumber = this.getAfterLineNumberForWhitespaceIndex(candidateIndex); return { id: candidateId, @@ -440,11 +807,12 @@ export class LinesLayout { * @return An array with all the whitespaces in the viewport. If no whitespace is in viewport, the array is empty. */ public getWhitespaceViewportData(verticalOffset1: number, verticalOffset2: number): IViewWhitespaceViewportData[] { + this._checkPendingChanges(); verticalOffset1 = verticalOffset1 | 0; verticalOffset2 = verticalOffset2 | 0; - let startIndex = this.getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset1); - let endIndex = this._whitespaces.getCount() - 1; + const startIndex = this.getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset1); + const endIndex = this.getWhitespacesCount() - 1; if (startIndex < 0) { return []; @@ -452,15 +820,15 @@ export class LinesLayout { let result: IViewWhitespaceViewportData[] = []; for (let i = startIndex; i <= endIndex; i++) { - let top = this.getVerticalOffsetForWhitespaceIndex(i); - let height = this._whitespaces.getHeightForWhitespaceIndex(i); + const top = this.getVerticalOffsetForWhitespaceIndex(i); + const height = this.getHeightForWhitespaceIndex(i); if (top >= verticalOffset2) { break; } result.push({ - id: this._whitespaces.getIdForWhitespaceIndex(i), - afterLineNumber: this._whitespaces.getAfterLineNumberForWhitespaceIndex(i), + id: this.getIdForWhitespaceIndex(i), + afterLineNumber: this.getAfterLineNumberForWhitespaceIndex(i), verticalOffset: top, height: height }); @@ -473,6 +841,54 @@ export class LinesLayout { * Get all whitespaces. */ public getWhitespaces(): IEditorWhitespace[] { - return this._whitespaces.getWhitespaces(this._lineHeight); + this._checkPendingChanges(); + return this._arr.slice(0); + } + + /** + * The number of whitespaces. + */ + public getWhitespacesCount(): number { + this._checkPendingChanges(); + return this._arr.length; + } + + /** + * Get the `id` for whitespace at index `index`. + * + * @param index The index of the whitespace. + * @return `id` of whitespace at `index`. + */ + public getIdForWhitespaceIndex(index: number): string { + this._checkPendingChanges(); + index = index | 0; + + return this._arr[index].id; + } + + /** + * Get the `afterLineNumber` for whitespace at index `index`. + * + * @param index The index of the whitespace. + * @return `afterLineNumber` of whitespace at `index`. + */ + public getAfterLineNumberForWhitespaceIndex(index: number): number { + this._checkPendingChanges(); + index = index | 0; + + return this._arr[index].afterLineNumber; + } + + /** + * Get the `height` for whitespace at index `index`. + * + * @param index The index of the whitespace. + * @return `height` of whitespace at `index`. + */ + public getHeightForWhitespaceIndex(index: number): number { + this._checkPendingChanges(); + index = index | 0; + + return this._arr[index].height; } } diff --git a/src/vs/editor/common/viewLayout/viewLayout.ts b/src/vs/editor/common/viewLayout/viewLayout.ts index a3c61eb31b..79f8a995a7 100644 --- a/src/vs/editor/common/viewLayout/viewLayout.ts +++ b/src/vs/editor/common/viewLayout/viewLayout.ts @@ -5,12 +5,11 @@ import { Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { IScrollDimensions, IScrollPosition, ScrollEvent, Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable'; +import { IScrollPosition, ScrollEvent, Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { LinesLayout } from 'vs/editor/common/viewLayout/linesLayout'; +import { LinesLayout, IEditorWhitespace, IWhitespaceChangeAccessor } from 'vs/editor/common/viewLayout/linesLayout'; import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; -import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer'; import { IViewLayout, IViewWhitespaceViewportData, Viewport } from 'vs/editor/common/viewModel/viewModel'; const SMOOTH_SCROLLING_TIME = 125; @@ -65,15 +64,23 @@ export class ViewLayout extends Disposable implements IViewLayout { } if (e.hasChanged(EditorOption.layoutInfo)) { const layoutInfo = options.get(EditorOption.layoutInfo); + const width = layoutInfo.contentWidth; + const height = layoutInfo.contentHeight; + const scrollDimensions = this.scrollable.getScrollDimensions(); + const scrollWidth = scrollDimensions.scrollWidth; + const scrollHeight = this._getTotalHeight(width, height, scrollWidth); + this.scrollable.setScrollDimensions({ - width: layoutInfo.contentWidth, - height: layoutInfo.contentHeight + width: width, + height: height, + scrollHeight: scrollHeight }); + } else { + this._updateHeight(); } if (e.hasChanged(EditorOption.smoothScrolling)) { this._configureSmoothScrollDuration(); } - this._updateHeight(); } public onFlushed(lineCount: number): void { this._linesLayout.onFlushed(lineCount); @@ -87,37 +94,41 @@ export class ViewLayout extends Disposable implements IViewLayout { // ---- end view event handlers - private _getHorizontalScrollbarHeight(scrollDimensions: IScrollDimensions): number { + private _getHorizontalScrollbarHeight(width: number, scrollWidth: number): number { const options = this._configuration.options; const scrollbar = options.get(EditorOption.scrollbar); if (scrollbar.horizontal === ScrollbarVisibility.Hidden) { // horizontal scrollbar not visible return 0; } - if (scrollDimensions.width >= scrollDimensions.scrollWidth) { + if (width >= scrollWidth) { // horizontal scrollbar not visible return 0; } return scrollbar.horizontalScrollbarSize; } - private _getTotalHeight(): number { + private _getTotalHeight(width: number, height: number, scrollWidth: number): number { const options = this._configuration.options; - const scrollDimensions = this.scrollable.getScrollDimensions(); let result = this._linesLayout.getLinesTotalHeight(); if (options.get(EditorOption.scrollBeyondLastLine)) { - result += scrollDimensions.height - options.get(EditorOption.lineHeight); + result += height - options.get(EditorOption.lineHeight); } else { - result += this._getHorizontalScrollbarHeight(scrollDimensions); + result += this._getHorizontalScrollbarHeight(width, scrollWidth); } - return Math.max(scrollDimensions.height, result); + return Math.max(height, result); } private _updateHeight(): void { + const scrollDimensions = this.scrollable.getScrollDimensions(); + const width = scrollDimensions.width; + const height = scrollDimensions.height; + const scrollWidth = scrollDimensions.scrollWidth; + const scrollHeight = this._getTotalHeight(width, height, scrollWidth); this.scrollable.setScrollDimensions({ - scrollHeight: this._getTotalHeight() + scrollHeight: scrollHeight }); } @@ -182,15 +193,8 @@ export class ViewLayout extends Disposable implements IViewLayout { } // ---- IVerticalLayoutProvider - - public addWhitespace(afterLineNumber: number, ordinal: number, height: number, minWidth: number): string { - return this._linesLayout.insertWhitespace(afterLineNumber, ordinal, height, minWidth); - } - public changeWhitespace(id: string, newAfterLineNumber: number, newHeight: number): boolean { - return this._linesLayout.changeWhitespace(id, newAfterLineNumber, newHeight); - } - public removeWhitespace(id: string): boolean { - return this._linesLayout.removeWhitespace(id); + public changeWhitespace(callback: (accessor: IWhitespaceChangeAccessor) => T): T { + return this._linesLayout.changeWhitespace(callback); } public getVerticalOffsetForLineNumber(lineNumber: number): number { return this._linesLayout.getVerticalOffsetForLineNumber(lineNumber); diff --git a/src/vs/editor/common/viewLayout/viewLineRenderer.ts b/src/vs/editor/common/viewLayout/viewLineRenderer.ts index 801192a143..d7aeddd3d9 100644 --- a/src/vs/editor/common/viewLayout/viewLineRenderer.ts +++ b/src/vs/editor/common/viewLayout/viewLineRenderer.ts @@ -783,7 +783,7 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render if (!fontIsMonospace) { const partIsOnlyWhitespace = (partType === 'vs-whitespace'); if (partIsOnlyWhitespace || !containsForeignElements) { - sb.appendASCIIString(' style="width:'); + sb.appendASCIIString(' style="display:inline-block;width:'); sb.appendASCIIString(String(spaceWidth * partContentCnt)); sb.appendASCIIString('px"'); } diff --git a/src/vs/editor/common/viewLayout/whitespaceComputer.ts b/src/vs/editor/common/viewLayout/whitespaceComputer.ts deleted file mode 100644 index 914aaecd5e..0000000000 --- a/src/vs/editor/common/viewLayout/whitespaceComputer.ts +++ /dev/null @@ -1,493 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as strings from 'vs/base/common/strings'; - -export interface IEditorWhitespace { - readonly id: string; - readonly afterLineNumber: number; - readonly heightInLines: number; -} - -/** - * Represent whitespaces in between lines and provide fast CRUD management methods. - * The whitespaces are sorted ascending by `afterLineNumber`. - */ -export class WhitespaceComputer { - - private static INSTANCE_COUNT = 0; - - private readonly _instanceId: string; - - /** - * heights[i] is the height in pixels for whitespace at index i - */ - private readonly _heights: number[]; - - /** - * minWidths[i] is the min width in pixels for whitespace at index i - */ - private readonly _minWidths: number[]; - - /** - * afterLineNumbers[i] is the line number whitespace at index i is after - */ - private readonly _afterLineNumbers: number[]; - - /** - * ordinals[i] is the orinal of the whitespace at index i - */ - private readonly _ordinals: number[]; - - /** - * prefixSum[i] = SUM(heights[j]), 1 <= j <= i - */ - private readonly _prefixSum: number[]; - - /** - * prefixSum[i], 1 <= i <= prefixSumValidIndex can be trusted - */ - private _prefixSumValidIndex: number; - - /** - * ids[i] is the whitespace id of whitespace at index i - */ - private readonly _ids: string[]; - - /** - * index at which a whitespace is positioned (inside heights, afterLineNumbers, prefixSum members) - */ - private readonly _whitespaceId2Index: { - [id: string]: number; - }; - - /** - * last whitespace id issued - */ - private _lastWhitespaceId: number; - - private _minWidth: number; - - constructor() { - this._instanceId = strings.singleLetterHash(++WhitespaceComputer.INSTANCE_COUNT); - this._heights = []; - this._minWidths = []; - this._ids = []; - this._afterLineNumbers = []; - this._ordinals = []; - this._prefixSum = []; - this._prefixSumValidIndex = -1; - this._whitespaceId2Index = {}; - this._lastWhitespaceId = 0; - this._minWidth = -1; /* marker for not being computed */ - } - - /** - * Find the insertion index for a new value inside a sorted array of values. - * If the value is already present in the sorted array, the insertion index will be after the already existing value. - */ - public static findInsertionIndex(sortedArray: number[], value: number, ordinals: number[], valueOrdinal: number): number { - let low = 0; - let high = sortedArray.length; - - while (low < high) { - let mid = ((low + high) >>> 1); - - if (value === sortedArray[mid]) { - if (valueOrdinal < ordinals[mid]) { - high = mid; - } else { - low = mid + 1; - } - } else if (value < sortedArray[mid]) { - high = mid; - } else { - low = mid + 1; - } - } - - return low; - } - - /** - * Insert a new whitespace of a certain height after a line number. - * The whitespace has a "sticky" characteristic. - * Irrespective of edits above or below `afterLineNumber`, the whitespace will follow the initial line. - * - * @param afterLineNumber The conceptual position of this whitespace. The whitespace will follow this line as best as possible even when deleting/inserting lines above/below. - * @param heightInPx The height of the whitespace, in pixels. - * @return An id that can be used later to mutate or delete the whitespace - */ - public insertWhitespace(afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): string { - afterLineNumber = afterLineNumber | 0; - ordinal = ordinal | 0; - heightInPx = heightInPx | 0; - minWidth = minWidth | 0; - - let id = this._instanceId + (++this._lastWhitespaceId); - let insertionIndex = WhitespaceComputer.findInsertionIndex(this._afterLineNumbers, afterLineNumber, this._ordinals, ordinal); - this._insertWhitespaceAtIndex(id, insertionIndex, afterLineNumber, ordinal, heightInPx, minWidth); - this._minWidth = -1; /* marker for not being computed */ - return id; - } - - private _insertWhitespaceAtIndex(id: string, insertIndex: number, afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): void { - insertIndex = insertIndex | 0; - afterLineNumber = afterLineNumber | 0; - ordinal = ordinal | 0; - heightInPx = heightInPx | 0; - minWidth = minWidth | 0; - - this._heights.splice(insertIndex, 0, heightInPx); - this._minWidths.splice(insertIndex, 0, minWidth); - this._ids.splice(insertIndex, 0, id); - this._afterLineNumbers.splice(insertIndex, 0, afterLineNumber); - this._ordinals.splice(insertIndex, 0, ordinal); - this._prefixSum.splice(insertIndex, 0, 0); - - let keys = Object.keys(this._whitespaceId2Index); - for (let i = 0, len = keys.length; i < len; i++) { - let sid = keys[i]; - let oldIndex = this._whitespaceId2Index[sid]; - if (oldIndex >= insertIndex) { - this._whitespaceId2Index[sid] = oldIndex + 1; - } - } - - this._whitespaceId2Index[id] = insertIndex; - this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, insertIndex - 1); - } - - /** - * Change properties associated with a certain whitespace. - */ - public changeWhitespace(id: string, newAfterLineNumber: number, newHeight: number): boolean { - newAfterLineNumber = newAfterLineNumber | 0; - newHeight = newHeight | 0; - - let hasChanges = false; - hasChanges = this.changeWhitespaceHeight(id, newHeight) || hasChanges; - hasChanges = this.changeWhitespaceAfterLineNumber(id, newAfterLineNumber) || hasChanges; - return hasChanges; - } - - /** - * Change the height of an existing whitespace - * - * @param id The whitespace to change - * @param newHeightInPx The new height of the whitespace, in pixels - * @return Returns true if the whitespace is found and if the new height is different than the old height - */ - public changeWhitespaceHeight(id: string, newHeightInPx: number): boolean { - newHeightInPx = newHeightInPx | 0; - - if (this._whitespaceId2Index.hasOwnProperty(id)) { - let index = this._whitespaceId2Index[id]; - if (this._heights[index] !== newHeightInPx) { - this._heights[index] = newHeightInPx; - this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, index - 1); - return true; - } - } - return false; - } - - /** - * Change the line number after which an existing whitespace flows. - * - * @param id The whitespace to change - * @param newAfterLineNumber The new line number the whitespace will follow - * @return Returns true if the whitespace is found and if the new line number is different than the old line number - */ - public changeWhitespaceAfterLineNumber(id: string, newAfterLineNumber: number): boolean { - newAfterLineNumber = newAfterLineNumber | 0; - - if (this._whitespaceId2Index.hasOwnProperty(id)) { - let index = this._whitespaceId2Index[id]; - if (this._afterLineNumbers[index] !== newAfterLineNumber) { - // `afterLineNumber` changed for this whitespace - - // Record old ordinal - let ordinal = this._ordinals[index]; - - // Record old height - let heightInPx = this._heights[index]; - - // Record old min width - let minWidth = this._minWidths[index]; - - // Since changing `afterLineNumber` can trigger a reordering, we're gonna remove this whitespace - this.removeWhitespace(id); - - // And add it again - let insertionIndex = WhitespaceComputer.findInsertionIndex(this._afterLineNumbers, newAfterLineNumber, this._ordinals, ordinal); - this._insertWhitespaceAtIndex(id, insertionIndex, newAfterLineNumber, ordinal, heightInPx, minWidth); - - return true; - } - } - return false; - } - - /** - * Remove an existing whitespace. - * - * @param id The whitespace to remove - * @return Returns true if the whitespace is found and it is removed. - */ - public removeWhitespace(id: string): boolean { - if (this._whitespaceId2Index.hasOwnProperty(id)) { - let index = this._whitespaceId2Index[id]; - delete this._whitespaceId2Index[id]; - this._removeWhitespaceAtIndex(index); - this._minWidth = -1; /* marker for not being computed */ - return true; - } - - return false; - } - - private _removeWhitespaceAtIndex(removeIndex: number): void { - removeIndex = removeIndex | 0; - - this._heights.splice(removeIndex, 1); - this._minWidths.splice(removeIndex, 1); - this._ids.splice(removeIndex, 1); - this._afterLineNumbers.splice(removeIndex, 1); - this._ordinals.splice(removeIndex, 1); - this._prefixSum.splice(removeIndex, 1); - this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, removeIndex - 1); - - let keys = Object.keys(this._whitespaceId2Index); - for (let i = 0, len = keys.length; i < len; i++) { - let sid = keys[i]; - let oldIndex = this._whitespaceId2Index[sid]; - if (oldIndex >= removeIndex) { - this._whitespaceId2Index[sid] = oldIndex - 1; - } - } - } - - /** - * Notify the computer that lines have been deleted (a continuous zone of lines). - * This gives it a chance to update `afterLineNumber` for whitespaces, giving the "sticky" characteristic. - * - * @param fromLineNumber The line number at which the deletion started, inclusive - * @param toLineNumber The line number at which the deletion ended, inclusive - */ - public onLinesDeleted(fromLineNumber: number, toLineNumber: number): void { - fromLineNumber = fromLineNumber | 0; - toLineNumber = toLineNumber | 0; - - for (let i = 0, len = this._afterLineNumbers.length; i < len; i++) { - let afterLineNumber = this._afterLineNumbers[i]; - - if (fromLineNumber <= afterLineNumber && afterLineNumber <= toLineNumber) { - // The line this whitespace was after has been deleted - // => move whitespace to before first deleted line - this._afterLineNumbers[i] = fromLineNumber - 1; - } else if (afterLineNumber > toLineNumber) { - // The line this whitespace was after has been moved up - // => move whitespace up - this._afterLineNumbers[i] -= (toLineNumber - fromLineNumber + 1); - } - } - } - - /** - * Notify the computer that lines have been inserted (a continuous zone of lines). - * This gives it a chance to update `afterLineNumber` for whitespaces, giving the "sticky" characteristic. - * - * @param fromLineNumber The line number at which the insertion started, inclusive - * @param toLineNumber The line number at which the insertion ended, inclusive. - */ - public onLinesInserted(fromLineNumber: number, toLineNumber: number): void { - fromLineNumber = fromLineNumber | 0; - toLineNumber = toLineNumber | 0; - - for (let i = 0, len = this._afterLineNumbers.length; i < len; i++) { - let afterLineNumber = this._afterLineNumbers[i]; - - if (fromLineNumber <= afterLineNumber) { - this._afterLineNumbers[i] += (toLineNumber - fromLineNumber + 1); - } - } - } - - /** - * Get the sum of all the whitespaces. - */ - public getTotalHeight(): number { - if (this._heights.length === 0) { - return 0; - } - return this.getAccumulatedHeight(this._heights.length - 1); - } - - /** - * Return the sum of the heights of the whitespaces at [0..index]. - * This includes the whitespace at `index`. - * - * @param index The index of the whitespace. - * @return The sum of the heights of all whitespaces before the one at `index`, including the one at `index`. - */ - public getAccumulatedHeight(index: number): number { - index = index | 0; - - let startIndex = Math.max(0, this._prefixSumValidIndex + 1); - if (startIndex === 0) { - this._prefixSum[0] = this._heights[0]; - startIndex++; - } - - for (let i = startIndex; i <= index; i++) { - this._prefixSum[i] = this._prefixSum[i - 1] + this._heights[i]; - } - this._prefixSumValidIndex = Math.max(this._prefixSumValidIndex, index); - return this._prefixSum[index]; - } - - /** - * Find all whitespaces with `afterLineNumber` < `lineNumber` and return the sum of their heights. - * - * @param lineNumber The line number whitespaces should be before. - * @return The sum of the heights of the whitespaces before `lineNumber`. - */ - public getAccumulatedHeightBeforeLineNumber(lineNumber: number): number { - lineNumber = lineNumber | 0; - - let lastWhitespaceBeforeLineNumber = this._findLastWhitespaceBeforeLineNumber(lineNumber); - - if (lastWhitespaceBeforeLineNumber === -1) { - return 0; - } - - return this.getAccumulatedHeight(lastWhitespaceBeforeLineNumber); - } - - private _findLastWhitespaceBeforeLineNumber(lineNumber: number): number { - lineNumber = lineNumber | 0; - - // Find the whitespace before line number - let afterLineNumbers = this._afterLineNumbers; - let low = 0; - let high = afterLineNumbers.length - 1; - - while (low <= high) { - let delta = (high - low) | 0; - let halfDelta = (delta / 2) | 0; - let mid = (low + halfDelta) | 0; - - if (afterLineNumbers[mid] < lineNumber) { - if (mid + 1 >= afterLineNumbers.length || afterLineNumbers[mid + 1] >= lineNumber) { - return mid; - } else { - low = (mid + 1) | 0; - } - } else { - high = (mid - 1) | 0; - } - } - - return -1; - } - - private _findFirstWhitespaceAfterLineNumber(lineNumber: number): number { - lineNumber = lineNumber | 0; - - let lastWhitespaceBeforeLineNumber = this._findLastWhitespaceBeforeLineNumber(lineNumber); - let firstWhitespaceAfterLineNumber = lastWhitespaceBeforeLineNumber + 1; - - if (firstWhitespaceAfterLineNumber < this._heights.length) { - return firstWhitespaceAfterLineNumber; - } - - return -1; - } - - /** - * Find the index of the first whitespace which has `afterLineNumber` >= `lineNumber`. - * @return The index of the first whitespace with `afterLineNumber` >= `lineNumber` or -1 if no whitespace is found. - */ - public getFirstWhitespaceIndexAfterLineNumber(lineNumber: number): number { - lineNumber = lineNumber | 0; - - return this._findFirstWhitespaceAfterLineNumber(lineNumber); - } - - /** - * The number of whitespaces. - */ - public getCount(): number { - return this._heights.length; - } - - /** - * The maximum min width for all whitespaces. - */ - public getMinWidth(): number { - if (this._minWidth === -1) { - let minWidth = 0; - for (let i = 0, len = this._minWidths.length; i < len; i++) { - minWidth = Math.max(minWidth, this._minWidths[i]); - } - this._minWidth = minWidth; - } - return this._minWidth; - } - - /** - * Get the `afterLineNumber` for whitespace at index `index`. - * - * @param index The index of the whitespace. - * @return `afterLineNumber` of whitespace at `index`. - */ - public getAfterLineNumberForWhitespaceIndex(index: number): number { - index = index | 0; - - return this._afterLineNumbers[index]; - } - - /** - * Get the `id` for whitespace at index `index`. - * - * @param index The index of the whitespace. - * @return `id` of whitespace at `index`. - */ - public getIdForWhitespaceIndex(index: number): string { - index = index | 0; - - return this._ids[index]; - } - - /** - * Get the `height` for whitespace at index `index`. - * - * @param index The index of the whitespace. - * @return `height` of whitespace at `index`. - */ - public getHeightForWhitespaceIndex(index: number): number { - index = index | 0; - - return this._heights[index]; - } - - /** - * Get all whitespaces. - */ - public getWhitespaces(deviceLineHeight: number): IEditorWhitespace[] { - deviceLineHeight = deviceLineHeight | 0; - - let result: IEditorWhitespace[] = []; - for (let i = 0; i < this._heights.length; i++) { - result.push({ - id: this._ids[i], - afterLineNumber: this._afterLineNumbers[i], - heightInLines: this._heights[i] / deviceLineHeight - }); - } - return result; - } -} diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 93e435b15b..88fd115463 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -129,9 +129,7 @@ export class CoordinatesConverter implements ICoordinatesConverter { } public convertModelRangeToViewRange(modelRange: Range): Range { - let start = this._lines.convertModelPositionToViewPosition(modelRange.startLineNumber, modelRange.startColumn); - let end = this._lines.convertModelPositionToViewPosition(modelRange.endLineNumber, modelRange.endColumn); - return new Range(start.lineNumber, start.column, end.lineNumber, end.column); + return this._lines.convertModelRangeToViewRange(modelRange); } public modelPositionIsVisible(modelPosition: Position): boolean { @@ -737,9 +735,9 @@ export class SplitLinesCollection implements IViewModelLinesCollection { public convertModelPositionToViewPosition(_modelLineNumber: number, _modelColumn: number): Position { this._ensureValidState(); - let validPosition = this.model.validatePosition(new Position(_modelLineNumber, _modelColumn)); - let inputLineNumber = validPosition.lineNumber; - let inputColumn = validPosition.column; + const validPosition = this.model.validatePosition(new Position(_modelLineNumber, _modelColumn)); + const inputLineNumber = validPosition.lineNumber; + const inputColumn = validPosition.column; let lineIndex = inputLineNumber - 1, lineIndexChanged = false; while (lineIndex > 0 && !this.lines[lineIndex].isVisible()) { @@ -751,7 +749,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { // console.log('in -> out ' + inputLineNumber + ',' + inputColumn + ' ===> ' + 1 + ',' + 1); return new Position(1, 1); } - let deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.prefixSumComputer.getAccumulatedValue(lineIndex - 1)); + const deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.prefixSumComputer.getAccumulatedValue(lineIndex - 1)); let r: Position; if (lineIndexChanged) { @@ -764,6 +762,19 @@ export class SplitLinesCollection implements IViewModelLinesCollection { return r; } + public convertModelRangeToViewRange(modelRange: Range): Range { + let start = this.convertModelPositionToViewPosition(modelRange.startLineNumber, modelRange.startColumn); + let end = this.convertModelPositionToViewPosition(modelRange.endLineNumber, modelRange.endColumn); + if (modelRange.startLineNumber === modelRange.endLineNumber && start.lineNumber !== end.lineNumber) { + // This is a single line range that ends up taking more lines due to wrapping + if (end.column === this.getViewLineMinColumn(end.lineNumber)) { + // the end column lands on the first column of the next line + return new Range(start.lineNumber, start.column, end.lineNumber - 1, this.getViewLineMaxColumn(end.lineNumber - 1)); + } + } + return new Range(start.lineNumber, start.column, end.lineNumber, end.column); + } + private _getViewLineNumberForModelPosition(inputLineNumber: number, inputColumn: number): number { let lineIndex = inputLineNumber - 1; if (this.lines[lineIndex].isVisible()) { diff --git a/src/vs/editor/common/viewModel/viewModel.ts b/src/vs/editor/common/viewModel/viewModel.ts index 18be24c1eb..2aaa893db1 100644 --- a/src/vs/editor/common/viewModel/viewModel.ts +++ b/src/vs/editor/common/viewModel/viewModel.ts @@ -13,7 +13,7 @@ import { INewScrollPosition } from 'vs/editor/common/editorCommon'; import { EndOfLinePreference, IActiveIndentGuideInfo, IModelDecorationOptions, TextModelResolvedOptions } from 'vs/editor/common/model'; import { IViewEventListener } from 'vs/editor/common/view/viewEvents'; import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; -import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer'; +import { IEditorWhitespace, IWhitespaceChangeAccessor } from 'vs/editor/common/viewLayout/linesLayout'; import { ITheme } from 'vs/platform/theme/common/themeService'; export interface IViewWhitespaceViewportData { @@ -69,20 +69,8 @@ export interface IViewLayout { getWhitespaceAtVerticalOffset(verticalOffset: number): IViewWhitespaceViewportData | null; // --------------- Begin vertical whitespace management + changeWhitespace(callback: (accessor: IWhitespaceChangeAccessor) => T): T; - /** - * Reserve rendering space. - * @return an identifier that can be later used to remove or change the whitespace. - */ - addWhitespace(afterLineNumber: number, ordinal: number, height: number, minWidth: number): string; - /** - * Change the properties of a whitespace. - */ - changeWhitespace(id: string, newAfterLineNumber: number, newHeight: number): boolean; - /** - * Remove rendering space - */ - removeWhitespace(id: string): boolean; /** * Get the layout information for whitespaces currently in the viewport */ diff --git a/src/vs/editor/contrib/clipboard/clipboard.ts b/src/vs/editor/contrib/clipboard/clipboard.ts index 6ab11c4f69..8f573c6efc 100644 --- a/src/vs/editor/contrib/clipboard/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/clipboard.ts @@ -77,11 +77,11 @@ class ExecCommandCutAction extends ExecCommandAction { alias: 'Cut', precondition: EditorContextKeys.writable, kbOpts: kbOpts, - menuOpts: { + contextMenuOpts: { group: CLIPBOARD_CONTEXT_MENU_GROUP, order: 1 }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarEditMenu, group: '2_ccp', title: nls.localize({ key: 'miCut', comment: ['&& denotes a mnemonic'] }, "Cu&&t"), @@ -126,11 +126,11 @@ class ExecCommandCopyAction extends ExecCommandAction { alias: 'Copy', precondition: undefined, kbOpts: kbOpts, - menuOpts: { + contextMenuOpts: { group: CLIPBOARD_CONTEXT_MENU_GROUP, order: 2 }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarEditMenu, group: '2_ccp', title: nls.localize({ key: 'miCopy', comment: ['&& denotes a mnemonic'] }, "&&Copy"), @@ -175,11 +175,11 @@ class ExecCommandPasteAction extends ExecCommandAction { alias: 'Paste', precondition: EditorContextKeys.writable, kbOpts: kbOpts, - menuOpts: { + contextMenuOpts: { group: CLIPBOARD_CONTEXT_MENU_GROUP, order: 3 }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarEditMenu, group: '2_ccp', title: nls.localize({ key: 'miPaste', comment: ['&& denotes a mnemonic'] }, "&&Paste"), diff --git a/src/vs/editor/contrib/codeAction/codeAction.ts b/src/vs/editor/contrib/codeAction/codeAction.ts index 31c6373e08..408e429a32 100644 --- a/src/vs/editor/contrib/codeAction/codeAction.ts +++ b/src/vs/editor/contrib/codeAction/codeAction.ts @@ -13,12 +13,19 @@ import { Selection } from 'vs/editor/common/core/selection'; import { ITextModel } from 'vs/editor/common/model'; import { CodeAction, CodeActionContext, CodeActionProviderRegistry, CodeActionTrigger as CodeActionTriggerKind } from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { CodeActionFilter, CodeActionKind, CodeActionTrigger, filtersAction, mayIncludeActionsOfKind } from './codeActionTrigger'; +import { CodeActionFilter, CodeActionKind, CodeActionTrigger, filtersAction, mayIncludeActionsOfKind } from './types'; import { TextModelCancellationTokenSource } from 'vs/editor/browser/core/editorState'; import { DisposableStore, IDisposable, Disposable } from 'vs/base/common/lifecycle'; +export const codeActionCommandId = 'editor.action.codeAction'; +export const refactorCommandId = 'editor.action.refactor'; +export const sourceActionCommandId = 'editor.action.sourceAction'; +export const organizeImportsCommandId = 'editor.action.organizeImports'; +export const fixAllCommandId = 'editor.action.fixAll'; + export interface CodeActionSet extends IDisposable { - readonly actions: readonly CodeAction[]; + readonly validActions: readonly CodeAction[]; + readonly allActions: readonly CodeAction[]; readonly hasAutoFix: boolean; } @@ -38,16 +45,18 @@ class ManagedCodeActionSet extends Disposable implements CodeActionSet { } } - public readonly actions: readonly CodeAction[]; + public readonly validActions: readonly CodeAction[]; + public readonly allActions: readonly CodeAction[]; public constructor(actions: readonly CodeAction[], disposables: DisposableStore) { super(); this._register(disposables); - this.actions = mergeSort([...actions], ManagedCodeActionSet.codeActionsComparator); + this.allActions = mergeSort([...actions], ManagedCodeActionSet.codeActionsComparator); + this.validActions = this.allActions.filter(action => !action.disabled); } public get hasAutoFix() { - return this.actions.some(fix => !!fix.kind && CodeActionKind.QuickFix.contains(new CodeActionKind(fix.kind)) && !!fix.isPreferred); + return this.validActions.some(fix => !!fix.kind && CodeActionKind.QuickFix.contains(new CodeActionKind(fix.kind)) && !!fix.isPreferred); } } @@ -60,7 +69,7 @@ export function getCodeActions( const filter = trigger.filter || {}; const codeActionContext: CodeActionContext = { - only: filter.kind ? filter.kind.value : undefined, + only: filter.include?.value, trigger: trigger.type === 'manual' ? CodeActionTriggerKind.Manual : CodeActionTriggerKind.Automatic }; @@ -68,21 +77,21 @@ export function getCodeActions( const providers = getCodeActionProviders(model, filter); const disposables = new DisposableStore(); - const promises = providers.map(provider => { - return Promise.resolve(provider.provideCodeActions(model, rangeOrSelection, codeActionContext, cts.token)).then(providedCodeActions => { + const promises = providers.map(async provider => { + try { + const providedCodeActions = await provider.provideCodeActions(model, rangeOrSelection, codeActionContext, cts.token); if (cts.token.isCancellationRequested || !providedCodeActions) { return []; } disposables.add(providedCodeActions); return providedCodeActions.actions.filter(action => action && filtersAction(filter, action)); - }, (err): CodeAction[] => { + } catch (err) { if (isPromiseCanceledError(err)) { throw err; } - onUnexpectedExternalError(err); return []; - }); + } }); const listener = CodeActionProviderRegistry.onDidChange(() => { @@ -140,9 +149,9 @@ registerLanguageCommand('_executeCodeActionProvider', async function (accessor, const codeActionSet = await getCodeActions( model, validatedRangeOrSelection, - { type: 'manual', filter: { includeSourceActions: true, kind: kind && kind.value ? new CodeActionKind(kind.value) : undefined } }, + { type: 'manual', filter: { includeSourceActions: true, include: kind && kind.value ? new CodeActionKind(kind.value) : undefined } }, CancellationToken.None); setTimeout(() => codeActionSet.dispose(), 100); - return codeActionSet.actions; + return codeActionSet.validActions; }); diff --git a/src/vs/editor/contrib/codeAction/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/codeActionCommands.ts index 71902363c6..f984998d89 100644 --- a/src/vs/editor/contrib/codeAction/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/codeActionCommands.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -15,7 +16,7 @@ import { IPosition } from 'vs/editor/common/core/position'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { CodeAction } from 'vs/editor/common/modes'; -import { CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; +import { CodeActionSet, refactorCommandId, sourceActionCommandId, codeActionCommandId, organizeImportsCommandId, fixAllCommandId } from 'vs/editor/contrib/codeAction/codeAction'; import { CodeActionUi } from 'vs/editor/contrib/codeAction/codeActionUi'; import { MessageController } from 'vs/editor/contrib/message/messageController'; import * as nls from 'vs/nls'; @@ -29,7 +30,7 @@ import { IMarkerService } from 'vs/platform/markers/common/markers'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { CodeActionModel, CodeActionsState, SUPPORTED_CODE_ACTIONS } from './codeActionModel'; -import { CodeActionAutoApply, CodeActionFilter, CodeActionKind, CodeActionTrigger } from './codeActionTrigger'; +import { CodeActionAutoApply, CodeActionFilter, CodeActionKind, CodeActionTrigger, CodeActionCommandArgs } from './types'; function contextKeyForSupportedActions(kind: CodeActionKind) { return ContextKeyExpr.regex( @@ -37,6 +38,33 @@ function contextKeyForSupportedActions(kind: CodeActionKind) { new RegExp('(\\s|^)' + escapeRegExpCharacters(kind.value) + '\\b')); } +const argsSchema: IJSONSchema = { + type: 'object', + required: ['kind'], + defaultSnippets: [{ body: { kind: '' } }], + properties: { + 'kind': { + type: 'string', + description: nls.localize('args.schema.kind', "Kind of the code action to run."), + }, + 'apply': { + type: 'string', + description: nls.localize('args.schema.apply', "Controls when the returned actions are applied."), + default: CodeActionAutoApply.IfSingle, + enum: [CodeActionAutoApply.First, CodeActionAutoApply.IfSingle, CodeActionAutoApply.Never], + enumDescriptions: [ + nls.localize('args.schema.apply.first', "Always apply the first returned code action."), + nls.localize('args.schema.apply.ifSingle', "Apply the first returned code action if it is the only one."), + nls.localize('args.schema.apply.never', "Do not apply the returned code actions."), + ] + }, + 'preferred': { + type: 'boolean', + default: false, + description: nls.localize('args.schema.preferred', "Controls if only preferred code actions should be returned."), + } + } +}; export class QuickFixController extends Disposable implements IEditorContribution { @@ -78,7 +106,7 @@ export class QuickFixController extends Disposable implements IEditorContributio } } } - }, contextMenuService, keybindingService)) + }, this._instantiationService)) ); } @@ -87,7 +115,7 @@ export class QuickFixController extends Disposable implements IEditorContributio } public showCodeActions(actions: CodeActionSet, at: IAnchor | IPosition) { - return this._ui.getValue().showCodeActionList(actions, at); + return this._ui.getValue().showCodeActionList(actions, at, { includeDisabledActions: false }); } public manualTriggerAtCurrentPosition( @@ -185,85 +213,34 @@ export class QuickFixAction extends EditorAction { } } - -class CodeActionCommandArgs { - public static fromUser(arg: any, defaults: { kind: CodeActionKind, apply: CodeActionAutoApply }): CodeActionCommandArgs { - if (!arg || typeof arg !== 'object') { - return new CodeActionCommandArgs(defaults.kind, defaults.apply, false); - } - return new CodeActionCommandArgs( - CodeActionCommandArgs.getKindFromUser(arg, defaults.kind), - CodeActionCommandArgs.getApplyFromUser(arg, defaults.apply), - CodeActionCommandArgs.getPreferredUser(arg)); - } - - private static getApplyFromUser(arg: any, defaultAutoApply: CodeActionAutoApply) { - switch (typeof arg.apply === 'string' ? arg.apply.toLowerCase() : '') { - case 'first': return CodeActionAutoApply.First; - case 'never': return CodeActionAutoApply.Never; - case 'ifsingle': return CodeActionAutoApply.IfSingle; - default: return defaultAutoApply; - } - } - - private static getKindFromUser(arg: any, defaultKind: CodeActionKind) { - return typeof arg.kind === 'string' - ? new CodeActionKind(arg.kind) - : defaultKind; - } - - private static getPreferredUser(arg: any): boolean { - return typeof arg.preferred === 'boolean' - ? arg.preferred - : false; - } - - private constructor( - public readonly kind: CodeActionKind, - public readonly apply: CodeActionAutoApply, - public readonly preferred: boolean, - ) { } -} - export class CodeActionCommand extends EditorCommand { - static readonly Id = 'editor.action.codeAction'; - constructor() { super({ - id: CodeActionCommand.Id, + id: codeActionCommandId, precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCodeActionsProvider), description: { - description: `Trigger a code action`, - args: [{ - name: 'args', - schema: { - 'type': 'object', - 'required': ['kind'], - 'properties': { - 'kind': { - 'type': 'string' - }, - 'apply': { - 'type': 'string', - 'default': 'ifSingle', - 'enum': ['first', 'ifSingle', 'never'] - } - } - } - }] + description: 'Trigger a code action', + args: [{ name: 'args', schema: argsSchema, }] } }); } - public runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, userArg: any) { - const args = CodeActionCommandArgs.fromUser(userArg, { + public runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, userArgs: any) { + const args = CodeActionCommandArgs.fromUser(userArgs, { kind: CodeActionKind.Empty, apply: CodeActionAutoApply.IfSingle, }); - return triggerCodeActionsForEditorSelection(editor, nls.localize('editor.action.quickFix.noneMessage', "No code actions available"), + return triggerCodeActionsForEditorSelection(editor, + typeof userArgs?.kind === 'string' + ? args.preferred + ? nls.localize('editor.action.codeAction.noneMessage.preferred.kind', "No preferred code actions for '{0}' available", userArgs.kind) + : nls.localize('editor.action.codeAction.noneMessage.kind', "No code actions for '{0}' available", userArgs.kind) + : args.preferred + ? nls.localize('editor.action.codeAction.noneMessage.preferred', "No preferred code actions available") + : nls.localize('editor.action.codeAction.noneMessage', "No code actions available"), { - kind: args.kind, + include: args.kind, includeSourceActions: true, onlyIncludePreferredActions: args.preferred, }, @@ -274,11 +251,9 @@ export class CodeActionCommand extends EditorCommand { export class RefactorAction extends EditorAction { - static readonly Id = 'editor.action.refactor'; - constructor() { super({ - id: RefactorAction.Id, + id: refactorCommandId, label: nls.localize('refactor.label', "Refactor..."), alias: 'Refactor...', precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCodeActionsProvider), @@ -290,7 +265,7 @@ export class RefactorAction extends EditorAction { }, weight: KeybindingWeight.EditorContrib }, - menuOpts: { + contextMenuOpts: { group: '1_modification', order: 2, when: ContextKeyExpr.and( @@ -299,53 +274,41 @@ export class RefactorAction extends EditorAction { }, description: { description: 'Refactor...', - args: [{ - name: 'args', - schema: { - 'type': 'object', - 'properties': { - 'kind': { - 'type': 'string' - }, - 'apply': { - 'type': 'string', - 'default': 'never', - 'enum': ['first', 'ifSingle', 'never'] - } - } - } - }] + args: [{ name: 'args', schema: argsSchema }] } }); } - public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArg: any): void { - const args = CodeActionCommandArgs.fromUser(userArg, { + public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArgs: any): void { + const args = CodeActionCommandArgs.fromUser(userArgs, { kind: CodeActionKind.Refactor, apply: CodeActionAutoApply.Never }); return triggerCodeActionsForEditorSelection(editor, - nls.localize('editor.action.refactor.noneMessage', "No refactorings available"), + typeof userArgs?.kind === 'string' + ? args.preferred + ? nls.localize('editor.action.refactor.noneMessage.preferred.kind', "No preferred refactorings for '{0}' available", userArgs.kind) + : nls.localize('editor.action.refactor.noneMessage.kind', "No refactorings for '{0}' available", userArgs.kind) + : args.preferred + ? nls.localize('editor.action.refactor.noneMessage.preferred', "No preferred refactorings available") + : nls.localize('editor.action.refactor.noneMessage', "No refactorings available"), { - kind: CodeActionKind.Refactor.contains(args.kind) ? args.kind : CodeActionKind.Empty, + include: CodeActionKind.Refactor.contains(args.kind) ? args.kind : CodeActionKind.None, onlyIncludePreferredActions: args.preferred, }, args.apply); } } - export class SourceAction extends EditorAction { - static readonly Id = 'editor.action.sourceAction'; - constructor() { super({ - id: SourceAction.Id, + id: sourceActionCommandId, label: nls.localize('source.label', "Source Action..."), alias: 'Source Action...', precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCodeActionsProvider), - menuOpts: { + contextMenuOpts: { group: '1_modification', order: 2.1, when: ContextKeyExpr.and( @@ -354,35 +317,26 @@ export class SourceAction extends EditorAction { }, description: { description: 'Source Action...', - args: [{ - name: 'args', - schema: { - 'type': 'object', - 'properties': { - 'kind': { - 'type': 'string' - }, - 'apply': { - 'type': 'string', - 'default': 'never', - 'enum': ['first', 'ifSingle', 'never'] - } - } - } - }] + args: [{ name: 'args', schema: argsSchema }] } }); } - public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArg: any): void { - const args = CodeActionCommandArgs.fromUser(userArg, { + public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArgs: any): void { + const args = CodeActionCommandArgs.fromUser(userArgs, { kind: CodeActionKind.Source, apply: CodeActionAutoApply.Never }); return triggerCodeActionsForEditorSelection(editor, - nls.localize('editor.action.source.noneMessage', "No source actions available"), + typeof userArgs?.kind === 'string' + ? args.preferred + ? nls.localize('editor.action.source.noneMessage.preferred.kind', "No preferred source actions for '{0}' available", userArgs.kind) + : nls.localize('editor.action.source.noneMessage.kind', "No source actions for '{0}' available", userArgs.kind) + : args.preferred + ? nls.localize('editor.action.source.noneMessage.preferred', "No preferred source actions available") + : nls.localize('editor.action.source.noneMessage', "No source actions available"), { - kind: CodeActionKind.Source.contains(args.kind) ? args.kind : CodeActionKind.Empty, + include: CodeActionKind.Source.contains(args.kind) ? args.kind : CodeActionKind.None, includeSourceActions: true, onlyIncludePreferredActions: args.preferred, }, @@ -392,11 +346,9 @@ export class SourceAction extends EditorAction { export class OrganizeImportsAction extends EditorAction { - static readonly Id = 'editor.action.organizeImports'; - constructor() { super({ - id: OrganizeImportsAction.Id, + id: organizeImportsCommandId, label: nls.localize('organizeImports.label', "Organize Imports"), alias: 'Organize Imports', precondition: ContextKeyExpr.and( @@ -406,25 +358,23 @@ export class OrganizeImportsAction extends EditorAction { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_O, weight: KeybindingWeight.EditorContrib - } + }, }); } public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { return triggerCodeActionsForEditorSelection(editor, nls.localize('editor.action.organize.noneMessage', "No organize imports action available"), - { kind: CodeActionKind.SourceOrganizeImports, includeSourceActions: true }, + { include: CodeActionKind.SourceOrganizeImports, includeSourceActions: true }, CodeActionAutoApply.IfSingle); } } export class FixAllAction extends EditorAction { - static readonly Id = 'editor.action.fixAll'; - constructor() { super({ - id: FixAllAction.Id, + id: fixAllCommandId, label: nls.localize('fixAll.label', "Fix All"), alias: 'Fix All', precondition: ContextKeyExpr.and( @@ -436,7 +386,7 @@ export class FixAllAction extends EditorAction { public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { return triggerCodeActionsForEditorSelection(editor, nls.localize('fixAll.noneMessage', "No fix all action available"), - { kind: CodeActionKind.SourceFixAll, includeSourceActions: true }, + { include: CodeActionKind.SourceFixAll, includeSourceActions: true }, CodeActionAutoApply.IfSingle); } } @@ -468,7 +418,7 @@ export class AutoFixAction extends EditorAction { return triggerCodeActionsForEditorSelection(editor, nls.localize('editor.action.autoFix.noneMessage', "No auto fixes available"), { - kind: CodeActionKind.QuickFix, + include: CodeActionKind.QuickFix, onlyIncludePreferredActions: true }, CodeActionAutoApply.IfSingle); diff --git a/src/vs/editor/contrib/codeAction/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/codeActionMenu.ts new file mode 100644 index 0000000000..b30ec28bc7 --- /dev/null +++ b/src/vs/editor/contrib/codeAction/codeActionMenu.ts @@ -0,0 +1,195 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { getDomNodePagePosition } from 'vs/base/browser/dom'; +import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; +import { Action } from 'vs/base/common/actions'; +import { canceled } from 'vs/base/common/errors'; +import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; +import { Lazy } from 'vs/base/common/lazy'; +import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IPosition, Position } from 'vs/editor/common/core/position'; +import { ScrollType } from 'vs/editor/common/editorCommon'; +import { CodeAction } from 'vs/editor/common/modes'; +import { CodeActionSet, refactorCommandId, sourceActionCommandId, codeActionCommandId, organizeImportsCommandId, fixAllCommandId } from 'vs/editor/contrib/codeAction/codeAction'; +import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionKind } from 'vs/editor/contrib/codeAction/types'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; + +interface CodeActionWidgetDelegate { + onSelectCodeAction: (action: CodeAction) => Promise; +} + +interface ResolveCodeActionKeybinding { + readonly kind: CodeActionKind; + readonly preferred: boolean; + readonly resolvedKeybinding: ResolvedKeybinding; +} + +class CodeActionAction extends Action { + constructor( + public readonly action: CodeAction, + callback: () => Promise, + ) { + super(action.command ? action.command.id : action.title, action.title, undefined, !action.disabled, callback); + } +} + +export interface CodeActionShowOptions { + readonly includeDisabledActions: boolean; +} + +export class CodeActionMenu extends Disposable { + + private _visible: boolean = false; + private readonly _showingActions = this._register(new MutableDisposable()); + + private readonly _keybindingResolver: CodeActionKeybindingResolver; + + constructor( + private readonly _editor: ICodeEditor, + private readonly _delegate: CodeActionWidgetDelegate, + @IContextMenuService private readonly _contextMenuService: IContextMenuService, + @IKeybindingService keybindingService: IKeybindingService, + ) { + super(); + + this._keybindingResolver = new CodeActionKeybindingResolver({ + getKeybindings: () => keybindingService.getKeybindings() + }); + } + + get isVisible(): boolean { + return this._visible; + } + + public async show(codeActions: CodeActionSet, at: IAnchor | IPosition, options: CodeActionShowOptions): Promise { + const actionsToShow = options.includeDisabledActions ? codeActions.allActions : codeActions.validActions; + if (!actionsToShow.length) { + this._visible = false; + return; + } + + if (!this._editor.getDomNode()) { + // cancel when editor went off-dom + this._visible = false; + throw canceled(); + } + + this._visible = true; + this._showingActions.value = codeActions; + + const menuActions = actionsToShow.map(action => + new CodeActionAction(action, () => this._delegate.onSelectCodeAction(action))); + + const anchor = Position.isIPosition(at) ? this._toCoords(at) : at || { x: 0, y: 0 }; + const resolver = this._keybindingResolver.getResolver(); + + this._contextMenuService.showContextMenu({ + getAnchor: () => anchor, + getActions: () => menuActions, + onHide: () => { + this._visible = false; + this._editor.focus(); + }, + autoSelectFirstItem: true, + getKeyBinding: action => action instanceof CodeActionAction ? resolver(action.action) : undefined, + }); + } + + private _toCoords(position: IPosition): { x: number, y: number } { + if (!this._editor.hasModel()) { + return { x: 0, y: 0 }; + } + this._editor.revealPosition(position, ScrollType.Immediate); + this._editor.render(); + + // Translate to absolute editor position + const cursorCoords = this._editor.getScrolledVisiblePosition(position); + const editorCoords = getDomNodePagePosition(this._editor.getDomNode()); + const x = editorCoords.left + cursorCoords.left; + const y = editorCoords.top + cursorCoords.top + cursorCoords.height; + + return { x, y }; + } +} + +export class CodeActionKeybindingResolver { + private static readonly codeActionCommands: readonly string[] = [ + refactorCommandId, + codeActionCommandId, + sourceActionCommandId, + organizeImportsCommandId, + fixAllCommandId + ]; + + constructor( + private readonly _keybindingProvider: { + getKeybindings(): readonly ResolvedKeybindingItem[], + }, + ) { } + + public getResolver(): (action: CodeAction) => ResolvedKeybinding | undefined { + // Lazy since we may not actually ever read the value + const allCodeActionBindings = new Lazy(() => + this._keybindingProvider.getKeybindings() + .filter(item => CodeActionKeybindingResolver.codeActionCommands.indexOf(item.command!) >= 0) + .filter(item => item.resolvedKeybinding) + .map((item): ResolveCodeActionKeybinding => { + // Special case these commands since they come built-in with VS Code and don't use 'commandArgs' + let commandArgs = item.commandArgs; + if (item.command === organizeImportsCommandId) { + commandArgs = { kind: CodeActionKind.SourceOrganizeImports.value }; + } else if (item.command === fixAllCommandId) { + commandArgs = { kind: CodeActionKind.SourceFixAll.value }; + } + + return { + resolvedKeybinding: item.resolvedKeybinding!, + ...CodeActionCommandArgs.fromUser(commandArgs, { + kind: CodeActionKind.None, + apply: CodeActionAutoApply.Never + }) + }; + })); + + return (action) => { + if (action.kind) { + const binding = this.bestKeybindingForCodeAction(action, allCodeActionBindings.getValue()); + return binding?.resolvedKeybinding; + } + return undefined; + }; + } + + private bestKeybindingForCodeAction( + action: CodeAction, + candidates: readonly ResolveCodeActionKeybinding[], + ): ResolveCodeActionKeybinding | undefined { + if (!action.kind) { + return undefined; + } + const kind = new CodeActionKind(action.kind); + + return candidates + .filter(candidate => candidate.kind.contains(kind)) + .filter(candidate => { + if (candidate.preferred) { + // If the candidate keybinding only applies to preferred actions, the this action must also be preferred + return action.isPreferred; + } + return true; + }) + .reduceRight((currentBest, candidate) => { + if (!currentBest) { + return candidate; + } + // Select the more specific binding + return currentBest.kind.contains(candidate.kind) ? candidate : currentBest; + }, undefined as ResolveCodeActionKeybinding | undefined); + } +} diff --git a/src/vs/editor/contrib/codeAction/codeActionModel.ts b/src/vs/editor/contrib/codeAction/codeActionModel.ts index 520eeee395..b6203ae7f8 100644 --- a/src/vs/editor/contrib/codeAction/codeActionModel.ts +++ b/src/vs/editor/contrib/codeAction/codeActionModel.ts @@ -16,7 +16,7 @@ import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/cont import { IMarkerService } from 'vs/platform/markers/common/markers'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { getCodeActions, CodeActionSet } from './codeAction'; -import { CodeActionTrigger } from './codeActionTrigger'; +import { CodeActionTrigger } from './types'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { isEqual } from 'vs/base/common/resources'; @@ -73,10 +73,12 @@ class CodeActionOracle extends Disposable { return undefined; } for (const marker of this._markerService.read({ resource: model.uri })) { - if (Range.intersectRanges(marker, selection)) { - return Range.lift(marker); + const markerRange = model.validateRange(marker); + if (Range.intersectRanges(markerRange, selection)) { + return Range.lift(markerRange); } } + return undefined; } @@ -140,7 +142,7 @@ export namespace CodeActionsState { Triggered, } - export const Empty = new class { readonly type = Type.Empty; }; + export const Empty = { type: Type.Empty } as const; export class Triggered { readonly type = Type.Triggered; diff --git a/src/vs/editor/contrib/codeAction/codeActionTrigger.ts b/src/vs/editor/contrib/codeAction/codeActionTrigger.ts deleted file mode 100644 index 382a4bea1a..0000000000 --- a/src/vs/editor/contrib/codeAction/codeActionTrigger.ts +++ /dev/null @@ -1,98 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { startsWith } from 'vs/base/common/strings'; -import { CodeAction } from 'vs/editor/common/modes'; -import { Position } from 'vs/editor/common/core/position'; - -export class CodeActionKind { - private static readonly sep = '.'; - - public static readonly Empty = new CodeActionKind(''); - public static readonly QuickFix = new CodeActionKind('quickfix'); - public static readonly Refactor = new CodeActionKind('refactor'); - public static readonly Source = new CodeActionKind('source'); - public static readonly SourceOrganizeImports = new CodeActionKind('source.organizeImports'); - public static readonly SourceFixAll = new CodeActionKind('source.fixAll'); - - constructor( - public readonly value: string - ) { } - - public equals(other: CodeActionKind): boolean { - return this.value === other.value; - } - - public contains(other: CodeActionKind): boolean { - return this.equals(other) || startsWith(other.value, this.value + CodeActionKind.sep); - } - - public intersects(other: CodeActionKind): boolean { - return this.contains(other) || other.contains(this); - } -} - -export const enum CodeActionAutoApply { - IfSingle, - First, - Never, -} - -export interface CodeActionFilter { - readonly kind?: CodeActionKind; - readonly includeSourceActions?: boolean; - readonly onlyIncludePreferredActions?: boolean; -} - -export function mayIncludeActionsOfKind(filter: CodeActionFilter, providedKind: CodeActionKind): boolean { - // A provided kind may be a subset or superset of our filtered kind. - if (filter.kind && !filter.kind.intersects(providedKind)) { - return false; - } - - // Don't return source actions unless they are explicitly requested - if (CodeActionKind.Source.contains(providedKind) && !filter.includeSourceActions) { - return false; - } - - return true; -} - - -export function filtersAction(filter: CodeActionFilter, action: CodeAction): boolean { - const actionKind = action.kind ? new CodeActionKind(action.kind) : undefined; - - // Filter out actions by kind - if (filter.kind) { - if (!actionKind || !filter.kind.contains(actionKind)) { - return false; - } - } - - // Don't return source actions unless they are explicitly requested - if (!filter.includeSourceActions) { - if (actionKind && CodeActionKind.Source.contains(actionKind)) { - return false; - } - } - - if (filter.onlyIncludePreferredActions) { - if (!action.isPreferred) { - return false; - } - } - - return true; -} - -export interface CodeActionTrigger { - readonly type: 'auto' | 'manual'; - readonly filter?: CodeActionFilter; - readonly autoApply?: CodeActionAutoApply; - readonly context?: { - readonly notAvailableMessage: string; - readonly position: Position; - }; -} \ No newline at end of file diff --git a/src/vs/editor/contrib/codeAction/codeActionUi.ts b/src/vs/editor/contrib/codeAction/codeActionUi.ts index 734f71f502..5aa3970814 100644 --- a/src/vs/editor/contrib/codeAction/codeActionUi.ts +++ b/src/vs/editor/contrib/codeAction/codeActionUi.ts @@ -3,25 +3,25 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; +import { find } from 'vs/base/common/arrays'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { Lazy } from 'vs/base/common/lazy'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IPosition } from 'vs/editor/common/core/position'; import { CodeAction } from 'vs/editor/common/modes'; import { CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; import { MessageController } from 'vs/editor/contrib/message/messageController'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { CodeActionsState } from './codeActionModel'; -import { CodeActionAutoApply } from './codeActionTrigger'; -import { CodeActionWidget } from './codeActionWidget'; +import { CodeActionMenu, CodeActionShowOptions } from './codeActionMenu'; import { LightBulbWidget } from './lightBulbWidget'; -import { IPosition } from 'vs/editor/common/core/position'; -import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; -import { Lazy } from 'vs/base/common/lazy'; +import { CodeActionAutoApply, CodeActionTrigger } from './types'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export class CodeActionUi extends Disposable { - private readonly _codeActionWidget: Lazy; + private readonly _codeActionWidget: Lazy; private readonly _lightBulbWidget: Lazy; private readonly _activeCodeActions = this._register(new MutableDisposable()); @@ -30,15 +30,14 @@ export class CodeActionUi extends Disposable { quickFixActionId: string, preferredFixActionId: string, private readonly delegate: { - applyCodeAction: (action: CodeAction, regtriggerAfterApply: boolean) => void + applyCodeAction: (action: CodeAction, regtriggerAfterApply: boolean) => Promise }, - @IContextMenuService contextMenuService: IContextMenuService, - @IKeybindingService keybindingService: IKeybindingService, + @IInstantiationService instantiationService: IInstantiationService, ) { super(); this._codeActionWidget = new Lazy(() => { - return this._register(new CodeActionWidget(this._editor, contextMenuService, { + return this._register(instantiationService.createInstance(CodeActionMenu, this._editor, { onSelectCodeAction: async (action) => { this.delegate.applyCodeAction(action, /* retrigger */ true); } @@ -46,17 +45,15 @@ export class CodeActionUi extends Disposable { }); this._lightBulbWidget = new Lazy(() => { - const widget = this._register(new LightBulbWidget(this._editor, quickFixActionId, preferredFixActionId, keybindingService)); - this._register(widget.onClick(this._handleLightBulbSelect, this)); + const widget = this._register(instantiationService.createInstance(LightBulbWidget, this._editor, quickFixActionId, preferredFixActionId)); + this._register(widget.onClick(e => this.showCodeActionList(e.actions, e, { includeDisabledActions: false }))); return widget; }); } public async update(newState: CodeActionsState.State): Promise { if (newState.type !== CodeActionsState.Type.Triggered) { - if (this._lightBulbWidget.hasValue()) { - this._lightBulbWidget.getValue().hide(); - } + this._lightBulbWidget.rawValue?.hide(); return; } @@ -70,29 +67,43 @@ export class CodeActionUi extends Disposable { this._lightBulbWidget.getValue().update(actions, newState.position); - if (!actions.actions.length && newState.trigger.context) { - MessageController.get(this._editor).showMessage(newState.trigger.context.notAvailableMessage, newState.trigger.context.position); - this._activeCodeActions.value = actions; - return; - } - if (newState.trigger.type === 'manual') { - if (newState.trigger.filter && newState.trigger.filter.kind) { - // Triggered for specific scope - if (actions.actions.length > 0) { - // Apply if we only have one action or requested autoApply - if (newState.trigger.autoApply === CodeActionAutoApply.First || (newState.trigger.autoApply === CodeActionAutoApply.IfSingle && actions.actions.length === 1)) { - try { - await this.delegate.applyCodeAction(actions.actions[0], false); - } finally { - actions.dispose(); - } + if (newState.trigger.filter?.include) { // Triggered for specific scope + // Check to see if we want to auto apply. + + const validActionToApply = this.tryGetValidActionToApply(newState.trigger, actions); + if (validActionToApply) { + try { + await this.delegate.applyCodeAction(validActionToApply, false); + } finally { + actions.dispose(); + } + return; + } + + // Check to see if there is an action that we would have applied were it not invalid + if (newState.trigger.context) { + const invalidAction = this.getInvalidActionThatWouldHaveBeenApplied(newState.trigger, actions); + if (invalidAction && invalidAction.disabled) { + MessageController.get(this._editor).showMessage(invalidAction.disabled, newState.trigger.context.position); + actions.dispose(); return; } } } + + const includeDisabledActions = !!newState.trigger.filter?.include; + if (newState.trigger.context) { + if (!actions.allActions.length || !includeDisabledActions && !actions.validActions.length) { + MessageController.get(this._editor).showMessage(newState.trigger.context.notAvailableMessage, newState.trigger.context.position); + this._activeCodeActions.value = actions; + actions.dispose(); + return; + } + } + this._activeCodeActions.value = actions; - this._codeActionWidget.getValue().show(actions, newState.position); + this._codeActionWidget.getValue().show(actions, newState.position, { includeDisabledActions }); } else { // auto magically triggered if (this._codeActionWidget.getValue().isVisible) { @@ -104,11 +115,35 @@ export class CodeActionUi extends Disposable { } } - public async showCodeActionList(actions: CodeActionSet, at?: IAnchor | IPosition): Promise { - this._codeActionWidget.getValue().show(actions, at); + private getInvalidActionThatWouldHaveBeenApplied(trigger: CodeActionTrigger, actions: CodeActionSet): CodeAction | undefined { + if (!actions.allActions.length) { + return undefined; + } + + if ((trigger.autoApply === CodeActionAutoApply.First && actions.validActions.length === 0) + || (trigger.autoApply === CodeActionAutoApply.IfSingle && actions.allActions.length === 1) + ) { + return find(actions.allActions, action => action.disabled); + } + + return undefined; } - private _handleLightBulbSelect(e: { x: number, y: number, actions: CodeActionSet }): void { - this._codeActionWidget.getValue().show(e.actions, e); + private tryGetValidActionToApply(trigger: CodeActionTrigger, actions: CodeActionSet): CodeAction | undefined { + if (!actions.validActions.length) { + return undefined; + } + + if ((trigger.autoApply === CodeActionAutoApply.First && actions.validActions.length > 0) + || (trigger.autoApply === CodeActionAutoApply.IfSingle && actions.validActions.length === 1) + ) { + return actions.validActions[0]; + } + + return undefined; + } + + public async showCodeActionList(actions: CodeActionSet, at: IAnchor | IPosition, options: CodeActionShowOptions): Promise { + this._codeActionWidget.getValue().show(actions, at, options); } } diff --git a/src/vs/editor/contrib/codeAction/codeActionWidget.ts b/src/vs/editor/contrib/codeAction/codeActionWidget.ts deleted file mode 100644 index 4a922ad895..0000000000 --- a/src/vs/editor/contrib/codeAction/codeActionWidget.ts +++ /dev/null @@ -1,91 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { getDomNodePagePosition } from 'vs/base/browser/dom'; -import { Action } from 'vs/base/common/actions'; -import { canceled } from 'vs/base/common/errors'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { Position, IPosition } from 'vs/editor/common/core/position'; -import { ScrollType } from 'vs/editor/common/editorCommon'; -import { CodeAction } from 'vs/editor/common/modes'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; -import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; -import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; - -interface CodeActionWidgetDelegate { - onSelectCodeAction: (action: CodeAction) => Promise; -} - -export class CodeActionWidget extends Disposable { - - private _visible: boolean = false; - private readonly _showingActions = this._register(new MutableDisposable()); - - constructor( - private readonly _editor: ICodeEditor, - private readonly _contextMenuService: IContextMenuService, - private readonly _delegate: CodeActionWidgetDelegate, - ) { - super(); - } - - public async show(codeActions: CodeActionSet, at?: IAnchor | IPosition): Promise { - if (!codeActions.actions.length) { - this._visible = false; - return; - } - if (!this._editor.getDomNode()) { - // cancel when editor went off-dom - this._visible = false; - return Promise.reject(canceled()); - } - - this._visible = true; - const actions = codeActions.actions.map(action => this.codeActionToAction(action)); - - this._showingActions.value = codeActions; - this._contextMenuService.showContextMenu({ - getAnchor: () => { - if (Position.isIPosition(at)) { - at = this._toCoords(at); - } - return at || { x: 0, y: 0 }; - }, - getActions: () => actions, - onHide: () => { - this._visible = false; - this._editor.focus(); - }, - autoSelectFirstItem: true - }); - } - - private codeActionToAction(action: CodeAction): Action { - const id = action.command ? action.command.id : action.title; - const title = action.title; - return new Action(id, title, undefined, true, () => this._delegate.onSelectCodeAction(action)); - } - - get isVisible(): boolean { - return this._visible; - } - - private _toCoords(position: IPosition): { x: number, y: number } { - if (!this._editor.hasModel()) { - return { x: 0, y: 0 }; - } - this._editor.revealPosition(position, ScrollType.Immediate); - this._editor.render(); - - // Translate to absolute editor position - const cursorCoords = this._editor.getScrolledVisiblePosition(position); - const editorCoords = getDomNodePagePosition(this._editor.getDomNode()); - const x = editorCoords.left + cursorCoords.left; - const y = editorCoords.top + cursorCoords.top + cursorCoords.height; - - return { x, y }; - } -} diff --git a/src/vs/editor/contrib/codeAction/lightBulbWidget.ts b/src/vs/editor/contrib/codeAction/lightBulbWidget.ts index 2e0d2d85b1..e68b5e45c4 100644 --- a/src/vs/editor/contrib/codeAction/lightBulbWidget.ts +++ b/src/vs/editor/contrib/codeAction/lightBulbWidget.ts @@ -17,6 +17,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { editorLightBulbForeground, editorLightBulbAutoFixForeground } from 'vs/platform/theme/common/colorRegistry'; +import { Gesture } from 'vs/base/browser/touch'; namespace LightBulbState { @@ -25,7 +26,7 @@ namespace LightBulbState { Showing, } - export const Hidden = new class { readonly type = Type.Hidden; }; + export const Hidden = { type: Type.Hidden } as const; export class Showing { readonly type = Type.Showing; @@ -71,7 +72,9 @@ export class LightBulbWidget extends Disposable implements IContentWidget { this.hide(); } })); - this._register(dom.addStandardDisposableListener(this._domNode, 'mousedown', e => { + + Gesture.ignoreTarget(this._domNode); + this._register(dom.addStandardDisposableGenericMouseDownListner(this._domNode, e => { if (this.state.type !== LightBulbState.Type.Showing) { return; } @@ -137,7 +140,7 @@ export class LightBulbWidget extends Disposable implements IContentWidget { } public update(actions: CodeActionSet, atPosition: IPosition) { - if (actions.actions.length <= 0) { + if (actions.validActions.length <= 0) { return this.hide(); } diff --git a/src/vs/editor/contrib/codeAction/test/codeAction.test.ts b/src/vs/editor/contrib/codeAction/test/codeAction.test.ts index 07c0b11d41..fd3010577c 100644 --- a/src/vs/editor/contrib/codeAction/test/codeAction.test.ts +++ b/src/vs/editor/contrib/codeAction/test/codeAction.test.ts @@ -9,7 +9,7 @@ import { Range } from 'vs/editor/common/core/range'; import { TextModel } from 'vs/editor/common/model/textModel'; import * as modes from 'vs/editor/common/modes'; import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; -import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -125,7 +125,7 @@ suite('CodeAction', () => { testData.tsLint.abc ]; - const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'manual' }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'manual' }, CancellationToken.None); assert.equal(actions.length, 6); assert.deepEqual(actions, expected); }); @@ -140,20 +140,20 @@ suite('CodeAction', () => { disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); { - const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: new CodeActionKind('a') } }, CancellationToken.None); assert.equal(actions.length, 2); assert.strictEqual(actions[0].title, 'a'); assert.strictEqual(actions[1].title, 'a.b'); } { - const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a.b') } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: new CodeActionKind('a.b') } }, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'a.b'); } { - const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a.b.c') } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: new CodeActionKind('a.b.c') } }, CancellationToken.None); assert.equal(actions.length, 0); } }); @@ -172,7 +172,7 @@ suite('CodeAction', () => { disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); - const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: new CodeActionKind('a') } }, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'a'); }); @@ -186,18 +186,40 @@ suite('CodeAction', () => { disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); { - const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto' }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto' }, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'b'); } { - const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: CodeActionKind.Source, includeSourceActions: true } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: CodeActionKind.Source, includeSourceActions: true } }, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'a'); } }); + test('getCodeActions should support filtering out some requested source code actions #84602', async function () { + const provider = staticCodeActionProvider( + { title: 'a', kind: CodeActionKind.Source.value }, + { title: 'b', kind: CodeActionKind.Source.append('test').value }, + { title: 'c', kind: 'c' } + ); + + disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); + + { + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { + type: 'auto', filter: { + include: CodeActionKind.Source.append('test'), + excludes: [CodeActionKind.Source], + includeSourceActions: true, + } + }, CancellationToken.None); + assert.equal(actions.length, 1); + assert.strictEqual(actions[0].title, 'b'); + } + }); + test('getCodeActions should not invoke code action providers filtered out by providedCodeActionKinds', async function () { let wasInvoked = false; const provider = new class implements modes.CodeActionProvider { @@ -211,10 +233,10 @@ suite('CodeAction', () => { disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); - const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { - kind: CodeActionKind.QuickFix + include: CodeActionKind.QuickFix } }, CancellationToken.None); assert.strictEqual(actions.length, 0); diff --git a/src/vs/editor/contrib/codeAction/test/codeActionKeybindingResolver.test.ts b/src/vs/editor/contrib/codeAction/test/codeActionKeybindingResolver.test.ts new file mode 100644 index 0000000000..34f3313611 --- /dev/null +++ b/src/vs/editor/contrib/codeAction/test/codeActionKeybindingResolver.test.ts @@ -0,0 +1,94 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { ChordKeybinding, KeyCode, SimpleKeybinding } from 'vs/base/common/keyCodes'; +import { OperatingSystem } from 'vs/base/common/platform'; +import { refactorCommandId, organizeImportsCommandId } from 'vs/editor/contrib/codeAction/codeAction'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; +import { CodeActionKeybindingResolver } from 'vs/editor/contrib/codeAction/codeActionMenu'; +import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; +import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; + +suite('CodeActionKeybindingResolver', () => { + const refactorKeybinding = createCodeActionKeybinding( + KeyCode.KEY_A, + refactorCommandId, + { kind: CodeActionKind.Refactor.value }); + + const refactorExtractKeybinding = createCodeActionKeybinding( + KeyCode.KEY_B, + refactorCommandId, + { kind: CodeActionKind.Refactor.append('extract').value }); + + const organizeImportsKeybinding = createCodeActionKeybinding( + KeyCode.KEY_C, + organizeImportsCommandId, + undefined); + + test('Should match refactor keybindings', async function () { + const resolver = new CodeActionKeybindingResolver({ + getKeybindings: (): readonly ResolvedKeybindingItem[] => { + return [refactorKeybinding]; + }, + }).getResolver(); + + assert.equal( + resolver({ title: '' }), + undefined); + + assert.equal( + resolver({ title: '', kind: CodeActionKind.Refactor.value }), + refactorKeybinding.resolvedKeybinding); + + assert.equal( + resolver({ title: '', kind: CodeActionKind.Refactor.append('extract').value }), + refactorKeybinding.resolvedKeybinding); + + assert.equal( + resolver({ title: '', kind: CodeActionKind.QuickFix.value }), + undefined); + }); + + test('Should prefer most specific keybinding', async function () { + const resolver = new CodeActionKeybindingResolver({ + getKeybindings: (): readonly ResolvedKeybindingItem[] => { + return [refactorKeybinding, refactorExtractKeybinding, organizeImportsKeybinding]; + }, + }).getResolver(); + + assert.equal( + resolver({ title: '', kind: CodeActionKind.Refactor.value }), + refactorKeybinding.resolvedKeybinding); + + assert.equal( + resolver({ title: '', kind: CodeActionKind.Refactor.append('extract').value }), + refactorExtractKeybinding.resolvedKeybinding); + }); + + test('Organize imports should still return a keybinding even though it does not have args', async function () { + const resolver = new CodeActionKeybindingResolver({ + getKeybindings: (): readonly ResolvedKeybindingItem[] => { + return [refactorKeybinding, refactorExtractKeybinding, organizeImportsKeybinding]; + }, + }).getResolver(); + + assert.equal( + resolver({ title: '', kind: CodeActionKind.SourceOrganizeImports.value }), + organizeImportsKeybinding.resolvedKeybinding); + }); +}); + +function createCodeActionKeybinding(keycode: KeyCode, command: string, commandArgs: any) { + return new ResolvedKeybindingItem( + new USLayoutResolvedKeybinding( + new ChordKeybinding([new SimpleKeybinding(false, true, false, false, keycode)]), + OperatingSystem.Linux), + command, + commandArgs, + undefined, + false); +} + diff --git a/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts b/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts index 59b98899d9..d0b480a33f 100644 --- a/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts +++ b/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts @@ -5,6 +5,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 { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Selection } from 'vs/editor/common/core/selection'; @@ -55,13 +56,15 @@ suite('CodeActionModel', () => { const contextKeys = new MockContextKeyService(); const model = disposables.add(new CodeActionModel(editor, markerService, contextKeys, undefined)); - disposables.add(model.onDidChangeState((e: CodeActionsState.Triggered) => { - assert.equal(e.trigger.type, 'auto'); + disposables.add(model.onDidChangeState((e: CodeActionsState.State) => { + assertType(e.type === CodeActionsState.Type.Triggered); + + assert.strictEqual(e.trigger.type, 'auto'); assert.ok(e.actions); e.actions.then(fixes => { model.dispose(); - assert.equal(fixes.actions.length, 1); + assert.equal(fixes.validActions.length, 1); done(); }, done); })); @@ -94,12 +97,14 @@ suite('CodeActionModel', () => { 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.Triggered) => { + disposables.add(model.onDidChangeState((e: CodeActionsState.State) => { + assertType(e.type === CodeActionsState.Type.Triggered); + assert.equal(e.trigger.type, 'auto'); assert.ok(e.actions); e.actions.then(fixes => { model.dispose(); - assert.equal(fixes.actions.length, 1); + assert.equal(fixes.validActions.length, 1); resolve(undefined); }, reject); })); @@ -130,7 +135,9 @@ suite('CodeActionModel', () => { await new Promise(resolve => { const contextKeys = new MockContextKeyService(); const model = disposables.add(new CodeActionModel(editor, markerService, contextKeys, undefined)); - disposables.add(model.onDidChangeState((e: CodeActionsState.Triggered) => { + disposables.add(model.onDidChangeState((e: CodeActionsState.State) => { + assertType(e.type === CodeActionsState.Type.Triggered); + assert.equal(e.trigger.type, 'auto'); const selection = e.rangeOrSelection; assert.deepEqual(selection.selectionStartLineNumber, 1); @@ -153,7 +160,9 @@ suite('CodeActionModel', () => { let triggerCount = 0; const contextKeys = new MockContextKeyService(); const model = disposables.add(new CodeActionModel(editor, markerService, contextKeys, undefined)); - disposables.add(model.onDidChangeState((e: CodeActionsState.Triggered) => { + disposables.add(model.onDidChangeState((e: CodeActionsState.State) => { + assertType(e.type === CodeActionsState.Type.Triggered); + assert.equal(e.trigger.type, 'auto'); ++triggerCount; diff --git a/src/vs/editor/contrib/codeAction/types.ts b/src/vs/editor/contrib/codeAction/types.ts new file mode 100644 index 0000000000..c4ade7f152 --- /dev/null +++ b/src/vs/editor/contrib/codeAction/types.ts @@ -0,0 +1,151 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { startsWith } from 'vs/base/common/strings'; +import { CodeAction } from 'vs/editor/common/modes'; +import { Position } from 'vs/editor/common/core/position'; + +export class CodeActionKind { + private static readonly sep = '.'; + + public static readonly None = new CodeActionKind('@@none@@'); // Special code action that contains nothing + public static readonly Empty = new CodeActionKind(''); + public static readonly QuickFix = new CodeActionKind('quickfix'); + public static readonly Refactor = new CodeActionKind('refactor'); + public static readonly Source = new CodeActionKind('source'); + public static readonly SourceOrganizeImports = CodeActionKind.Source.append('organizeImports'); + public static readonly SourceFixAll = CodeActionKind.Source.append('fixAll'); + + constructor( + public readonly value: string + ) { } + + public equals(other: CodeActionKind): boolean { + return this.value === other.value; + } + + public contains(other: CodeActionKind): boolean { + return this.equals(other) || this.value === '' || startsWith(other.value, this.value + CodeActionKind.sep); + } + + public intersects(other: CodeActionKind): boolean { + return this.contains(other) || other.contains(this); + } + + public append(part: string): CodeActionKind { + return new CodeActionKind(this.value + CodeActionKind.sep + part); + } +} + +export const enum CodeActionAutoApply { + IfSingle = 'ifSingle', + First = 'first', + Never = 'never', +} + +export interface CodeActionFilter { + readonly include?: CodeActionKind; + readonly excludes?: readonly CodeActionKind[]; + readonly includeSourceActions?: boolean; + readonly onlyIncludePreferredActions?: boolean; +} + +export function mayIncludeActionsOfKind(filter: CodeActionFilter, providedKind: CodeActionKind): boolean { + // A provided kind may be a subset or superset of our filtered kind. + if (filter.include && !filter.include.intersects(providedKind)) { + return false; + } + + // Don't return source actions unless they are explicitly requested + if (!filter.includeSourceActions && CodeActionKind.Source.contains(providedKind)) { + return false; + } + + return true; +} + +export function filtersAction(filter: CodeActionFilter, action: CodeAction): boolean { + const actionKind = action.kind ? new CodeActionKind(action.kind) : undefined; + + // Filter out actions by kind + if (filter.include) { + if (!actionKind || !filter.include.contains(actionKind)) { + return false; + } + } + + if (filter.excludes) { + if (actionKind && filter.excludes.some(exclude => { + // Excludes are overwritten by includes + return exclude.contains(actionKind) && (!filter.include || !filter.include.contains(actionKind)); + })) { + return false; + } + } + + // Don't return source actions unless they are explicitly requested + if (!filter.includeSourceActions) { + if (actionKind && CodeActionKind.Source.contains(actionKind)) { + return false; + } + } + + if (filter.onlyIncludePreferredActions) { + if (!action.isPreferred) { + return false; + } + } + + return true; +} + +export interface CodeActionTrigger { + readonly type: 'auto' | 'manual'; + readonly filter?: CodeActionFilter; + readonly autoApply?: CodeActionAutoApply; + readonly context?: { + readonly notAvailableMessage: string; + readonly position: Position; + }; +} + +export class CodeActionCommandArgs { + public static fromUser(arg: any, defaults: { kind: CodeActionKind, apply: CodeActionAutoApply }): CodeActionCommandArgs { + if (!arg || typeof arg !== 'object') { + return new CodeActionCommandArgs(defaults.kind, defaults.apply, false); + } + return new CodeActionCommandArgs( + CodeActionCommandArgs.getKindFromUser(arg, defaults.kind), + CodeActionCommandArgs.getApplyFromUser(arg, defaults.apply), + CodeActionCommandArgs.getPreferredUser(arg)); + } + + private static getApplyFromUser(arg: any, defaultAutoApply: CodeActionAutoApply) { + switch (typeof arg.apply === 'string' ? arg.apply.toLowerCase() : '') { + case 'first': return CodeActionAutoApply.First; + case 'never': return CodeActionAutoApply.Never; + case 'ifsingle': return CodeActionAutoApply.IfSingle; + default: return defaultAutoApply; + } + } + + private static getKindFromUser(arg: any, defaultKind: CodeActionKind) { + return typeof arg.kind === 'string' + ? new CodeActionKind(arg.kind) + : defaultKind; + } + + private static getPreferredUser(arg: any): boolean { + return typeof arg.preferred === 'boolean' + ? arg.preferred + : false; + } + + private constructor( + public readonly kind: CodeActionKind, + public readonly apply: CodeActionAutoApply, + public readonly preferred: boolean, + ) { } +} diff --git a/src/vs/editor/contrib/codelens/codeLensCache.ts b/src/vs/editor/contrib/codelens/codeLensCache.ts index abf40be2fe..b2ba7b2753 100644 --- a/src/vs/editor/contrib/codelens/codeLensCache.ts +++ b/src/vs/editor/contrib/codelens/codeLensCache.ts @@ -68,11 +68,18 @@ export class CodeLensCache implements ICodeLensCache { } put(model: ITextModel, data: CodeLensModel): void { + // create a copy of the model that is without command-ids + // but with comand-labels + const copyItems = data.lenses.map(item => { + return { + range: item.symbol.range, + command: item.symbol.command && { id: '', title: item.symbol.command?.title }, + }; + }); + const copyModel = new CodeLensModel(); + copyModel.add({ lenses: copyItems, dispose: () => { } }, this._fakeProvider); - const lensModel = new CodeLensModel(); - lensModel.add({ lenses: data.lenses.map(v => v.symbol), dispose() { } }, this._fakeProvider); - - const item = new CacheItem(model.getLineCount(), lensModel); + const item = new CacheItem(model.getLineCount(), copyModel); this._cache.set(model.uri.toString(), item); } diff --git a/src/vs/editor/contrib/codelens/codelensController.ts b/src/vs/editor/contrib/codelens/codelensController.ts index 5e0ef96870..fc914f859a 100644 --- a/src/vs/editor/contrib/codelens/codelensController.ts +++ b/src/vs/editor/contrib/codelens/codelensController.ts @@ -18,6 +18,8 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ICodeLensCache } from 'vs/editor/contrib/codelens/codeLensCache'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { createStyleSheet } from 'vs/base/browser/dom'; +import { hash } from 'vs/base/common/hash'; export class CodeLensContribution implements editorCommon.IEditorContribution { @@ -27,6 +29,8 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { private readonly _globalToDispose = new DisposableStore(); private readonly _localToDispose = new DisposableStore(); + private readonly _styleElement: HTMLStyleElement; + private readonly _styleClassName: string; private _lenses: CodeLensWidget[] = []; private _currentFindCodeLensSymbolsPromise: CancelablePromise | undefined; private _oldCodeLensModels = new DisposableStore(); @@ -53,7 +57,16 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { } })); this._globalToDispose.add(CodeLensProviderRegistry.onDidChange(this._onModelChange, this)); + this._globalToDispose.add(this._editor.onDidChangeConfiguration(e => { + if (e.hasChanged(EditorOption.fontInfo)) { + this._updateLensStyle(); + } + })); this._onModelChange(); + + this._styleClassName = hash(this._editor.getId()).toString(16); + this._styleElement = createStyleSheet(); + this._updateLensStyle(); } dispose(): void { @@ -63,6 +76,15 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { dispose(this._currentCodeLensModel); } + private _updateLensStyle(): void { + const options = this._editor.getOptions(); + const fontInfo = options.get(EditorOption.fontInfo); + const lineHeight = options.get(EditorOption.lineHeight); + + const newStyle = `.monaco-editor .codelens-decoration.${this._styleClassName} { height: ${Math.round(lineHeight * 1.1)}px; line-height: ${lineHeight}px; font-size: ${Math.round(fontInfo.fontSize * 0.9)}px; padding-right: ${Math.round(fontInfo.fontSize * 0.45)}px;}`; + this._styleElement.innerHTML = newStyle; + } + private _localDispose(): void { if (this._currentFindCodeLensSymbolsPromise) { this._currentFindCodeLensSymbolsPromise.cancel(); @@ -200,17 +222,17 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { this._disposeAllLenses(undefined, undefined); } })); - this._localToDispose.add(this._editor.onDidChangeConfiguration(e => { - if (e.hasChanged(EditorOption.fontInfo)) { - for (const lens of this._lenses) { - lens.updateHeight(); - } - } - })); this._localToDispose.add(this._editor.onMouseUp(e => { - if (e.target.type === editorBrowser.MouseTargetType.CONTENT_WIDGET && e.target.element && e.target.element.tagName === 'A') { + if (e.target.type !== editorBrowser.MouseTargetType.CONTENT_WIDGET) { + return; + } + let target = e.target.element; + if (target?.tagName === 'SPAN') { + target = target.parentElement; + } + if (target?.tagName === 'A') { for (const lens of this._lenses) { - let command = lens.getCommand(e.target.element as HTMLLinkElement); + let command = lens.getCommand(target as HTMLLinkElement); if (command) { this._commandService.executeCommand(command.id, ...(command.arguments || [])).catch(err => this._notificationService.error(err)); break; @@ -222,8 +244,10 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { } private _disposeAllLenses(decChangeAccessor: IModelDecorationsChangeAccessor | undefined, viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor | undefined): void { - let helper = new CodeLensHelper(); - this._lenses.forEach((lens) => lens.dispose(helper, viewZoneChangeAccessor)); + const helper = new CodeLensHelper(); + for (const lens of this._lenses) { + lens.dispose(helper, viewZoneChangeAccessor); + } if (decChangeAccessor) { helper.commit(decChangeAccessor); } @@ -276,7 +300,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { groupsIndex++; codeLensIndex++; } else { - this._lenses.splice(codeLensIndex, 0, new CodeLensWidget(groups[groupsIndex], this._editor, helper, viewZoneAccessor, () => this._detectVisibleLenses && this._detectVisibleLenses.schedule())); + this._lenses.splice(codeLensIndex, 0, new CodeLensWidget(groups[groupsIndex], this._editor, this._styleClassName, helper, viewZoneAccessor, () => this._detectVisibleLenses && this._detectVisibleLenses.schedule())); codeLensIndex++; groupsIndex++; } @@ -290,7 +314,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { // Create extra symbols while (groupsIndex < groups.length) { - this._lenses.push(new CodeLensWidget(groups[groupsIndex], this._editor, helper, viewZoneAccessor, () => this._detectVisibleLenses && this._detectVisibleLenses.schedule())); + this._lenses.push(new CodeLensWidget(groups[groupsIndex], this._editor, this._styleClassName, helper, viewZoneAccessor, () => this._detectVisibleLenses && this._detectVisibleLenses.schedule())); groupsIndex++; } @@ -326,7 +350,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { return; } - this._currentResolveCodeLensSymbolsPromise = createCancelablePromise(token => { + const resolvePromise = createCancelablePromise(token => { const promises = toResolve.map((request, i) => { @@ -343,7 +367,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { }); return Promise.all(promises).then(() => { - if (!token.isCancellationRequested) { + if (!token.isCancellationRequested && !lenses[i].isDisposed()) { lenses[i].updateCommands(resolvedSymbols); } }); @@ -351,13 +375,21 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { return Promise.all(promises); }); + this._currentResolveCodeLensSymbolsPromise = resolvePromise; this._currentResolveCodeLensSymbolsPromise.then(() => { + if (this._currentCodeLensModel) { // update the cached state with new resolved items + this._codeLensCache.put(model, this._currentCodeLensModel); + } this._oldCodeLensModels.clear(); // dispose old models once we have updated the UI with the current model - this._currentResolveCodeLensSymbolsPromise = undefined; + if (resolvePromise === this._currentResolveCodeLensSymbolsPromise) { + this._currentResolveCodeLensSymbolsPromise = undefined; + } }, err => { onUnexpectedError(err); // can also be cancellation! - this._currentResolveCodeLensSymbolsPromise = undefined; + if (resolvePromise === this._currentResolveCodeLensSymbolsPromise) { + this._currentResolveCodeLensSymbolsPromise = undefined; + } }); } } diff --git a/src/vs/editor/contrib/codelens/codelensWidget.css b/src/vs/editor/contrib/codelens/codelensWidget.css index 6234ee6958..d09173886d 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.css +++ b/src/vs/editor/contrib/codelens/codelensWidget.css @@ -11,10 +11,9 @@ .monaco-editor .codelens-decoration > span, .monaco-editor .codelens-decoration > a { - -moz-user-select: none; + user-select: none; -webkit-user-select: none; -ms-user-select: none; - user-select: none; white-space: nowrap; vertical-align: sub; } @@ -28,10 +27,6 @@ cursor: pointer; } -.monaco-editor .codelens-decoration.invisible-cl { - opacity: 0; -} - @keyframes fadein { 0% { opacity: 0; visibility: visible;} 100% { opacity: 1; } diff --git a/src/vs/editor/contrib/codelens/codelensWidget.ts b/src/vs/editor/contrib/codelens/codelensWidget.ts index faacba4fd9..04433943e9 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.ts +++ b/src/vs/editor/contrib/codelens/codelensWidget.ts @@ -5,7 +5,6 @@ import 'vs/css!./codelensWidget'; import * as dom from 'vs/base/browser/dom'; -import { coalesce, isFalsyOrEmpty } from 'vs/base/common/arrays'; import { renderCodicons } from 'vs/base/browser/ui/codiconLabel/codiconLabel'; import * as editorBrowser from 'vs/editor/browser/editorBrowser'; import { Range } from 'vs/editor/common/core/range'; @@ -16,7 +15,6 @@ import { editorCodeLensForeground } from 'vs/editor/common/view/editorColorRegis import { CodeLensItem } from 'vs/editor/contrib/codelens/codelens'; import { editorActiveLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; class CodeLensViewZone implements editorBrowser.IViewZone { @@ -58,69 +56,65 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget { private readonly _id: string; private readonly _domNode: HTMLElement; - private readonly _editor: editorBrowser.ICodeEditor; + private readonly _editor: editorBrowser.IActiveCodeEditor; private readonly _commands = new Map(); private _widgetPosition?: editorBrowser.IContentWidgetPosition; + private _isEmpty: boolean = true; constructor( - editor: editorBrowser.ICodeEditor, - symbolRange: Range, - data: CodeLensItem[] + editor: editorBrowser.IActiveCodeEditor, + className: string, + line: number, ) { - this._id = 'codeLensWidget' + (++CodeLensContentWidget._idPool); this._editor = editor; + this._id = `codelens.widget-${(CodeLensContentWidget._idPool++)}`; - this.setSymbolRange(symbolRange); + this.updatePosition(line); this._domNode = document.createElement('span'); - this._domNode.innerHTML = ' '; - dom.addClass(this._domNode, 'codelens-decoration'); - this.updateHeight(); - this.withCommands(data.map(data => data.symbol), false); + this._domNode.className = `codelens-decoration ${className}`; } - updateHeight(): void { - const options = this._editor.getOptions(); - const fontInfo = options.get(EditorOption.fontInfo); - const lineHeight = options.get(EditorOption.lineHeight); - this._domNode.style.height = `${Math.round(lineHeight * 1.1)}px`; - this._domNode.style.lineHeight = `${lineHeight}px`; - this._domNode.style.fontSize = `${Math.round(fontInfo.fontSize * 0.9)}px`; - this._domNode.style.paddingRight = `${Math.round(fontInfo.fontSize * 0.45)}px`; - this._domNode.innerHTML = ' '; - } - - withCommands(inSymbols: Array, animate: boolean): void { + withCommands(lenses: Array, animate: boolean): void { this._commands.clear(); - const symbols = coalesce(inSymbols); - if (isFalsyOrEmpty(symbols)) { - this._domNode.innerHTML = 'no commands'; - return; - } - - let html: string[] = []; - for (let i = 0; i < symbols.length; i++) { - const command = symbols[i].command; - if (command) { - const title = renderCodicons(command.title); - let part: string; - if (command.id) { - part = `${title}`; - this._commands.set(String(i), command); + let innerHtml = ''; + let hasSymbol = false; + for (let i = 0; i < lenses.length; i++) { + const lens = lenses[i]; + if (!lens) { + continue; + } + hasSymbol = true; + if (lens.command) { + const title = renderCodicons(lens.command.title); + if (lens.command.id) { + innerHtml += `${title}`; + this._commands.set(String(i), lens.command); } else { - part = `${title}`; + innerHtml += `${title}`; + } + if (i + 1 < lenses.length) { + innerHtml += ' | '; } - html.push(part); } } - const wasEmpty = this._domNode.innerHTML === '' || this._domNode.innerHTML === ' '; - this._domNode.innerHTML = html.join(' | '); - this._editor.layoutContentWidget(this); - if (wasEmpty && animate) { - dom.addClass(this._domNode, 'fadein'); + if (!hasSymbol) { + // symbols but no commands + this._domNode.innerHTML = 'no commands'; + + } else { + // symbols and commands + if (!innerHtml) { + innerHtml = ' '; + } + this._domNode.innerHTML = innerHtml; + if (this._isEmpty && animate) { + dom.addClass(this._domNode, 'fadein'); + } + this._isEmpty = false; } } @@ -138,14 +132,10 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget { return this._domNode; } - setSymbolRange(range: Range): void { - if (!this._editor.hasModel()) { - return; - } - const lineNumber = range.startLineNumber; - const column = this._editor.getModel().getLineFirstNonWhitespaceColumn(lineNumber); + updatePosition(line: number): void { + const column = this._editor.getModel().getLineFirstNonWhitespaceColumn(line); this._widgetPosition = { - position: { lineNumber: lineNumber, column: column }, + position: { lineNumber: line, column: column }, preference: [editorBrowser.ContentWidgetPositionPreference.ABOVE] }; } @@ -153,10 +143,6 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget { getPosition(): editorBrowser.IContentWidgetPosition | null { return this._widgetPosition || null; } - - isVisible(): boolean { - return this._domNode.hasAttribute('monaco-visible-content-widget'); - } } export interface IDecorationIdCallback { @@ -194,27 +180,40 @@ export class CodeLensHelper { export class CodeLensWidget { - private readonly _editor: editorBrowser.ICodeEditor; + private readonly _editor: editorBrowser.IActiveCodeEditor; + private readonly _className: string; private readonly _viewZone!: CodeLensViewZone; private readonly _viewZoneId!: string; - private readonly _contentWidget!: CodeLensContentWidget; + + private _contentWidget?: CodeLensContentWidget; private _decorationIds: string[]; private _data: CodeLensItem[]; + private _isDisposed: boolean = false; constructor( data: CodeLensItem[], - editor: editorBrowser.ICodeEditor, + editor: editorBrowser.IActiveCodeEditor, + className: string, helper: CodeLensHelper, viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor, updateCallback: Function ) { this._editor = editor; + this._className = className; this._data = data; - this._decorationIds = new Array(this._data.length); + // create combined range, track all ranges with decorations, + // check if there is already something to render + this._decorationIds = []; let range: Range | undefined; + let lenses: CodeLens[] = []; + this._data.forEach((codeLensData, i) => { + if (codeLensData.symbol.command) { + lenses.push(codeLensData.symbol); + } + helper.addDecoration({ range: codeLensData.symbol.range, options: ModelDecorationOptions.EMPTY @@ -228,43 +227,51 @@ export class CodeLensWidget { } }); - if (range) { - this._contentWidget = new CodeLensContentWidget(editor, range, this._data); - this._viewZone = new CodeLensViewZone(range.startLineNumber - 1, updateCallback); + this._viewZone = new CodeLensViewZone(range!.startLineNumber - 1, updateCallback); + this._viewZoneId = viewZoneChangeAccessor.addZone(this._viewZone); - this._viewZoneId = viewZoneChangeAccessor.addZone(this._viewZone); - this._editor.addContentWidget(this._contentWidget); + if (lenses.length > 0) { + this._createContentWidgetIfNecessary(); + this._contentWidget!.withCommands(lenses, false); + } + } + + private _createContentWidgetIfNecessary(): void { + if (!this._contentWidget) { + this._contentWidget = new CodeLensContentWidget(this._editor, this._className, this._viewZone.afterLineNumber + 1); + this._editor.addContentWidget(this._contentWidget!); } } dispose(helper: CodeLensHelper, viewZoneChangeAccessor?: editorBrowser.IViewZoneChangeAccessor): void { - while (this._decorationIds.length) { - helper.removeDecoration(this._decorationIds.pop()!); - } + this._decorationIds.forEach(helper.removeDecoration, helper); + this._decorationIds = []; if (viewZoneChangeAccessor) { viewZoneChangeAccessor.removeZone(this._viewZoneId); } - this._editor.removeContentWidget(this._contentWidget); + if (this._contentWidget) { + this._editor.removeContentWidget(this._contentWidget); + this._contentWidget = undefined; + } + this._isDisposed = true; + } + + isDisposed(): boolean { + return this._isDisposed; } isValid(): boolean { - if (!this._editor.hasModel()) { - return false; - } - const model = this._editor.getModel(); return this._decorationIds.some((id, i) => { - const range = model.getDecorationRange(id); + const range = this._editor.getModel().getDecorationRange(id); const symbol = this._data[i].symbol; return !!(range && Range.isEmpty(symbol.range) === range.isEmpty()); }); } updateCodeLensSymbols(data: CodeLensItem[], helper: CodeLensHelper): void { - while (this._decorationIds.length) { - helper.removeDecoration(this._decorationIds.pop()!); - } + this._decorationIds.forEach(helper.removeDecoration, helper); + this._decorationIds = []; this._data = data; - this._decorationIds = new Array(this._data.length); this._data.forEach((codeLensData, i) => { helper.addDecoration({ range: codeLensData.symbol.range, @@ -274,7 +281,7 @@ export class CodeLensWidget { } computeIfNecessary(model: ITextModel): CodeLensItem[] | null { - if (!this._contentWidget.isVisible()) { + if (!this._viewZone.domNode.hasAttribute('monaco-visible-view-zone')) { return null; } @@ -289,7 +296,10 @@ export class CodeLensWidget { } updateCommands(symbols: Array): void { - this._contentWidget.withCommands(symbols, true); + + this._createContentWidgetIfNecessary(); + this._contentWidget!.withCommands(symbols, true); + for (let i = 0; i < this._data.length; i++) { const resolved = symbols[i]; if (resolved) { @@ -299,33 +309,29 @@ export class CodeLensWidget { } } - updateHeight(): void { - this._contentWidget.updateHeight(); - } - getCommand(link: HTMLLinkElement): Command | undefined { - return this._contentWidget.getCommand(link); + return this._contentWidget?.getCommand(link); } getLineNumber(): number { - if (this._editor.hasModel()) { - const range = this._editor.getModel().getDecorationRange(this._decorationIds[0]); - if (range) { - return range.startLineNumber; - } + const range = this._editor.getModel().getDecorationRange(this._decorationIds[0]); + if (range) { + return range.startLineNumber; } return -1; } update(viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor): void { - if (this.isValid() && this._editor.hasModel()) { + if (this.isValid()) { const range = this._editor.getModel().getDecorationRange(this._decorationIds[0]); if (range) { this._viewZone.afterLineNumber = range.startLineNumber - 1; viewZoneChangeAccessor.layoutZone(this._viewZoneId); - this._contentWidget.setSymbolRange(range); - this._editor.layoutContentWidget(this._contentWidget); + if (this._contentWidget) { + this._contentWidget.updatePosition(range.startLineNumber); + this._editor.layoutContentWidget(this._contentWidget); + } } } } diff --git a/src/vs/editor/contrib/colorPicker/colorPicker.css b/src/vs/editor/contrib/colorPicker/colorPicker.css index d929b4dd47..28da1f2671 100644 --- a/src/vs/editor/contrib/colorPicker/colorPicker.css +++ b/src/vs/editor/contrib/colorPicker/colorPicker.css @@ -6,6 +6,8 @@ .colorpicker-widget { height: 190px; user-select: none; + -webkit-user-select: none; + -ms-user-select: none; } .monaco-editor .colorpicker-hover:focus { @@ -115,4 +117,4 @@ .colorpicker-body .strip .overlay { height: 150px; pointer-events: none; -} \ No newline at end of file +} diff --git a/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts b/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts index 29bb526c5f..e8f689e6bf 100644 --- a/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts +++ b/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts @@ -150,7 +150,7 @@ class SaturationBox extends Disposable { this.layout(); - this._register(dom.addDisposableListener(this.domNode, dom.EventType.MOUSE_DOWN, e => this.onMouseDown(e))); + this._register(dom.addDisposableGenericMouseDownListner(this.domNode, e => this.onMouseDown(e))); this._register(this.model.onDidChangeColor(this.onDidChangeColor, this)); this.monitor = null; } @@ -165,7 +165,7 @@ class SaturationBox extends Disposable { this.monitor.startMonitoring(standardMouseMoveMerger, event => this.onDidChangePosition(event.posx - origin.left, event.posy - origin.top), () => null); - const mouseUpListener = dom.addDisposableListener(document, dom.EventType.MOUSE_UP, () => { + const mouseUpListener = dom.addDisposableGenericMouseUpListner(document, () => { this._onColorFlushed.fire(); mouseUpListener.dispose(); if (this.monitor) { @@ -250,7 +250,7 @@ abstract class Strip extends Disposable { this.slider = dom.append(this.domNode, $('.slider')); this.slider.style.top = `0px`; - this._register(dom.addDisposableListener(this.domNode, dom.EventType.MOUSE_DOWN, e => this.onMouseDown(e))); + this._register(dom.addDisposableGenericMouseDownListner(this.domNode, e => this.onMouseDown(e))); this.layout(); } @@ -272,7 +272,7 @@ abstract class Strip extends Disposable { monitor.startMonitoring(standardMouseMoveMerger, event => this.onDidChangeTop(event.posy - origin.top), () => null); - const mouseUpListener = dom.addDisposableListener(document, dom.EventType.MOUSE_UP, () => { + const mouseUpListener = dom.addDisposableGenericMouseUpListner(document, () => { this._onColorFlushed.fire(); mouseUpListener.dispose(); monitor.stopMonitoring(true); diff --git a/src/vs/editor/contrib/comment/comment.ts b/src/vs/editor/contrib/comment/comment.ts index f8129f86cc..a11f92dd17 100644 --- a/src/vs/editor/contrib/comment/comment.ts +++ b/src/vs/editor/contrib/comment/comment.ts @@ -56,13 +56,12 @@ class ToggleCommentLineAction extends CommentLineAction { primary: KeyMod.CtrlCmd | KeyCode.US_SLASH, weight: KeybindingWeight.EditorContrib }, - // {{SQL CARBON EDIT}} - Remove from menu - // menubarOpts: { - // menuId: MenuId.MenubarEditMenu, - // group: '5_insert', - // title: nls.localize({ key: 'miToggleLineComment', comment: ['&& denotes a mnemonic'] }, "&&Toggle Line Comment"), - // order: 1 - // } + /*menuOpts: { {{SQL CARBON EDIT}} - Remove from menu + menuId: MenuId.MenubarEditMenu, + group: '5_insert', + title: nls.localize({ key: 'miToggleLineComment', comment: ['&& denotes a mnemonic'] }, "&&Toggle Line Comment"), + order: 1 + }*/ }); } } @@ -113,13 +112,12 @@ class BlockCommentAction extends EditorAction { linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_A }, weight: KeybindingWeight.EditorContrib }, - // {{SQL CARBON EDIT}} - Remove from menu - // menubarOpts: { - // menuId: MenuId.MenubarEditMenu, - // group: '5_insert', - // title: nls.localize({ key: 'miToggleBlockComment', comment: ['&& denotes a mnemonic'] }, "Toggle &&Block Comment"), - // order: 2 - // } + /*menuOpts: { {{SQL CARBON EDIT}} - Remove from menu + menuId: MenuId.MenubarEditMenu, + group: '5_insert', + title: nls.localize({ key: 'miToggleBlockComment', comment: ['&& denotes a mnemonic'] }, "Toggle &&Block Comment"), + order: 2 + }*/ }); } diff --git a/src/vs/editor/contrib/contextmenu/contextmenu.ts b/src/vs/editor/contrib/contextmenu/contextmenu.ts index f219f43ce1..06b332c8a9 100644 --- a/src/vs/editor/contrib/contextmenu/contextmenu.ts +++ b/src/vs/editor/contrib/contextmenu/contextmenu.ts @@ -15,7 +15,7 @@ import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/brows import { EditorAction, ServicesAccessor, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { IMenuService, MenuId, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -23,6 +23,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { ITextModel } from 'vs/editor/common/model'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { ContextSubMenu } from 'vs/base/browser/contextmenu'; export class ContextMenuController implements IEditorContribution { @@ -128,7 +129,7 @@ export class ContextMenuController implements IEditorContribution { } // Find actions available for menu - const menuActions = this._getMenuActions(this._editor.getModel()); + const menuActions = this._getMenuActions(this._editor.getModel(), MenuId.EditorContext); // Show menu if we have actions to show if (menuActions.length > 0) { @@ -136,16 +137,27 @@ export class ContextMenuController implements IEditorContribution { } } - private _getMenuActions(model: ITextModel): ReadonlyArray { + private _getMenuActions(model: ITextModel, menuId: MenuId): IAction[] { const result: IAction[] = []; - let contextMenu = this._menuService.createMenu(MenuId.EditorContext, this._contextKeyService); - const groups = contextMenu.getActions({ arg: model.uri }); - contextMenu.dispose(); + // get menu groups + const menu = this._menuService.createMenu(menuId, this._contextKeyService); + const groups = menu.getActions({ arg: model.uri }); + menu.dispose(); + // translate them into other actions for (let group of groups) { const [, actions] = group; - result.push(...actions); + for (const action of actions) { + if (action instanceof SubmenuItemAction) { + const subActions = this._getMenuActions(model, action.item.submenu); + if (subActions.length > 0) { + result.push(new ContextSubMenu(action.label, subActions)); + } + } else { + result.push(action); + } + } result.push(new Separator()); } result.pop(); // remove last separator diff --git a/src/vs/editor/contrib/cursorUndo/cursorUndo.ts b/src/vs/editor/contrib/cursorUndo/cursorUndo.ts index b0bfe023b3..9919172166 100644 --- a/src/vs/editor/contrib/cursorUndo/cursorUndo.ts +++ b/src/vs/editor/contrib/cursorUndo/cursorUndo.ts @@ -48,7 +48,6 @@ export class CursorUndoRedoController extends Disposable implements IEditorContr private _undoStack: CursorState[]; private _redoStack: CursorState[]; - private _prevState: CursorState | null; constructor(editor: ICodeEditor) { super(); @@ -57,38 +56,36 @@ export class CursorUndoRedoController extends Disposable implements IEditorContr this._undoStack = []; this._redoStack = []; - this._prevState = null; this._register(editor.onDidChangeModel((e) => { this._undoStack = []; this._redoStack = []; - this._prevState = null; })); this._register(editor.onDidChangeModelContent((e) => { this._undoStack = []; this._redoStack = []; - this._prevState = null; - this._pushStateIfNecessary(); })); - this._register(editor.onDidChangeCursorSelection(() => this._pushStateIfNecessary())); - } - - private _pushStateIfNecessary(): void { - const newState = new CursorState(this._editor.getSelections()!); - - if (!this._isCursorUndoRedo && this._prevState) { - const isEqualToLastUndoStack = (this._undoStack.length > 0 && this._undoStack[this._undoStack.length - 1].equals(this._prevState)); + this._register(editor.onDidChangeCursorSelection((e) => { + if (this._isCursorUndoRedo) { + return; + } + if (!e.oldSelections) { + return; + } + if (e.oldModelVersionId !== e.modelVersionId) { + return; + } + const prevState = new CursorState(e.oldSelections); + const isEqualToLastUndoStack = (this._undoStack.length > 0 && this._undoStack[this._undoStack.length - 1].equals(prevState)); if (!isEqualToLastUndoStack) { - this._undoStack.push(this._prevState); + this._undoStack.push(prevState); this._redoStack = []; if (this._undoStack.length > 50) { // keep the cursor undo stack bounded this._undoStack.shift(); } } - } - - this._prevState = newState; + })); } public cursorUndo(): void { diff --git a/src/vs/editor/contrib/cursorUndo/test/cursorUndo.test.ts b/src/vs/editor/contrib/cursorUndo/test/cursorUndo.test.ts new file mode 100644 index 0000000000..3f9f85f554 --- /dev/null +++ b/src/vs/editor/contrib/cursorUndo/test/cursorUndo.test.ts @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { Selection } from 'vs/editor/common/core/selection'; +import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; +import { CursorUndo, CursorUndoRedoController } from 'vs/editor/contrib/cursorUndo/cursorUndo'; +import { Handler } from 'vs/editor/common/editorCommon'; +import { CoreNavigationCommands, CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; + +suite('FindController', () => { + + const cursorUndoAction = new CursorUndo(); + + test('issue #82535: Edge case with cursorUndo', () => { + withTestCodeEditor([ + '' + ], {}, (editor) => { + + editor.registerAndInstantiateContribution(CursorUndoRedoController.ID, CursorUndoRedoController); + + // type hello + editor.trigger('test', Handler.Type, { text: 'hello' }); + + // press left + CoreNavigationCommands.CursorLeft.runEditorCommand(null, editor, {}); + + // press Delete + CoreEditingCommands.DeleteRight.runEditorCommand(null, editor, {}); + assert.deepEqual(editor.getValue(), 'hell'); + assert.deepEqual(editor.getSelections(), [new Selection(1, 5, 1, 5)]); + + // press left + CoreNavigationCommands.CursorLeft.runEditorCommand(null, editor, {}); + assert.deepEqual(editor.getSelections(), [new Selection(1, 4, 1, 4)]); + + // press Ctrl+U + cursorUndoAction.run(null!, editor, {}); + assert.deepEqual(editor.getSelections(), [new Selection(1, 5, 1, 5)]); + }); + }); + + test('issue #82535: Edge case with cursorUndo (reverse)', () => { + withTestCodeEditor([ + '' + ], {}, (editor) => { + + editor.registerAndInstantiateContribution(CursorUndoRedoController.ID, CursorUndoRedoController); + + // type hello + editor.trigger('test', Handler.Type, { text: 'hell' }); + editor.trigger('test', Handler.Type, { text: 'o' }); + assert.deepEqual(editor.getValue(), 'hello'); + assert.deepEqual(editor.getSelections(), [new Selection(1, 6, 1, 6)]); + + // press Ctrl+U + cursorUndoAction.run(null!, editor, {}); + assert.deepEqual(editor.getSelections(), [new Selection(1, 6, 1, 6)]); + }); + }); +}); diff --git a/src/vs/editor/contrib/dnd/dnd.ts b/src/vs/editor/contrib/dnd/dnd.ts index 06119ec2c3..c8c2d20ba6 100644 --- a/src/vs/editor/contrib/dnd/dnd.ts +++ b/src/vs/editor/contrib/dnd/dnd.ts @@ -8,7 +8,7 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Disposable } from 'vs/base/common/lifecycle'; import { isMacintosh } from 'vs/base/common/platform'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { ICodeEditor, IEditorMouseEvent, IMouseTarget, MouseTargetType } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, IEditorMouseEvent, IMouseTarget, MouseTargetType, IPartialEditorMouseEvent } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { Position } from 'vs/editor/common/core/position'; @@ -50,7 +50,7 @@ export class DragAndDropController extends Disposable implements editorCommon.IE this._register(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onEditorMouseDown(e))); this._register(this._editor.onMouseUp((e: IEditorMouseEvent) => this._onEditorMouseUp(e))); this._register(this._editor.onMouseDrag((e: IEditorMouseEvent) => this._onEditorMouseDrag(e))); - this._register(this._editor.onMouseDrop((e: IEditorMouseEvent) => this._onEditorMouseDrop(e))); + this._register(this._editor.onMouseDrop((e: IPartialEditorMouseEvent) => this._onEditorMouseDrop(e))); this._register(this._editor.onKeyDown((e: IKeyboardEvent) => this.onEditorKeyDown(e))); this._register(this._editor.onKeyUp((e: IKeyboardEvent) => this.onEditorKeyUp(e))); this._register(this._editor.onDidBlurEditorWidget(() => this.onEditorBlur())); @@ -143,7 +143,7 @@ export class DragAndDropController extends Disposable implements editorCommon.IE } } - private _onEditorMouseDrop(mouseEvent: IEditorMouseEvent): void { + private _onEditorMouseDrop(mouseEvent: IPartialEditorMouseEvent): void { if (mouseEvent.target && (this._hitContent(mouseEvent.target) || this._hitMargin(mouseEvent.target)) && mouseEvent.target.position) { let newCursorPosition = new Position(mouseEvent.target.position.lineNumber, mouseEvent.target.position.column); diff --git a/src/vs/editor/contrib/documentSymbols/outlineTree.ts b/src/vs/editor/contrib/documentSymbols/outlineTree.ts index d56f906b9e..fc2703d143 100644 --- a/src/vs/editor/contrib/documentSymbols/outlineTree.ts +++ b/src/vs/editor/contrib/documentSymbols/outlineTree.ts @@ -22,6 +22,8 @@ import { MarkerSeverity } from 'vs/platform/markers/common/markers'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { registerColor, listErrorForeground, listWarningForeground, foreground } from 'vs/platform/theme/common/colorRegistry'; import { IdleValue } from 'vs/base/common/async'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { URI } from 'vs/base/common/uri'; export type OutlineItem = OutlineGroup | OutlineElement; @@ -220,26 +222,84 @@ export const enum OutlineSortOrder { export class OutlineFilter implements ITreeFilter { - private readonly _filteredTypes = new Set(); + static readonly configNameToKind = Object.freeze({ + ['showFiles']: SymbolKind.File, + ['showModules']: SymbolKind.Module, + ['showNamespaces']: SymbolKind.Namespace, + ['showPackages']: SymbolKind.Package, + ['showClasses']: SymbolKind.Class, + ['showMethods']: SymbolKind.Method, + ['showProperties']: SymbolKind.Property, + ['showFields']: SymbolKind.Field, + ['showConstructors']: SymbolKind.Constructor, + ['showEnums']: SymbolKind.Enum, + ['showInterfaces']: SymbolKind.Interface, + ['showFunctions']: SymbolKind.Function, + ['showVariables']: SymbolKind.Variable, + ['showConstants']: SymbolKind.Constant, + ['showStrings']: SymbolKind.String, + ['showNumbers']: SymbolKind.Number, + ['showBooleans']: SymbolKind.Boolean, + ['showArrays']: SymbolKind.Array, + ['showObjects']: SymbolKind.Object, + ['showKeys']: SymbolKind.Key, + ['showNull']: SymbolKind.Null, + ['showEnumMembers']: SymbolKind.EnumMember, + ['showStructs']: SymbolKind.Struct, + ['showEvents']: SymbolKind.Event, + ['showOperators']: SymbolKind.Operator, + ['showTypeParameters']: SymbolKind.TypeParameter, + }); + + static readonly kindToConfigName = Object.freeze({ + [SymbolKind.File]: 'showFiles', + [SymbolKind.Module]: 'showModules', + [SymbolKind.Namespace]: 'showNamespaces', + [SymbolKind.Package]: 'showPackages', + [SymbolKind.Class]: 'showClasses', + [SymbolKind.Method]: 'showMethods', + [SymbolKind.Property]: 'showProperties', + [SymbolKind.Field]: 'showFields', + [SymbolKind.Constructor]: 'showConstructors', + [SymbolKind.Enum]: 'showEnums', + [SymbolKind.Interface]: 'showInterfaces', + [SymbolKind.Function]: 'showFunctions', + [SymbolKind.Variable]: 'showVariables', + [SymbolKind.Constant]: 'showConstants', + [SymbolKind.String]: 'showStrings', + [SymbolKind.Number]: 'showNumbers', + [SymbolKind.Boolean]: 'showBooleans', + [SymbolKind.Array]: 'showArrays', + [SymbolKind.Object]: 'showObjects', + [SymbolKind.Key]: 'showKeys', + [SymbolKind.Null]: 'showNull', + [SymbolKind.EnumMember]: 'showEnumMembers', + [SymbolKind.Struct]: 'showStructs', + [SymbolKind.Event]: 'showEvents', + [SymbolKind.Operator]: 'showOperators', + [SymbolKind.TypeParameter]: 'showTypeParameters', + }); constructor( private readonly _prefix: string, - @IConfigurationService private readonly _configService: IConfigurationService, - ) { - - } - - update() { - this._filteredTypes.clear(); - for (const name of SymbolKinds.names()) { - if (!this._configService.getValue(`${this._prefix}.${name}`)) { - this._filteredTypes.add(SymbolKinds.fromString(name) || -1); - } - } - } + @ITextResourceConfigurationService private readonly _textResourceConfigService: ITextResourceConfigurationService, + ) { } filter(element: OutlineItem): boolean { - return !(element instanceof OutlineElement) || !this._filteredTypes.has(element.symbol.kind); + const outline = OutlineModel.get(element); + let uri: URI | undefined; + + if (outline) { + uri = outline.textModel.uri; + } + + if (!(element instanceof OutlineElement)) { + return true; + } + + const configName = OutlineFilter.kindToConfigName[element.symbol.kind]; + const configKey = `${this._prefix}.${configName}`; + return this._textResourceConfigService.getValue(uri, configKey); } } @@ -296,11 +356,17 @@ export const SYMBOL_ICON_CLASS_FOREGROUND = registerColor('symbolIcon.classForeg hc: '#EE9D28' }, localize('symbolIcon.classForeground', 'The foreground color for class symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); -export const SYMBOL_ICON_CONSTANT_FOREGROUND = registerColor('symbolIcon.contstantForeground', { +export const SYMBOL_ICON_COLOR_FOREGROUND = registerColor('symbolIcon.colorForeground', { dark: foreground, light: foreground, hc: foreground -}, localize('symbolIcon.contstantForeground', 'The foreground color for contstant symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); +}, localize('symbolIcon.colorForeground', 'The foreground color for color symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); + +export const SYMBOL_ICON_CONSTANT_FOREGROUND = registerColor('symbolIcon.constantForeground', { + dark: foreground, + light: foreground, + hc: foreground +}, localize('symbolIcon.constantForeground', 'The foreground color for constant symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); export const SYMBOL_ICON_CONSTRUCTOR_FOREGROUND = registerColor('symbolIcon.constructorForeground', { dark: '#B180D7', @@ -338,6 +404,12 @@ export const SYMBOL_ICON_FILE_FOREGROUND = registerColor('symbolIcon.fileForegro hc: foreground }, localize('symbolIcon.fileForeground', 'The foreground color for file symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); +export const SYMBOL_ICON_FOLDER_FOREGROUND = registerColor('symbolIcon.folderForeground', { + dark: foreground, + light: foreground, + hc: foreground +}, localize('symbolIcon.folderForeground', 'The foreground color for folder symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); + export const SYMBOL_ICON_FUNCTION_FOREGROUND = registerColor('symbolIcon.functionForeground', { dark: '#B180D7', light: '#652D90', @@ -356,6 +428,12 @@ export const SYMBOL_ICON_KEY_FOREGROUND = registerColor('symbolIcon.keyForegroun hc: foreground }, localize('symbolIcon.keyForeground', 'The foreground color for key symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); +export const SYMBOL_ICON_KEYWORD_FOREGROUND = registerColor('symbolIcon.keywordForeground', { + dark: foreground, + light: foreground, + hc: foreground +}, localize('symbolIcon.keywordForeground', 'The foreground color for keyword symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); + export const SYMBOL_ICON_METHOD_FOREGROUND = registerColor('symbolIcon.methodForeground', { dark: '#B180D7', light: '#652D90', @@ -410,6 +488,18 @@ export const SYMBOL_ICON_PROPERTY_FOREGROUND = registerColor('symbolIcon.propert hc: foreground }, localize('symbolIcon.propertyForeground', 'The foreground color for property symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); +export const SYMBOL_ICON_REFERENCE_FOREGROUND = registerColor('symbolIcon.referenceForeground', { + dark: foreground, + light: foreground, + hc: foreground +}, localize('symbolIcon.referenceForeground', 'The foreground color for reference symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); + +export const SYMBOL_ICON_SNIPPET_FOREGROUND = registerColor('symbolIcon.snippetForeground', { + dark: foreground, + light: foreground, + hc: foreground +}, localize('symbolIcon.snippetForeground', 'The foreground color for snippet symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); + export const SYMBOL_ICON_STRING_FOREGROUND = registerColor('symbolIcon.stringForeground', { dark: foreground, light: foreground, @@ -422,12 +512,24 @@ export const SYMBOL_ICON_STRUCT_FOREGROUND = registerColor('symbolIcon.structFor hc: foreground }, localize('symbolIcon.structForeground', 'The foreground color for struct symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); +export const SYMBOL_ICON_TEXT_FOREGROUND = registerColor('symbolIcon.textForeground', { + dark: foreground, + light: foreground, + hc: foreground +}, localize('symbolIcon.textForeground', 'The foreground color for text symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); + export const SYMBOL_ICON_TYPEPARAMETER_FOREGROUND = registerColor('symbolIcon.typeParameterForeground', { dark: foreground, light: foreground, hc: foreground }, localize('symbolIcon.typeParameterForeground', 'The foreground color for type parameter symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); +export const SYMBOL_ICON_UNIT_FOREGROUND = registerColor('symbolIcon.unitForeground', { + dark: foreground, + light: foreground, + hc: foreground +}, localize('symbolIcon.unitForeground', 'The foreground color for unit symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); + export const SYMBOL_ICON_VARIABLE_FOREGROUND = registerColor('symbolIcon.variableForeground', { dark: '#75BEFF', light: '#007ACC', @@ -472,6 +574,15 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { `); } + const symbolIconColorColor = theme.getColor(SYMBOL_ICON_COLOR_FOREGROUND); + if (symbolIconColorColor) { + collector.addRule(` + .monaco-workbench .codicon-symbol-color { + color: ${symbolIconColorColor} !important; + } + `); + } + const symbolIconConstantColor = theme.getColor(SYMBOL_ICON_CONSTANT_FOREGROUND); if (symbolIconConstantColor) { collector.addRule(` @@ -536,6 +647,15 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { `); } + const symbolIconFolderColor = theme.getColor(SYMBOL_ICON_FOLDER_FOREGROUND); + if (symbolIconFolderColor) { + collector.addRule(` + .monaco-workbench .codicon-symbol-folder { + color: ${symbolIconFolderColor} !important; + } + `); + } + const symbolIconFunctionColor = theme.getColor(SYMBOL_ICON_FUNCTION_FOREGROUND); if (symbolIconFunctionColor) { collector.addRule(` @@ -563,6 +683,15 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { `); } + const symbolIconKeywordColor = theme.getColor(SYMBOL_ICON_KEYWORD_FOREGROUND); + if (symbolIconKeywordColor) { + collector.addRule(` + .monaco-workbench .codicon-symbol-keyword { + color: ${symbolIconKeywordColor} !important; + } + `); + } + const symbolIconModuleColor = theme.getColor(SYMBOL_ICON_MODULE_FOREGROUND); if (symbolIconModuleColor) { collector.addRule(` @@ -635,6 +764,24 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { `); } + const symbolIconReferenceColor = theme.getColor(SYMBOL_ICON_REFERENCE_FOREGROUND); + if (symbolIconReferenceColor) { + collector.addRule(` + .monaco-workbench .codicon-symbol-reference { + color: ${symbolIconReferenceColor} !important; + } + `); + } + + const symbolIconSnippetColor = theme.getColor(SYMBOL_ICON_SNIPPET_FOREGROUND); + if (symbolIconSnippetColor) { + collector.addRule(` + .monaco-workbench .codicon-symbol-snippet { + color: ${symbolIconSnippetColor} !important; + } + `); + } + const symbolIconStringColor = theme.getColor(SYMBOL_ICON_STRING_FOREGROUND); if (symbolIconStringColor) { collector.addRule(` @@ -653,6 +800,15 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { `); } + const symbolIconTextColor = theme.getColor(SYMBOL_ICON_TEXT_FOREGROUND); + if (symbolIconTextColor) { + collector.addRule(` + .monaco-workbench .codicon-symbol-text { + color: ${symbolIconTextColor} !important; + } + `); + } + const symbolIconTypeParameterColor = theme.getColor(SYMBOL_ICON_TYPEPARAMETER_FOREGROUND); if (symbolIconTypeParameterColor) { collector.addRule(` @@ -662,6 +818,15 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { `); } + const symbolIconUnitColor = theme.getColor(SYMBOL_ICON_UNIT_FOREGROUND); + if (symbolIconUnitColor) { + collector.addRule(` + .monaco-workbench .codicon-symbol-unit { + color: ${symbolIconUnitColor} !important; + } + `); + } + const symbolIconVariableColor = theme.getColor(SYMBOL_ICON_VARIABLE_FOREGROUND); if (symbolIconVariableColor) { collector.addRule(` diff --git a/src/vs/editor/contrib/find/findController.ts b/src/vs/editor/contrib/find/findController.ts index 5699df5548..c57a8e426c 100644 --- a/src/vs/editor/contrib/find/findController.ts +++ b/src/vs/editor/contrib/find/findController.ts @@ -395,11 +395,27 @@ export class FindController extends CommonFindController implements IFindControl this._createFindWidget(); } - if (!this._widget!.getPosition() && this._editor.getOption(EditorOption.find).autoFindInSelection) { - // not visible yet so we need to set search scope if `editor.find.autoFindInSelection` is `true` - opts.updateSearchScope = true; + const selection = this._editor.getSelection(); + let updateSearchScope = false; + + switch (this._editor.getOption(EditorOption.find).autoFindInSelection) { + case 'always': + updateSearchScope = true; + break; + case 'never': + updateSearchScope = false; + break; + case 'multiline': + const isSelectionMultipleLine = !!selection && selection.startLineNumber !== selection.endLineNumber; + updateSearchScope = isSelectionMultipleLine; + break; + + default: + break; } + opts.updateSearchScope = updateSearchScope; + super._start(opts); if (opts.shouldFocus === FindStartFocusAction.FocusReplaceInput) { @@ -439,7 +455,7 @@ export class StartFindAction extends EditorAction { primary: KeyMod.CtrlCmd | KeyCode.KEY_F, weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarEditMenu, group: '3_find', title: nls.localize({ key: 'miFind', comment: ['&& denotes a mnemonic'] }, "&&Find"), @@ -489,7 +505,7 @@ export class StartFindWithSelectionAction extends EditorAction { forceRevealReplace: false, seedSearchStringFromSelection: true, seedSearchStringFromGlobalClipboard: false, - shouldFocus: FindStartFocusAction.FocusFindInput, + shouldFocus: FindStartFocusAction.NoFocusChange, shouldAnimate: true, updateSearchScope: false }); @@ -685,7 +701,7 @@ export class StartFindReplaceAction extends EditorAction { mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_F }, weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarEditMenu, group: '3_find', title: nls.localize({ key: 'miReplace', comment: ['&& denotes a mnemonic'] }, "&&Replace"), diff --git a/src/vs/editor/contrib/find/findOptionsWidget.ts b/src/vs/editor/contrib/find/findOptionsWidget.ts index eeb61ffa09..a74bd30cd6 100644 --- a/src/vs/editor/contrib/find/findOptionsWidget.ts +++ b/src/vs/editor/contrib/find/findOptionsWidget.ts @@ -11,7 +11,7 @@ import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPosit import { FIND_IDS } from 'vs/editor/contrib/find/findModel'; import { FindReplaceState } from 'vs/editor/contrib/find/findState'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { contrastBorder, editorWidgetBackground, inputActiveOptionBorder, inputActiveOptionBackground, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; +import { contrastBorder, editorWidgetBackground, inputActiveOptionBorder, inputActiveOptionBackground, widgetShadow, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry'; import { ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; export class FindOptionsWidget extends Widget implements IOverlayWidget { @@ -200,6 +200,12 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.monaco-editor .findOptionsWidget { background-color: ${widgetBackground}; }`); } + const widgetForeground = theme.getColor(editorWidgetForeground); + if (widgetForeground) { + collector.addRule(`.monaco-editor .findOptionsWidget { color: ${widgetForeground}; }`); + } + + const widgetShadowColor = theme.getColor(widgetShadow); if (widgetShadowColor) { collector.addRule(`.monaco-editor .findOptionsWidget { box-shadow: 0 2px 8px ${widgetShadowColor}; }`); @@ -209,4 +215,4 @@ registerThemingParticipant((theme, collector) => { if (hcBorder) { collector.addRule(`.monaco-editor .findOptionsWidget { border: 2px solid ${hcBorder}; }`); } -}); \ No newline at end of file +}); diff --git a/src/vs/editor/contrib/find/findState.ts b/src/vs/editor/contrib/find/findState.ts index 42fec44612..f4f7d02018 100644 --- a/src/vs/editor/contrib/find/findState.ts +++ b/src/vs/editor/contrib/find/findState.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { Range } from 'vs/editor/common/core/range'; export interface FindReplaceStateChangedEvent { @@ -57,7 +57,7 @@ function effectiveOptionValue(override: FindOptionOverride, value: boolean): boo return value; } -export class FindReplaceState implements IDisposable { +export class FindReplaceState extends Disposable { private _searchString: string; private _replaceString: string; private _isRevealed: boolean; @@ -74,7 +74,7 @@ export class FindReplaceState implements IDisposable { private _matchesPosition: number; private _matchesCount: number; private _currentMatch: Range | null; - private readonly _onFindReplaceStateChange = new Emitter(); + private readonly _onFindReplaceStateChange = this._register(new Emitter()); public get searchString(): string { return this._searchString; } public get replaceString(): string { return this._replaceString; } @@ -97,6 +97,7 @@ export class FindReplaceState implements IDisposable { public readonly onFindReplaceStateChange: Event = this._onFindReplaceStateChange.event; constructor() { + super(); this._searchString = ''; this._replaceString = ''; this._isRevealed = false; @@ -115,9 +116,6 @@ export class FindReplaceState implements IDisposable { this._currentMatch = null; } - public dispose(): void { - } - public changeMatchInfo(matchesPosition: number, matchesCount: number, currentMatch: Range | undefined): void { let changeEvent: FindReplaceStateChangedEvent = { moveCursor: false, diff --git a/src/vs/editor/contrib/find/findWidget.css b/src/vs/editor/contrib/find/findWidget.css index d19194e0e1..11ccae5150 100644 --- a/src/vs/editor/contrib/find/findWidget.css +++ b/src/vs/editor/contrib/find/findWidget.css @@ -32,13 +32,17 @@ .monaco-editor .find-widget { position: absolute; z-index: 10; - top: -44px; height: 33px; overflow: hidden; line-height: 19px; - transition: top 200ms linear; + transition: transform 200ms linear; padding: 0 4px; box-sizing: border-box; + transform: translateY(Calc(-100% - 10px)); /* shadow (10px) */ +} + +.monaco-editor .find-widget textarea { + margin: 0px; } .monaco-editor .find-widget.hiddenEditor { @@ -46,30 +50,12 @@ } /* Find widget when replace is toggled on */ -.monaco-editor .find-widget.replaceToggled { - top: -74px; /* find input height + replace input height + shadow (10px) */ -} .monaco-editor .find-widget.replaceToggled > .replace-part { display: flex; - display: -webkit-flex; } -.monaco-editor .find-widget.visible, -.monaco-editor .find-widget.replaceToggled.visible { - top: 0; -} - -/* Multiple line find widget */ - -.monaco-editor .find-widget.multipleline { - top: unset; - bottom: 10px; -} - -.monaco-editor .find-widget.multipleline.visible, -.monaco-editor .find-widget.multipleline.replaceToggled.visible { - top: 0px; - bottom: unset; +.monaco-editor .find-widget.visible { + transform: translateY(0); } .monaco-editor .find-widget .monaco-inputbox.synthetic-focus { @@ -79,7 +65,6 @@ .monaco-editor .find-widget .monaco-inputbox .input { background-color: transparent; - /* Style to compensate for //winjs */ min-height: 0; } @@ -92,7 +77,6 @@ margin: 4px 0 0 17px; font-size: 12px; display: flex; - display: -webkit-flex; } .monaco-editor .find-widget > .find-part .monaco-inputbox, @@ -128,7 +112,6 @@ .monaco-editor .find-widget .monaco-findInput { vertical-align: middle; display: flex; - display: -webkit-flex; flex:1; } @@ -144,7 +127,6 @@ .monaco-editor .find-widget .matchesCount { display: flex; - display: -webkit-flex; flex: initial; margin: 0 0 0 3px; padding: 2px 0 0 2px; @@ -156,11 +138,9 @@ } .monaco-editor .find-widget .button { - min-width: 20px; width: 20px; height: 20px; display: flex; - display: -webkit-flex; flex: initial; margin-left: 3px; background-position: center center; @@ -189,14 +169,10 @@ .monaco-editor .find-widget .button.toggle { position: absolute; top: 0; - left: 0; + left: 3px; width: 18px; height: 100%; - -webkit-box-sizing: border-box; - -o-box-sizing: border-box; - -moz-box-sizing: border-box; - -ms-box-sizing: border-box; - box-sizing: border-box; + box-sizing: border-box; } .monaco-editor .find-widget .button.toggle.disabled { @@ -249,7 +225,6 @@ .monaco-editor .find-widget > .replace-part > .monaco-findInput { position: relative; display: flex; - display: -webkit-flex; vertical-align: middle; flex: auto; flex-grow: 0; @@ -287,12 +262,6 @@ } .monaco-editor .findMatch { - -webkit-animation-duration: 0; - -webkit-animation-name: inherit !important; - -moz-animation-duration: 0; - -moz-animation-name: inherit !important; - -ms-animation-duration: 0; - -ms-animation-name: inherit !important; animation-duration: 0; animation-name: inherit !important; } diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts index d3a5fe6758..6383ed6186 100644 --- a/src/vs/editor/contrib/find/findWidget.ts +++ b/src/vs/editor/contrib/find/findWidget.ts @@ -10,6 +10,7 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; import { alert as alertFn } from 'vs/base/browser/ui/aria/aria'; +import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; import { FindInput, IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput'; import { IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox'; import { ReplaceInput } from 'vs/base/browser/ui/findinput/replaceInput'; @@ -120,7 +121,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas private _matchesCount!: HTMLElement; private _prevBtn!: SimpleButton; private _nextBtn!: SimpleButton; - private _toggleSelectionFind!: SimpleCheckbox; + private _toggleSelectionFind!: Checkbox; private _closeBtn!: SimpleButton; private _replaceBtn!: SimpleButton; private _replaceAllBtn!: SimpleButton; @@ -290,12 +291,6 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas private _onStateChanged(e: FindReplaceStateChangedEvent): void { if (e.searchString) { - if (this._state.searchString.indexOf('\n') >= 0) { - dom.addClass(this._domNode, 'multipleline'); - } else { - dom.removeClass(this._domNode, 'multipleline'); - } - try { this._ignoreChangeEvent = true; this._findInput.setValue(this._state.searchString); @@ -436,7 +431,11 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas let isSelection = selection ? (selection.startLineNumber !== selection.endLineNumber || selection.startColumn !== selection.endColumn) : false; let isChecked = this._toggleSelectionFind.checked; - this._toggleSelectionFind.setEnabled(this._isVisible && (isChecked || isSelection)); + if (this._isVisible && (isChecked || isSelection)) { + this._toggleSelectionFind.enable(); + } else { + this._toggleSelectionFind.disable(); + } } private _updateButtons(): void { @@ -466,12 +465,23 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas this._isVisible = true; const selection = this._codeEditor.getSelection(); - const isSelection = selection ? (selection.startLineNumber !== selection.endLineNumber || selection.startColumn !== selection.endColumn) : false; - if (isSelection && this._codeEditor.getOption(EditorOption.find).autoFindInSelection) { - this._toggleSelectionFind.checked = true; - } else { - this._toggleSelectionFind.checked = false; + + switch (this._codeEditor.getOption(EditorOption.find).autoFindInSelection) { + case 'always': + this._toggleSelectionFind.checked = true; + break; + case 'never': + this._toggleSelectionFind.checked = false; + break; + case 'multiline': + const isSelectionMultipleLine = !!selection && selection.startLineNumber !== selection.endLineNumber; + this._toggleSelectionFind.checked = isSelectionMultipleLine; + break; + + default: + break; } + this._tryUpdateWidgetWidth(); this._updateButtons(); @@ -636,6 +646,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas }; this._findInput.style(inputStyles); this._replaceInput.style(inputStyles); + this._toggleSelectionFind.style(inputStyles); } private _tryUpdateWidgetWidth() { @@ -981,26 +992,30 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas actionsContainer.appendChild(this._nextBtn.domNode); // Toggle selection button - this._toggleSelectionFind = this._register(new SimpleCheckbox({ - parent: actionsContainer, + this._toggleSelectionFind = this._register(new Checkbox({ + actionClassName: 'codicon codicon-selection', title: NLS_TOGGLE_SELECTION_FIND_TITLE + this._keybindingLabelFor(FIND_IDS.ToggleSearchScopeCommand), - onChange: () => { - if (this._toggleSelectionFind.checked) { - if (this._codeEditor.hasModel()) { - let selection = this._codeEditor.getSelection(); - if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) { - selection = selection.setEndPosition(selection.endLineNumber - 1, this._codeEditor.getModel().getLineMaxColumn(selection.endLineNumber - 1)); - } - if (!selection.isEmpty()) { - this._state.change({ searchScope: selection }, true); - } + isChecked: false + })); + + this._register(this._toggleSelectionFind.onChange(() => { + if (this._toggleSelectionFind.checked) { + if (this._codeEditor.hasModel()) { + let selection = this._codeEditor.getSelection(); + if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) { + selection = selection.setEndPosition(selection.endLineNumber - 1, this._codeEditor.getModel().getLineMaxColumn(selection.endLineNumber - 1)); + } + if (!selection.isEmpty()) { + this._state.change({ searchScope: selection }, true); } - } else { - this._state.change({ searchScope: null }, true); } + } else { + this._state.change({ searchScope: null }, true); } })); + actionsContainer.appendChild(this._toggleSelectionFind.domNode); + // Close button this._closeBtn = this._register(new SimpleButton({ label: NLS_CLOSE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.CloseFindWidgetCommand), @@ -1054,7 +1069,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas this._prevBtn.focus(); } else if (this._nextBtn.isEnabled()) { this._nextBtn.focus(); - } else if (this._toggleSelectionFind.isEnabled()) { + } else if (this._toggleSelectionFind.enabled) { this._toggleSelectionFind.focus(); } else if (this._closeBtn.isEnabled()) { this._closeBtn.focus(); @@ -1200,91 +1215,6 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas } } -interface ISimpleCheckboxOpts { - readonly parent: HTMLElement; - readonly title: string; - readonly onChange: () => void; -} - -class SimpleCheckbox extends Widget { - - private static _COUNTER = 0; - - private readonly _opts: ISimpleCheckboxOpts; - private readonly _domNode: HTMLElement; - private readonly _checkbox: HTMLInputElement; - private readonly _label: HTMLLabelElement; - - constructor(opts: ISimpleCheckboxOpts) { - super(); - this._opts = opts; - - this._domNode = document.createElement('div'); - this._domNode.className = 'monaco-checkbox'; - this._domNode.title = this._opts.title; - this._domNode.tabIndex = 0; - - this._checkbox = document.createElement('input'); - this._checkbox.type = 'checkbox'; - this._checkbox.className = 'checkbox'; - this._checkbox.id = 'checkbox-' + SimpleCheckbox._COUNTER++; - this._checkbox.tabIndex = -1; - - this._label = document.createElement('label'); - this._label.className = 'codicon codicon-selection'; - // Connect the label and the checkbox. Checkbox will get checked when the label receives a click. - this._label.htmlFor = this._checkbox.id; - this._label.tabIndex = -1; - - this._domNode.appendChild(this._checkbox); - this._domNode.appendChild(this._label); - - this._opts.parent.appendChild(this._domNode); - - this.onchange(this._checkbox, () => { - this._opts.onChange(); - }); - } - - public get domNode(): HTMLElement { - return this._domNode; - } - - public isEnabled(): boolean { - return (this._domNode.tabIndex >= 0); - } - - public get checked(): boolean { - return this._checkbox.checked; - } - - public set checked(newValue: boolean) { - this._checkbox.checked = newValue; - } - - public focus(): void { - this._domNode.focus(); - } - - private enable(): void { - this._checkbox.removeAttribute('disabled'); - } - - private disable(): void { - this._checkbox.disabled = true; - } - - public setEnabled(enabled: boolean): void { - if (enabled) { - this.enable(); - this.domNode.tabIndex = 0; - } else { - this.disable(); - this.domNode.tabIndex = -1; - } - } -} - export interface ISimpleButtonOpts { readonly label: string; readonly className: string; @@ -1390,7 +1320,7 @@ registerThemingParticipant((theme, collector) => { const hcBorder = theme.getColor(contrastBorder); if (hcBorder) { - collector.addRule(`.monaco-editor .find-widget { border: 2px solid ${hcBorder}; }`); + collector.addRule(`.monaco-editor .find-widget { border: 1px solid ${hcBorder}; }`); } const foreground = theme.getColor(editorWidgetForeground); diff --git a/src/vs/editor/contrib/find/test/find.test.ts b/src/vs/editor/contrib/find/test/find.test.ts index d0b6fc9bdc..4884dbdc1f 100644 --- a/src/vs/editor/contrib/find/test/find.test.ts +++ b/src/vs/editor/contrib/find/test/find.test.ts @@ -75,7 +75,7 @@ suite('Find', () => { let searchStringSelectionTwoLines = getSelectionSearchString(editor); assert.equal(searchStringSelectionTwoLines, null); - // Select end of first line newline and and chunk of second + // Select end of first line newline and chunk of second editor.setSelection(new Range(1, 7, 2, 4)); let searchStringSelectionSpanLines = getSelectionSearchString(editor); assert.equal(searchStringSelectionSpanLines, null); diff --git a/src/vs/editor/contrib/find/test/findController.test.ts b/src/vs/editor/contrib/find/test/findController.test.ts index 9bfa18156b..2d580562bd 100644 --- a/src/vs/editor/contrib/find/test/findController.test.ts +++ b/src/vs/editor/contrib/find/test/findController.test.ts @@ -519,7 +519,7 @@ suite('FindController query options persistence', () => { 'var x = (3 * 5)', 'var y = (3 * 5)', 'var z = (3 * 5)', - ], { serviceCollection: serviceCollection, find: { autoFindInSelection: true, globalFindClipboard: false } }, (editor, cursor) => { + ], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, (editor, cursor) => { // clipboardState = ''; editor.setSelection(new Range(1, 1, 2, 1)); let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); @@ -542,7 +542,7 @@ suite('FindController query options persistence', () => { 'var x = (3 * 5)', 'var y = (3 * 5)', 'var z = (3 * 5)', - ], { serviceCollection: serviceCollection, find: { autoFindInSelection: true, globalFindClipboard: false } }, (editor, cursor) => { + ], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, (editor, cursor) => { // clipboardState = ''; editor.setSelection(new Range(1, 2, 1, 2)); let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); @@ -565,7 +565,7 @@ suite('FindController query options persistence', () => { 'var x = (3 * 5)', 'var y = (3 * 5)', 'var z = (3 * 5)', - ], { serviceCollection: serviceCollection, find: { autoFindInSelection: true, globalFindClipboard: false } }, (editor, cursor) => { + ], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, (editor, cursor) => { // clipboardState = ''; editor.setSelection(new Range(1, 2, 1, 3)); let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); @@ -582,4 +582,28 @@ suite('FindController query options persistence', () => { assert.deepEqual(findController.getState().searchScope, new Selection(1, 2, 1, 3)); }); }); + + + test('issue #27083: Find in selection when multiple lines are selected', () => { + withTestCodeEditor([ + 'var x = (3 * 5)', + 'var y = (3 * 5)', + 'var z = (3 * 5)', + ], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'multiline', globalFindClipboard: false } }, (editor, cursor) => { + // clipboardState = ''; + editor.setSelection(new Range(1, 6, 2, 1)); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + + findController.start({ + forceRevealReplace: false, + seedSearchStringFromSelection: false, + seedSearchStringFromGlobalClipboard: false, + shouldFocus: FindStartFocusAction.NoFocusChange, + shouldAnimate: false, + updateSearchScope: true + }); + + assert.deepEqual(findController.getState().searchScope, new Selection(1, 6, 2, 1)); + }); + }); }); diff --git a/src/vs/editor/contrib/find/test/replacePattern.test.ts b/src/vs/editor/contrib/find/test/replacePattern.test.ts index d7c8c391fd..d9d876ecb4 100644 --- a/src/vs/editor/contrib/find/test/replacePattern.test.ts +++ b/src/vs/editor/contrib/find/test/replacePattern.test.ts @@ -156,115 +156,58 @@ suite('Replace Pattern test', () => { }); test('buildReplaceStringWithCasePreserved test', () => { - let replacePattern = 'Def'; - let actual: string | string[] = 'abc'; + function assertReplace(target: string[], replaceString: string, expected: string): void { + let actual: string = ''; + actual = buildReplaceStringWithCasePreserved(target, replaceString); + assert.equal(actual, expected); + } - assert.equal(buildReplaceStringWithCasePreserved([actual], replacePattern), 'def'); - actual = 'Abc'; - assert.equal(buildReplaceStringWithCasePreserved([actual], replacePattern), 'Def'); - actual = 'ABC'; - assert.equal(buildReplaceStringWithCasePreserved([actual], replacePattern), 'DEF'); - - actual = ['abc', 'Abc']; - assert.equal(buildReplaceStringWithCasePreserved(actual, replacePattern), 'def'); - actual = ['Abc', 'abc']; - assert.equal(buildReplaceStringWithCasePreserved(actual, replacePattern), 'Def'); - actual = ['ABC', 'abc']; - assert.equal(buildReplaceStringWithCasePreserved(actual, replacePattern), 'DEF'); - - actual = ['AbC']; - assert.equal(buildReplaceStringWithCasePreserved(actual, replacePattern), 'Def'); - actual = ['aBC']; - assert.equal(buildReplaceStringWithCasePreserved(actual, replacePattern), 'Def'); - - actual = ['Foo-Bar']; - assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo-newbar'), 'Newfoo-Newbar'); - actual = ['Foo-Bar-Abc']; - assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo-newbar-newabc'), 'Newfoo-Newbar-Newabc'); - actual = ['Foo-Bar-abc']; - assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo-newbar'), 'Newfoo-newbar'); - actual = ['foo-Bar']; - assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo-newbar'), 'newfoo-Newbar'); - actual = ['foo-BAR']; - assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo-newbar'), 'newfoo-NEWBAR'); - - actual = ['Foo_Bar']; - assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo_newbar'), 'Newfoo_Newbar'); - actual = ['Foo_Bar_Abc']; - assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo_newbar_newabc'), 'Newfoo_Newbar_Newabc'); - actual = ['Foo_Bar_abc']; - assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo_newbar'), 'Newfoo_newbar'); - actual = ['Foo_Bar-abc']; - assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo_newbar-abc'), 'Newfoo_newbar-abc'); - actual = ['foo_Bar']; - assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo_newbar'), 'newfoo_Newbar'); - actual = ['Foo_BAR']; - assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo_newbar'), 'Newfoo_NEWBAR'); + assertReplace(['abc'], 'Def', 'def'); + assertReplace(['Abc'], 'Def', 'Def'); + assertReplace(['ABC'], 'Def', 'DEF'); + assertReplace(['abc', 'Abc'], 'Def', 'def'); + assertReplace(['Abc', 'abc'], 'Def', 'Def'); + assertReplace(['ABC', 'abc'], 'Def', 'DEF'); + assertReplace(['AbC'], 'Def', 'Def'); + assertReplace(['aBC'], 'Def', 'Def'); + assertReplace(['Foo-Bar'], 'newfoo-newbar', 'Newfoo-Newbar'); + assertReplace(['Foo-Bar-Abc'], 'newfoo-newbar-newabc', 'Newfoo-Newbar-Newabc'); + assertReplace(['Foo-Bar-abc'], 'newfoo-newbar', 'Newfoo-newbar'); + assertReplace(['foo-Bar'], 'newfoo-newbar', 'newfoo-Newbar'); + assertReplace(['foo-BAR'], 'newfoo-newbar', 'newfoo-NEWBAR'); + assertReplace(['Foo_Bar'], 'newfoo_newbar', 'Newfoo_Newbar'); + assertReplace(['Foo_Bar_Abc'], 'newfoo_newbar_newabc', 'Newfoo_Newbar_Newabc'); + assertReplace(['Foo_Bar_abc'], 'newfoo_newbar', 'Newfoo_newbar'); + assertReplace(['Foo_Bar-abc'], 'newfoo_newbar-abc', 'Newfoo_newbar-abc'); + assertReplace(['foo_Bar'], 'newfoo_newbar', 'newfoo_Newbar'); + assertReplace(['Foo_BAR'], 'newfoo_newbar', 'Newfoo_NEWBAR'); }); test('preserve case', () => { - let replacePattern = parseReplaceString('Def'); - let actual = replacePattern.buildReplaceString(['abc'], true); - assert.equal(actual, 'def'); - actual = replacePattern.buildReplaceString(['Abc'], true); - assert.equal(actual, 'Def'); - actual = replacePattern.buildReplaceString(['ABC'], true); - assert.equal(actual, 'DEF'); + function assertReplace(target: string[], replaceString: string, expected: string): void { + let replacePattern = parseReplaceString(replaceString); + let actual = replacePattern.buildReplaceString(target, true); + assert.equal(actual, expected); + } - actual = replacePattern.buildReplaceString(['abc', 'Abc'], true); - assert.equal(actual, 'def'); - actual = replacePattern.buildReplaceString(['Abc', 'abc'], true); - assert.equal(actual, 'Def'); - actual = replacePattern.buildReplaceString(['ABC', 'abc'], true); - assert.equal(actual, 'DEF'); - - actual = replacePattern.buildReplaceString(['AbC'], true); - assert.equal(actual, 'Def'); - actual = replacePattern.buildReplaceString(['aBC'], true); - assert.equal(actual, 'Def'); - - replacePattern = parseReplaceString('newfoo-newbar'); - actual = replacePattern.buildReplaceString(['Foo-Bar'], true); - assert.equal(actual, 'Newfoo-Newbar'); - - replacePattern = parseReplaceString('newfoo-newbar-newabc'); - actual = replacePattern.buildReplaceString(['Foo-Bar-Abc'], true); - assert.equal(actual, 'Newfoo-Newbar-Newabc'); - - replacePattern = parseReplaceString('newfoo-newbar'); - actual = replacePattern.buildReplaceString(['Foo-Bar-abc'], true); - assert.equal(actual, 'Newfoo-newbar'); - - replacePattern = parseReplaceString('newfoo-newbar'); - actual = replacePattern.buildReplaceString(['foo-Bar'], true); - assert.equal(actual, 'newfoo-Newbar'); - - replacePattern = parseReplaceString('newfoo-newbar'); - actual = replacePattern.buildReplaceString(['foo-BAR'], true); - assert.equal(actual, 'newfoo-NEWBAR'); - - replacePattern = parseReplaceString('newfoo_newbar'); - actual = replacePattern.buildReplaceString(['Foo_Bar'], true); - assert.equal(actual, 'Newfoo_Newbar'); - - replacePattern = parseReplaceString('newfoo_newbar_newabc'); - actual = replacePattern.buildReplaceString(['Foo_Bar_Abc'], true); - assert.equal(actual, 'Newfoo_Newbar_Newabc'); - - replacePattern = parseReplaceString('newfoo_newbar'); - actual = replacePattern.buildReplaceString(['Foo_Bar_abc'], true); - assert.equal(actual, 'Newfoo_newbar'); - - replacePattern = parseReplaceString('newfoo_newbar-abc'); - actual = replacePattern.buildReplaceString(['Foo_Bar-abc'], true); - assert.equal(actual, 'Newfoo_newbar-abc'); - - replacePattern = parseReplaceString('newfoo_newbar'); - actual = replacePattern.buildReplaceString(['foo_Bar'], true); - assert.equal(actual, 'newfoo_Newbar'); - - replacePattern = parseReplaceString('newfoo_newbar'); - actual = replacePattern.buildReplaceString(['foo_BAR'], true); - assert.equal(actual, 'newfoo_NEWBAR'); + assertReplace(['abc'], 'Def', 'def'); + assertReplace(['Abc'], 'Def', 'Def'); + assertReplace(['ABC'], 'Def', 'DEF'); + assertReplace(['abc', 'Abc'], 'Def', 'def'); + assertReplace(['Abc', 'abc'], 'Def', 'Def'); + assertReplace(['ABC', 'abc'], 'Def', 'DEF'); + assertReplace(['AbC'], 'Def', 'Def'); + assertReplace(['aBC'], 'Def', 'Def'); + assertReplace(['Foo-Bar'], 'newfoo-newbar', 'Newfoo-Newbar'); + assertReplace(['Foo-Bar-Abc'], 'newfoo-newbar-newabc', 'Newfoo-Newbar-Newabc'); + assertReplace(['Foo-Bar-abc'], 'newfoo-newbar', 'Newfoo-newbar'); + assertReplace(['foo-Bar'], 'newfoo-newbar', 'newfoo-Newbar'); + assertReplace(['foo-BAR'], 'newfoo-newbar', 'newfoo-NEWBAR'); + assertReplace(['Foo_Bar'], 'newfoo_newbar', 'Newfoo_Newbar'); + assertReplace(['Foo_Bar_Abc'], 'newfoo_newbar_newabc', 'Newfoo_Newbar_Newabc'); + assertReplace(['Foo_Bar_abc'], 'newfoo_newbar', 'Newfoo_newbar'); + assertReplace(['Foo_Bar-abc'], 'newfoo_newbar-abc', 'Newfoo_newbar-abc'); + assertReplace(['foo_Bar'], 'newfoo_newbar', 'newfoo_Newbar'); + assertReplace(['foo_BAR'], 'newfoo_newbar', 'newfoo_NEWBAR'); }); }); diff --git a/src/vs/editor/contrib/folding/folding.ts b/src/vs/editor/contrib/folding/folding.ts index fe119dab62..8241502b64 100644 --- a/src/vs/editor/contrib/folding/folding.ts +++ b/src/vs/editor/contrib/folding/folding.ts @@ -423,7 +423,7 @@ export class FoldingController extends Disposable implements IEditorContribution if (iconClicked || isCollapsed) { let toToggle = [region]; if (e.event.middleButton || e.event.shiftKey) { - toToggle.push(...foldingModel.getRegionsInside(region, r => r.isCollapsed === isCollapsed)); + toToggle.push(...foldingModel.getRegionsInside(region, (r: FoldingRegion) => r.isCollapsed === isCollapsed)); } foldingModel.toggleCollapseState(toToggle); this.reveal({ lineNumber, column: 1 }); diff --git a/src/vs/editor/contrib/folding/foldingModel.ts b/src/vs/editor/contrib/folding/foldingModel.ts index 0e8d76a170..e51f00c636 100644 --- a/src/vs/editor/contrib/folding/foldingModel.ts +++ b/src/vs/editor/contrib/folding/foldingModel.ts @@ -204,7 +204,7 @@ export class FoldingModel { return null; } - getRegionsInside(region: FoldingRegion | null, filter?: (r: FoldingRegion, level?: number) => boolean): FoldingRegion[] { + getRegionsInside(region: FoldingRegion | null, filter?: RegionFilter | RegionFilterWithLevel): FoldingRegion[] { let result: FoldingRegion[] = []; let index = region ? region.regionIndex + 1 : 0; let endLineNumber = region ? region.endLineNumber : Number.MAX_VALUE; @@ -229,7 +229,7 @@ export class FoldingModel { for (let i = index, len = this._regions.length; i < len; i++) { let current = this._regions.toRegion(i); if (this._regions.getStartLineNumber(i) < endLineNumber) { - if (!filter || filter(current)) { + if (!filter || (filter as RegionFilter)(current)) { result.push(current); } } else { @@ -242,6 +242,10 @@ export class FoldingModel { } +type RegionFilter = (r: FoldingRegion) => boolean; +type RegionFilterWithLevel = (r: FoldingRegion, level: number) => boolean; + + /** * Collapse or expand the regions at the given locations * @param levels The number of levels. Use 1 to only impact the regions at the location, use Number.MAX_VALUE for all levels. diff --git a/src/vs/editor/contrib/format/formatActions.ts b/src/vs/editor/contrib/format/formatActions.ts index 89a55d3741..6851ebeefa 100644 --- a/src/vs/editor/contrib/format/formatActions.ts +++ b/src/vs/editor/contrib/format/formatActions.ts @@ -219,7 +219,7 @@ class FormatDocumentAction extends EditorAction { linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_I }, weight: KeybindingWeight.EditorContrib }, - menuOpts: { + contextMenuOpts: { when: EditorContextKeys.hasDocumentFormattingProvider, group: '1_modification', order: 1.3 @@ -248,7 +248,7 @@ class FormatSelectionAction extends EditorAction { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_F), weight: KeybindingWeight.EditorContrib }, - menuOpts: { + contextMenuOpts: { when: ContextKeyExpr.and(EditorContextKeys.hasDocumentSelectionFormattingProvider, EditorContextKeys.hasNonEmptySelection), group: '1_modification', order: 1.31 diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts b/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts deleted file mode 100644 index c463125622..0000000000 --- a/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts +++ /dev/null @@ -1,505 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { alert } from 'vs/base/browser/ui/aria/aria'; -import { createCancelablePromise, raceCancellation } from 'vs/base/common/async'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import * as platform from 'vs/base/common/platform'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorAction, IActionOptions, registerEditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import * as corePosition from 'vs/editor/common/core/position'; -import { Range, IRange } from 'vs/editor/common/core/range'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { ITextModel, IWordAtPosition } from 'vs/editor/common/model'; -import { LocationLink, Location, isLocationLink } from 'vs/editor/common/modes'; -import { MessageController } from 'vs/editor/contrib/message/messageController'; -import { PeekContext } from 'vs/editor/contrib/referenceSearch/peekViewWidget'; -import { ReferencesController } from 'vs/editor/contrib/referenceSearch/referencesController'; -import { ReferencesModel } from 'vs/editor/contrib/referenceSearch/referencesModel'; -import * as nls from 'vs/nls'; -import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IEditorProgressService } from 'vs/platform/progress/common/progress'; -import { getDefinitionsAtPosition, getImplementationsAtPosition, getTypeDefinitionsAtPosition, getDeclarationsAtPosition } from './goToDefinition'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { EditorStateCancellationTokenSource, CodeEditorStateFlag } from 'vs/editor/browser/core/editorState'; -import { ISymbolNavigationService } from 'vs/editor/contrib/goToDefinition/goToDefinitionResultsNavigation'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; -import { isEqual } from 'vs/base/common/resources'; - -export class DefinitionActionConfig { - - constructor( - public readonly openToSide = false, - public readonly openInPeek = false, - public readonly filterCurrent = true, - public readonly showMessage = true, - ) { - // - } -} - -export class DefinitionAction extends EditorAction { - - private readonly _configuration: DefinitionActionConfig; - - constructor(configuration: DefinitionActionConfig, opts: IActionOptions) { - super(opts); - this._configuration = configuration; - } - - public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { - if (!editor.hasModel()) { - return Promise.resolve(undefined); - } - const notificationService = accessor.get(INotificationService); - const editorService = accessor.get(ICodeEditorService); - const progressService = accessor.get(IEditorProgressService); - const symbolNavService = accessor.get(ISymbolNavigationService); - - const model = editor.getModel(); - const pos = editor.getPosition(); - - const cts = new EditorStateCancellationTokenSource(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position); - - const definitionPromise = raceCancellation(this._getTargetLocationForPosition(model, pos, cts.token), cts.token).then(async references => { - - if (!references || model.isDisposed()) { - // new model, no more model - return; - } - - // * remove falsy references - // * find reference at the current pos - let idxOfCurrent = -1; - const result: LocationLink[] = []; - for (const reference of references) { - if (!reference || !reference.range) { - continue; - } - const newLen = result.push(reference); - if (this._configuration.filterCurrent - && isEqual(reference.uri, model.uri) - && Range.containsPosition(reference.range, pos) - && idxOfCurrent === -1 - ) { - idxOfCurrent = newLen - 1; - } - } - - if (result.length === 0) { - // no result -> show message - if (this._configuration.showMessage) { - const info = model.getWordAtPosition(pos); - MessageController.get(editor).showMessage(this._getNoResultFoundMessage(info), pos); - } - } else if (result.length === 1 && idxOfCurrent !== -1) { - // only the position at which we are -> adjust selection - let [current] = result; - return this._openReference(editor, editorService, current, false).then(() => undefined); - - } else { - // handle multile results - return this._onResult(editorService, symbolNavService, editor, new ReferencesModel(result)); - } - - }, (err) => { - // report an error - notificationService.error(err); - }).finally(() => { - cts.dispose(); - }); - - progressService.showWhile(definitionPromise, 250); - return definitionPromise; - } - - protected _getTargetLocationForPosition(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { - return getDefinitionsAtPosition(model, position, token); - } - - protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { - return info && info.word - ? nls.localize('noResultWord', "No definition found for '{0}'", info.word) - : nls.localize('generic.noResults', "No definition found"); - } - - protected _getMetaTitle(model: ReferencesModel): string { - return model.references.length > 1 ? nls.localize('meta.title', " – {0} definitions", model.references.length) : ''; - } - - private async _onResult(editorService: ICodeEditorService, symbolNavService: ISymbolNavigationService, editor: ICodeEditor, model: ReferencesModel): Promise { - - const msg = model.getAriaMessage(); - alert(msg); - - const gotoLocation = editor.getOption(EditorOption.gotoLocation); - if (this._configuration.openInPeek || (gotoLocation.multiple === 'peek' && model.references.length > 1)) { - this._openInPeek(editorService, editor, model); - - } else if (editor.hasModel()) { - const next = model.firstReference(); - if (!next) { - return; - } - const targetEditor = await this._openReference(editor, editorService, next, this._configuration.openToSide); - if (targetEditor && model.references.length > 1 && gotoLocation.multiple === 'gotoAndPeek') { - this._openInPeek(editorService, targetEditor, model); - } else { - model.dispose(); - } - - // keep remaining locations around when using - // 'goto'-mode - if (gotoLocation.multiple === 'goto') { - symbolNavService.put(next); - } - } - } - - private _openReference(editor: ICodeEditor, editorService: ICodeEditorService, reference: Location | LocationLink, sideBySide: boolean): Promise { - // range is the target-selection-range when we have one - // and the the fallback is the 'full' range - let range: IRange | undefined = undefined; - if (isLocationLink(reference)) { - range = reference.targetSelectionRange; - } - if (!range) { - range = reference.range; - } - - return editorService.openCodeEditor({ - resource: reference.uri, - options: { - selection: Range.collapseToStart(range), - revealInCenterIfOutsideViewport: true - } - }, editor, sideBySide); - } - - private _openInPeek(editorService: ICodeEditorService, target: ICodeEditor, model: ReferencesModel) { - let controller = ReferencesController.get(target); - if (controller && target.hasModel()) { - controller.toggleWidget(target.getSelection(), createCancelablePromise(_ => Promise.resolve(model)), { - getMetaTitle: (model) => { - return this._getMetaTitle(model); - }, - onGoto: (reference) => { - controller.closeWidget(); - return this._openReference(target, editorService, reference, false); - } - }); - } else { - model.dispose(); - } - } -} - -const goToDefinitionKb = platform.isWeb - ? KeyMod.CtrlCmd | KeyCode.F12 - : KeyCode.F12; - -export class GoToDefinitionAction extends DefinitionAction { - - static readonly id = 'editor.action.revealDefinition'; - - constructor() { - super(new DefinitionActionConfig(), { - id: GoToDefinitionAction.id, - label: nls.localize('actions.goToDecl.label', "Go to Definition"), - alias: 'Go to Definition', - precondition: ContextKeyExpr.and( - EditorContextKeys.hasDefinitionProvider, - EditorContextKeys.isInEmbeddedEditor.toNegated()), - kbOpts: { - kbExpr: EditorContextKeys.editorTextFocus, - primary: goToDefinitionKb, - weight: KeybindingWeight.EditorContrib - }, - menuOpts: { - group: 'navigation', - order: 1.1 - } - }); - CommandsRegistry.registerCommandAlias('editor.action.goToDeclaration', GoToDefinitionAction.id); - } -} - -export class OpenDefinitionToSideAction extends DefinitionAction { - - static readonly id = 'editor.action.revealDefinitionAside'; - - constructor() { - super(new DefinitionActionConfig(true), { - id: OpenDefinitionToSideAction.id, - label: nls.localize('actions.goToDeclToSide.label', "Open Definition to the Side"), - alias: 'Open Definition to the Side', - precondition: ContextKeyExpr.and( - EditorContextKeys.hasDefinitionProvider, - EditorContextKeys.isInEmbeddedEditor.toNegated()), - kbOpts: { - kbExpr: EditorContextKeys.editorTextFocus, - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, goToDefinitionKb), - weight: KeybindingWeight.EditorContrib - } - }); - CommandsRegistry.registerCommandAlias('editor.action.openDeclarationToTheSide', OpenDefinitionToSideAction.id); - } -} - -export class PeekDefinitionAction extends DefinitionAction { - - static readonly id = 'editor.action.peekDefinition'; - - constructor() { - super(new DefinitionActionConfig(undefined, true, false), { - id: PeekDefinitionAction.id, - label: nls.localize('actions.previewDecl.label', "Peek Definition"), - alias: 'Peek Definition', - precondition: ContextKeyExpr.and( - EditorContextKeys.hasDefinitionProvider, - PeekContext.notInPeekEditor, - EditorContextKeys.isInEmbeddedEditor.toNegated()), - kbOpts: { - kbExpr: EditorContextKeys.editorTextFocus, - primary: KeyMod.Alt | KeyCode.F12, - linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.F10 }, - weight: KeybindingWeight.EditorContrib - }, - menuOpts: { - group: 'navigation', - order: 1.2 - } - }); - CommandsRegistry.registerCommandAlias('editor.action.previewDeclaration', PeekDefinitionAction.id); - } -} - -export class DeclarationAction extends DefinitionAction { - - protected _getTargetLocationForPosition(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { - return getDeclarationsAtPosition(model, position, token); - } - - protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { - return info && info.word - ? nls.localize('decl.noResultWord', "No declaration found for '{0}'", info.word) - : nls.localize('decl.generic.noResults', "No declaration found"); - } - - protected _getMetaTitle(model: ReferencesModel): string { - return model.references.length > 1 ? nls.localize('decl.meta.title', " – {0} declarations", model.references.length) : ''; - } -} - -export class GoToDeclarationAction extends DeclarationAction { - - static readonly id = 'editor.action.revealDeclaration'; - - constructor() { - super(new DefinitionActionConfig(), { - id: GoToDeclarationAction.id, - label: nls.localize('actions.goToDeclaration.label', "Go to Declaration"), - alias: 'Go to Declaration', - precondition: ContextKeyExpr.and( - EditorContextKeys.hasDeclarationProvider, - EditorContextKeys.isInEmbeddedEditor.toNegated()), - menuOpts: { - group: 'navigation', - order: 1.3 - } - }); - } - - protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { - return info && info.word - ? nls.localize('decl.noResultWord', "No declaration found for '{0}'", info.word) - : nls.localize('decl.generic.noResults', "No declaration found"); - } - - protected _getMetaTitle(model: ReferencesModel): string { - return model.references.length > 1 ? nls.localize('decl.meta.title', " – {0} declarations", model.references.length) : ''; - } -} - -export class PeekDeclarationAction extends DeclarationAction { - constructor() { - super(new DefinitionActionConfig(undefined, true, false), { - id: 'editor.action.peekDeclaration', - label: nls.localize('actions.peekDecl.label', "Peek Declaration"), - alias: 'Peek Declaration', - precondition: ContextKeyExpr.and( - EditorContextKeys.hasDeclarationProvider, - PeekContext.notInPeekEditor, - EditorContextKeys.isInEmbeddedEditor.toNegated()), - menuOpts: { - group: 'navigation', - order: 1.31 - } - }); - } -} - -export class ImplementationAction extends DefinitionAction { - protected _getTargetLocationForPosition(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { - return getImplementationsAtPosition(model, position, token); - } - - protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { - return info && info.word - ? nls.localize('goToImplementation.noResultWord', "No implementation found for '{0}'", info.word) - : nls.localize('goToImplementation.generic.noResults', "No implementation found"); - } - - protected _getMetaTitle(model: ReferencesModel): string { - return model.references.length > 1 ? nls.localize('meta.implementations.title', " – {0} implementations", model.references.length) : ''; - } -} - -export class GoToImplementationAction extends ImplementationAction { - - public static readonly ID = 'editor.action.goToImplementation'; - - constructor() { - super(new DefinitionActionConfig(), { - id: GoToImplementationAction.ID, - label: nls.localize('actions.goToImplementation.label', "Go to Implementation"), - alias: 'Go to Implementation', - precondition: ContextKeyExpr.and( - EditorContextKeys.hasImplementationProvider, - EditorContextKeys.isInEmbeddedEditor.toNegated()), - kbOpts: { - kbExpr: EditorContextKeys.editorTextFocus, - primary: KeyMod.CtrlCmd | KeyCode.F12, - weight: KeybindingWeight.EditorContrib - } - }); - } -} - -export class PeekImplementationAction extends ImplementationAction { - - public static readonly ID = 'editor.action.peekImplementation'; - - constructor() { - super(new DefinitionActionConfig(false, true, false), { - id: PeekImplementationAction.ID, - label: nls.localize('actions.peekImplementation.label', "Peek Implementation"), - alias: 'Peek Implementation', - precondition: ContextKeyExpr.and( - EditorContextKeys.hasImplementationProvider, - EditorContextKeys.isInEmbeddedEditor.toNegated()), - kbOpts: { - kbExpr: EditorContextKeys.editorTextFocus, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.F12, - weight: KeybindingWeight.EditorContrib - } - }); - } -} - -export class TypeDefinitionAction extends DefinitionAction { - protected _getTargetLocationForPosition(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { - return getTypeDefinitionsAtPosition(model, position, token); - } - - protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { - return info && info.word - ? nls.localize('goToTypeDefinition.noResultWord', "No type definition found for '{0}'", info.word) - : nls.localize('goToTypeDefinition.generic.noResults', "No type definition found"); - } - - protected _getMetaTitle(model: ReferencesModel): string { - return model.references.length > 1 ? nls.localize('meta.typeDefinitions.title', " – {0} type definitions", model.references.length) : ''; - } -} - -export class GoToTypeDefinitionAction extends TypeDefinitionAction { - - public static readonly ID = 'editor.action.goToTypeDefinition'; - - constructor() { - super(new DefinitionActionConfig(), { - id: GoToTypeDefinitionAction.ID, - label: nls.localize('actions.goToTypeDefinition.label', "Go to Type Definition"), - alias: 'Go to Type Definition', - precondition: ContextKeyExpr.and( - EditorContextKeys.hasTypeDefinitionProvider, - EditorContextKeys.isInEmbeddedEditor.toNegated()), - kbOpts: { - kbExpr: EditorContextKeys.editorTextFocus, - primary: 0, - weight: KeybindingWeight.EditorContrib - }, - menuOpts: { - group: 'navigation', - order: 1.4 - } - }); - } -} - -export class PeekTypeDefinitionAction extends TypeDefinitionAction { - - public static readonly ID = 'editor.action.peekTypeDefinition'; - - constructor() { - super(new DefinitionActionConfig(false, true, false), { - id: PeekTypeDefinitionAction.ID, - label: nls.localize('actions.peekTypeDefinition.label', "Peek Type Definition"), - alias: 'Peek Type Definition', - precondition: ContextKeyExpr.and( - EditorContextKeys.hasTypeDefinitionProvider, - EditorContextKeys.isInEmbeddedEditor.toNegated()), - kbOpts: { - kbExpr: EditorContextKeys.editorTextFocus, - primary: 0, - weight: KeybindingWeight.EditorContrib - } - }); - } -} - -registerEditorAction(GoToDefinitionAction); -registerEditorAction(OpenDefinitionToSideAction); -registerEditorAction(PeekDefinitionAction); -registerEditorAction(GoToDeclarationAction); -registerEditorAction(PeekDeclarationAction); -registerEditorAction(GoToImplementationAction); -registerEditorAction(PeekImplementationAction); -registerEditorAction(GoToTypeDefinitionAction); -registerEditorAction(PeekTypeDefinitionAction); - -// Go to menu -MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { - group: '4_symbol_nav', - command: { - id: 'editor.action.goToDeclaration', - title: nls.localize({ key: 'miGotoDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Definition") - }, - order: 2 -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { - group: '4_symbol_nav', - command: { - id: 'editor.action.goToTypeDefinition', - title: nls.localize({ key: 'miGotoTypeDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Type Definition") - }, - order: 3 -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { - group: '4_symbol_nav', - command: { - id: 'editor.action.goToImplementation', - title: nls.localize({ key: 'miGotoImplementation', comment: ['&& denotes a mnemonic'] }, "Go to &&Implementation") - }, - order: 4 -}); diff --git a/src/vs/editor/contrib/gotoError/gotoError.ts b/src/vs/editor/contrib/gotoError/gotoError.ts index a30ebdc174..60c3621d37 100644 --- a/src/vs/editor/contrib/gotoError/gotoError.ts +++ b/src/vs/editor/contrib/gotoError/gotoError.ts @@ -43,7 +43,7 @@ class MarkerModel { this._markers = []; this._nextIdx = -1; this._ignoreSelectionChange = false; - this._onCurrentMarkerChanged = new Emitter(); + this._onCurrentMarkerChanged = new Emitter(); this._onMarkerSetChanged = new Emitter(); this.setMarkers(markers); @@ -245,7 +245,7 @@ export class MarkerController implements editorCommon.IEditorContribution { ]; this._widget = new MarkerNavigationWidget(this._editor, actions, this._themeService); this._widgetVisible.set(true); - this._widget.onDidClose(() => this._cleanUp(), this, this._disposeOnClose); + this._widget.onDidClose(() => this.closeMarkersNavigation(), this, this._disposeOnClose); this._disposeOnClose.add(this._model); this._disposeOnClose.add(this._widget); diff --git a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts index 4049d8a199..f71872e33a 100644 --- a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts +++ b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts @@ -20,11 +20,10 @@ import { ScrollType } from 'vs/editor/common/editorCommon'; import { getBaseLabel, getPathLabel } from 'vs/base/common/labels'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { Event, Emitter } from 'vs/base/common/event'; -import { PeekViewWidget } from 'vs/editor/contrib/referenceSearch/peekViewWidget'; +import { PeekViewWidget, peekViewTitleForeground, peekViewTitleInfoForeground } from 'vs/editor/contrib/peekView/peekView'; import { basename } from 'vs/base/common/resources'; import { IAction } from 'vs/base/common/actions'; import { IActionBarOptions, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; -import { peekViewTitleForeground, peekViewTitleInfoForeground } from 'vs/editor/contrib/referenceSearch/referencesWidget'; import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; diff --git a/src/vs/editor/contrib/gotoError/media/gotoErrorWidget.css b/src/vs/editor/contrib/gotoError/media/gotoErrorWidget.css index 1a0be0be1a..016634b514 100644 --- a/src/vs/editor/contrib/gotoError/media/gotoErrorWidget.css +++ b/src/vs/editor/contrib/gotoError/media/gotoErrorWidget.css @@ -29,8 +29,9 @@ .monaco-editor .marker-widget .descriptioncontainer { position: absolute; white-space: pre; - -webkit-user-select: text; user-select: text; + -webkit-user-select: text; + -ms-user-select: text; padding: 8px 12px 0px 20px; } diff --git a/src/vs/editor/contrib/gotoSymbol/goToCommands.ts b/src/vs/editor/contrib/gotoSymbol/goToCommands.ts new file mode 100644 index 0000000000..f25cdaf89f --- /dev/null +++ b/src/vs/editor/contrib/gotoSymbol/goToCommands.ts @@ -0,0 +1,781 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { alert } from 'vs/base/browser/ui/aria/aria'; +import { createCancelablePromise, raceCancellation } from 'vs/base/common/async'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { isWeb } from 'vs/base/common/platform'; +import { ICodeEditor, isCodeEditor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { EditorAction, IActionOptions, registerEditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import * as corePosition from 'vs/editor/common/core/position'; +import { Range, IRange } from 'vs/editor/common/core/range'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { ITextModel, IWordAtPosition } from 'vs/editor/common/model'; +import { LocationLink, Location, isLocationLink } from 'vs/editor/common/modes'; +import { MessageController } from 'vs/editor/contrib/message/messageController'; +import { PeekContext } from 'vs/editor/contrib/peekView/peekView'; +import { ReferencesController } from 'vs/editor/contrib/gotoSymbol/peek/referencesController'; +import { ReferencesModel } from 'vs/editor/contrib/gotoSymbol/referencesModel'; +import * as nls from 'vs/nls'; +import { MenuId, MenuRegistry, ISubmenuItem } from 'vs/platform/actions/common/actions'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IEditorProgressService } from 'vs/platform/progress/common/progress'; +import { getDefinitionsAtPosition, getImplementationsAtPosition, getTypeDefinitionsAtPosition, getDeclarationsAtPosition, getReferencesAtPosition } from './goToSymbol'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { EditorStateCancellationTokenSource, CodeEditorStateFlag } from 'vs/editor/browser/core/editorState'; +import { ISymbolNavigationService } from 'vs/editor/contrib/gotoSymbol/symbolNavigation'; +import { EditorOption, GoToLocationValues } from 'vs/editor/common/config/editorOptions'; +import { isStandalone } from 'vs/base/browser/browser'; +import { URI } from 'vs/base/common/uri'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ScrollType, IEditorAction } from 'vs/editor/common/editorCommon'; +import { assertType } from 'vs/base/common/types'; + + +MenuRegistry.appendMenuItem(MenuId.EditorContext, { + submenu: MenuId.EditorContextPeek, + title: nls.localize('peek.submenu', "Peek"), + group: 'navigation', + order: 100 +}); + +export interface SymbolNavigationActionConfig { + openToSide: boolean; + openInPeek: boolean; + muteMessage: boolean; +} + +abstract class SymbolNavigationAction extends EditorAction { + + private readonly _configuration: SymbolNavigationActionConfig; + + constructor(configuration: SymbolNavigationActionConfig, opts: IActionOptions) { + super(opts); + this._configuration = configuration; + } + + run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { + if (!editor.hasModel()) { + return Promise.resolve(undefined); + } + const notificationService = accessor.get(INotificationService); + const editorService = accessor.get(ICodeEditorService); + const progressService = accessor.get(IEditorProgressService); + const symbolNavService = accessor.get(ISymbolNavigationService); + + const model = editor.getModel(); + const pos = editor.getPosition(); + + const cts = new EditorStateCancellationTokenSource(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position); + + const promise = raceCancellation(this._getLocationModel(model, pos, cts.token), cts.token).then(async references => { + + if (!references || cts.token.isCancellationRequested) { + return; + } + + alert(references.ariaMessage); + + let altAction: IEditorAction | null | undefined; + if (references.referenceAt(model.uri, pos)) { + const altActionId = this._getAlternativeCommand(editor); + if (altActionId !== this.id) { + altAction = editor.getAction(altActionId); + } + } + + const referenceCount = references.references.length; + + if (referenceCount === 0) { + // no result -> show message + if (!this._configuration.muteMessage) { + const info = model.getWordAtPosition(pos); + MessageController.get(editor).showMessage(this._getNoResultFoundMessage(info), pos); + } + } else if (referenceCount === 1 && altAction) { + // already at the only result, run alternative + altAction.run(); + + } else { + // normal results handling + return this._onResult(editorService, symbolNavService, editor, references); + } + + }, (err) => { + // report an error + notificationService.error(err); + }).finally(() => { + cts.dispose(); + }); + + progressService.showWhile(promise, 250); + return promise; + } + + protected abstract _getLocationModel(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise; + + protected abstract _getNoResultFoundMessage(info: IWordAtPosition | null): string; + + protected abstract _getAlternativeCommand(editor: IActiveCodeEditor): string; + + protected abstract _getGoToPreference(editor: IActiveCodeEditor): GoToLocationValues; + + private async _onResult(editorService: ICodeEditorService, symbolNavService: ISymbolNavigationService, editor: IActiveCodeEditor, model: ReferencesModel): Promise { + + const gotoLocation = this._getGoToPreference(editor); + if (this._configuration.openInPeek || (gotoLocation === 'peek' && model.references.length > 1)) { + this._openInPeek(editor, model); + + } else { + const next = model.firstReference()!; + const peek = model.references.length > 1 && gotoLocation === 'gotoAndPeek'; + const targetEditor = await this._openReference(editor, editorService, next, this._configuration.openToSide, !peek); + if (peek && targetEditor) { + this._openInPeek(targetEditor, model); + } else { + model.dispose(); + } + + // keep remaining locations around when using + // 'goto'-mode + if (gotoLocation === 'goto') { + symbolNavService.put(next); + } + } + } + + private async _openReference(editor: ICodeEditor, editorService: ICodeEditorService, reference: Location | LocationLink, sideBySide: boolean, highlight: boolean): Promise { + // range is the target-selection-range when we have one + // and the fallback is the 'full' range + let range: IRange | undefined = undefined; + if (isLocationLink(reference)) { + range = reference.targetSelectionRange; + } + if (!range) { + range = reference.range; + } + + const targetEditor = await editorService.openCodeEditor({ + resource: reference.uri, + options: { + selection: Range.collapseToStart(range), + revealInCenterIfOutsideViewport: true + } + }, editor, sideBySide); + + if (!targetEditor) { + return undefined; + } + + if (highlight) { + const modelNow = targetEditor.getModel(); + const ids = targetEditor.deltaDecorations([], [{ range, options: { className: 'symbolHighlight' } }]); + setTimeout(() => { + if (targetEditor.getModel() === modelNow) { + targetEditor.deltaDecorations(ids, []); + } + }, 350); + } + + return targetEditor; + } + + private _openInPeek(target: ICodeEditor, model: ReferencesModel) { + let controller = ReferencesController.get(target); + if (controller && target.hasModel()) { + controller.toggleWidget(target.getSelection(), createCancelablePromise(_ => Promise.resolve(model)), this._configuration.openInPeek); + } else { + model.dispose(); + } + } +} + +//#region --- DEFINITION + +export class DefinitionAction extends SymbolNavigationAction { + + protected async _getLocationModel(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { + return new ReferencesModel(await getDefinitionsAtPosition(model, position, token), nls.localize('def.title', 'Definitions')); + } + + protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { + return info && info.word + ? nls.localize('noResultWord', "No definition found for '{0}'", info.word) + : nls.localize('generic.noResults', "No definition found"); + } + + protected _getAlternativeCommand(editor: IActiveCodeEditor): string { + return editor.getOption(EditorOption.gotoLocation).alternativeDefinitionCommand; + } + + protected _getGoToPreference(editor: IActiveCodeEditor): GoToLocationValues { + return editor.getOption(EditorOption.gotoLocation).multipleDefinitions; + } +} + +const goToDefinitionKb = isWeb && !isStandalone + ? KeyMod.CtrlCmd | KeyCode.F12 + : KeyCode.F12; + +registerEditorAction(class GoToDefinitionAction extends DefinitionAction { + + static readonly id = 'editor.action.revealDefinition'; + + constructor() { + super({ + openToSide: false, + openInPeek: false, + muteMessage: false + }, { + id: GoToDefinitionAction.id, + label: nls.localize('actions.goToDecl.label', "Go to Definition"), + alias: 'Go to Definition', + precondition: ContextKeyExpr.and( + EditorContextKeys.hasDefinitionProvider, + EditorContextKeys.isInEmbeddedEditor.toNegated()), + kbOpts: { + kbExpr: EditorContextKeys.editorTextFocus, + primary: goToDefinitionKb, + weight: KeybindingWeight.EditorContrib + }, + contextMenuOpts: { + group: 'navigation', + order: 1.1 + }, + /*menuOpts: { {{SQL CARBON EDIT}} remove entry + menuId: MenuId.MenubarGoMenu, + group: '4_symbol_nav', + order: 2, + title: nls.localize({ key: 'miGotoDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Definition") + }*/ + }); + CommandsRegistry.registerCommandAlias('editor.action.goToDeclaration', GoToDefinitionAction.id); + } +}); + +registerEditorAction(class OpenDefinitionToSideAction extends DefinitionAction { + + static readonly id = 'editor.action.revealDefinitionAside'; + + constructor() { + super({ + openToSide: true, + openInPeek: false, + muteMessage: false + }, { + id: OpenDefinitionToSideAction.id, + label: nls.localize('actions.goToDeclToSide.label', "Open Definition to the Side"), + alias: 'Open Definition to the Side', + precondition: ContextKeyExpr.and( + EditorContextKeys.hasDefinitionProvider, + EditorContextKeys.isInEmbeddedEditor.toNegated()), + kbOpts: { + kbExpr: EditorContextKeys.editorTextFocus, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, goToDefinitionKb), + weight: KeybindingWeight.EditorContrib + } + }); + CommandsRegistry.registerCommandAlias('editor.action.openDeclarationToTheSide', OpenDefinitionToSideAction.id); + } +}); + +registerEditorAction(class PeekDefinitionAction extends DefinitionAction { + + static readonly id = 'editor.action.peekDefinition'; + + constructor() { + super({ + openToSide: false, + openInPeek: true, + muteMessage: false + }, { + id: PeekDefinitionAction.id, + label: nls.localize('actions.previewDecl.label', "Peek Definition"), + alias: 'Peek Definition', + precondition: ContextKeyExpr.and( + EditorContextKeys.hasDefinitionProvider, + PeekContext.notInPeekEditor, + EditorContextKeys.isInEmbeddedEditor.toNegated() + ), + kbOpts: { + kbExpr: EditorContextKeys.editorTextFocus, + primary: KeyMod.Alt | KeyCode.F12, + linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.F10 }, + weight: KeybindingWeight.EditorContrib + }, + contextMenuOpts: { + menuId: MenuId.EditorContextPeek, + group: 'peek', + order: 2 + } + }); + CommandsRegistry.registerCommandAlias('editor.action.previewDeclaration', PeekDefinitionAction.id); + } +}); + +//#endregion + +//#region --- DECLARATION + +class DeclarationAction extends SymbolNavigationAction { + + protected async _getLocationModel(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { + return new ReferencesModel(await getDeclarationsAtPosition(model, position, token), nls.localize('decl.title', 'Declarations')); + } + + protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { + return info && info.word + ? nls.localize('decl.noResultWord', "No declaration found for '{0}'", info.word) + : nls.localize('decl.generic.noResults', "No declaration found"); + } + + protected _getAlternativeCommand(editor: IActiveCodeEditor): string { + return editor.getOption(EditorOption.gotoLocation).alternativeDeclarationCommand; + } + + protected _getGoToPreference(editor: IActiveCodeEditor): GoToLocationValues { + return editor.getOption(EditorOption.gotoLocation).multipleDeclarations; + } +} + +registerEditorAction(class GoToDeclarationAction extends DeclarationAction { + + static readonly id = 'editor.action.revealDeclaration'; + + constructor() { + super({ + openToSide: false, + openInPeek: false, + muteMessage: false + }, { + id: GoToDeclarationAction.id, + label: nls.localize('actions.goToDeclaration.label', "Go to Declaration"), + alias: 'Go to Declaration', + precondition: ContextKeyExpr.and( + EditorContextKeys.hasDeclarationProvider, + EditorContextKeys.isInEmbeddedEditor.toNegated() + ), + contextMenuOpts: { + group: 'navigation', + order: 1.3 + }, + /*menuOpts: { {{SQL CARBON EDIT}} remove entry + menuId: MenuId.MenubarGoMenu, + group: '4_symbol_nav', + order: 3, + title: nls.localize({ key: 'miGotoDeclaration', comment: ['&& denotes a mnemonic'] }, "Go to &&Declaration") + },*/ + }); + } + + protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { + return info && info.word + ? nls.localize('decl.noResultWord', "No declaration found for '{0}'", info.word) + : nls.localize('decl.generic.noResults', "No declaration found"); + } +}); + +registerEditorAction(class PeekDeclarationAction extends DeclarationAction { + constructor() { + super({ + openToSide: false, + openInPeek: true, + muteMessage: false + }, { + id: 'editor.action.peekDeclaration', + label: nls.localize('actions.peekDecl.label', "Peek Declaration"), + alias: 'Peek Declaration', + precondition: ContextKeyExpr.and( + EditorContextKeys.hasDeclarationProvider, + PeekContext.notInPeekEditor, + EditorContextKeys.isInEmbeddedEditor.toNegated() + ), + contextMenuOpts: { + menuId: MenuId.EditorContextPeek, + group: 'peek', + order: 3 + } + }); + } +}); + +//#endregion + +//#region --- TYPE DEFINITION + +class TypeDefinitionAction extends SymbolNavigationAction { + + protected async _getLocationModel(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { + return new ReferencesModel(await getTypeDefinitionsAtPosition(model, position, token), nls.localize('typedef.title', 'Type Definitions')); + } + + protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { + return info && info.word + ? nls.localize('goToTypeDefinition.noResultWord', "No type definition found for '{0}'", info.word) + : nls.localize('goToTypeDefinition.generic.noResults', "No type definition found"); + } + + protected _getAlternativeCommand(editor: IActiveCodeEditor): string { + return editor.getOption(EditorOption.gotoLocation).alternativeTypeDefinitionCommand; + } + + protected _getGoToPreference(editor: IActiveCodeEditor): GoToLocationValues { + return editor.getOption(EditorOption.gotoLocation).multipleTypeDefinitions; + } +} + +registerEditorAction(class GoToTypeDefinitionAction extends TypeDefinitionAction { + + public static readonly ID = 'editor.action.goToTypeDefinition'; + + constructor() { + super({ + openToSide: false, + openInPeek: false, + muteMessage: false + }, { + id: GoToTypeDefinitionAction.ID, + label: nls.localize('actions.goToTypeDefinition.label', "Go to Type Definition"), + alias: 'Go to Type Definition', + precondition: ContextKeyExpr.and( + EditorContextKeys.hasTypeDefinitionProvider, + EditorContextKeys.isInEmbeddedEditor.toNegated()), + kbOpts: { + kbExpr: EditorContextKeys.editorTextFocus, + primary: 0, + weight: KeybindingWeight.EditorContrib + }, + contextMenuOpts: { + group: 'navigation', + order: 1.4 + }, + /*menuOpts: { {{SQL CARBON EDIT}} remove entry + menuId: MenuId.MenubarGoMenu, + group: '4_symbol_nav', + order: 3, + title: nls.localize({ key: 'miGotoTypeDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Type Definition") + }*/ + }); + } +}); + +registerEditorAction(class PeekTypeDefinitionAction extends TypeDefinitionAction { + + public static readonly ID = 'editor.action.peekTypeDefinition'; + + constructor() { + super({ + openToSide: false, + openInPeek: true, + muteMessage: false + }, { + id: PeekTypeDefinitionAction.ID, + label: nls.localize('actions.peekTypeDefinition.label', "Peek Type Definition"), + alias: 'Peek Type Definition', + precondition: ContextKeyExpr.and( + EditorContextKeys.hasTypeDefinitionProvider, + PeekContext.notInPeekEditor, + EditorContextKeys.isInEmbeddedEditor.toNegated() + ), + contextMenuOpts: { + menuId: MenuId.EditorContextPeek, + group: 'peek', + order: 4 + } + }); + } +}); + +//#endregion + +//#region --- IMPLEMENTATION + +class ImplementationAction extends SymbolNavigationAction { + + protected async _getLocationModel(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { + return new ReferencesModel(await getImplementationsAtPosition(model, position, token), nls.localize('impl.title', 'Implementations')); + } + + protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { + return info && info.word + ? nls.localize('goToImplementation.noResultWord', "No implementation found for '{0}'", info.word) + : nls.localize('goToImplementation.generic.noResults', "No implementation found"); + } + + protected _getAlternativeCommand(editor: IActiveCodeEditor): string { + return editor.getOption(EditorOption.gotoLocation).alternativeImplementationCommand; + } + + protected _getGoToPreference(editor: IActiveCodeEditor): GoToLocationValues { + return editor.getOption(EditorOption.gotoLocation).multipleImplementations; + } +} + +registerEditorAction(class GoToImplementationAction extends ImplementationAction { + + public static readonly ID = 'editor.action.goToImplementation'; + + constructor() { + super({ + openToSide: false, + openInPeek: false, + muteMessage: false + }, { + id: GoToImplementationAction.ID, + label: nls.localize('actions.goToImplementation.label', "Go to Implementations"), + alias: 'Go to Implementations', + precondition: ContextKeyExpr.and( + EditorContextKeys.hasImplementationProvider, + EditorContextKeys.isInEmbeddedEditor.toNegated()), + kbOpts: { + kbExpr: EditorContextKeys.editorTextFocus, + primary: KeyMod.CtrlCmd | KeyCode.F12, + weight: KeybindingWeight.EditorContrib + }, + /*menuOpts: { {{SQL CARBON EDIT}} remove entry + menuId: MenuId.MenubarGoMenu, + group: '4_symbol_nav', + order: 4, + title: nls.localize({ key: 'miGotoImplementation', comment: ['&& denotes a mnemonic'] }, "Go to &&Implementations") + },*/ + contextMenuOpts: { + group: 'navigation', + order: 1.45 + } + }); + } +}); + +registerEditorAction(class PeekImplementationAction extends ImplementationAction { + + public static readonly ID = 'editor.action.peekImplementation'; + + constructor() { + super({ + openToSide: false, + openInPeek: true, + muteMessage: false + }, { + id: PeekImplementationAction.ID, + label: nls.localize('actions.peekImplementation.label', "Peek Implementations"), + alias: 'Peek Implementations', + precondition: ContextKeyExpr.and( + EditorContextKeys.hasImplementationProvider, + PeekContext.notInPeekEditor, + EditorContextKeys.isInEmbeddedEditor.toNegated() + ), + kbOpts: { + kbExpr: EditorContextKeys.editorTextFocus, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.F12, + weight: KeybindingWeight.EditorContrib + }, + contextMenuOpts: { + menuId: MenuId.EditorContextPeek, + group: 'peek', + order: 5 + } + }); + } +}); + +//#endregion + +//#region --- REFERENCES + +abstract class ReferencesAction extends SymbolNavigationAction { + + protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { + return info + ? nls.localize('references.no', "No references found for '{0}'", info.word) + : nls.localize('references.noGeneric', "No references found"); + } + + protected _getAlternativeCommand(editor: IActiveCodeEditor): string { + return editor.getOption(EditorOption.gotoLocation).alternativeReferenceCommand; + } + + protected _getGoToPreference(editor: IActiveCodeEditor): GoToLocationValues { + return editor.getOption(EditorOption.gotoLocation).multipleReferences; + } +} + +registerEditorAction(class GoToReferencesAction extends ReferencesAction { + + constructor() { + super({ + openToSide: false, + openInPeek: false, + muteMessage: false + }, { + id: 'editor.action.goToReferences', + label: nls.localize('goToReferences.label', "Go to References"), + alias: 'Go to References', + precondition: ContextKeyExpr.and( + EditorContextKeys.hasReferenceProvider, + PeekContext.notInPeekEditor, + EditorContextKeys.isInEmbeddedEditor.toNegated() + ), + kbOpts: { + kbExpr: EditorContextKeys.editorTextFocus, + primary: KeyMod.Shift | KeyCode.F12, + weight: KeybindingWeight.EditorContrib + }, + contextMenuOpts: { + group: 'navigation', + order: 1.45 + }, + /*menuOpts: { {{SQL CARBON EDIT}} remove entry + menuId: MenuId.MenubarGoMenu, + group: '4_symbol_nav', + order: 5, + title: nls.localize({ key: 'miGotoReference', comment: ['&& denotes a mnemonic'] }, "Go to &&References") + },*/ + }); + } + + protected async _getLocationModel(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { + return new ReferencesModel(await getReferencesAtPosition(model, position, true, token), nls.localize('ref.title', 'References')); + } +}); + +registerEditorAction(class PeekReferencesAction extends ReferencesAction { + + constructor() { + super({ + openToSide: false, + openInPeek: true, + muteMessage: false + }, { + id: 'editor.action.referenceSearch.trigger', + label: nls.localize('references.action.label', "Peek References"), + alias: 'Peek References', + precondition: ContextKeyExpr.and( + EditorContextKeys.hasReferenceProvider, + PeekContext.notInPeekEditor, + EditorContextKeys.isInEmbeddedEditor.toNegated() + ), + contextMenuOpts: { + menuId: MenuId.EditorContextPeek, + group: 'peek', + order: 6 + } + }); + } + + protected async _getLocationModel(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { + return new ReferencesModel(await getReferencesAtPosition(model, position, false, token), nls.localize('ref.title', 'References')); + } +}); + +//#endregion + + +//#region --- GENERIC goto symbols command + +class GenericGoToLocationAction extends SymbolNavigationAction { + + constructor( + private readonly _references: Location[], + private readonly _gotoMultipleBehaviour: GoToLocationValues | undefined + ) { + super({ + muteMessage: true, + openInPeek: false, + openToSide: false + }, { + id: 'editor.action.goToLocation', + label: nls.localize('label.generic', "Go To Any Symbol"), + alias: 'Go To Any Symbol', + precondition: ContextKeyExpr.and( + PeekContext.notInPeekEditor, + EditorContextKeys.isInEmbeddedEditor.toNegated() + ), + }); + } + + protected async _getLocationModel(_model: ITextModel, _position: corePosition.Position, _token: CancellationToken): Promise { + return new ReferencesModel(this._references, nls.localize('generic.title', 'Locations')); + } + + protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { + return info && nls.localize('generic.noResult', "No results for '{0}'", info.word) || ''; + } + + protected _getGoToPreference(editor: IActiveCodeEditor): GoToLocationValues { + return this._gotoMultipleBehaviour ?? editor.getOption(EditorOption.gotoLocation).multipleReferences; + } + + protected _getAlternativeCommand() { return ''; } +} + +CommandsRegistry.registerCommand({ + id: 'editor.action.goToLocations', + description: { + description: 'Go to locations from a position in a file', + args: [ + { name: 'uri', description: 'The text document in which to start', constraint: URI }, + { name: 'position', description: 'The position at which to start', constraint: corePosition.Position.isIPosition }, + { name: 'locations', description: 'An array of locations.', constraint: Array }, + { name: 'multiple', description: 'Define what to do when having multiple results, either `peek`, `gotoAndPeek`, or `goto' }, + ] + }, + handler: async (accessor: ServicesAccessor, resource: any, position: any, references: any, multiple?: any) => { + assertType(URI.isUri(resource)); + assertType(corePosition.Position.isIPosition(position)); + assertType(Array.isArray(references)); + assertType(typeof multiple === 'undefined' || typeof multiple === 'string'); + + const editorService = accessor.get(ICodeEditorService); + const editor = await editorService.openCodeEditor({ resource }, editorService.getFocusedCodeEditor()); + + if (isCodeEditor(editor)) { + editor.setPosition(position); + editor.revealPositionInCenterIfOutsideViewport(position, ScrollType.Smooth); + + return editor.invokeWithinContext(accessor => { + const command = new GenericGoToLocationAction(references, multiple as GoToLocationValues); + accessor.get(IInstantiationService).invokeFunction(command.run.bind(command), editor); + }); + } + } +}); + +//#endregion + + +//#region --- REFERENCE search special commands + +CommandsRegistry.registerCommand({ + id: 'editor.action.findReferences', + handler: (accessor: ServicesAccessor, resource: any, position: any) => { + assertType(URI.isUri(resource)); + assertType(corePosition.Position.isIPosition(position)); + + const codeEditorService = accessor.get(ICodeEditorService); + return codeEditorService.openCodeEditor({ resource }, codeEditorService.getFocusedCodeEditor()).then(control => { + if (!isCodeEditor(control) || !control.hasModel()) { + return undefined; + } + + const controller = ReferencesController.get(control); + if (!controller) { + return undefined; + } + + const references = createCancelablePromise(token => getReferencesAtPosition(control.getModel(), corePosition.Position.lift(position), false, token).then(references => new ReferencesModel(references, nls.localize('ref.title', 'References')))); + const range = new Range(position.lineNumber, position.column, position.lineNumber, position.column); + return Promise.resolve(controller.toggleWidget(range, references, false)); + }); + } +}); + +// use NEW command +CommandsRegistry.registerCommandAlias('editor.action.showReferences', 'editor.action.goToLocations'); + +//#endregion diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinition.ts b/src/vs/editor/contrib/gotoSymbol/goToSymbol.ts similarity index 68% rename from src/vs/editor/contrib/goToDefinition/goToDefinition.ts rename to src/vs/editor/contrib/gotoSymbol/goToSymbol.ts index bc5de99e2a..fe0444bddd 100644 --- a/src/vs/editor/contrib/goToDefinition/goToDefinition.ts +++ b/src/vs/editor/contrib/gotoSymbol/goToSymbol.ts @@ -9,11 +9,11 @@ import { onUnexpectedExternalError } from 'vs/base/common/errors'; import { registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions'; import { Position } from 'vs/editor/common/core/position'; import { ITextModel } from 'vs/editor/common/model'; -import { LocationLink, DefinitionProviderRegistry, ImplementationProviderRegistry, TypeDefinitionProviderRegistry, DeclarationProviderRegistry, ProviderResult } from 'vs/editor/common/modes'; +import { LocationLink, DefinitionProviderRegistry, ImplementationProviderRegistry, TypeDefinitionProviderRegistry, DeclarationProviderRegistry, ProviderResult, ReferenceProviderRegistry } from 'vs/editor/common/modes'; import { LanguageFeatureRegistry } from 'vs/editor/common/modes/languageFeatureRegistry'; -function getDefinitions( +function getLocationLinks( model: ITextModel, position: Position, registry: LanguageFeatureRegistry, @@ -35,30 +35,45 @@ function getDefinitions( export function getDefinitionsAtPosition(model: ITextModel, position: Position, token: CancellationToken): Promise { - return getDefinitions(model, position, DefinitionProviderRegistry, (provider, model, position) => { + return getLocationLinks(model, position, DefinitionProviderRegistry, (provider, model, position) => { return provider.provideDefinition(model, position, token); }); } export function getDeclarationsAtPosition(model: ITextModel, position: Position, token: CancellationToken): Promise { - return getDefinitions(model, position, DeclarationProviderRegistry, (provider, model, position) => { + return getLocationLinks(model, position, DeclarationProviderRegistry, (provider, model, position) => { return provider.provideDeclaration(model, position, token); }); } export function getImplementationsAtPosition(model: ITextModel, position: Position, token: CancellationToken): Promise { - return getDefinitions(model, position, ImplementationProviderRegistry, (provider, model, position) => { + return getLocationLinks(model, position, ImplementationProviderRegistry, (provider, model, position) => { return provider.provideImplementation(model, position, token); }); } export function getTypeDefinitionsAtPosition(model: ITextModel, position: Position, token: CancellationToken): Promise { - return getDefinitions(model, position, TypeDefinitionProviderRegistry, (provider, model, position) => { + return getLocationLinks(model, position, TypeDefinitionProviderRegistry, (provider, model, position) => { return provider.provideTypeDefinition(model, position, token); }); } +export function getReferencesAtPosition(model: ITextModel, position: Position, compact: boolean, token: CancellationToken): Promise { + return getLocationLinks(model, position, ReferenceProviderRegistry, async (provider, model, position) => { + const result = await provider.provideReferences(model, position, { includeDeclaration: true }, token); + if (!compact || !result || result.length !== 2) { + return result; + } + const resultWithoutDeclaration = await provider.provideReferences(model, position, { includeDeclaration: false }, token); + if (resultWithoutDeclaration && resultWithoutDeclaration.length === 1) { + return resultWithoutDeclaration; + } + return result; + }); +} + registerDefaultLanguageCommand('_executeDefinitionProvider', (model, position) => getDefinitionsAtPosition(model, position, CancellationToken.None)); registerDefaultLanguageCommand('_executeDeclarationProvider', (model, position) => getDeclarationsAtPosition(model, position, CancellationToken.None)); registerDefaultLanguageCommand('_executeImplementationProvider', (model, position) => getImplementationsAtPosition(model, position, CancellationToken.None)); registerDefaultLanguageCommand('_executeTypeDefinitionProvider', (model, position) => getTypeDefinitionsAtPosition(model, position, CancellationToken.None)); +registerDefaultLanguageCommand('_executeReferenceProvider', (model, position) => getReferencesAtPosition(model, position, false, CancellationToken.None)); diff --git a/src/vs/editor/contrib/goToDefinition/clickLinkGesture.ts b/src/vs/editor/contrib/gotoSymbol/link/clickLinkGesture.ts similarity index 99% rename from src/vs/editor/contrib/goToDefinition/clickLinkGesture.ts rename to src/vs/editor/contrib/gotoSymbol/link/clickLinkGesture.ts index c6c55e1bb5..4c3252ea7a 100644 --- a/src/vs/editor/contrib/goToDefinition/clickLinkGesture.ts +++ b/src/vs/editor/contrib/gotoSymbol/link/clickLinkGesture.ts @@ -3,7 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./goToDefinitionMouse'; import { KeyCode } from 'vs/base/common/keyCodes'; import * as browser from 'vs/base/browser/browser'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.css b/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.css similarity index 100% rename from src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.css rename to src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.css diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts b/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts similarity index 67% rename from src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts rename to src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts index 81f95ab59f..69160c41c2 100644 --- a/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts +++ b/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./goToDefinitionMouse'; +import 'vs/css!./goToDefinitionAtPosition'; import * as nls from 'vs/nls'; import { createCancelablePromise, CancelablePromise } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -13,29 +13,31 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import { Range, IRange } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { DefinitionProviderRegistry, LocationLink } from 'vs/editor/common/modes'; -import { ICodeEditor, IMouseTarget, MouseTargetType } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; -import { getDefinitionsAtPosition } from './goToDefinition'; +import { getDefinitionsAtPosition } from '../goToSymbol'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { editorActiveLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { EditorState, CodeEditorStateFlag } from 'vs/editor/browser/core/editorState'; -import { DefinitionAction, DefinitionActionConfig } from './goToDefinitionCommands'; -import { ClickLinkGesture, ClickLinkMouseEvent, ClickLinkKeyboardEvent } from 'vs/editor/contrib/goToDefinition/clickLinkGesture'; +import { DefinitionAction } from '../goToCommands'; +import { ClickLinkGesture, ClickLinkMouseEvent, ClickLinkKeyboardEvent } from 'vs/editor/contrib/gotoSymbol/link/clickLinkGesture'; import { IWordAtPosition, IModelDeltaDecoration, ITextModel, IFoundBracket } from 'vs/editor/common/model'; import { Position } from 'vs/editor/common/core/position'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorContribution { +export class GotoDefinitionAtPositionEditorContribution implements editorCommon.IEditorContribution { - public static readonly ID = 'editor.contrib.gotodefinitionwithmouse'; + public static readonly ID = 'editor.contrib.gotodefinitionatposition'; static readonly MAX_SOURCE_PREVIEW_LINES = 8; private readonly editor: ICodeEditor; private readonly toUnhook = new DisposableStore(); - private decorations: string[] = []; - private currentWordUnderMouse: IWordAtPosition | null = null; + private readonly toUnhookForKeyboard = new DisposableStore(); + private linkDecorations: string[] = []; + private currentWordAtPosition: IWordAtPosition | null = null; private previousPromise: CancelablePromise | null = null; constructor( @@ -49,55 +51,96 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC this.toUnhook.add(linkGesture); this.toUnhook.add(linkGesture.onMouseMoveOrRelevantKeyDown(([mouseEvent, keyboardEvent]) => { - this.startFindDefinition(mouseEvent, withNullAsUndefined(keyboardEvent)); + this.startFindDefinitionFromMouse(mouseEvent, withNullAsUndefined(keyboardEvent)); })); this.toUnhook.add(linkGesture.onExecute((mouseEvent: ClickLinkMouseEvent) => { if (this.isEnabled(mouseEvent)) { - this.gotoDefinition(mouseEvent.target, mouseEvent.hasSideBySideModifier).then(() => { - this.removeDecorations(); + this.gotoDefinition(mouseEvent.target.position!, mouseEvent.hasSideBySideModifier).then(() => { + this.removeLinkDecorations(); }, (error: Error) => { - this.removeDecorations(); + this.removeLinkDecorations(); onUnexpectedError(error); }); } })); this.toUnhook.add(linkGesture.onCancel(() => { - this.removeDecorations(); - this.currentWordUnderMouse = null; + this.removeLinkDecorations(); + this.currentWordAtPosition = null; })); - } - private startFindDefinition(mouseEvent: ClickLinkMouseEvent, withKey?: ClickLinkKeyboardEvent): void { + static get(editor: ICodeEditor): GotoDefinitionAtPositionEditorContribution { + return editor.getContribution(GotoDefinitionAtPositionEditorContribution.ID); + } + + startFindDefinitionFromCursor(position: Position) { + // For issue: https://github.com/microsoft/vscode/issues/46257 + // equivalent to mouse move with meta/ctrl key + + // First find the definition and add decorations + // to the editor to be shown with the content hover widget + return this.startFindDefinition(position).then(() => { + + // Add listeners for editor cursor move and key down events + // Dismiss the "extended" editor decorations when the user hides + // the hover widget. There is no event for the widget itself so these + // serve as a best effort. After removing the link decorations, the hover + // widget is clean and will only show declarations per next request. + this.toUnhookForKeyboard.add(this.editor.onDidChangeCursorPosition(() => { + this.currentWordAtPosition = null; + this.removeLinkDecorations(); + this.toUnhookForKeyboard.clear(); + })); + + this.toUnhookForKeyboard.add(this.editor.onKeyDown((e: IKeyboardEvent) => { + if (e) { + this.currentWordAtPosition = null; + this.removeLinkDecorations(); + this.toUnhookForKeyboard.clear(); + } + })); + }); + } + + private startFindDefinitionFromMouse(mouseEvent: ClickLinkMouseEvent, withKey?: ClickLinkKeyboardEvent): void { // check if we are active and on a content widget - if (mouseEvent.target.type === MouseTargetType.CONTENT_WIDGET && this.decorations.length > 0) { + if (mouseEvent.target.type === MouseTargetType.CONTENT_WIDGET && this.linkDecorations.length > 0) { return; } if (!this.editor.hasModel() || !this.isEnabled(mouseEvent, withKey)) { - this.currentWordUnderMouse = null; - this.removeDecorations(); + this.currentWordAtPosition = null; + this.removeLinkDecorations(); return; } - // Find word at mouse position - const word = mouseEvent.target.position ? this.editor.getModel().getWordAtPosition(mouseEvent.target.position) : null; - if (!word) { - this.currentWordUnderMouse = null; - this.removeDecorations(); - return; - } const position = mouseEvent.target.position!; - // Return early if word at position is still the same - if (this.currentWordUnderMouse && this.currentWordUnderMouse.startColumn === word.startColumn && this.currentWordUnderMouse.endColumn === word.endColumn && this.currentWordUnderMouse.word === word.word) { - return; + this.startFindDefinition(position); + } + + private startFindDefinition(position: Position): Promise { + + // Dispose listeners for updating decorations when using keyboard to show definition hover + this.toUnhookForKeyboard.clear(); + + // Find word at mouse position + const word = position ? this.editor.getModel()?.getWordAtPosition(position) : null; + if (!word) { + this.currentWordAtPosition = null; + this.removeLinkDecorations(); + return Promise.resolve(0); } - this.currentWordUnderMouse = word; + // Return early if word at position is still the same + if (this.currentWordAtPosition && this.currentWordAtPosition.startColumn === word.startColumn && this.currentWordAtPosition.endColumn === word.endColumn && this.currentWordAtPosition.word === word.word) { + return Promise.resolve(0); + } + + this.currentWordAtPosition = word; // Find definition and decorate word if found let state = new EditorState(this.editor, CodeEditorStateFlag.Position | CodeEditorStateFlag.Value | CodeEditorStateFlag.Selection | CodeEditorStateFlag.Scroll); @@ -107,11 +150,11 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC this.previousPromise = null; } - this.previousPromise = createCancelablePromise(token => this.findDefinition(mouseEvent.target, token)); + this.previousPromise = createCancelablePromise(token => this.findDefinition(position, token)); - this.previousPromise.then(results => { + return this.previousPromise.then(results => { if (!results || !results.length || !state.validate(this.editor)) { - this.removeDecorations(); + this.removeLinkDecorations(); return; } @@ -170,7 +213,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC private getPreviewValue(textEditorModel: ITextModel, startLineNumber: number, result: LocationLink) { let rangeToUse = result.targetSelectionRange ? result.range : this.getPreviewRangeBasedOnBrackets(textEditorModel, startLineNumber); const numberOfLinesInRange = rangeToUse.endLineNumber - rangeToUse.startLineNumber; - if (numberOfLinesInRange >= GotoDefinitionWithMouseEditorContribution.MAX_SOURCE_PREVIEW_LINES) { + if (numberOfLinesInRange >= GotoDefinitionAtPositionEditorContribution.MAX_SOURCE_PREVIEW_LINES) { rangeToUse = this.getPreviewRangeBasedOnIndentation(textEditorModel, startLineNumber); } @@ -193,7 +236,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC private getPreviewRangeBasedOnIndentation(textEditorModel: ITextModel, startLineNumber: number) { const startIndent = textEditorModel.getLineFirstNonWhitespaceColumn(startLineNumber); - const maxLineNumber = Math.min(textEditorModel.getLineCount(), startLineNumber + GotoDefinitionWithMouseEditorContribution.MAX_SOURCE_PREVIEW_LINES); + const maxLineNumber = Math.min(textEditorModel.getLineCount(), startLineNumber + GotoDefinitionAtPositionEditorContribution.MAX_SOURCE_PREVIEW_LINES); let endLineNumber = startLineNumber + 1; for (; endLineNumber < maxLineNumber; endLineNumber++) { @@ -208,7 +251,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC } private getPreviewRangeBasedOnBrackets(textEditorModel: ITextModel, startLineNumber: number) { - const maxLineNumber = Math.min(textEditorModel.getLineCount(), startLineNumber + GotoDefinitionWithMouseEditorContribution.MAX_SOURCE_PREVIEW_LINES); + const maxLineNumber = Math.min(textEditorModel.getLineCount(), startLineNumber + GotoDefinitionAtPositionEditorContribution.MAX_SOURCE_PREVIEW_LINES); const brackets: IFoundBracket[] = []; @@ -263,12 +306,12 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC } }; - this.decorations = this.editor.deltaDecorations(this.decorations, [newDecorations]); + this.linkDecorations = this.editor.deltaDecorations(this.linkDecorations, [newDecorations]); } - private removeDecorations(): void { - if (this.decorations.length > 0) { - this.decorations = this.editor.deltaDecorations(this.decorations, []); + private removeLinkDecorations(): void { + if (this.linkDecorations.length > 0) { + this.linkDecorations = this.editor.deltaDecorations(this.linkDecorations, []); } } @@ -280,18 +323,18 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC DefinitionProviderRegistry.has(this.editor.getModel()); } - private findDefinition(target: IMouseTarget, token: CancellationToken): Promise { + private findDefinition(position: Position, token: CancellationToken): Promise { const model = this.editor.getModel(); if (!model) { return Promise.resolve(null); } - return getDefinitionsAtPosition(model, target.position!, token); + return getDefinitionsAtPosition(model, position, token); } - private gotoDefinition(target: IMouseTarget, sideBySide: boolean): Promise { - this.editor.setPosition(target.position!); - const action = new DefinitionAction(new DefinitionActionConfig(sideBySide, false, true, false), { alias: '', label: '', id: '', precondition: undefined }); + private gotoDefinition(position: Position, openToSide: boolean): Promise { + this.editor.setPosition(position); + const action = new DefinitionAction({ openToSide, openInPeek: false, muteMessage: true }, { alias: '', label: '', id: '', precondition: undefined }); return this.editor.invokeWithinContext(accessor => action.run(accessor, this.editor)); } @@ -300,7 +343,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC } } -registerEditorContribution(GotoDefinitionWithMouseEditorContribution.ID, GotoDefinitionWithMouseEditorContribution); +registerEditorContribution(GotoDefinitionAtPositionEditorContribution.ID, GotoDefinitionAtPositionEditorContribution); registerThemingParticipant((theme, collector) => { const activeLinkForeground = theme.getColor(editorActiveLinkForeground); diff --git a/src/vs/editor/contrib/referenceSearch/referencesController.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts similarity index 52% rename from src/vs/editor/contrib/referenceSearch/referencesController.ts rename to src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts index 1195948b7f..27c1ead87b 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesController.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts @@ -7,47 +7,47 @@ import * as nls from 'vs/nls'; import { onUnexpectedError } from 'vs/base/common/errors'; import { dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IContextKey, IContextKeyService, RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { ReferencesModel } from './referencesModel'; +import { ReferencesModel, OneReference } from '../referencesModel'; import { ReferenceWidget, LayoutData } from './referencesWidget'; import { Range } from 'vs/editor/common/core/range'; import { Position } from 'vs/editor/common/core/position'; import { Location } from 'vs/editor/common/modes'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { CancelablePromise } from 'vs/base/common/async'; +import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; +import { getOuterEditor, PeekContext } from 'vs/editor/contrib/peekView/peekView'; +import { IListService, WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService'; +import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; export const ctxReferenceSearchVisible = new RawContextKey('referenceSearchVisible', false); -export interface RequestOptions { - getMetaTitle(model: ReferencesModel): string; - onGoto?: (reference: Location) => Promise; -} - export abstract class ReferencesController implements editorCommon.IEditorContribution { - public static readonly ID = 'editor.contrib.referencesController'; + static readonly ID = 'editor.contrib.referencesController'; private readonly _disposables = new DisposableStore(); - private readonly _editor: ICodeEditor; + private _widget?: ReferenceWidget; private _model?: ReferencesModel; + private _peekMode?: boolean; private _requestIdPool = 0; private _ignoreModelChangeEvent = false; private readonly _referenceSearchVisible: IContextKey; - public static get(editor: ICodeEditor): ReferencesController { + static get(editor: ICodeEditor): ReferencesController { return editor.getContribution(ReferencesController.ID); } - public constructor( + constructor( private readonly _defaultTreeKeyboardSupport: boolean, - editor: ICodeEditor, + private readonly _editor: ICodeEditor, @IContextKeyService contextKeyService: IContextKeyService, @ICodeEditorService private readonly _editorService: ICodeEditorService, @INotificationService private readonly _notificationService: INotificationService, @@ -55,24 +55,20 @@ export abstract class ReferencesController implements editorCommon.IEditorContri @IStorageService private readonly _storageService: IStorageService, @IConfigurationService private readonly _configurationService: IConfigurationService, ) { - this._editor = editor; + this._referenceSearchVisible = ctxReferenceSearchVisible.bindTo(contextKeyService); } - public dispose(): void { + dispose(): void { this._referenceSearchVisible.reset(); - dispose(this._disposables); - if (this._widget) { - dispose(this._widget); - this._widget = undefined; - } - if (this._model) { - dispose(this._model); - this._model = undefined; - } + this._disposables.dispose(); + dispose(this._widget); + dispose(this._model); + this._widget = undefined; + this._model = undefined; } - public toggleWidget(range: Range, modelPromise: CancelablePromise, options: RequestOptions): void { + toggleWidget(range: Range, modelPromise: CancelablePromise, peekMode: boolean): void { // close current widget and return early is position didn't change let widgetPosition: Position | undefined; @@ -84,6 +80,7 @@ export abstract class ReferencesController implements editorCommon.IEditorContri return; } + this._peekMode = peekMode; this._referenceSearchVisible.set(true); // close the widget on model/mode changes @@ -110,27 +107,25 @@ export abstract class ReferencesController implements editorCommon.IEditorContri this._disposables.add(this._widget.onDidSelectReference(event => { let { element, kind } = event; + if (!element) { + return; + } switch (kind) { case 'open': - if (event.source === 'editor' - && this._configurationService.getValue('editor.stablePeek')) { - + if (event.source !== 'editor' || !this._configurationService.getValue('editor.stablePeek')) { // when stable peek is configured we don't close // the peek window on selecting the editor - break; - } - case 'side': - if (element) { - this.openReference(element, kind === 'side'); + this.openReference(element, false); } break; + case 'side': + this.openReference(element, true); + break; case 'goto': - if (element) { - if (options.onGoto) { - options.onGoto(element); - } else { - this._gotoReference(element); - } + if (peekMode) { + this._gotoReference(element); + } else { + this.openReference(element, false); } break; } @@ -154,8 +149,13 @@ export abstract class ReferencesController implements editorCommon.IEditorContri // show widget return this._widget.setModel(this._model).then(() => { if (this._widget && this._model && this._editor.hasModel()) { // might have been closed + // set title - this._widget.setMetaTitle(options.getMetaTitle(this._model)); + if (!this._model.isEmpty) { + this._widget.setMetaTitle(nls.localize('metaTitle.N', "{0} ({1})", this._model.title, this._model.references.length)); + } else { + this._widget.setMetaTitle(''); + } // set 'best' selection let uri = this._editor.getModel().uri; @@ -173,7 +173,7 @@ export abstract class ReferencesController implements editorCommon.IEditorContri }); } - public async goToNextOrPreviousReference(fwd: boolean) { + async goToNextOrPreviousReference(fwd: boolean) { if (!this._editor.hasModel() || !this._model || !this._widget) { // can be called while still resolving... return; @@ -195,17 +195,13 @@ export abstract class ReferencesController implements editorCommon.IEditorContri } } - public closeWidget(): void { - if (this._widget) { - dispose(this._widget); - this._widget = undefined; - } + closeWidget(): void { this._referenceSearchVisible.reset(); this._disposables.clear(); - if (this._model) { - dispose(this._model); - this._model = undefined; - } + dispose(this._widget); + dispose(this._model); + this._widget = undefined; + this._model = undefined; this._editor.focus(); this._requestIdPool += 1; // Cancel pending requests } @@ -224,21 +220,31 @@ export abstract class ReferencesController implements editorCommon.IEditorContri }, this._editor).then(openedEditor => { this._ignoreModelChangeEvent = false; - if (!openedEditor || openedEditor !== this._editor) { - // TODO@Alex TODO@Joh - // when opening the current reference we might end up - // in a different editor instance. that means we also have - // a different instance of this reference search controller - // and cannot hold onto the widget (which likely doesn't - // exist). Instead of bailing out we should find the - // 'sister' action and pass our current model on to it. + if (!openedEditor || !this._widget) { + // something went wrong... this.closeWidget(); return; } - if (this._widget) { + if (this._editor === openedEditor) { + // this._widget.show(range); this._widget.focus(); + + } else { + // we opened a different editor instance which means a different controller instance. + // therefore we stop with this controller and continue with the other + const other = ReferencesController.get(openedEditor); + const model = this._model!.clone(); + + this.closeWidget(); + openedEditor.focus(); + + other.toggleWidget( + range, + createCancelablePromise(_ => Promise.resolve(model)), + this._peekMode ?? false + ); } }, (err) => { @@ -247,7 +253,7 @@ export abstract class ReferencesController implements editorCommon.IEditorContri }); } - public openReference(ref: Location, sideBySide: boolean): void { + openReference(ref: Location, sideBySide: boolean): void { // clear stage if (!sideBySide) { this.closeWidget(); @@ -260,3 +266,105 @@ export abstract class ReferencesController implements editorCommon.IEditorContri }, this._editor, sideBySide); } } + +function withController(accessor: ServicesAccessor, fn: (controller: ReferencesController) => void): void { + const outerEditor = getOuterEditor(accessor); + if (!outerEditor) { + return; + } + let controller = ReferencesController.get(outerEditor); + if (controller) { + fn(controller); + } +} + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'goToNextReference', + weight: KeybindingWeight.WorkbenchContrib + 50, + primary: KeyCode.F4, + secondary: [KeyCode.F12], + when: ctxReferenceSearchVisible, + handler(accessor) { + withController(accessor, controller => { + controller.goToNextOrPreviousReference(true); + }); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'goToNextReferenceFromEmbeddedEditor', + weight: KeybindingWeight.EditorContrib + 50, + primary: KeyCode.F4, + secondary: [KeyCode.F12], + when: PeekContext.inPeekEditor, + handler(accessor) { + withController(accessor, controller => { + controller.goToNextOrPreviousReference(true); + }); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'goToPreviousReference', + weight: KeybindingWeight.WorkbenchContrib + 50, + primary: KeyMod.Shift | KeyCode.F4, + secondary: [KeyMod.Shift | KeyCode.F12], + when: ctxReferenceSearchVisible, + handler(accessor) { + withController(accessor, controller => { + controller.goToNextOrPreviousReference(false); + }); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'goToPreviousReferenceFromEmbeddedEditor', + weight: KeybindingWeight.EditorContrib + 50, + primary: KeyMod.Shift | KeyCode.F4, + secondary: [KeyMod.Shift | KeyCode.F12], + when: PeekContext.inPeekEditor, + handler(accessor) { + withController(accessor, controller => { + controller.goToNextOrPreviousReference(false); + }); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'closeReferenceSearch', + weight: KeybindingWeight.WorkbenchContrib + 50, + primary: KeyCode.Escape, + secondary: [KeyMod.Shift | KeyCode.Escape], + when: ContextKeyExpr.and(ctxReferenceSearchVisible, ContextKeyExpr.not('config.editor.stablePeek')), + handler(accessor: ServicesAccessor) { + withController(accessor, controller => controller.closeWidget()); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'closeReferenceSearchEditor', + weight: KeybindingWeight.EditorContrib - 101, + primary: KeyCode.Escape, + secondary: [KeyMod.Shift | KeyCode.Escape], + when: ContextKeyExpr.and(PeekContext.inPeekEditor, ContextKeyExpr.not('config.editor.stablePeek')), + handler(accessor: ServicesAccessor) { + withController(accessor, controller => controller.closeWidget()); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'openReferenceToSide', + weight: KeybindingWeight.EditorContrib, + primary: KeyMod.CtrlCmd | KeyCode.Enter, + mac: { + primary: KeyMod.WinCtrl | KeyCode.Enter + }, + when: ContextKeyExpr.and(ctxReferenceSearchVisible, WorkbenchListFocusContextKey), + handler(accessor: ServicesAccessor) { + const listService = accessor.get(IListService); + const focus = listService.lastFocusedList?.getFocus(); + if (Array.isArray(focus) && focus[0] instanceof OneReference) { + withController(accessor, controller => controller.openReference(focus[0], true)); + } + } +}); diff --git a/src/vs/editor/contrib/referenceSearch/referencesTree.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesTree.ts similarity index 97% rename from src/vs/editor/contrib/referenceSearch/referencesTree.ts rename to src/vs/editor/contrib/gotoSymbol/peek/referencesTree.ts index d10cbd6430..b0150bf9b6 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesTree.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesTree.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ReferencesModel, FileReferences, OneReference } from './referencesModel'; +import { ReferencesModel, FileReferences, OneReference } from '../referencesModel'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ITreeRenderer, ITreeNode, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; @@ -101,7 +101,7 @@ export class StringRepresentationProvider implements IKeyboardNavigationLabelPro export class IdentityProvider implements IIdentityProvider { getId(element: TreeElement): { toString(): string; } { - return element.id; + return element instanceof OneReference ? element.id : element.uri; } } @@ -216,12 +216,6 @@ export class OneReferenceRenderer implements ITreeRenderer { getAriaLabel(element: FileReferences | OneReference): string | null { - if (element instanceof FileReferences) { - return element.getAriaMessage(); - } else if (element instanceof OneReference) { - return element.getAriaMessage(); - } else { - return null; - } + return element.ariaMessage; } } diff --git a/src/vs/editor/contrib/referenceSearch/media/referencesWidget.css b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.css similarity index 100% rename from src/vs/editor/contrib/referenceSearch/media/referencesWidget.css rename to src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.css diff --git a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts similarity index 72% rename from src/vs/editor/contrib/referenceSearch/referencesWidget.ts rename to src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts index 5c66be0691..3c887377fc 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts @@ -3,15 +3,16 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./referencesWidget'; import * as dom from 'vs/base/browser/dom'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; +import { GestureEvent } from 'vs/base/browser/touch'; import { Orientation } from 'vs/base/browser/ui/sash/sash'; import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; import { dispose, IDisposable, IReference, DisposableStore } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { basenameOrAuthority, dirname, isEqual } from 'vs/base/common/resources'; -import 'vs/css!./media/referencesWidget'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; @@ -21,19 +22,15 @@ import { IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/ import { ModelDecorationOptions, TextModel } from 'vs/editor/common/model/textModel'; import { Location } from 'vs/editor/common/modes'; import { ITextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; -import { AriaProvider, DataSource, Delegate, FileReferencesRenderer, OneReferenceRenderer, TreeElement, StringRepresentationProvider, IdentityProvider } from 'vs/editor/contrib/referenceSearch/referencesTree'; +import { AriaProvider, DataSource, Delegate, FileReferencesRenderer, OneReferenceRenderer, TreeElement, StringRepresentationProvider, IdentityProvider } from 'vs/editor/contrib/gotoSymbol/peek/referencesTree'; import * as nls from 'vs/nls'; -import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; -import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; -import { activeContrastBorder, contrastBorder, registerColor } from 'vs/platform/theme/common/colorRegistry'; +import { WorkbenchAsyncDataTree, IWorkbenchAsyncDataTreeOptions } from 'vs/platform/list/browser/listService'; +import { activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { PeekViewWidget, IPeekViewService } from './peekViewWidget'; -import { FileReferences, OneReference, ReferencesModel } from './referencesModel'; -import { ITreeRenderer, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; -import { IAsyncDataTreeOptions } from 'vs/base/browser/ui/tree/asyncDataTree'; -import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import * as peekView from 'vs/editor/contrib/peekView/peekView'; +import { FileReferences, OneReference, ReferencesModel } from '../referencesModel'; import { FuzzyScore } from 'vs/base/common/filters'; import { SplitView, Sizing } from 'vs/base/browser/ui/splitview/splitview'; @@ -55,7 +52,7 @@ class DecorationsManager implements IDisposable { this._onModelChanged(); } - public dispose(): void { + dispose(): void { this._callOnModelChange.dispose(); this._callOnDispose.dispose(); this.removeDecorations(); @@ -147,7 +144,7 @@ class DecorationsManager implements IDisposable { this._editor.deltaDecorations(toRemove, []); } - public removeDecorations(): void { + removeDecorations(): void { let toRemove: string[] = []; this._decorations.forEach((value, key) => { toRemove.push(key); @@ -158,8 +155,8 @@ class DecorationsManager implements IDisposable { } export class LayoutData { - public ratio: number = 0.7; - public heightInLines: number = 18; + ratio: number = 0.7; + heightInLines: number = 18; static fromJSON(raw: string): LayoutData { let ratio: number | undefined; @@ -184,19 +181,19 @@ export interface SelectionEvent { readonly element?: Location; } -export const ctxReferenceWidgetSearchTreeFocused = new RawContextKey('referenceSearchTreeFocused', true); - /** * ZoneWidget that is shown inside the editor */ -export class ReferenceWidget extends PeekViewWidget { +export class ReferenceWidget extends peekView.PeekViewWidget { private _model?: ReferencesModel; private _decorationsManager?: DecorationsManager; private readonly _disposeOnNewModel = new DisposableStore(); private readonly _callOnDispose = new DisposableStore(); + private readonly _onDidSelectReference = new Emitter(); + readonly onDidSelectReference = this._onDidSelectReference.event; private _tree!: WorkbenchAsyncDataTree; private _treeContainer!: HTMLElement; @@ -215,7 +212,7 @@ export class ReferenceWidget extends PeekViewWidget { @IThemeService themeService: IThemeService, @ITextModelService private readonly _textModelResolverService: ITextModelService, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IPeekViewService private readonly _peekViewService: IPeekViewService, + @peekView.IPeekViewService private readonly _peekViewService: peekView.IPeekViewService, @ILabelService private readonly _uriLabel: ILabelService ) { super(editor, { showFrame: false, showArrow: true, isResizeable: true, isAccessible: true }); @@ -239,20 +236,16 @@ export class ReferenceWidget extends PeekViewWidget { } private _applyTheme(theme: ITheme) { - const borderColor = theme.getColor(peekViewBorder) || Color.transparent; + const borderColor = theme.getColor(peekView.peekViewBorder) || Color.transparent; this.style({ arrowColor: borderColor, frameColor: borderColor, - headerBackgroundColor: theme.getColor(peekViewTitleBackground) || Color.transparent, - primaryHeadingColor: theme.getColor(peekViewTitleForeground), - secondaryHeadingColor: theme.getColor(peekViewTitleInfoForeground) + headerBackgroundColor: theme.getColor(peekView.peekViewTitleBackground) || Color.transparent, + primaryHeadingColor: theme.getColor(peekView.peekViewTitleForeground), + secondaryHeadingColor: theme.getColor(peekView.peekViewTitleInfoForeground) }); } - get onDidSelectReference(): Event { - return this._onDidSelectReference.event; - } - show(where: IRange) { this.editor.revealRangeInCenterIfOutsideViewport(where, editorCommon.ScrollType.Smooth); super.show(where, this.layoutData.heightInLines || 18); @@ -304,14 +297,17 @@ export class ReferenceWidget extends PeekViewWidget { // tree this._treeContainer = dom.append(containerElement, dom.$('div.ref-tree.inline')); - const treeOptions: IAsyncDataTreeOptions = { + const treeOptions: IWorkbenchAsyncDataTreeOptions = { ariaLabel: nls.localize('treeAriaLabel', "References"), keyboardSupport: this._defaultTreeKeyboardSupport, accessibilityProvider: new AriaProvider(), keyboardNavigationLabelProvider: this._instantiationService.createInstance(StringRepresentationProvider), - identityProvider: new IdentityProvider() + identityProvider: new IdentityProvider(), + overrideStyles: { + listBackground: peekView.peekViewResultsBackground + } }; - this._tree = this._instantiationService.createInstance, ITreeRenderer[], IAsyncDataSource, IAsyncDataTreeOptions, WorkbenchAsyncDataTree>( + this._tree = this._instantiationService.createInstance>( WorkbenchAsyncDataTree, 'ReferencesWidget', this._treeContainer, @@ -321,9 +317,8 @@ export class ReferenceWidget extends PeekViewWidget { this._instantiationService.createInstance(OneReferenceRenderer), ], this._instantiationService.createInstance(DataSource), - treeOptions + treeOptions, ); - ctxReferenceWidgetSearchTreeFocused.bindTo(this._tree.contextKeyService); // split stuff this._splitView.addView({ @@ -367,17 +362,17 @@ export class ReferenceWidget extends PeekViewWidget { onEvent(e.elements[0], 'show'); }); this._tree.onDidOpen(e => { - const aside = (e.browserEvent instanceof MouseEvent) && (e.browserEvent.ctrlKey || e.browserEvent.metaKey || e.browserEvent.altKey); - let goto = !e.browserEvent || ((e.browserEvent instanceof MouseEvent) && e.browserEvent.detail === 2); - if (e.browserEvent instanceof KeyboardEvent) { - // todo@joh make this a command - goto = true; - } - if (aside) { + if (e.browserEvent instanceof MouseEvent && (e.browserEvent.ctrlKey || e.browserEvent.metaKey || e.browserEvent.altKey)) { + // modifier-click -> open to the side onEvent(e.elements[0], 'side'); - } else if (goto) { + } else if (e.browserEvent instanceof KeyboardEvent || (e.browserEvent instanceof MouseEvent && e.browserEvent.detail === 2) || (e.browserEvent).tapCount === 2) { + // keybinding (list service command) + // OR double click + // OR double tap + // -> close widget and goto target onEvent(e.elements[0], 'goto'); } else { + // preview location onEvent(e.elements[0], 'show'); } }); @@ -399,7 +394,7 @@ export class ReferenceWidget extends PeekViewWidget { this._splitView.resizeView(0, widthInPixel * this.layoutData.ratio); } - public setSelection(selection: OneReference): Promise { + setSelection(selection: OneReference): Promise { return this._revealReference(selection, true).then(() => { if (!this._model) { // disposed @@ -411,7 +406,7 @@ export class ReferenceWidget extends PeekViewWidget { }); } - public setModel(newModel: ReferencesModel | undefined): Promise { + setModel(newModel: ReferencesModel | undefined): Promise { // clean up this._disposeOnNewModel.clear(); this._model = newModel; @@ -426,7 +421,7 @@ export class ReferenceWidget extends PeekViewWidget { return Promise.resolve(undefined); } - if (this._model.empty) { + if (this._model.isEmpty) { this.setTitle(''); this._messageContainer.innerHTML = nls.localize('noResults', "No results"); dom.show(this._messageContainer); @@ -537,34 +532,17 @@ export class ReferenceWidget extends PeekViewWidget { // theming -export const peekViewTitleBackground = registerColor('peekViewTitle.background', { dark: '#1E1E1E', light: '#FFFFFF', hc: '#0C141F' }, nls.localize('peekViewTitleBackground', 'Background color of the peek view title area.')); -export const peekViewTitleForeground = registerColor('peekViewTitleLabel.foreground', { dark: '#FFFFFF', light: '#333333', hc: '#FFFFFF' }, nls.localize('peekViewTitleForeground', 'Color of the peek view title.')); -export const peekViewTitleInfoForeground = registerColor('peekViewTitleDescription.foreground', { dark: '#ccccccb3', light: '#6c6c6cb3', hc: '#FFFFFF99' }, nls.localize('peekViewTitleInfoForeground', 'Color of the peek view title info.')); -export const peekViewBorder = registerColor('peekView.border', { dark: '#007acc', light: '#007acc', hc: contrastBorder }, nls.localize('peekViewBorder', 'Color of the peek view borders and arrow.')); - -export const peekViewResultsBackground = registerColor('peekViewResult.background', { dark: '#252526', light: '#F3F3F3', hc: Color.black }, nls.localize('peekViewResultsBackground', 'Background color of the peek view result list.')); -export const peekViewResultsMatchForeground = registerColor('peekViewResult.lineForeground', { dark: '#bbbbbb', light: '#646465', hc: Color.white }, nls.localize('peekViewResultsMatchForeground', 'Foreground color for line nodes in the peek view result list.')); -export const peekViewResultsFileForeground = registerColor('peekViewResult.fileForeground', { dark: Color.white, light: '#1E1E1E', hc: Color.white }, nls.localize('peekViewResultsFileForeground', 'Foreground color for file nodes in the peek view result list.')); -export const peekViewResultsSelectionBackground = registerColor('peekViewResult.selectionBackground', { dark: '#3399ff33', light: '#3399ff33', hc: null }, nls.localize('peekViewResultsSelectionBackground', 'Background color of the selected entry in the peek view result list.')); -export const peekViewResultsSelectionForeground = registerColor('peekViewResult.selectionForeground', { dark: Color.white, light: '#6C6C6C', hc: Color.white }, nls.localize('peekViewResultsSelectionForeground', 'Foreground color of the selected entry in the peek view result list.')); -export const peekViewEditorBackground = registerColor('peekViewEditor.background', { dark: '#001F33', light: '#F2F8FC', hc: Color.black }, nls.localize('peekViewEditorBackground', 'Background color of the peek view editor.')); -export const peekViewEditorGutterBackground = registerColor('peekViewEditorGutter.background', { dark: peekViewEditorBackground, light: peekViewEditorBackground, hc: peekViewEditorBackground }, nls.localize('peekViewEditorGutterBackground', 'Background color of the gutter in the peek view editor.')); - -export const peekViewResultsMatchHighlight = registerColor('peekViewResult.matchHighlightBackground', { dark: '#ea5c004d', light: '#ea5c004d', hc: null }, nls.localize('peekViewResultsMatchHighlight', 'Match highlight color in the peek view result list.')); -export const peekViewEditorMatchHighlight = registerColor('peekViewEditor.matchHighlightBackground', { dark: '#ff8f0099', light: '#f5d802de', hc: null }, nls.localize('peekViewEditorMatchHighlight', 'Match highlight color in the peek view editor.')); -export const peekViewEditorMatchHighlightBorder = registerColor('peekViewEditor.matchHighlightBorder', { dark: null, light: null, hc: activeContrastBorder }, nls.localize('peekViewEditorMatchHighlightBorder', 'Match highlight border in the peek view editor.')); - registerThemingParticipant((theme, collector) => { - const findMatchHighlightColor = theme.getColor(peekViewResultsMatchHighlight); + const findMatchHighlightColor = theme.getColor(peekView.peekViewResultsMatchHighlight); if (findMatchHighlightColor) { collector.addRule(`.monaco-editor .reference-zone-widget .ref-tree .referenceMatch .highlight { background-color: ${findMatchHighlightColor}; }`); } - const referenceHighlightColor = theme.getColor(peekViewEditorMatchHighlight); + const referenceHighlightColor = theme.getColor(peekView.peekViewEditorMatchHighlight); if (referenceHighlightColor) { collector.addRule(`.monaco-editor .reference-zone-widget .preview .reference-decoration { background-color: ${referenceHighlightColor}; }`); } - const referenceHighlightBorder = theme.getColor(peekViewEditorMatchHighlightBorder); + const referenceHighlightBorder = theme.getColor(peekView.peekViewEditorMatchHighlightBorder); if (referenceHighlightBorder) { collector.addRule(`.monaco-editor .reference-zone-widget .preview .reference-decoration { border: 2px solid ${referenceHighlightBorder}; box-sizing: border-box; }`); } @@ -572,27 +550,27 @@ registerThemingParticipant((theme, collector) => { if (hcOutline) { collector.addRule(`.monaco-editor .reference-zone-widget .ref-tree .referenceMatch .highlight { border: 1px dotted ${hcOutline}; box-sizing: border-box; }`); } - const resultsBackground = theme.getColor(peekViewResultsBackground); + const resultsBackground = theme.getColor(peekView.peekViewResultsBackground); if (resultsBackground) { collector.addRule(`.monaco-editor .reference-zone-widget .ref-tree { background-color: ${resultsBackground}; }`); } - const resultsMatchForeground = theme.getColor(peekViewResultsMatchForeground); + const resultsMatchForeground = theme.getColor(peekView.peekViewResultsMatchForeground); if (resultsMatchForeground) { collector.addRule(`.monaco-editor .reference-zone-widget .ref-tree { color: ${resultsMatchForeground}; }`); } - const resultsFileForeground = theme.getColor(peekViewResultsFileForeground); + const resultsFileForeground = theme.getColor(peekView.peekViewResultsFileForeground); if (resultsFileForeground) { collector.addRule(`.monaco-editor .reference-zone-widget .ref-tree .reference-file { color: ${resultsFileForeground}; }`); } - const resultsSelectedBackground = theme.getColor(peekViewResultsSelectionBackground); + const resultsSelectedBackground = theme.getColor(peekView.peekViewResultsSelectionBackground); if (resultsSelectedBackground) { collector.addRule(`.monaco-editor .reference-zone-widget .ref-tree .monaco-list:focus .monaco-list-rows > .monaco-list-row.selected:not(.highlighted) { background-color: ${resultsSelectedBackground}; }`); } - const resultsSelectedForeground = theme.getColor(peekViewResultsSelectionForeground); + const resultsSelectedForeground = theme.getColor(peekView.peekViewResultsSelectionForeground); if (resultsSelectedForeground) { collector.addRule(`.monaco-editor .reference-zone-widget .ref-tree .monaco-list:focus .monaco-list-rows > .monaco-list-row.selected:not(.highlighted) { color: ${resultsSelectedForeground} !important; }`); } - const editorBackground = theme.getColor(peekViewEditorBackground); + const editorBackground = theme.getColor(peekView.peekViewEditorBackground); if (editorBackground) { collector.addRule( `.monaco-editor .reference-zone-widget .preview .monaco-editor .monaco-editor-background,` + @@ -600,7 +578,7 @@ registerThemingParticipant((theme, collector) => { ` background-color: ${editorBackground};` + `}`); } - const editorGutterBackground = theme.getColor(peekViewEditorGutterBackground); + const editorGutterBackground = theme.getColor(peekView.peekViewEditorGutterBackground); if (editorGutterBackground) { collector.addRule( `.monaco-editor .reference-zone-widget .preview .monaco-editor .margin {` + diff --git a/src/vs/editor/contrib/referenceSearch/referencesModel.ts b/src/vs/editor/contrib/gotoSymbol/referencesModel.ts similarity index 78% rename from src/vs/editor/contrib/referenceSearch/referencesModel.ts rename to src/vs/editor/contrib/gotoSymbol/referencesModel.ts index 7135aae7c4..ce83d9b8e8 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesModel.ts +++ b/src/vs/editor/contrib/gotoSymbol/referencesModel.ts @@ -18,18 +18,15 @@ import { IMatch } from 'vs/base/common/filters'; import { Constants } from 'vs/base/common/uint'; export class OneReference { - readonly id: string; - private readonly _onRefChanged = new Emitter(); - readonly onRefChanged: Event = this._onRefChanged.event; + readonly id: string = defaultGenerator.nextId(); constructor( + readonly isProviderFirst: boolean, readonly parent: FileReferences, private _range: IRange, - readonly isProviderFirst: boolean - ) { - this.id = defaultGenerator.nextId(); - } + private _rangeCallback: (ref: OneReference) => void + ) { } get uri(): URI { return this.parent.uri; @@ -41,10 +38,10 @@ export class OneReference { set range(value: IRange) { this._range = value; - this._onRefChanged.fire(this); + this._rangeCallback(this); } - getAriaMessage(): string { + get ariaMessage(): string { return localize( 'aria.oneReference', "symbol in {0} on line {1} at column {2}", basename(this.uri), this.range.startLineNumber, this.range.startColumn @@ -56,11 +53,10 @@ export class FilePreview implements IDisposable { constructor( private readonly _modelReference: IReference - ) { - } + ) { } dispose(): void { - dispose(this._modelReference); + this._modelReference.dispose(); } preview(range: IRange, n: number = 8): { value: string; highlight: IMatch } | undefined { @@ -88,29 +84,20 @@ export class FilePreview implements IDisposable { export class FileReferences implements IDisposable { - private _children: OneReference[]; + readonly children: OneReference[] = []; + private _preview?: FilePreview; private _resolved?: boolean; - private _loadFailure: any; + private _loadFailure?: any; - constructor(private readonly _parent: ReferencesModel, private readonly _uri: URI) { - this._children = []; - } + constructor( + readonly parent: ReferencesModel, + readonly uri: URI + ) { } - get id(): string { - return this._uri.toString(); - } - - get parent(): ReferencesModel { - return this._parent; - } - - get children(): OneReference[] { - return this._children; - } - - get uri(): URI { - return this._uri; + dispose(): void { + dispose(this._preview); + this._preview = undefined; } get preview(): FilePreview | undefined { @@ -121,7 +108,7 @@ export class FileReferences implements IDisposable { return this._loadFailure; } - getAriaMessage(): string { + get ariaMessage(): string { const len = this.children.length; if (len === 1) { return localize('aria.fileReferences.1', "1 symbol in {0}, full path {1}", basename(this.uri), this.uri.fsPath); @@ -136,7 +123,7 @@ export class FileReferences implements IDisposable { return Promise.resolve(this); } - return Promise.resolve(textModelResolverService.createModelReference(this._uri).then(modelReference => { + return Promise.resolve(textModelResolverService.createModelReference(this.uri).then(modelReference => { const model = modelReference.object; if (!model) { @@ -150,62 +137,76 @@ export class FileReferences implements IDisposable { }, err => { // something wrong here - this._children = []; + this.children.length = 0; this._resolved = true; this._loadFailure = err; return this; })); } - - dispose(): void { - if (this._preview) { - this._preview.dispose(); - this._preview = undefined; - } - } } export class ReferencesModel implements IDisposable { private readonly _disposables = new DisposableStore(); + private readonly _links: LocationLink[]; + private readonly _title: string; + readonly groups: FileReferences[] = []; readonly references: OneReference[] = []; readonly _onDidChangeReferenceRange = new Emitter(); readonly onDidChangeReferenceRange: Event = this._onDidChangeReferenceRange.event; - constructor(references: LocationLink[]) { + constructor(links: LocationLink[], title: string) { + this._links = links; + this._title = title; // grouping and sorting - const [providersFirst] = references; - references.sort(ReferencesModel._compareReferences); + const [providersFirst] = links; + links.sort(ReferencesModel._compareReferences); let current: FileReferences | undefined; - for (let ref of references) { - if (!current || current.uri.toString() !== ref.uri.toString()) { + for (let link of links) { + if (!current || current.uri.toString() !== link.uri.toString()) { // new group - current = new FileReferences(this, ref.uri); + current = new FileReferences(this, link.uri); this.groups.push(current); } // append, check for equality first! - if (current.children.length === 0 - || !Range.equalsRange(ref.range, current.children[current.children.length - 1].range)) { + if (current.children.length === 0 || !Range.equalsRange(link.range, current.children[current.children.length - 1].range)) { - let oneRef = new OneReference(current, ref.targetSelectionRange || ref.range, providersFirst === ref); - this._disposables.add(oneRef.onRefChanged((e) => this._onDidChangeReferenceRange.fire(e))); + const oneRef = new OneReference( + providersFirst === link, current, link.targetSelectionRange || link.range, + ref => this._onDidChangeReferenceRange.fire(ref) + ); this.references.push(oneRef); current.children.push(oneRef); } } } - get empty(): boolean { + dispose(): void { + dispose(this.groups); + this._disposables.dispose(); + this._onDidChangeReferenceRange.dispose(); + this.groups.length = 0; + } + + clone(): ReferencesModel { + return new ReferencesModel(this._links, this._title); + } + + get title(): string { + return this._title; + } + + get isEmpty(): boolean { return this.groups.length === 0; } - getAriaMessage(): string { - if (this.empty) { + get ariaMessage(): string { + if (this.isEmpty) { return localize('aria.result.0', "No results found"); } else if (this.references.length === 1) { return localize('aria.result.1', "Found 1 symbol in {0}", this.references[0].uri.fsPath); @@ -272,6 +273,17 @@ export class ReferencesModel implements IDisposable { return undefined; } + referenceAt(resource: URI, position: Position): OneReference | undefined { + for (const ref of this.references) { + if (ref.uri.toString() === resource.toString()) { + if (Range.containsPosition(ref.range, position)) { + return ref; + } + } + } + return undefined; + } + firstReference(): OneReference | undefined { for (const ref of this.references) { if (ref.isProviderFirst) { @@ -281,21 +293,7 @@ export class ReferencesModel implements IDisposable { return this.references[0]; } - dispose(): void { - dispose(this.groups); - this._disposables.dispose(); - this.groups.length = 0; - } - private static _compareReferences(a: Location, b: Location): number { - const auri = a.uri.toString(); - const buri = b.uri.toString(); - if (auri < buri) { - return -1; - } else if (auri > buri) { - return 1; - } else { - return Range.compareRangesUsingStarts(a.range, b.range); - } + return strings.compare(a.uri.toString(), b.uri.toString()) || Range.compareRangesUsingStarts(a.range, b.range); } } diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinitionResultsNavigation.ts b/src/vs/editor/contrib/gotoSymbol/symbolNavigation.ts similarity index 99% rename from src/vs/editor/contrib/goToDefinition/goToDefinitionResultsNavigation.ts rename to src/vs/editor/contrib/gotoSymbol/symbolNavigation.ts index 495cf11f11..85b00f975e 100644 --- a/src/vs/editor/contrib/goToDefinition/goToDefinitionResultsNavigation.ts +++ b/src/vs/editor/contrib/gotoSymbol/symbolNavigation.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ReferencesModel, OneReference } from 'vs/editor/contrib/referenceSearch/referencesModel'; +import { ReferencesModel, OneReference } from 'vs/editor/contrib/gotoSymbol/referencesModel'; import { RawContextKey, IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; diff --git a/src/vs/editor/contrib/referenceSearch/test/referencesModel.test.ts b/src/vs/editor/contrib/gotoSymbol/test/referencesModel.test.ts similarity index 93% rename from src/vs/editor/contrib/referenceSearch/test/referencesModel.test.ts rename to src/vs/editor/contrib/gotoSymbol/test/referencesModel.test.ts index 1f8f64c1d8..742c6d8b22 100644 --- a/src/vs/editor/contrib/referenceSearch/test/referencesModel.test.ts +++ b/src/vs/editor/contrib/gotoSymbol/test/referencesModel.test.ts @@ -2,11 +2,12 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ + import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { Range } from 'vs/editor/common/core/range'; import { Position } from 'vs/editor/common/core/position'; -import { ReferencesModel } from 'vs/editor/contrib/referenceSearch/referencesModel'; +import { ReferencesModel } from 'vs/editor/contrib/gotoSymbol/referencesModel'; suite('references', function () { @@ -20,7 +21,7 @@ suite('references', function () { }, { uri: URI.file('/src/can'), range: new Range(1, 1, 1, 1) - }]); + }], 'FOO'); let ref = model.nearestReference(URI.file('/src/can'), new Position(1, 1)); assert.equal(ref!.uri.path, '/src/can'); diff --git a/src/vs/editor/contrib/hover/hover.css b/src/vs/editor/contrib/hover/hover.css index b1a4f32e97..4984d83707 100644 --- a/src/vs/editor/contrib/hover/hover.css +++ b/src/vs/editor/contrib/hover/hover.css @@ -8,12 +8,9 @@ position: absolute; overflow: hidden; z-index: 50; + user-select: text; -webkit-user-select: text; -ms-user-select: text; - -khtml-user-select: text; - -moz-user-select: text; - -o-user-select: text; - user-select: text; box-sizing: initial; animation: fadein 100ms linear; line-height: 1.5em; @@ -32,6 +29,10 @@ word-wrap: break-word; } +.monaco-editor-hover .markdown-hover > .hover-contents:not(.code-hover-contents) hr { + min-width: 100vw; +} + .monaco-editor-hover p, .monaco-editor-hover ul { margin: 8px 0; @@ -103,3 +104,9 @@ .monaco-editor-hover .hover-row.status-bar .actions .action-container .action .icon { padding-right: 4px; } + +.monaco-editor-hover .markdown-hover .hover-contents .codicon { + color: inherit; + font-size: inherit; + vertical-align: middle; +} diff --git a/src/vs/editor/contrib/hover/hover.ts b/src/vs/editor/contrib/hover/hover.ts index c970018737..5642e4c6d5 100644 --- a/src/vs/editor/contrib/hover/hover.ts +++ b/src/vs/editor/contrib/hover/hover.ts @@ -21,11 +21,12 @@ import { ModesContentHoverWidget } from 'vs/editor/contrib/hover/modesContentHov import { ModesGlyphHoverWidget } from 'vs/editor/contrib/hover/modesGlyphHover'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { editorHoverBackground, editorHoverBorder, editorHoverHighlight, textCodeBlockBackground, textLinkForeground, editorHoverStatusBarBackground } from 'vs/platform/theme/common/colorRegistry'; +import { editorHoverBackground, editorHoverBorder, editorHoverHighlight, textCodeBlockBackground, textLinkForeground, editorHoverStatusBarBackground, editorHoverForeground } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; +import { GotoDefinitionAtPositionEditorContribution } from 'vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition'; export class ModesHoverController implements IEditorContribution { @@ -258,8 +259,50 @@ class ShowHoverAction extends EditorAction { } } +class ShowDefinitionPreviewHoverAction extends EditorAction { + + constructor() { + super({ + id: 'editor.action.showDefinitionPreviewHover', + label: nls.localize({ + key: 'showDefinitionPreviewHover', + comment: [ + 'Label for action that will trigger the showing of definition preview hover in the editor.', + 'This allows for users to show the definition preview hover without using the mouse.' + ] + }, "Show Definition Preview Hover"), + alias: 'Show Definition Preview Hover', + precondition: undefined + }); + } + + public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + let controller = ModesHoverController.get(editor); + if (!controller) { + return; + } + const position = editor.getPosition(); + + if (!position) { + return; + } + + const range = new Range(position.lineNumber, position.column, position.lineNumber, position.column); + const goto = GotoDefinitionAtPositionEditorContribution.get(editor); + const promise = goto.startFindDefinitionFromCursor(position); + if (promise) { + promise.then(() => { + controller.showContentHover(range, HoverStartMode.Immediate, true); + }); + } else { + controller.showContentHover(range, HoverStartMode.Immediate, true); + } + } +} + registerEditorContribution(ModesHoverController.ID, ModesHoverController); registerEditorAction(ShowHoverAction); +registerEditorAction(ShowDefinitionPreviewHoverAction); // theming registerThemingParticipant((theme, collector) => { @@ -282,6 +325,10 @@ registerThemingParticipant((theme, collector) => { if (link) { collector.addRule(`.monaco-editor .monaco-editor-hover a { color: ${link}; }`); } + const hoverForeground = theme.getColor(editorHoverForeground); + if (hoverForeground) { + collector.addRule(`.monaco-editor .monaco-editor-hover { color: ${hoverForeground}; }`); + } const actionsBackground = theme.getColor(editorHoverStatusBarBackground); if (actionsBackground) { collector.addRule(`.monaco-editor .monaco-editor-hover .hover-row .actions { background-color: ${actionsBackground}; }`); diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index c44fe4dd2b..dd2d5bff25 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -34,7 +34,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { getCodeActions, CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; import { QuickFixAction, QuickFixController } from 'vs/editor/contrib/codeAction/codeActionCommands'; -import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -207,7 +207,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { private readonly _themeService: IThemeService, private readonly _keybindingService: IKeybindingService, private readonly _modeService: IModeService, - private readonly _openerService: IOpenerService | null = NullOpenerService, + private readonly _openerService: IOpenerService = NullOpenerService, ) { super(ModesContentHoverWidget.ID, editor); @@ -354,7 +354,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { containColorPicker = true; const { red, green, blue, alpha } = msg.color; - const rgba = new RGBA(red * 255, green * 255, blue * 255, alpha); + const rgba = new RGBA(Math.round(red * 255), Math.round(green * 255), Math.round(blue * 255), alpha); const color = new Color(rgba); if (!this._editor.hasModel()) { @@ -548,7 +548,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { quickfixPlaceholderElement.style.transition = ''; quickfixPlaceholderElement.style.opacity = '1'; - if (!actions.actions.length) { + if (!actions.validActions.length) { actions.dispose(); quickfixPlaceholderElement.textContent = nls.localize('noQuickFixes', "No quick fixes available"); return; @@ -586,7 +586,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { return getCodeActions( this._editor.getModel()!, new Range(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn), - { type: 'manual', filter: { kind: CodeActionKind.QuickFix } }, + { type: 'manual', filter: { include: CodeActionKind.QuickFix } }, cancellationToken); }); } diff --git a/src/vs/editor/contrib/hover/modesGlyphHover.ts b/src/vs/editor/contrib/hover/modesGlyphHover.ts index 2975d685b2..06afb72578 100644 --- a/src/vs/editor/contrib/hover/modesGlyphHover.ts +++ b/src/vs/editor/contrib/hover/modesGlyphHover.ts @@ -97,7 +97,7 @@ export class ModesGlyphHoverWidget extends GlyphHoverWidget { constructor( editor: ICodeEditor, modeService: IModeService, - openerService: IOpenerService | null = NullOpenerService, + openerService: IOpenerService = NullOpenerService, ) { super(ModesGlyphHoverWidget.ID, editor); diff --git a/src/vs/editor/contrib/indentation/indentation.ts b/src/vs/editor/contrib/indentation/indentation.ts index 97f773336f..2be294e28b 100644 --- a/src/vs/editor/contrib/indentation/indentation.ts +++ b/src/vs/editor/contrib/indentation/indentation.ts @@ -22,7 +22,7 @@ import { IndentConsts } from 'vs/editor/common/modes/supports/indentRules'; import { IModelService } from 'vs/editor/common/services/modelService'; import * as indentUtils from 'vs/editor/contrib/indentation/indentUtils'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { EditorOption, EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions'; export function getReindentEditOperations(model: ITextModel, startLineNumber: number, endLineNumber: number, inheritedIndent?: string): IIdentifiedSingleEditOperation[] { if (model.getLineCount() === 1 && model.getLineMaxColumn(1) === 1) { @@ -442,7 +442,7 @@ export class AutoIndentOnPaste implements IEditorContribution { this.callOnModel.clear(); // we are disabled - if (!this.editor.getOption(EditorOption.autoIndent) || this.editor.getOption(EditorOption.formatOnPaste)) { + if (this.editor.getOption(EditorOption.autoIndent) < EditorAutoIndentStrategy.Full || this.editor.getOption(EditorOption.formatOnPaste)) { return; } @@ -470,6 +470,7 @@ export class AutoIndentOnPaste implements IEditorContribution { if (!model.isCheapToTokenize(range.getStartPosition().lineNumber)) { return; } + const autoIndent = this.editor.getOption(EditorOption.autoIndent); const { tabSize, indentSize, insertSpaces } = model.getOptions(); this.editor.pushUndoStop(); let textEdits: TextEdit[] = []; @@ -499,7 +500,7 @@ export class AutoIndentOnPaste implements IEditorContribution { let firstLineText = model.getLineContent(startLineNumber); if (!/\S/.test(firstLineText.substring(0, range.startColumn - 1))) { - let indentOfFirstLine = LanguageConfigurationRegistry.getGoodIndentForLine(model, model.getLanguageIdentifier().id, startLineNumber, indentConverter); + let indentOfFirstLine = LanguageConfigurationRegistry.getGoodIndentForLine(autoIndent, model, model.getLanguageIdentifier().id, startLineNumber, indentConverter); if (indentOfFirstLine !== null) { let oldIndentation = strings.getLeadingWhitespace(firstLineText); @@ -557,7 +558,7 @@ export class AutoIndentOnPaste implements IEditorContribution { } } }; - let indentOfSecondLine = LanguageConfigurationRegistry.getGoodIndentForLine(virtualModel, model.getLanguageIdentifier().id, startLineNumber + 1, indentConverter); + let indentOfSecondLine = LanguageConfigurationRegistry.getGoodIndentForLine(autoIndent, virtualModel, model.getLanguageIdentifier().id, startLineNumber + 1, indentConverter); if (indentOfSecondLine !== null) { let newSpaceCntOfSecondLine = indentUtils.getSpaceCnt(indentOfSecondLine, tabSize); let oldSpaceCntOfSecondLine = indentUtils.getSpaceCnt(strings.getLeadingWhitespace(model.getLineContent(startLineNumber + 1)), tabSize); diff --git a/src/vs/editor/contrib/linesOperations/linesOperations.ts b/src/vs/editor/contrib/linesOperations/linesOperations.ts index 6416da84d4..087cb8d303 100644 --- a/src/vs/editor/contrib/linesOperations/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/linesOperations.ts @@ -64,7 +64,7 @@ class CopyLinesUpAction extends AbstractCopyLinesAction { linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.UpArrow }, weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarSelectionMenu, group: '2_line', title: nls.localize({ key: 'miCopyLinesUp', comment: ['&& denotes a mnemonic'] }, "&&Copy Line Up"), @@ -87,7 +87,7 @@ class CopyLinesDownAction extends AbstractCopyLinesAction { linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.DownArrow }, weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarSelectionMenu, group: '2_line', title: nls.localize({ key: 'miCopyLinesDown', comment: ['&& denotes a mnemonic'] }, "Co&&py Line Down"), @@ -105,7 +105,7 @@ export class DuplicateSelectionAction extends EditorAction { label: nls.localize('duplicateSelection', "Duplicate Selection"), alias: 'Duplicate Selection', precondition: EditorContextKeys.writable, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarSelectionMenu, group: '2_line', title: nls.localize({ key: 'miDuplicateSelection', comment: ['&& denotes a mnemonic'] }, "&&Duplicate Selection"), @@ -178,7 +178,7 @@ class MoveLinesUpAction extends AbstractMoveLinesAction { linux: { primary: KeyMod.Alt | KeyCode.UpArrow }, weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarSelectionMenu, group: '2_line', title: nls.localize({ key: 'miMoveLinesUp', comment: ['&& denotes a mnemonic'] }, "Mo&&ve Line Up"), @@ -201,7 +201,7 @@ class MoveLinesDownAction extends AbstractMoveLinesAction { linux: { primary: KeyMod.Alt | KeyCode.DownArrow }, weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarSelectionMenu, group: '2_line', title: nls.localize({ key: 'miMoveLinesDown', comment: ['&& denotes a mnemonic'] }, "Move &&Line Down"), diff --git a/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts b/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts index 6ff27f71a5..0319b62a64 100644 --- a/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts +++ b/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts @@ -13,18 +13,19 @@ import { IndentAction } from 'vs/editor/common/modes/languageConfiguration'; import { IIndentConverter, LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { IndentConsts } from 'vs/editor/common/modes/supports/indentRules'; import * as indentUtils from 'vs/editor/contrib/indentation/indentUtils'; +import { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions'; export class MoveLinesCommand implements ICommand { private readonly _selection: Selection; private readonly _isMovingDown: boolean; - private readonly _autoIndent: boolean; + private readonly _autoIndent: EditorAutoIndentStrategy; private _selectionId: string | null; private _moveEndPositionDown?: boolean; private _moveEndLineSelectionShrink: boolean; - constructor(selection: Selection, isMovingDown: boolean, autoIndent: boolean) { + constructor(selection: Selection, isMovingDown: boolean, autoIndent: EditorAutoIndentStrategy) { this._selection = selection; this._isMovingDown = isMovingDown; this._autoIndent = autoIndent; @@ -117,7 +118,7 @@ export class MoveLinesCommand implements ICommand { return model.getLineContent(lineNumber); } }; - let indentOfMovingLine = LanguageConfigurationRegistry.getGoodIndentForLine(virtualModel, model.getLanguageIdAtPosition( + let indentOfMovingLine = LanguageConfigurationRegistry.getGoodIndentForLine(this._autoIndent, virtualModel, model.getLanguageIdAtPosition( movingLineNumber, 1), s.startLineNumber, indentConverter); if (indentOfMovingLine !== null) { let oldIndentation = strings.getLeadingWhitespace(model.getLineContent(movingLineNumber)); @@ -152,7 +153,7 @@ export class MoveLinesCommand implements ICommand { } }; - let newIndentatOfMovingBlock = LanguageConfigurationRegistry.getGoodIndentForLine(virtualModel, model.getLanguageIdAtPosition( + let newIndentatOfMovingBlock = LanguageConfigurationRegistry.getGoodIndentForLine(this._autoIndent, virtualModel, model.getLanguageIdAtPosition( movingLineNumber, 1), s.startLineNumber + 1, indentConverter); if (newIndentatOfMovingBlock !== null) { @@ -197,7 +198,7 @@ export class MoveLinesCommand implements ICommand { } } else { // it doesn't match any onEnter rule, let's check indentation rules then. - let indentOfFirstLine = LanguageConfigurationRegistry.getGoodIndentForLine(virtualModel, model.getLanguageIdAtPosition(s.startLineNumber, 1), movingLineNumber, indentConverter); + let indentOfFirstLine = LanguageConfigurationRegistry.getGoodIndentForLine(this._autoIndent, virtualModel, model.getLanguageIdAtPosition(s.startLineNumber, 1), movingLineNumber, indentConverter); if (indentOfFirstLine !== null) { // adjust the indentation of the moving block let oldIndent = strings.getLeadingWhitespace(model.getLineContent(s.startLineNumber)); @@ -251,20 +252,19 @@ export class MoveLinesCommand implements ICommand { } let maxColumn = model.getLineMaxColumn(validPrecedingLine); - let enter = LanguageConfigurationRegistry.getEnterAction(model, new Range(validPrecedingLine, maxColumn, validPrecedingLine, maxColumn)); + let enter = LanguageConfigurationRegistry.getEnterAction(this._autoIndent, model, new Range(validPrecedingLine, maxColumn, validPrecedingLine, maxColumn)); if (enter) { let enterPrefix = enter.indentation; - let enterAction = enter.enterAction; - if (enterAction.indentAction === IndentAction.None) { - enterPrefix = enter.indentation + enterAction.appendText; - } else if (enterAction.indentAction === IndentAction.Indent) { - enterPrefix = enter.indentation + enterAction.appendText; - } else if (enterAction.indentAction === IndentAction.IndentOutdent) { + if (enter.indentAction === IndentAction.None) { + enterPrefix = enter.indentation + enter.appendText; + } else if (enter.indentAction === IndentAction.Indent) { + enterPrefix = enter.indentation + enter.appendText; + } else if (enter.indentAction === IndentAction.IndentOutdent) { enterPrefix = enter.indentation; - } else if (enterAction.indentAction === IndentAction.Outdent) { - enterPrefix = indentConverter.unshiftIndent(enter.indentation) + enterAction.appendText; + } else if (enter.indentAction === IndentAction.Outdent) { + enterPrefix = indentConverter.unshiftIndent(enter.indentation) + enter.appendText; } let movingLineText = model.getLineContent(line); if (this.trimLeft(movingLineText).indexOf(this.trimLeft(enterPrefix)) >= 0) { @@ -288,7 +288,7 @@ export class MoveLinesCommand implements ICommand { } private shouldAutoIndent(model: ITextModel, selection: Selection) { - if (!this._autoIndent) { + if (this._autoIndent < EditorAutoIndentStrategy.Full) { return false; } // if it's not easy to tokenize, we stop auto indent. diff --git a/src/vs/editor/contrib/linesOperations/sortLinesCommand.ts b/src/vs/editor/contrib/linesOperations/sortLinesCommand.ts index 3dad41df20..48b63cfc05 100644 --- a/src/vs/editor/contrib/linesOperations/sortLinesCommand.ts +++ b/src/vs/editor/contrib/linesOperations/sortLinesCommand.ts @@ -11,6 +11,14 @@ import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/mod export class SortLinesCommand implements editorCommon.ICommand { + private static _COLLATOR: Intl.Collator | null = null; + public static getCollator(): Intl.Collator { + if (!SortLinesCommand._COLLATOR) { + SortLinesCommand._COLLATOR = new Intl.Collator(); + } + return SortLinesCommand._COLLATOR; + } + private readonly selection: Selection; private readonly descending: boolean; private selectionId: string | null; @@ -76,9 +84,7 @@ function getSortData(model: ITextModel, selection: Selection, descending: boolea } let sorted = linesToSort.slice(0); - sorted.sort((a, b) => { - return a.toLowerCase().localeCompare(b.toLowerCase()); - }); + sorted.sort(SortLinesCommand.getCollator().compare); // If descending, reverse the order. if (descending === true) { diff --git a/src/vs/editor/contrib/linesOperations/test/moveLinesCommand.test.ts b/src/vs/editor/contrib/linesOperations/test/moveLinesCommand.test.ts index ca6ef0bc44..cfc8d7aa32 100644 --- a/src/vs/editor/contrib/linesOperations/test/moveLinesCommand.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/moveLinesCommand.test.ts @@ -9,21 +9,22 @@ import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageCo import { MoveLinesCommand } from 'vs/editor/contrib/linesOperations/moveLinesCommand'; import { testCommand } from 'vs/editor/test/browser/testCommand'; import { MockMode } from 'vs/editor/test/common/mocks/mockMode'; +import { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions'; function testMoveLinesDownCommand(lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void { - testCommand(lines, null, selection, (sel) => new MoveLinesCommand(sel, true, false), expectedLines, expectedSelection); + testCommand(lines, null, selection, (sel) => new MoveLinesCommand(sel, true, EditorAutoIndentStrategy.Advanced), expectedLines, expectedSelection); } function testMoveLinesUpCommand(lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void { - testCommand(lines, null, selection, (sel) => new MoveLinesCommand(sel, false, false), expectedLines, expectedSelection); + testCommand(lines, null, selection, (sel) => new MoveLinesCommand(sel, false, EditorAutoIndentStrategy.Advanced), expectedLines, expectedSelection); } function testMoveLinesDownWithIndentCommand(languageId: LanguageIdentifier, lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void { - testCommand(lines, languageId, selection, (sel) => new MoveLinesCommand(sel, true, true), expectedLines, expectedSelection); + testCommand(lines, languageId, selection, (sel) => new MoveLinesCommand(sel, true, EditorAutoIndentStrategy.Full), expectedLines, expectedSelection); } function testMoveLinesUpWithIndentCommand(languageId: LanguageIdentifier, lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void { - testCommand(lines, languageId, selection, (sel) => new MoveLinesCommand(sel, false, true), expectedLines, expectedSelection); + testCommand(lines, languageId, selection, (sel) => new MoveLinesCommand(sel, false, EditorAutoIndentStrategy.Full), expectedLines, expectedSelection); } suite('Editor Contrib - Move Lines Command', () => { diff --git a/src/vs/editor/contrib/links/getLinks.ts b/src/vs/editor/contrib/links/getLinks.ts index 883b00ba04..2dbd50bf8a 100644 --- a/src/vs/editor/contrib/links/getLinks.ts +++ b/src/vs/editor/contrib/links/getLinks.ts @@ -44,17 +44,9 @@ export class Link implements ILink { return this._link.tooltip; } - resolve(token: CancellationToken): Promise { + async resolve(token: CancellationToken): Promise { if (this._link.url) { - try { - if (typeof this._link.url === 'string') { - return Promise.resolve(URI.parse(this._link.url)); - } else { - return Promise.resolve(this._link.url); - } - } catch (e) { - return Promise.reject(new Error('invalid')); - } + return this._link.url; } if (typeof this._provider.resolveLink === 'function') { diff --git a/src/vs/editor/contrib/links/links.ts b/src/vs/editor/contrib/links/links.ts index d6b1646443..19b1df1e12 100644 --- a/src/vs/editor/contrib/links/links.ts +++ b/src/vs/editor/contrib/links/links.ts @@ -18,7 +18,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { LinkProviderRegistry } from 'vs/editor/common/modes'; -import { ClickLinkGesture, ClickLinkKeyboardEvent, ClickLinkMouseEvent } from 'vs/editor/contrib/goToDefinition/clickLinkGesture'; +import { ClickLinkGesture, ClickLinkKeyboardEvent, ClickLinkMouseEvent } from 'vs/editor/contrib/gotoSymbol/link/clickLinkGesture'; import { Link, getLinks, LinksList } from 'vs/editor/contrib/links/getLinks'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; diff --git a/src/vs/editor/contrib/markdown/markdownRenderer.ts b/src/vs/editor/contrib/markdown/markdownRenderer.ts index d11fa34ca4..d280470bb4 100644 --- a/src/vs/editor/contrib/markdown/markdownRenderer.ts +++ b/src/vs/editor/contrib/markdown/markdownRenderer.ts @@ -7,7 +7,6 @@ import { IMarkdownString } from 'vs/base/common/htmlContent'; import { renderMarkdown, MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer'; import { IOpenerService, NullOpenerService } from 'vs/platform/opener/common/opener'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { URI } from 'vs/base/common/uri'; import { onUnexpectedError } from 'vs/base/common/errors'; import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -29,7 +28,7 @@ export class MarkdownRenderer extends Disposable { constructor( private readonly _editor: ICodeEditor, @IModeService private readonly _modeService: IModeService, - @optional(IOpenerService) private readonly _openerService: IOpenerService | null = NullOpenerService, + @optional(IOpenerService) private readonly _openerService: IOpenerService = NullOpenerService, ) { super(); } @@ -64,15 +63,7 @@ export class MarkdownRenderer extends Disposable { codeBlockRenderCallback: () => this._onDidRenderCodeBlock.fire(), actionHandler: { callback: (content) => { - let uri: URI | undefined; - try { - uri = URI.parse(content); - } catch { - // ignore - } - if (uri && this._openerService) { - this._openerService.open(uri, { fromUserGesture: true }).catch(onUnexpectedError); - } + this._openerService.open(content, { fromUserGesture: true }).catch(onUnexpectedError); }, disposeables } diff --git a/src/vs/editor/contrib/multicursor/multicursor.ts b/src/vs/editor/contrib/multicursor/multicursor.ts index 4e5db3b092..69e303ab99 100644 --- a/src/vs/editor/contrib/multicursor/multicursor.ts +++ b/src/vs/editor/contrib/multicursor/multicursor.ts @@ -46,7 +46,7 @@ export class InsertCursorAbove extends EditorAction { }, weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarSelectionMenu, group: '3_multi', title: nls.localize({ key: 'miInsertCursorAbove', comment: ['&& denotes a mnemonic'] }, "&&Add Cursor Above"), @@ -95,7 +95,7 @@ export class InsertCursorBelow extends EditorAction { }, weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarSelectionMenu, group: '3_multi', title: nls.localize({ key: 'miInsertCursorBelow', comment: ['&& denotes a mnemonic'] }, "A&&dd Cursor Below"), @@ -140,7 +140,7 @@ class InsertCursorAtEndOfEachLineSelected extends EditorAction { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_I, weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarSelectionMenu, group: '3_multi', title: nls.localize({ key: 'miInsertCursorAtEndOfEachLineSelected', comment: ['&& denotes a mnemonic'] }, "Add C&&ursors to Line Ends"), @@ -603,6 +603,17 @@ export class MultiCursorSelectionController extends Disposable implements IEdito matches = this._session.selectAll(); } + if (findState.searchScope) { + const state = findState.searchScope; + let inSelection: FindMatch[] | null = []; + for (let i = 0; i < matches.length; i++) { + if (matches[i].range.endLineNumber <= state.endLineNumber && matches[i].range.startLineNumber >= state.startLineNumber) { + inSelection.push(matches[i]); + } + } + matches = inSelection; + } + if (matches.length > 0) { const editorSelection = this._editor.getSelection(); // Have the primary cursor remain the one where the action was invoked @@ -620,6 +631,12 @@ export class MultiCursorSelectionController extends Disposable implements IEdito this._setSelections(matches.map(m => new Selection(m.range.startLineNumber, m.range.startColumn, m.range.endLineNumber, m.range.endColumn))); } } + + public selectAllUsingSelections(selections: Selection[]): void { + if (selections.length > 0) { + this._setSelections(selections); + } + } } export abstract class MultiCursorSelectionControllerAction extends EditorAction { @@ -651,7 +668,7 @@ export class AddSelectionToNextFindMatchAction extends MultiCursorSelectionContr primary: KeyMod.CtrlCmd | KeyCode.KEY_D, weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarSelectionMenu, group: '3_multi', title: nls.localize({ key: 'miAddSelectionToNextFindMatch', comment: ['&& denotes a mnemonic'] }, "Add &&Next Occurrence"), @@ -671,7 +688,7 @@ export class AddSelectionToPreviousFindMatchAction extends MultiCursorSelectionC label: nls.localize('addSelectionToPreviousFindMatch', "Add Selection To Previous Find Match"), alias: 'Add Selection To Previous Find Match', precondition: undefined, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarSelectionMenu, group: '3_multi', title: nls.localize({ key: 'miAddSelectionToPreviousFindMatch', comment: ['&& denotes a mnemonic'] }, "Add P&&revious Occurrence"), @@ -729,7 +746,7 @@ export class SelectHighlightsAction extends MultiCursorSelectionControllerAction primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_L, weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarSelectionMenu, group: '3_multi', title: nls.localize({ key: 'miSelectHighlights', comment: ['&& denotes a mnemonic'] }, "Select All &&Occurrences"), @@ -754,7 +771,7 @@ export class CompatChangeAll extends MultiCursorSelectionControllerAction { primary: KeyMod.CtrlCmd | KeyCode.F2, weight: KeybindingWeight.EditorContrib }, - menuOpts: { + contextMenuOpts: { group: '1_modification', order: 1.2 } diff --git a/src/vs/editor/contrib/parameterHints/parameterHints.css b/src/vs/editor/contrib/parameterHints/parameterHints.css index c63bd0381b..7420daafd3 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHints.css +++ b/src/vs/editor/contrib/parameterHints/parameterHints.css @@ -13,12 +13,12 @@ .monaco-editor .parameter-hints-widget > .wrapper { max-width: 440px; display: flex; - flex-direction: column; + flex-direction: row; } .monaco-editor .parameter-hints-widget.multiple { min-height: 3.3em; - padding: 0 0 0 1.9em; + padding: 0; } .monaco-editor .parameter-hints-widget.visible { @@ -62,20 +62,19 @@ padding: 0 0.4em; } -.monaco-editor .parameter-hints-widget .buttons { - position: absolute; +.monaco-editor .parameter-hints-widget .controls { display: none; - bottom: 0; - left: 0; + flex-direction: column; + align-items: center; + min-width: 22px; + justify-content: flex-end; } -.monaco-editor .parameter-hints-widget.multiple .buttons { - display: block; +.monaco-editor .parameter-hints-widget.multiple .controls { + display: flex; } .monaco-editor .parameter-hints-widget.multiple .button { - position: absolute; - left: 2px; width: 16px; height: 16px; background-repeat: no-repeat; @@ -88,26 +87,16 @@ } .monaco-editor .parameter-hints-widget .button.next { - bottom: 0; background-image: url('arrow-down.svg'); } .monaco-editor .parameter-hints-widget .overloads { - position: absolute; - display: none; text-align: center; - bottom: 14px; - left: 0; - width: 22px; height: 12px; line-height: 12px; opacity: 0.5; } -.monaco-editor .parameter-hints-widget.multiple .overloads { - display: block; -} - .monaco-editor .parameter-hints-widget .signature .parameter.active { font-weight: bold; text-decoration: underline; diff --git a/src/vs/editor/contrib/parameterHints/parameterHintsModel.ts b/src/vs/editor/contrib/parameterHints/parameterHintsModel.ts index d6b7720afe..623d7a4859 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHintsModel.ts +++ b/src/vs/editor/contrib/parameterHints/parameterHintsModel.ts @@ -26,7 +26,7 @@ namespace ParameterHintState { Pending, } - export const Default = new class { readonly type = Type.Default; }; + export const Default = { type: Type.Default } as const; export class Pending { readonly type = Type.Pending; diff --git a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts index 5b7bdee9d5..9765ac757b 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts +++ b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts @@ -8,7 +8,7 @@ import { domEvent, stop } from 'vs/base/browser/event'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { Event } from 'vs/base/common/event'; -import { IDisposable, Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import 'vs/css!./parameterHints'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -19,19 +19,20 @@ import { Context } from 'vs/editor/contrib/parameterHints/provideSignatureHelp'; import * as nls from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { editorHoverBackground, editorHoverBorder, textCodeBlockBackground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; +import { editorHoverBackground, editorHoverBorder, textCodeBlockBackground, textLinkForeground, editorHoverForeground } from 'vs/platform/theme/common/colorRegistry'; import { HIGH_CONTRAST, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ParameterHintsModel, TriggerContext } from 'vs/editor/contrib/parameterHints/parameterHintsModel'; +import { pad } from 'vs/base/common/strings'; const $ = dom.$; -export class ParameterHintsWidget extends Disposable implements IContentWidget, IDisposable { +export class ParameterHintsWidget extends Disposable implements IContentWidget { private static readonly ID = 'editor.widget.parameterHintsWidget'; private readonly markdownRenderer: MarkdownRenderer; private readonly renderDisposeables = this._register(new DisposableStore()); - private readonly model = this._register(new MutableDisposable()); + private readonly model: ParameterHintsModel; private readonly keyVisible: IContextKey; private readonly keyMultipleSignatures: IContextKey; @@ -57,11 +58,11 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, ) { super(); this.markdownRenderer = this._register(new MarkdownRenderer(editor, modeService, openerService)); - this.model.value = new ParameterHintsModel(editor); + this.model = this._register(new ParameterHintsModel(editor)); this.keyVisible = Context.Visible.bindTo(contextKeyService); this.keyMultipleSignatures = Context.MultipleSignatures.bindTo(contextKeyService); - this._register(this.model.value.onChangedHints(newParameterHints => { + this._register(this.model.onChangedHints(newParameterHints => { if (newParameterHints) { this.show(); this.render(newParameterHints); @@ -76,9 +77,10 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, const wrapper = dom.append(element, $('.wrapper')); wrapper.tabIndex = -1; - const buttons = dom.append(wrapper, $('.buttons')); - const previous = dom.append(buttons, $('.button.previous')); - const next = dom.append(buttons, $('.button.next')); + const controls = dom.append(wrapper, $('.controls')); + const previous = dom.append(controls, $('.button.previous')); + const overloads = dom.append(controls, $('.overloads')); + const next = dom.append(controls, $('.button.next')); const onPreviousClick = stop(domEvent(previous, 'click')); this._register(onPreviousClick(this.previous, this)); @@ -86,8 +88,6 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, const onNextClick = stop(domEvent(next, 'click')); this._register(onNextClick(this.next, this)); - const overloads = dom.append(wrapper, $('.overloads')); - const body = $('.body'); const scrollbar = new DomScrollableElement(body, {}); this._register(scrollbar); @@ -134,7 +134,7 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, } private show(): void { - if (!this.model || this.visible) { + if (this.visible) { return; } @@ -153,7 +153,7 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, } private hide(): void { - if (!this.model || !this.visible) { + if (!this.visible) { return; } @@ -189,7 +189,6 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, this.domNodes.docs.innerHTML = ''; const signature = hints.signatures[hints.activeSignature]; - if (!signature) { return; } @@ -204,14 +203,13 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, if (!hasParameters) { const label = dom.append(code, $('span')); label.textContent = signature.label; - } else { this.renderParameters(code, signature, hints.activeParameter); } this.renderDisposeables.clear(); - const activeParameter = signature.parameters[hints.activeParameter]; + const activeParameter: modes.ParameterInformation | undefined = signature.parameters[hints.activeParameter]; if (activeParameter && activeParameter.documentation) { const documentation = $('span.documentation'); @@ -236,30 +234,13 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, dom.append(this.domNodes.docs, renderedContents.element); } - let hasDocs = false; - if (activeParameter && typeof (activeParameter.documentation) === 'string' && activeParameter.documentation.length > 0) { - hasDocs = true; - } - if (activeParameter && typeof (activeParameter.documentation) === 'object' && activeParameter.documentation.value.length > 0) { - hasDocs = true; - } - if (typeof (signature.documentation) === 'string' && signature.documentation.length > 0) { - hasDocs = true; - } - if (typeof (signature.documentation) === 'object' && signature.documentation.value.length > 0) { - hasDocs = true; - } + const hasDocs = this.hasDocs(signature, activeParameter); dom.toggleClass(this.domNodes.signature, 'has-docs', hasDocs); dom.toggleClass(this.domNodes.docs, 'empty', !hasDocs); - let currentOverload = String(hints.activeSignature + 1); - - if (hints.signatures.length < 10) { - currentOverload += `/${hints.signatures.length}`; - } - - this.domNodes.overloads.textContent = currentOverload; + this.domNodes.overloads.textContent = + pad(hints.activeSignature + 1, hints.signatures.length.toString().length) + '/' + hints.signatures.length; if (activeParameter) { const labelToAnnounce = this.getParameterLabel(signature, hints.activeParameter); @@ -276,8 +257,23 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, this.domNodes.scrollbar.scanDomNode(); } - private renderParameters(parent: HTMLElement, signature: modes.SignatureInformation, currentParameter: number): void { + private hasDocs(signature: modes.SignatureInformation, activeParameter: modes.ParameterInformation | undefined): boolean { + if (activeParameter && typeof (activeParameter.documentation) === 'string' && activeParameter.documentation.length > 0) { + return true; + } + if (activeParameter && typeof (activeParameter.documentation) === 'object' && activeParameter.documentation.value.length > 0) { + return true; + } + if (typeof (signature.documentation) === 'string' && signature.documentation.length > 0) { + return true; + } + if (typeof (signature.documentation) === 'object' && signature.documentation.value.length > 0) { + return true; + } + return false; + } + private renderParameters(parent: HTMLElement, signature: modes.SignatureInformation, currentParameter: number): void { const [start, end] = this.getParameterLabelOffsets(signature, currentParameter); const beforeSpan = document.createElement('span'); @@ -317,23 +313,17 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, } next(): void { - if (this.model.value) { - this.editor.focus(); - this.model.value.next(); - } + this.editor.focus(); + this.model.next(); } previous(): void { - if (this.model.value) { - this.editor.focus(); - this.model.value.previous(); - } + this.editor.focus(); + this.model.previous(); } cancel(): void { - if (this.model.value) { - this.model.value.cancel(); - } + this.model.cancel(); } getDomNode(): HTMLElement { @@ -348,9 +338,7 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, } trigger(context: TriggerContext): void { - if (this.model.value) { - this.model.value.trigger(context, 0); - } + this.model.trigger(context, 0); } private updateMaxHeight(): void { @@ -385,6 +373,11 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.monaco-editor .parameter-hints-widget a { color: ${link}; }`); } + const foreground = theme.getColor(editorHoverForeground); + if (foreground) { + collector.addRule(`.monaco-editor .parameter-hints-widget { color: ${foreground}; }`); + } + const codeBackground = theme.getColor(textCodeBlockBackground); if (codeBackground) { collector.addRule(`.monaco-editor .parameter-hints-widget code { background-color: ${codeBackground}; }`); diff --git a/src/vs/editor/contrib/referenceSearch/media/peekViewWidget.css b/src/vs/editor/contrib/peekView/media/peekViewWidget.css similarity index 87% rename from src/vs/editor/contrib/referenceSearch/media/peekViewWidget.css rename to src/vs/editor/contrib/peekView/media/peekViewWidget.css index 11a1d68eb0..966475cbb3 100644 --- a/src/vs/editor/contrib/referenceSearch/media/peekViewWidget.css +++ b/src/vs/editor/contrib/peekView/media/peekViewWidget.css @@ -4,16 +4,13 @@ *--------------------------------------------------------------------------------------------*/ .monaco-editor .peekview-widget .head { - -webkit-box-sizing: border-box; - -o-box-sizing: border-box; - -moz-box-sizing: border-box; - -ms-box-sizing: border-box; box-sizing: border-box; display: flex; } .monaco-editor .peekview-widget .head .peekview-title { - display: inline-block; + display: flex; + align-items: center; font-size: 13px; margin-left: 20px; cursor: pointer; @@ -24,6 +21,15 @@ margin-left: 0.5em; } +.monaco-editor .peekview-widget .head .peekview-title .meta { + white-space: nowrap; +} + +.monaco-editor .peekview-widget .head .peekview-title .meta:not(:empty)::before { + content: '-'; + padding: 0 0.3em; +} + .monaco-editor .peekview-widget .head .peekview-actions { flex: 1; text-align: right; diff --git a/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts b/src/vs/editor/contrib/peekView/peekView.ts similarity index 61% rename from src/vs/editor/contrib/referenceSearch/peekViewWidget.ts rename to src/vs/editor/contrib/peekView/peekView.ts index 973c90449b..6030a54d9a 100644 --- a/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts +++ b/src/vs/editor/contrib/peekView/peekView.ts @@ -3,25 +3,28 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./media/peekViewWidget'; import * as dom from 'vs/base/browser/dom'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { ActionBar, IActionBarOptions } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action } from 'vs/base/common/actions'; import { Color } from 'vs/base/common/color'; -import { Emitter, Event } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import * as objects from 'vs/base/common/objects'; import * as strings from 'vs/base/common/strings'; -import 'vs/css!./media/peekViewWidget'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { IOptions, IStyles, ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget'; import * as nls from 'vs/nls'; -import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor, createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable } from 'vs/base/common/lifecycle'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { registerColor, contrastBorder, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; export const IPeekViewService = createDecorator('IPeekViewService'); @@ -33,7 +36,7 @@ export interface IPeekViewService { registerSingleton(IPeekViewService, class implements IPeekViewService { _serviceBrand: undefined; - private _widgets = new Map(); + private readonly _widgets = new Map(); addExclusiveWidget(editor: ICodeEditor, widget: PeekViewWidget): void { const existing = this._widgets.get(editor); @@ -57,6 +60,24 @@ export namespace PeekContext { export const notInPeekEditor: ContextKeyExpr = inPeekEditor.toNegated(); } +class PeekContextController implements IEditorContribution { + + static readonly ID = 'editor.contrib.referenceController'; + + constructor( + editor: ICodeEditor, + @IContextKeyService contextKeyService: IContextKeyService + ) { + if (editor instanceof EmbeddedCodeEditorWidget) { + PeekContext.inPeekEditor.bindTo(contextKeyService); + } + } + + dispose(): void { } +} + +registerEditorContribution(PeekContextController.ID, PeekContextController); + export function getOuterEditor(accessor: ServicesAccessor): ICodeEditor | null { let editor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); if (editor instanceof EmbeddedCodeEditorWidget) { @@ -81,9 +102,10 @@ const defaultOptions: IPeekViewOptions = { export abstract class PeekViewWidget extends ZoneWidget { - public _serviceBrand: undefined; + _serviceBrand: undefined; private readonly _onDidClose = new Emitter(); + readonly onDidClose = this._onDidClose.event; protected _headElement?: HTMLDivElement; protected _primaryHeading?: HTMLElement; @@ -97,16 +119,12 @@ export abstract class PeekViewWidget extends ZoneWidget { objects.mixin(this.options, defaultOptions, false); } - public dispose(): void { + dispose(): void { super.dispose(); this._onDidClose.fire(this); } - public get onDidClose(): Event { - return this._onDidClose.event; - } - - public style(styles: IPeekViewStyles): void { + style(styles: IPeekViewStyles): void { let options = this.options; if (styles.headerBackgroundColor) { options.headerBackgroundColor = styles.headerBackgroundColor; @@ -185,7 +203,7 @@ export abstract class PeekViewWidget extends ZoneWidget { // implement me } - public setTitle(primaryHeading: string, secondaryHeading?: string): void { + setTitle(primaryHeading: string, secondaryHeading?: string): void { if (this._primaryHeading && this._secondaryHeading) { this._primaryHeading.innerHTML = strings.escape(primaryHeading); this._primaryHeading.setAttribute('aria-label', primaryHeading); @@ -197,19 +215,20 @@ export abstract class PeekViewWidget extends ZoneWidget { } } - public setMetaTitle(value: string): void { + setMetaTitle(value: string): void { if (this._metaHeading) { if (value) { this._metaHeading.innerHTML = strings.escape(value); + dom.show(this._metaHeading); } else { - dom.clearNode(this._metaHeading); + dom.hide(this._metaHeading); } } } protected abstract _fillBody(container: HTMLElement): void; - public _doLayout(heightInPixel: number, widthInPixel: number): void { + protected _doLayout(heightInPixel: number, widthInPixel: number): void { if (!this._isShowing && heightInPixel < 0) { // Looks like the view zone got folded away! @@ -237,3 +256,21 @@ export abstract class PeekViewWidget extends ZoneWidget { } } } + + +export const peekViewTitleBackground = registerColor('peekViewTitle.background', { dark: '#1E1E1E', light: '#FFFFFF', hc: '#0C141F' }, nls.localize('peekViewTitleBackground', 'Background color of the peek view title area.')); +export const peekViewTitleForeground = registerColor('peekViewTitleLabel.foreground', { dark: '#FFFFFF', light: '#333333', hc: '#FFFFFF' }, nls.localize('peekViewTitleForeground', 'Color of the peek view title.')); +export const peekViewTitleInfoForeground = registerColor('peekViewTitleDescription.foreground', { dark: '#ccccccb3', light: '#6c6c6cb3', hc: '#FFFFFF99' }, nls.localize('peekViewTitleInfoForeground', 'Color of the peek view title info.')); +export const peekViewBorder = registerColor('peekView.border', { dark: '#007acc', light: '#007acc', hc: contrastBorder }, nls.localize('peekViewBorder', 'Color of the peek view borders and arrow.')); + +export const peekViewResultsBackground = registerColor('peekViewResult.background', { dark: '#252526', light: '#F3F3F3', hc: Color.black }, nls.localize('peekViewResultsBackground', 'Background color of the peek view result list.')); +export const peekViewResultsMatchForeground = registerColor('peekViewResult.lineForeground', { dark: '#bbbbbb', light: '#646465', hc: Color.white }, nls.localize('peekViewResultsMatchForeground', 'Foreground color for line nodes in the peek view result list.')); +export const peekViewResultsFileForeground = registerColor('peekViewResult.fileForeground', { dark: Color.white, light: '#1E1E1E', hc: Color.white }, nls.localize('peekViewResultsFileForeground', 'Foreground color for file nodes in the peek view result list.')); +export const peekViewResultsSelectionBackground = registerColor('peekViewResult.selectionBackground', { dark: '#3399ff33', light: '#3399ff33', hc: null }, nls.localize('peekViewResultsSelectionBackground', 'Background color of the selected entry in the peek view result list.')); +export const peekViewResultsSelectionForeground = registerColor('peekViewResult.selectionForeground', { dark: Color.white, light: '#6C6C6C', hc: Color.white }, nls.localize('peekViewResultsSelectionForeground', 'Foreground color of the selected entry in the peek view result list.')); +export const peekViewEditorBackground = registerColor('peekViewEditor.background', { dark: '#001F33', light: '#F2F8FC', hc: Color.black }, nls.localize('peekViewEditorBackground', 'Background color of the peek view editor.')); +export const peekViewEditorGutterBackground = registerColor('peekViewEditorGutter.background', { dark: peekViewEditorBackground, light: peekViewEditorBackground, hc: peekViewEditorBackground }, nls.localize('peekViewEditorGutterBackground', 'Background color of the gutter in the peek view editor.')); + +export const peekViewResultsMatchHighlight = registerColor('peekViewResult.matchHighlightBackground', { dark: '#ea5c004d', light: '#ea5c004d', hc: null }, nls.localize('peekViewResultsMatchHighlight', 'Match highlight color in the peek view result list.')); +export const peekViewEditorMatchHighlight = registerColor('peekViewEditor.matchHighlightBackground', { dark: '#ff8f0099', light: '#f5d802de', hc: null }, nls.localize('peekViewEditorMatchHighlight', 'Match highlight color in the peek view editor.')); +export const peekViewEditorMatchHighlightBorder = registerColor('peekViewEditor.matchHighlightBorder', { dark: null, light: null, hc: activeContrastBorder }, nls.localize('peekViewEditorMatchHighlightBorder', 'Match highlight border in the peek view editor.')); diff --git a/src/vs/editor/contrib/referenceSearch/referenceSearch.ts b/src/vs/editor/contrib/referenceSearch/referenceSearch.ts deleted file mode 100644 index cfc11e26cc..0000000000 --- a/src/vs/editor/contrib/referenceSearch/referenceSearch.ts +++ /dev/null @@ -1,290 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { Position, IPosition } from 'vs/editor/common/core/position'; -import * as editorCommon from 'vs/editor/common/editorCommon'; -import { registerEditorAction, ServicesAccessor, EditorAction, registerEditorContribution, registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions'; -import { Location, ReferenceProviderRegistry } from 'vs/editor/common/modes'; -import { Range } from 'vs/editor/common/core/range'; -import { PeekContext, getOuterEditor } from './peekViewWidget'; -import { ReferencesController, RequestOptions, ctxReferenceSearchVisible } from './referencesController'; -import { ReferencesModel, OneReference } from './referencesModel'; -import { createCancelablePromise } from 'vs/base/common/async'; -import { onUnexpectedExternalError } from 'vs/base/common/errors'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; -import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { ITextModel } from 'vs/editor/common/model'; -import { IListService } from 'vs/platform/list/browser/listService'; -import { ctxReferenceWidgetSearchTreeFocused } from 'vs/editor/contrib/referenceSearch/referencesWidget'; -import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; -import { URI } from 'vs/base/common/uri'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { coalesce, flatten } from 'vs/base/common/arrays'; - -export const defaultReferenceSearchOptions: RequestOptions = { - getMetaTitle(model) { - return model.references.length > 1 ? nls.localize('meta.titleReference', " – {0} references", model.references.length) : ''; - } -}; - -export class ReferenceController implements editorCommon.IEditorContribution { - - public static readonly ID = 'editor.contrib.referenceController'; - - constructor( - editor: ICodeEditor, - @IContextKeyService contextKeyService: IContextKeyService - ) { - if (editor instanceof EmbeddedCodeEditorWidget) { - PeekContext.inPeekEditor.bindTo(contextKeyService); - } - } - - public dispose(): void { - } -} - -export class ReferenceAction extends EditorAction { - - constructor() { - super({ - id: 'editor.action.referenceSearch.trigger', - label: nls.localize('references.action.label', "Peek References"), - alias: 'Peek References', - precondition: ContextKeyExpr.and( - EditorContextKeys.hasReferenceProvider, - PeekContext.notInPeekEditor, - EditorContextKeys.isInEmbeddedEditor.toNegated()), - kbOpts: { - kbExpr: EditorContextKeys.editorTextFocus, - primary: KeyMod.Shift | KeyCode.F12, - weight: KeybindingWeight.EditorContrib - }, - menuOpts: { - group: 'navigation', - order: 1.5 - } - }); - } - - public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { - let controller = ReferencesController.get(editor); - if (!controller) { - return; - } - if (editor.hasModel()) { - const range = editor.getSelection(); - const model = editor.getModel(); - const references = createCancelablePromise(token => provideReferences(model, range.getStartPosition(), token).then(references => new ReferencesModel(references))); - controller.toggleWidget(range, references, defaultReferenceSearchOptions); - } - } -} - -registerEditorContribution(ReferenceController.ID, ReferenceController); - -registerEditorAction(ReferenceAction); - -let findReferencesCommand: ICommandHandler = (accessor: ServicesAccessor, resource: URI, position: IPosition) => { - if (!(resource instanceof URI)) { - throw new Error('illegal argument, uri'); - } - if (!position) { - throw new Error('illegal argument, position'); - } - - const codeEditorService = accessor.get(ICodeEditorService); - return codeEditorService.openCodeEditor({ resource }, codeEditorService.getFocusedCodeEditor()).then(control => { - if (!isCodeEditor(control) || !control.hasModel()) { - return undefined; - } - - let controller = ReferencesController.get(control); - if (!controller) { - return undefined; - } - - let references = createCancelablePromise(token => provideReferences(control.getModel(), Position.lift(position), token).then(references => new ReferencesModel(references))); - let range = new Range(position.lineNumber, position.column, position.lineNumber, position.column); - return Promise.resolve(controller.toggleWidget(range, references, defaultReferenceSearchOptions)); - }); -}; - -let showReferencesCommand: ICommandHandler = (accessor: ServicesAccessor, resource: URI, position: IPosition, references: Location[]) => { - if (!(resource instanceof URI)) { - throw new Error('illegal argument, uri expected'); - } - - if (!references) { - throw new Error('missing references'); - } - - const codeEditorService = accessor.get(ICodeEditorService); - return codeEditorService.openCodeEditor({ resource }, codeEditorService.getFocusedCodeEditor()).then(control => { - if (!isCodeEditor(control)) { - return undefined; - } - - let controller = ReferencesController.get(control); - if (!controller) { - return undefined; - } - - return controller.toggleWidget( - new Range(position.lineNumber, position.column, position.lineNumber, position.column), - createCancelablePromise(_ => Promise.resolve(new ReferencesModel(references))), - defaultReferenceSearchOptions - ); - }); -}; - -// register commands - -CommandsRegistry.registerCommand({ - id: 'editor.action.findReferences', - handler: findReferencesCommand -}); - -CommandsRegistry.registerCommand({ - id: 'editor.action.showReferences', - handler: showReferencesCommand, - description: { - description: 'Show references at a position in a file', - args: [ - { name: 'uri', description: 'The text document in which to show references', constraint: URI }, - { name: 'position', description: 'The position at which to show', constraint: Position.isIPosition }, - { name: 'locations', description: 'An array of locations.', constraint: Array }, - ] - } -}); - -function closeActiveReferenceSearch(accessor: ServicesAccessor, args: any) { - withController(accessor, controller => controller.closeWidget()); -} - -function openReferenceToSide(accessor: ServicesAccessor, args: any) { - const listService = accessor.get(IListService); - - const focus = listService.lastFocusedList && listService.lastFocusedList.getFocus(); - if (focus instanceof OneReference) { - withController(accessor, controller => controller.openReference(focus, true)); - } -} - -function withController(accessor: ServicesAccessor, fn: (controller: ReferencesController) => void): void { - const outerEditor = getOuterEditor(accessor); - if (!outerEditor) { - return; - } - - let controller = ReferencesController.get(outerEditor); - if (!controller) { - return; - } - - fn(controller); -} - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'goToNextReference', - weight: KeybindingWeight.WorkbenchContrib + 50, - primary: KeyCode.F4, - when: ctxReferenceSearchVisible, - handler(accessor) { - withController(accessor, controller => { - controller.goToNextOrPreviousReference(true); - }); - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'goToNextReferenceFromEmbeddedEditor', - weight: KeybindingWeight.EditorContrib + 50, - primary: KeyCode.F4, - when: PeekContext.inPeekEditor, - handler(accessor) { - withController(accessor, controller => { - controller.goToNextOrPreviousReference(true); - }); - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'goToPreviousReference', - weight: KeybindingWeight.WorkbenchContrib + 50, - primary: KeyMod.Shift | KeyCode.F4, - when: ctxReferenceSearchVisible, - handler(accessor) { - withController(accessor, controller => { - controller.goToNextOrPreviousReference(false); - }); - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'goToPreviousReferenceFromEmbeddedEditor', - weight: KeybindingWeight.EditorContrib + 50, - primary: KeyMod.Shift | KeyCode.F4, - when: PeekContext.inPeekEditor, - handler(accessor) { - withController(accessor, controller => { - controller.goToNextOrPreviousReference(false); - }); - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'closeReferenceSearch', - weight: KeybindingWeight.WorkbenchContrib + 50, - primary: KeyCode.Escape, - secondary: [KeyMod.Shift | KeyCode.Escape], - when: ContextKeyExpr.and(ctxReferenceSearchVisible, ContextKeyExpr.not('config.editor.stablePeek')), - handler: closeActiveReferenceSearch -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'closeReferenceSearchEditor', - weight: KeybindingWeight.EditorContrib - 101, - primary: KeyCode.Escape, - secondary: [KeyMod.Shift | KeyCode.Escape], - when: ContextKeyExpr.and(PeekContext.inPeekEditor, ContextKeyExpr.not('config.editor.stablePeek')), - handler: closeActiveReferenceSearch -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'openReferenceToSide', - weight: KeybindingWeight.EditorContrib, - primary: KeyMod.CtrlCmd | KeyCode.Enter, - mac: { - primary: KeyMod.WinCtrl | KeyCode.Enter - }, - when: ContextKeyExpr.and(ctxReferenceSearchVisible, ctxReferenceWidgetSearchTreeFocused), - handler: openReferenceToSide -}); - -export function provideReferences(model: ITextModel, position: Position, token: CancellationToken): Promise { - - // collect references from all providers - const promises = ReferenceProviderRegistry.ordered(model).map(provider => { - return Promise.resolve(provider.provideReferences(model, position, { includeDeclaration: true }, token)).then(result => { - if (Array.isArray(result)) { - return result; - } - return undefined; - }, err => { - onUnexpectedExternalError(err); - }); - }); - - return Promise.all(promises).then(references => flatten(coalesce(references))); -} - -registerDefaultLanguageCommand('_executeReferenceProvider', (model, position) => provideReferences(model, position, CancellationToken.None)); diff --git a/src/vs/editor/contrib/rename/rename.ts b/src/vs/editor/contrib/rename/rename.ts index 47cb4d1b97..56ae606fa0 100644 --- a/src/vs/editor/contrib/rename/rename.ts +++ b/src/vs/editor/contrib/rename/rename.ts @@ -238,7 +238,7 @@ export class RenameAction extends EditorAction { primary: KeyCode.F2, weight: KeybindingWeight.EditorContrib }, - menuOpts: { + contextMenuOpts: { group: '1_modification', order: 1.1 } diff --git a/src/vs/editor/contrib/smartSelect/smartSelect.ts b/src/vs/editor/contrib/smartSelect/smartSelect.ts index e285feac89..12a442950d 100644 --- a/src/vs/editor/contrib/smartSelect/smartSelect.ts +++ b/src/vs/editor/contrib/smartSelect/smartSelect.ts @@ -167,7 +167,7 @@ class GrowSelectionAction extends AbstractSmartSelect { }, weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarSelectionMenu, group: '1_basic', title: nls.localize({ key: 'miSmartSelectGrow', comment: ['&& denotes a mnemonic'] }, "&&Expand Selection"), @@ -196,7 +196,7 @@ class ShrinkSelectionAction extends AbstractSmartSelect { }, weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarSelectionMenu, group: '1_basic', title: nls.localize({ key: 'miSmartSelectShrink', comment: ['&& denotes a mnemonic'] }, "&&Shrink Selection"), diff --git a/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts b/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts index 0aa8ff2673..12482fa7ae 100644 --- a/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts +++ b/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts @@ -17,6 +17,7 @@ import { provideSelectionRanges } from 'vs/editor/contrib/smartSelect/smartSelec import { CancellationToken } from 'vs/base/common/cancellation'; import { WordSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/wordSelections'; import { TestTextResourcePropertiesService } from 'vs/editor/test/common/services/modelService.test'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; class MockJSMode extends MockMode { @@ -45,7 +46,7 @@ suite('SmartSelect', () => { setup(() => { const configurationService = new TestConfigurationService(); - modelService = new ModelServiceImpl(configurationService, new TestTextResourcePropertiesService(configurationService)); + modelService = new ModelServiceImpl(configurationService, new TestTextResourcePropertiesService(configurationService), new TestThemeService()); mode = new MockJSMode(); }); diff --git a/src/vs/editor/contrib/snippet/snippetSession.ts b/src/vs/editor/contrib/snippet/snippetSession.ts index 9ae417fd36..8547662cc6 100644 --- a/src/vs/editor/contrib/snippet/snippetSession.ts +++ b/src/vs/editor/contrib/snippet/snippetSession.ts @@ -333,7 +333,7 @@ const _defaultOptions: ISnippetSessionInsertOptions = { export class SnippetSession { - static adjustWhitespace(model: ITextModel, position: IPosition, snippet: TextmateSnippet): void { + static adjustWhitespace(model: ITextModel, position: IPosition, snippet: TextmateSnippet, adjustIndentation: boolean, adjustNewlines: boolean): void { const line = model.getLineContent(position.lineNumber); const lineLeadingWhitespace = getLeadingWhitespace(line, 0, position.column - 1); @@ -342,13 +342,19 @@ export class SnippetSession { // adjust indentation of text markers, except for choise elements // which get adjusted when being selected const lines = marker.value.split(/\r\n|\r|\n/); - for (let i = 1; i < lines.length; i++) { - let templateLeadingWhitespace = getLeadingWhitespace(lines[i]); - lines[i] = model.normalizeIndentation(lineLeadingWhitespace + templateLeadingWhitespace) + lines[i].substr(templateLeadingWhitespace.length); + + if (adjustIndentation) { + for (let i = 1; i < lines.length; i++) { + let templateLeadingWhitespace = getLeadingWhitespace(lines[i]); + lines[i] = model.normalizeIndentation(lineLeadingWhitespace + templateLeadingWhitespace) + lines[i].substr(templateLeadingWhitespace.length); + } } - const newValue = lines.join(model.getEOL()); - if (newValue !== marker.value) { - marker.parent.replace(marker, [new Text(newValue)]); + + if (adjustNewlines) { + const newValue = lines.join(model.getEOL()); + if (newValue !== marker.value) { + marker.parent.replace(marker, [new Text(newValue)]); + } } } return true; @@ -439,9 +445,11 @@ export class SnippetSession { // happens when being asked for (default) or when this is a secondary // cursor and the leading whitespace is different const start = snippetSelection.getStartPosition(); - if (adjustWhitespace || (idx > 0 && firstLineFirstNonWhitespace !== model.getLineFirstNonWhitespaceColumn(selection.positionLineNumber))) { - SnippetSession.adjustWhitespace(model, start, snippet); - } + SnippetSession.adjustWhitespace( + model, start, snippet, + adjustWhitespace || (idx > 0 && firstLineFirstNonWhitespace !== model.getLineFirstNonWhitespaceColumn(selection.positionLineNumber)), + true + ); snippet.resolveVariables(new CompositeSnippetVariableResolver([ modelBasedVariableResolver, diff --git a/src/vs/editor/contrib/snippet/test/snippetSession.test.ts b/src/vs/editor/contrib/snippet/test/snippetSession.test.ts index b3527cd014..4b6b41a7e7 100644 --- a/src/vs/editor/contrib/snippet/test/snippetSession.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetSession.test.ts @@ -41,7 +41,7 @@ suite('SnippetSession', function () { function assertNormalized(position: IPosition, input: string, expected: string): void { const snippet = new SnippetParser().parse(input); - SnippetSession.adjustWhitespace(model, position, snippet); + SnippetSession.adjustWhitespace(model, position, snippet, true, true); assert.equal(snippet.toTextmateString(), expected); } diff --git a/src/vs/editor/contrib/suggest/completionModel.ts b/src/vs/editor/contrib/suggest/completionModel.ts index 8d93593f0d..8f756495de 100644 --- a/src/vs/editor/contrib/suggest/completionModel.ts +++ b/src/vs/editor/contrib/suggest/completionModel.ts @@ -160,7 +160,7 @@ export class CompletionModel { // 'word' is that remainder of the current line that we // filter and score against. In theory each suggestion uses a // different word, but in practice not - that's why we cache - const overwriteBefore = item.position.column - item.completion.range.startColumn; + const overwriteBefore = item.position.column - item.editStart.column; const wordLen = overwriteBefore + characterCountDelta - (item.position.column - this._column); if (word.length !== wordLen) { word = wordLen === 0 ? '' : leadingLineContent.slice(-wordLen); diff --git a/src/vs/editor/contrib/suggest/media/suggest.css b/src/vs/editor/contrib/suggest/media/suggest.css index 001e1819f7..0472b8bdc5 100644 --- a/src/vs/editor/contrib/suggest/media/suggest.css +++ b/src/vs/editor/contrib/suggest/media/suggest.css @@ -68,12 +68,9 @@ } .monaco-editor .suggest-widget .monaco-list { - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: -moz-none; - -ms-user-select: none; - -o-user-select: none; user-select: none; + -webkit-user-select: none; + -ms-user-select: none; } /** Styles for each row in the list element **/ @@ -279,3 +276,11 @@ border-radius: 3px; padding: 0 0.4em; } + + +/* replace/insert decorations */ + +.monaco-editor .suggest-insert-unexpected { + font-style: italic; +} + diff --git a/src/vs/editor/contrib/suggest/suggest.ts b/src/vs/editor/contrib/suggest/suggest.ts index 83acb3f39f..3dd590a444 100644 --- a/src/vs/editor/contrib/suggest/suggest.ts +++ b/src/vs/editor/contrib/suggest/suggest.ts @@ -31,6 +31,11 @@ export class CompletionItem { readonly resolve: (token: CancellationToken) => Promise; + // + readonly editStart: IPosition; + readonly editInsertEnd: IPosition; + readonly editReplaceEnd: IPosition; + // perf readonly labelLow: string; readonly sortTextLow?: string; @@ -54,6 +59,17 @@ export class CompletionItem { this.sortTextLow = completion.sortText && completion.sortText.toLowerCase(); this.filterTextLow = completion.filterText && completion.filterText.toLowerCase(); + // normalize ranges + if (Range.isIRange(completion.range)) { + this.editStart = new Position(completion.range.startLineNumber, completion.range.startColumn); + this.editInsertEnd = new Position(completion.range.endLineNumber, completion.range.endColumn); + this.editReplaceEnd = new Position(completion.range.endLineNumber, completion.range.endColumn); + } else { + this.editStart = new Position(completion.range.insert.startLineNumber, completion.range.insert.startColumn); + this.editInsertEnd = new Position(completion.range.insert.endLineNumber, completion.range.insert.endColumn); + this.editReplaceEnd = new Position(completion.range.replace.endLineNumber, completion.range.replace.endColumn); + } + // create the suggestion resolver const { resolveCompletionItem } = provider; if (typeof resolveCompletionItem !== 'function') { @@ -122,8 +138,12 @@ export function provideSuggestionItems( token: CancellationToken = CancellationToken.None ): Promise { - const wordUntil = model.getWordUntilPosition(position); - const defaultRange = new Range(position.lineNumber, wordUntil.startColumn, position.lineNumber, wordUntil.endColumn); + const word = model.getWordAtPosition(position); + const defaultReplaceRange = word ? new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn) : Range.fromPositions(position); + const defaultInsertRange = defaultReplaceRange.setEndPosition(position.lineNumber, position.column); + + // const wordUntil = model.getWordUntilPosition(position); + // const defaultRange = new Range(position.lineNumber, wordUntil.startColumn, position.lineNumber, wordUntil.endColumn); position = position.clone(); @@ -159,7 +179,7 @@ export function provideSuggestionItems( // fill in default range when missing if (!suggestion.range) { - suggestion.range = defaultRange; + suggestion.range = { insert: defaultInsertRange, replace: defaultReplaceRange }; } // fill in default sortText when missing if (!suggestion.sortText) { diff --git a/src/vs/editor/contrib/suggest/suggestCommitCharacters.ts b/src/vs/editor/contrib/suggest/suggestCommitCharacters.ts index 8d02ff482d..dd75bf641f 100644 --- a/src/vs/editor/contrib/suggest/suggestCommitCharacters.ts +++ b/src/vs/editor/contrib/suggest/suggestCommitCharacters.ts @@ -26,7 +26,7 @@ export class CommitCharacterController { this._disposables.add(widget.onDidHide(this.reset, this)); this._disposables.add(editor.onWillType(text => { - if (this._active) { + if (this._active && !widget.isFrozen()) { const ch = text.charCodeAt(text.length - 1); if (this._active.acceptCharacters.has(ch) && editor.getOption(EditorOption.acceptSuggestionOnCommitCharacter)) { accept(this._active.item); diff --git a/src/vs/editor/contrib/suggest/suggestController.ts b/src/vs/editor/contrib/suggest/suggestController.ts index 61c1cafff3..e1e7a785ab 100644 --- a/src/vs/editor/contrib/suggest/suggestController.ts +++ b/src/vs/editor/contrib/suggest/suggestController.ts @@ -31,12 +31,13 @@ import { WordContextKey } from 'vs/editor/contrib/suggest/wordContextKey'; import { Event } from 'vs/base/common/event'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import { IdleValue } from 'vs/base/common/async'; -import { isObject } from 'vs/base/common/types'; +import { isObject, assertType } from 'vs/base/common/types'; import { CommitCharacterController } from './suggestCommitCharacters'; import { IPosition } from 'vs/editor/common/core/position'; import { TrackedRangeStickiness, ITextModel } from 'vs/editor/common/model'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import * as platform from 'vs/base/common/platform'; +import { SuggestRangeHighlighter } from 'vs/editor/contrib/suggest/suggestRangeHighlighter'; /** * Stop suggest widget from disappearing when clicking into other areas @@ -101,33 +102,36 @@ export class SuggestController implements IEditorContribution { return editor.getContribution(SuggestController.ID); } - private readonly _model: SuggestModel; - private readonly _widget: IdleValue; + readonly editor: ICodeEditor; + readonly model: SuggestModel; + readonly widget: IdleValue; + private readonly _alternatives: IdleValue; private readonly _lineSuffix = new MutableDisposable(); private readonly _toDispose = new DisposableStore(); constructor( - private _editor: ICodeEditor, + editor: ICodeEditor, @IEditorWorkerService editorWorker: IEditorWorkerService, @ISuggestMemoryService private readonly _memoryService: ISuggestMemoryService, @ICommandService private readonly _commandService: ICommandService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { - this._model = new SuggestModel(this._editor, editorWorker); + this.editor = editor; + this.model = new SuggestModel(this.editor, editorWorker); - this._widget = new IdleValue(() => { + this.widget = new IdleValue(() => { - const widget = this._instantiationService.createInstance(SuggestWidget, this._editor); + const widget = this._instantiationService.createInstance(SuggestWidget, this.editor); this._toDispose.add(widget); this._toDispose.add(widget.onDidSelect(item => this._insertSuggestion(item, 0), this)); // Wire up logic to accept a suggestion on certain characters - const commitCharacterController = new CommitCharacterController(this._editor, widget, item => this._insertSuggestion(item, InsertFlags.NoAfterUndoStop)); + const commitCharacterController = new CommitCharacterController(this.editor, widget, item => this._insertSuggestion(item, InsertFlags.NoAfterUndoStop)); this._toDispose.add(commitCharacterController); - this._toDispose.add(this._model.onDidSuggest(e => { + this._toDispose.add(this.model.onDidSuggest(e => { if (e.completionModel.items.length === 0) { commitCharacterController.reset(); } @@ -137,19 +141,19 @@ export class SuggestController implements IEditorContribution { let makesTextEdit = SuggestContext.MakesTextEdit.bindTo(this._contextKeyService); this._toDispose.add(widget.onDidFocus(({ item }) => { - const position = this._editor.getPosition()!; - const startColumn = item.completion.range.startColumn; + const position = this.editor.getPosition()!; + const startColumn = item.editStart.column; const endColumn = position.column; let value = true; if ( - this._editor.getOption(EditorOption.acceptSuggestionOnEnter) === 'smart' - && this._model.state === State.Auto + this.editor.getOption(EditorOption.acceptSuggestionOnEnter) === 'smart' + && this.model.state === State.Auto && !item.completion.command && !item.completion.additionalTextEdits && !(item.completion.insertTextRules! & CompletionItemInsertTextRule.InsertAsSnippet) && endColumn - startColumn === item.completion.insertText.length ) { - const oldText = this._editor.getModel()!.getValueInRange({ + const oldText = this.editor.getModel()!.getValueInRange({ startLineNumber: position.lineNumber, startColumn, endLineNumber: position.lineNumber, @@ -161,38 +165,40 @@ export class SuggestController implements IEditorContribution { })); this._toDispose.add(toDisposable(() => makesTextEdit.reset())); + + return widget; }); this._alternatives = new IdleValue(() => { - return this._toDispose.add(new SuggestAlternatives(this._editor, this._contextKeyService)); + return this._toDispose.add(new SuggestAlternatives(this.editor, this._contextKeyService)); }); - this._toDispose.add(_instantiationService.createInstance(WordContextKey, _editor)); + this._toDispose.add(_instantiationService.createInstance(WordContextKey, editor)); - this._toDispose.add(this._model.onDidTrigger(e => { - this._widget.getValue().showTriggered(e.auto, e.shy ? 250 : 50); - this._lineSuffix.value = new LineSuffix(this._editor.getModel()!, e.position); + this._toDispose.add(this.model.onDidTrigger(e => { + this.widget.getValue().showTriggered(e.auto, e.shy ? 250 : 50); + this._lineSuffix.value = new LineSuffix(this.editor.getModel()!, e.position); })); - this._toDispose.add(this._model.onDidSuggest(e => { + this._toDispose.add(this.model.onDidSuggest(e => { if (!e.shy) { - let index = this._memoryService.select(this._editor.getModel()!, this._editor.getPosition()!, e.completionModel.items); - this._widget.getValue().showSuggestions(e.completionModel, index, e.isFrozen, e.auto); + let index = this._memoryService.select(this.editor.getModel()!, this.editor.getPosition()!, e.completionModel.items); + this.widget.getValue().showSuggestions(e.completionModel, index, e.isFrozen, e.auto); } })); - this._toDispose.add(this._model.onDidCancel(e => { + this._toDispose.add(this.model.onDidCancel(e => { if (!e.retrigger) { - this._widget.getValue().hideWidget(); + this.widget.getValue().hideWidget(); } })); - this._toDispose.add(this._editor.onDidBlurEditorWidget(() => { + this._toDispose.add(this.editor.onDidBlurEditorWidget(() => { if (!_sticky) { - this._model.cancel(); - this._model.clear(); + this.model.cancel(); + this.model.clear(); } })); - this._toDispose.add(this._widget.getValue().onDetailsKeyDown(e => { + this._toDispose.add(this.widget.getValue().onDetailsKeyDown(e => { // cmd + c on macOS, ctrl + c on Win / Linux if ( e.toKeybinding().equals(new SimpleKeybinding(true, false, false, false, KeyCode.KEY_C)) || @@ -203,25 +209,28 @@ export class SuggestController implements IEditorContribution { } if (!e.toKeybinding().isModifierKey()) { - this._editor.focus(); + this.editor.focus(); } })); // Manage the acceptSuggestionsOnEnter context key let acceptSuggestionsOnEnter = SuggestContext.AcceptSuggestionsOnEnter.bindTo(_contextKeyService); let updateFromConfig = () => { - const acceptSuggestionOnEnter = this._editor.getOption(EditorOption.acceptSuggestionOnEnter); + const acceptSuggestionOnEnter = this.editor.getOption(EditorOption.acceptSuggestionOnEnter); acceptSuggestionsOnEnter.set(acceptSuggestionOnEnter === 'on' || acceptSuggestionOnEnter === 'smart'); }; - this._toDispose.add(this._editor.onDidChangeConfiguration(() => updateFromConfig())); + this._toDispose.add(this.editor.onDidChangeConfiguration(() => updateFromConfig())); updateFromConfig(); + + // create range highlighter + this._toDispose.add(new SuggestRangeHighlighter(this)); } dispose(): void { this._alternatives.dispose(); this._toDispose.dispose(); - this._widget.dispose(); - this._model.dispose(); + this.widget.dispose(); + this.model.dispose(); this._lineSuffix.dispose(); } @@ -231,85 +240,66 @@ export class SuggestController implements IEditorContribution { ): void { if (!event || !event.item) { this._alternatives.getValue().reset(); - this._model.cancel(); - this._model.clear(); + this.model.cancel(); + this.model.clear(); return; } - if (!this._editor.hasModel()) { + if (!this.editor.hasModel()) { return; } - const model = this._editor.getModel(); + const model = this.editor.getModel(); const modelVersionNow = model.getAlternativeVersionId(); - const { completion: suggestion, position } = event.item; - const columnDelta = this._editor.getPosition().column - position.column; + const { item } = event; + const { completion: suggestion } = item; // pushing undo stops *before* additional text edits and // *after* the main edit if (!(flags & InsertFlags.NoBeforeUndoStop)) { - this._editor.pushUndoStop(); + this.editor.pushUndoStop(); } if (Array.isArray(suggestion.additionalTextEdits)) { - this._editor.executeEdits('suggestController.additionalTextEdits', suggestion.additionalTextEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text))); + this.editor.executeEdits('suggestController.additionalTextEdits', suggestion.additionalTextEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text))); } // keep item in memory - this._memoryService.memorize(model, this._editor.getPosition(), event.item); + this._memoryService.memorize(model, this.editor.getPosition(), item); let { insertText } = suggestion; if (!(suggestion.insertTextRules! & CompletionItemInsertTextRule.InsertAsSnippet)) { insertText = SnippetParser.escape(insertText); } - let overwriteBefore = position.column - suggestion.range.startColumn; - let overwriteAfter = suggestion.range.endColumn - position.column; - let suffixDelta = this._lineSuffix.value ? this._lineSuffix.value.delta(this._editor.getPosition()) : 0; - let word = model.getWordAtPosition(this._editor.getPosition()); + const info = this.getOverwriteInfo(item, Boolean(flags & InsertFlags.AlternativeOverwriteConfig)); - const overwriteConfig = flags & InsertFlags.AlternativeOverwriteConfig - ? !this._editor.getOption(EditorOption.suggest).overwriteOnAccept - : this._editor.getOption(EditorOption.suggest).overwriteOnAccept; - if (!overwriteConfig) { - if (overwriteAfter > 0 && word && suggestion.range.endColumn === word.endColumn) { - // don't overwrite anything right of the cursor, overrule extension even when the - // completion only replaces a word... - overwriteAfter = 0; - } - } else { - if (overwriteAfter === 0 && word) { - // compute fallback overwrite length - overwriteAfter = word.endColumn - this._editor.getPosition().column; - } - } - - SnippetController2.get(this._editor).insert(insertText, { - overwriteBefore: overwriteBefore + columnDelta, - overwriteAfter: overwriteAfter + suffixDelta, + SnippetController2.get(this.editor).insert(insertText, { + overwriteBefore: info.overwriteBefore, + overwriteAfter: info.overwriteAfter, undoStopBefore: false, undoStopAfter: false, adjustWhitespace: !(suggestion.insertTextRules! & CompletionItemInsertTextRule.KeepWhitespace) }); if (!(flags & InsertFlags.NoAfterUndoStop)) { - this._editor.pushUndoStop(); + this.editor.pushUndoStop(); } if (!suggestion.command) { // done - this._model.cancel(); - this._model.clear(); + this.model.cancel(); + this.model.clear(); } else if (suggestion.command.id === TriggerSuggestAction.id) { // retigger - this._model.trigger({ auto: true, shy: false }, true); + this.model.trigger({ auto: true, shy: false }, true); } else { // exec command, done this._commandService.executeCommand(suggestion.command.id, ...(suggestion.command.arguments ? [...suggestion.command.arguments] : [])) .catch(onUnexpectedError) - .finally(() => this._model.clear()); // <- clear only now, keep commands alive - this._model.cancel(); + .finally(() => this.model.clear()); // <- clear only now, keep commands alive + this.model.cancel(); } if (flags & InsertFlags.KeepAlternativeSuggestions) { @@ -333,6 +323,24 @@ export class SuggestController implements IEditorContribution { this._alertCompletionItem(event.item); } + getOverwriteInfo(item: CompletionItem, toggleMode: boolean): { overwriteBefore: number, overwriteAfter: number } { + assertType(this.editor.hasModel()); + + let replace = this.editor.getOption(EditorOption.suggest).insertMode === 'replace'; + if (toggleMode) { + replace = !replace; + } + const overwriteBefore = item.position.column - item.editStart.column; + const overwriteAfter = (replace ? item.editReplaceEnd.column : item.editInsertEnd.column) - item.position.column; + const columnDelta = this.editor.getPosition().column - item.position.column; + const suffixDelta = this._lineSuffix.value ? this._lineSuffix.value.delta(this.editor.getPosition()) : 0; + + return { + overwriteBefore: overwriteBefore + columnDelta, + overwriteAfter: overwriteAfter + suffixDelta + }; + } + private _alertCompletionItem({ completion: suggestion }: CompletionItem): void { if (isNonEmptyArray(suggestion.additionalTextEdits)) { let msg = nls.localize('arai.alert.snippet', "Accepting '{0}' made {1} additional edits", suggestion.label, suggestion.additionalTextEdits.length); @@ -341,22 +349,22 @@ export class SuggestController implements IEditorContribution { } triggerSuggest(onlyFrom?: Set): void { - if (this._editor.hasModel()) { - this._model.trigger({ auto: false, shy: false }, false, onlyFrom); - this._editor.revealLine(this._editor.getPosition().lineNumber, ScrollType.Smooth); - this._editor.focus(); + if (this.editor.hasModel()) { + this.model.trigger({ auto: false, shy: false }, false, onlyFrom); + this.editor.revealLine(this.editor.getPosition().lineNumber, ScrollType.Smooth); + this.editor.focus(); } } triggerSuggestAndAcceptBest(arg: { fallback: string }): void { - if (!this._editor.hasModel()) { + if (!this.editor.hasModel()) { return; } - const positionNow = this._editor.getPosition(); + const positionNow = this.editor.getPosition(); const fallback = () => { - if (positionNow.equals(this._editor.getPosition()!)) { + if (positionNow.equals(this.editor.getPosition()!)) { this._commandService.executeCommand(arg.fallback); } }; @@ -366,14 +374,14 @@ export class SuggestController implements IEditorContribution { // snippet, other editor -> makes edit return true; } - const position = this._editor.getPosition()!; - const startColumn = item.completion.range.startColumn; + const position = this.editor.getPosition()!; + const startColumn = item.editStart.column; const endColumn = position.column; if (endColumn - startColumn !== item.completion.insertText.length) { // unequal lengths -> makes edit return true; } - const textNow = this._editor.getModel()!.getValueInRange({ + const textNow = this.editor.getModel()!.getValueInRange({ startLineNumber: position.lineNumber, startColumn, endLineNumber: position.lineNumber, @@ -383,41 +391,41 @@ export class SuggestController implements IEditorContribution { return textNow !== item.completion.insertText; }; - Event.once(this._model.onDidTrigger)(_ => { + Event.once(this.model.onDidTrigger)(_ => { // wait for trigger because only then the cancel-event is trustworthy let listener: IDisposable[] = []; - Event.any(this._model.onDidTrigger, this._model.onDidCancel)(() => { + Event.any(this.model.onDidTrigger, this.model.onDidCancel)(() => { // retrigger or cancel -> try to type default text dispose(listener); fallback(); }, undefined, listener); - this._model.onDidSuggest(({ completionModel }) => { + this.model.onDidSuggest(({ completionModel }) => { dispose(listener); if (completionModel.items.length === 0) { fallback(); return; } - const index = this._memoryService.select(this._editor.getModel()!, this._editor.getPosition()!, completionModel.items); + const index = this._memoryService.select(this.editor.getModel()!, this.editor.getPosition()!, completionModel.items); const item = completionModel.items[index]; if (!makesTextEdit(item)) { fallback(); return; } - this._editor.pushUndoStop(); + this.editor.pushUndoStop(); this._insertSuggestion({ index, item, model: completionModel }, InsertFlags.KeepAlternativeSuggestions | InsertFlags.NoBeforeUndoStop | InsertFlags.NoAfterUndoStop); }, undefined, listener); }); - this._model.trigger({ auto: false, shy: true }); - this._editor.revealLine(positionNow.lineNumber, ScrollType.Smooth); - this._editor.focus(); + this.model.trigger({ auto: false, shy: true }); + this.editor.revealLine(positionNow.lineNumber, ScrollType.Smooth); + this.editor.focus(); } acceptSelectedSuggestion(keepAlternativeSuggestions: boolean, alternativeOverwriteConfig: boolean): void { - const item = this._widget.getValue().getFocusedItem(); + const item = this.widget.getValue().getFocusedItem(); let flags = 0; if (keepAlternativeSuggestions) { flags |= InsertFlags.KeepAlternativeSuggestions; @@ -437,45 +445,45 @@ export class SuggestController implements IEditorContribution { } cancelSuggestWidget(): void { - this._model.cancel(); - this._model.clear(); - this._widget.getValue().hideWidget(); + this.model.cancel(); + this.model.clear(); + this.widget.getValue().hideWidget(); } selectNextSuggestion(): void { - this._widget.getValue().selectNext(); + this.widget.getValue().selectNext(); } selectNextPageSuggestion(): void { - this._widget.getValue().selectNextPage(); + this.widget.getValue().selectNextPage(); } selectLastSuggestion(): void { - this._widget.getValue().selectLast(); + this.widget.getValue().selectLast(); } selectPrevSuggestion(): void { - this._widget.getValue().selectPrevious(); + this.widget.getValue().selectPrevious(); } selectPrevPageSuggestion(): void { - this._widget.getValue().selectPreviousPage(); + this.widget.getValue().selectPreviousPage(); } selectFirstSuggestion(): void { - this._widget.getValue().selectFirst(); + this.widget.getValue().selectFirst(); } toggleSuggestionDetails(): void { - this._widget.getValue().toggleDetails(); + this.widget.getValue().toggleDetails(); } toggleExplainMode(): void { - this._widget.getValue().toggleExplainMode(); + this.widget.getValue().toggleExplainMode(); } toggleSuggestionFocus(): void { - this._widget.getValue().toggleDetailsFocus(); + this.widget.getValue().toggleDetailsFocus(); } } @@ -492,7 +500,7 @@ export class TriggerSuggestAction extends EditorAction { kbOpts: { kbExpr: EditorContextKeys.textInputFocus, primary: KeyMod.CtrlCmd | KeyCode.Space, - mac: { primary: KeyMod.WinCtrl | KeyCode.Space }, + mac: { primary: KeyMod.WinCtrl | KeyCode.Space, secondary: [KeyMod.Alt | KeyCode.Escape] }, weight: KeybindingWeight.EditorContrib } }); diff --git a/src/vs/editor/contrib/suggest/suggestModel.ts b/src/vs/editor/contrib/suggest/suggestModel.ts index 512a63f58c..43a9cae6ff 100644 --- a/src/vs/editor/contrib/suggest/suggestModel.ts +++ b/src/vs/editor/contrib/suggest/suggestModel.ts @@ -13,7 +13,7 @@ import { CursorChangeReason, ICursorSelectionChangedEvent } from 'vs/editor/comm import { Position, IPosition } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; import { ITextModel, IWordAtPosition } from 'vs/editor/common/model'; -import { CompletionItemProvider, StandardTokenType, CompletionContext, CompletionProviderRegistry, CompletionTriggerKind, CompletionItemKind, completionKindFromString } from 'vs/editor/common/modes'; +import { CompletionItemProvider, StandardTokenType, CompletionContext, CompletionProviderRegistry, CompletionTriggerKind, CompletionItemKind } from 'vs/editor/common/modes'; import { CompletionModel } from './completionModel'; import { CompletionItem, getSuggestionComparator, provideSuggestionItems, getSnippetSuggestSupport, SnippetSortOrder, CompletionOptions } from './suggest'; import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; @@ -388,9 +388,7 @@ export class SuggestModel implements IDisposable { this._requestToken = new CancellationTokenSource(); // kind filter and snippet sort rules - const suggestOptions = this._editor.getOption(EditorOption.suggest); const snippetSuggestions = this._editor.getOption(EditorOption.snippetSuggestions); - let itemKindFilter = new Set(); let snippetSortOrder = SnippetSortOrder.Inline; switch (snippetSuggestions) { case 'top': @@ -403,19 +401,9 @@ export class SuggestModel implements IDisposable { case 'bottom': snippetSortOrder = SnippetSortOrder.Bottom; break; - case 'none': - itemKindFilter.add(CompletionItemKind.Snippet); - break; - } - - // kind filter - for (const key in suggestOptions.filteredTypes) { - const kind = completionKindFromString(key, true); - if (typeof kind !== 'undefined' && suggestOptions.filteredTypes[key] === false) { - itemKindFilter.add(kind); - } } + let itemKindFilter = SuggestModel._createItemKindFilter(this._editor); let wordDistance = WordDistance.create(this._editorWorker, this._editor); let items = provideSuggestionItems( @@ -467,6 +455,48 @@ export class SuggestModel implements IDisposable { }).catch(onUnexpectedError); } + private static _createItemKindFilter(editor: ICodeEditor): Set { + // kind filter and snippet sort rules + const result = new Set(); + + // snippet setting + const snippetSuggestions = editor.getOption(EditorOption.snippetSuggestions); + if (snippetSuggestions === 'none') { + result.add(CompletionItemKind.Snippet); + } + + // type setting + const suggestOptions = editor.getOption(EditorOption.suggest); + if (!suggestOptions.showMethods) { result.add(CompletionItemKind.Method); } + if (!suggestOptions.showFunctions) { result.add(CompletionItemKind.Function); } + if (!suggestOptions.showConstructors) { result.add(CompletionItemKind.Constructor); } + if (!suggestOptions.showFields) { result.add(CompletionItemKind.Field); } + if (!suggestOptions.showVariables) { result.add(CompletionItemKind.Variable); } + if (!suggestOptions.showClasses) { result.add(CompletionItemKind.Class); } + if (!suggestOptions.showStructs) { result.add(CompletionItemKind.Struct); } + if (!suggestOptions.showInterfaces) { result.add(CompletionItemKind.Interface); } + if (!suggestOptions.showModules) { result.add(CompletionItemKind.Module); } + if (!suggestOptions.showProperties) { result.add(CompletionItemKind.Property); } + if (!suggestOptions.showEvents) { result.add(CompletionItemKind.Event); } + if (!suggestOptions.showOperators) { result.add(CompletionItemKind.Operator); } + if (!suggestOptions.showUnits) { result.add(CompletionItemKind.Unit); } + if (!suggestOptions.showValues) { result.add(CompletionItemKind.Value); } + if (!suggestOptions.showConstants) { result.add(CompletionItemKind.Constant); } + if (!suggestOptions.showEnums) { result.add(CompletionItemKind.Enum); } + if (!suggestOptions.showEnumMembers) { result.add(CompletionItemKind.EnumMember); } + if (!suggestOptions.showKeywords) { result.add(CompletionItemKind.Keyword); } + if (!suggestOptions.showWords) { result.add(CompletionItemKind.Text); } + if (!suggestOptions.showColors) { result.add(CompletionItemKind.Color); } + if (!suggestOptions.showFiles) { result.add(CompletionItemKind.File); } + if (!suggestOptions.showReferences) { result.add(CompletionItemKind.Reference); } + if (!suggestOptions.showColors) { result.add(CompletionItemKind.Customcolor); } + if (!suggestOptions.showFolders) { result.add(CompletionItemKind.Folder); } + if (!suggestOptions.showTypeParameters) { result.add(CompletionItemKind.TypeParameter); } + if (!suggestOptions.showSnippets) { result.add(CompletionItemKind.Snippet); } + + return result; + } + private _onNewContext(ctx: LineContext): void { if (!this._context) { diff --git a/src/vs/editor/contrib/suggest/suggestRangeHighlighter.ts b/src/vs/editor/contrib/suggest/suggestRangeHighlighter.ts new file mode 100644 index 0000000000..282fc71baa --- /dev/null +++ b/src/vs/editor/contrib/suggest/suggestRangeHighlighter.ts @@ -0,0 +1,125 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { Range } from 'vs/editor/common/core/range'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { CompletionItem } from 'vs/editor/contrib/suggest/suggest'; +import { IModelDeltaDecoration } from 'vs/editor/common/model'; +import { SuggestController } from 'vs/editor/contrib/suggest/suggestController'; +import { Emitter } from 'vs/base/common/event'; +import { domEvent } from 'vs/base/browser/event'; + +export class SuggestRangeHighlighter { + + private readonly _disposables = new DisposableStore(); + + private _decorations: string[] = []; + private _widgetListener?: IDisposable; + private _shiftKeyListener?: IDisposable; + private _currentItem?: CompletionItem; + + constructor(private readonly _controller: SuggestController) { + + this._disposables.add(_controller.model.onDidSuggest(e => { + if (!e.shy) { + const widget = this._controller.widget.getValue(); + const focused = widget.getFocusedItem(); + if (focused) { + this._highlight(focused.item); + } + if (!this._widgetListener) { + this._widgetListener = widget.onDidFocus(e => this._highlight(e.item)); + } + } + })); + + this._disposables.add(_controller.model.onDidCancel(() => { + this._reset(); + })); + } + + dispose(): void { + this._reset(); + this._disposables.dispose(); + dispose(this._widgetListener); + dispose(this._shiftKeyListener); + } + + private _reset(): void { + this._decorations = this._controller.editor.deltaDecorations(this._decorations, []); + if (this._shiftKeyListener) { + this._shiftKeyListener.dispose(); + this._shiftKeyListener = undefined; + } + } + + private _highlight(item: CompletionItem) { + + this._currentItem = item; + const opts = this._controller.editor.getOption(EditorOption.suggest); + let newDeco: IModelDeltaDecoration[] = []; + + if (opts.insertHighlight) { + if (!this._shiftKeyListener) { + this._shiftKeyListener = shiftKey.event(() => this._highlight(this._currentItem!)); + } + + const info = this._controller.getOverwriteInfo(item, shiftKey.isPressed); + const position = this._controller.editor.getPosition()!; + + if (opts.insertMode === 'insert' && info.overwriteAfter > 0) { + // wants inserts but got replace-mode -> highlight AFTER range + newDeco = [{ + range: new Range(position.lineNumber, position.column, position.lineNumber, position.column + info.overwriteAfter), + options: { inlineClassName: 'suggest-insert-unexpected' } + }]; + + } else if (opts.insertMode === 'replace' && info.overwriteAfter === 0) { + // want replace but likely got insert -> highlight AFTER range + const wordInfo = this._controller.editor.getModel()?.getWordAtPosition(position); + if (wordInfo && wordInfo.endColumn > position.column) { + newDeco = [{ + range: new Range(position.lineNumber, position.column, position.lineNumber, wordInfo.endColumn), + options: { inlineClassName: 'suggest-insert-unexpected' } + }]; + } + } + } + + // update editor decorations + this._decorations = this._controller.editor.deltaDecorations(this._decorations, newDeco); + } +} + +const shiftKey = new class ShiftKey extends Emitter { + + private readonly _subscriptions = new DisposableStore(); + private _isPressed: boolean = false; + + constructor() { + super(); + this._subscriptions.add(domEvent(document.body, 'keydown')(e => this.isPressed = e.shiftKey)); + this._subscriptions.add(domEvent(document.body, 'keyup')(() => this.isPressed = false)); + this._subscriptions.add(domEvent(document.body, 'mouseleave')(() => this.isPressed = false)); + this._subscriptions.add(domEvent(document.body, 'blur')(() => this.isPressed = false)); + } + + get isPressed(): boolean { + return this._isPressed; + } + + set isPressed(value: boolean) { + if (this._isPressed !== value) { + this._isPressed = value; + this.fire(value); + } + } + + dispose() { + this._subscriptions.dispose(); + super.dispose(); + } +}; diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index ff2edb3aa7..6cf2b36d43 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -172,7 +172,7 @@ class Renderer implements IListRenderer if (suggestion.kind === CompletionItemKind.Color && extractColor(element, color)) { // special logic for 'color' completion items data.icon.className = 'icon customcolor'; - data.iconContainer.className = 'icon customcolor'; + data.iconContainer.className = 'icon hide'; data.colorspan.style.backgroundColor = color[0]; } else if (suggestion.kind === CompletionItemKind.File && this._themeService.getIconTheme().hasFileIcons) { @@ -1074,6 +1074,10 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate{ + insertMode: 'insert', + snippetsPreventQuickSuggestions: true, + filterGraceful: true, + localityBonus: false, + shareSuggestSelections: false, + showIcons: true, + maxVisibleSuggestions: 12, + showMethods: true, + showFunctions: true, + showConstructors: true, + showFields: true, + showVariables: true, + showClasses: true, + showStructs: true, + showInterfaces: true, + showModules: true, + showProperties: true, + showEvents: true, + showOperators: true, + showUnits: true, + showValues: true, + showConstants: true, + showEnums: true, + showEnumMembers: true, + showKeywords: true, + showWords: true, + showColors: true, + showFiles: true, + showReferences: true, + showFolders: true, + showTypeParameters: true, + showSnippets: true, + }; let model: CompletionModel; @@ -158,16 +192,7 @@ suite('CompletionModel', function () { ], 1, { leadingLineContent: 's', characterCountDelta: 0 - }, WordDistance.None, { - overwriteOnAccept: false, - snippetsPreventQuickSuggestions: true, - filterGraceful: true, - localityBonus: false, - shareSuggestSelections: false, - showIcons: true, - maxVisibleSuggestions: 12, - filteredTypes: Object.create(null) - }, 'top'); + }, WordDistance.None, defaultOptions, 'top'); assert.equal(model.items.length, 2); const [a, b] = model.items; @@ -186,16 +211,7 @@ suite('CompletionModel', function () { ], 1, { leadingLineContent: 's', characterCountDelta: 0 - }, WordDistance.None, { - overwriteOnAccept: false, - snippetsPreventQuickSuggestions: true, - filterGraceful: true, - localityBonus: false, - shareSuggestSelections: false, - showIcons: true, - maxVisibleSuggestions: 12, - filteredTypes: Object.create(null) - }, 'bottom'); + }, WordDistance.None, defaultOptions, 'bottom'); assert.equal(model.items.length, 2); const [a, b] = model.items; @@ -213,16 +229,7 @@ suite('CompletionModel', function () { ], 1, { leadingLineContent: 's', characterCountDelta: 0 - }, WordDistance.None, { - overwriteOnAccept: false, - snippetsPreventQuickSuggestions: true, - filterGraceful: true, - localityBonus: false, - shareSuggestSelections: false, - showIcons: true, - maxVisibleSuggestions: 12, - filteredTypes: Object.create(null) - }, 'inline'); + }, WordDistance.None, defaultOptions, 'inline'); assert.equal(model.items.length, 2); const [a, b] = model.items; diff --git a/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts b/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts index 41aa36e8ab..80f870c72c 100644 --- a/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts +++ b/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts @@ -351,7 +351,7 @@ suite('WordOperations', () => { }); test('cursorWordAccessibilityRight', () => { - const EXPECTED = [' /* Just| some| more| text| a|+= 3| +5|-3| + 7| */ |'].join('\n'); + const EXPECTED = [' /* |Just |some |more |text |a+= |3 +|5-|3 + |7 */ |'].join('\n'); const [text,] = deserializePipePositions(EXPECTED); const actualStops = testRepeatedActionAndExtractPositions( text, diff --git a/src/vs/editor/editor.all.ts b/src/vs/editor/editor.all.ts index 6d03ec42c2..40321e61e3 100644 --- a/src/vs/editor/editor.all.ts +++ b/src/vs/editor/editor.all.ts @@ -22,8 +22,8 @@ import 'vs/editor/contrib/find/findController'; import 'vs/editor/contrib/folding/folding'; import 'vs/editor/contrib/fontZoom/fontZoom'; import 'vs/editor/contrib/format/formatActions'; -import 'vs/editor/contrib/goToDefinition/goToDefinitionCommands'; -import 'vs/editor/contrib/goToDefinition/goToDefinitionMouse'; +import 'vs/editor/contrib/gotoSymbol/goToCommands'; +import 'vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition'; import 'vs/editor/contrib/gotoError/gotoError'; import 'vs/editor/contrib/hover/hover'; import 'vs/editor/contrib/inPlaceReplace/inPlaceReplace'; @@ -31,7 +31,6 @@ import 'vs/editor/contrib/linesOperations/linesOperations'; import 'vs/editor/contrib/links/links'; import 'vs/editor/contrib/multicursor/multicursor'; import 'vs/editor/contrib/parameterHints/parameterHints'; -import 'vs/editor/contrib/referenceSearch/referenceSearch'; import 'vs/editor/contrib/rename/rename'; import 'vs/editor/contrib/smartSelect/smartSelect'; import 'vs/editor/contrib/snippet/snippetController2'; diff --git a/src/vs/editor/editor.api.ts b/src/vs/editor/editor.api.ts index f5abe0c592..9379fc3d34 100644 --- a/src/vs/editor/editor.api.ts +++ b/src/vs/editor/editor.api.ts @@ -13,7 +13,7 @@ const global: any = self; // Set defaults for standalone editor (EditorOptions.wrappingIndent).defaultValue = WrappingIndent.None; (EditorOptions.glyphMargin).defaultValue = false; -(EditorOptions.autoIndent).defaultValue = false; +(EditorOptions.autoIndent).defaultValue = 'advanced'; (EditorOptions.overviewRulerLanes).defaultValue = 2; const api = createMonacoBaseAPI(); diff --git a/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.css b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.css index a7bc05744d..8c91b0f954 100644 --- a/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.css +++ b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.css @@ -5,12 +5,9 @@ .monaco-editor .tokens-inspect-widget { z-index: 50; + user-select: text; -webkit-user-select: text; -ms-user-select: text; - -khtml-user-select: text; - -moz-user-select: text; - -o-user-select: text; - user-select: text; padding: 10px; } diff --git a/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts index 3b75c9a16b..71ab88a4c1 100644 --- a/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts +++ b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts @@ -18,7 +18,7 @@ import { FontStyle, IState, ITokenizationSupport, LanguageIdentifier, StandardTo import { NULL_STATE, nullTokenize, nullTokenize2 } from 'vs/editor/common/modes/nullMode'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneThemeService'; -import { editorHoverBackground, editorHoverBorder } from 'vs/platform/theme/common/colorRegistry'; +import { editorHoverBackground, editorHoverBorder, editorHoverForeground } from 'vs/platform/theme/common/colorRegistry'; import { HIGH_CONTRAST, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { InspectTokensNLS } from 'vs/editor/common/standaloneStrings'; @@ -335,4 +335,8 @@ registerThemingParticipant((theme, collector) => { if (background) { collector.addRule(`.monaco-editor .tokens-inspect-widget { background-color: ${background}; }`); } + const foreground = theme.getColor(editorHoverForeground); + if (foreground) { + collector.addRule(`.monaco-editor .tokens-inspect-widget { color: ${foreground}; }`); + } }); diff --git a/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts b/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts index 8f1f0fbce7..2d6254d0c2 100644 --- a/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts +++ b/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts @@ -88,7 +88,7 @@ export class QuickCommandAction extends BaseEditorQuickOpenAction { primary: (browser.isIE ? KeyMod.Alt | KeyCode.F1 : KeyCode.F1), weight: KeybindingWeight.EditorContrib }, - menuOpts: { + contextMenuOpts: { group: 'z_commands', order: 1 } diff --git a/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts b/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts index 580e0ce766..f7363952ac 100644 --- a/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts +++ b/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts @@ -121,7 +121,7 @@ export class QuickOutlineAction extends BaseEditorQuickOpenAction { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_O, weight: KeybindingWeight.EditorContrib }, - menuOpts: { + contextMenuOpts: { group: 'navigation', order: 3 } diff --git a/src/vs/editor/standalone/browser/referenceSearch/standaloneReferenceSearch.ts b/src/vs/editor/standalone/browser/referenceSearch/standaloneReferenceSearch.ts index df140715d4..c491003162 100644 --- a/src/vs/editor/standalone/browser/referenceSearch/standaloneReferenceSearch.ts +++ b/src/vs/editor/standalone/browser/referenceSearch/standaloneReferenceSearch.ts @@ -6,7 +6,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { ReferencesController } from 'vs/editor/contrib/referenceSearch/referencesController'; +import { ReferencesController } from 'vs/editor/contrib/gotoSymbol/peek/referencesController'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 7dbe8f57c5..347747d311 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -31,7 +31,7 @@ import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/commo import { IConfirmation, IConfirmationResult, IDialogOptions, IDialogService, IShowResult } from 'vs/platform/dialogs/common/dialogs'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { AbstractKeybindingService } from 'vs/platform/keybinding/common/abstractKeybindingService'; -import { IKeybindingEvent, IKeyboardEvent, KeybindingSource } from 'vs/platform/keybinding/common/keybinding'; +import { IKeybindingEvent, IKeyboardEvent, KeybindingSource, KeybindingsSchemaContribution } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver'; import { IKeybindingItem, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; @@ -99,8 +99,13 @@ function withTypedEditor(widget: editorCommon.IEditor, codeEditorCallback: (e export class SimpleEditorModelResolverService implements ITextModelService { public _serviceBrand: undefined; + private readonly modelService: IModelService | undefined; private editor?: editorCommon.IEditor; + constructor(modelService: IModelService | undefined) { + this.modelService = modelService; + } + public setEditor(editor: editorCommon.IEditor): void { this.editor = editor; } @@ -132,7 +137,7 @@ export class SimpleEditorModelResolverService implements ITextModelService { } private findModel(editor: ICodeEditor, resource: URI): ITextModel | null { - let model = editor.getModel(); + let model = this.modelService ? this.modelService.getModel(resource) : editor.getModel(); if (model && model.uri.toString() !== resource.toString()) { return null; } @@ -409,6 +414,10 @@ export class StandaloneKeybindingService extends AbstractKeybindingService { public _dumpDebugInfoJSON(): string { return ''; } + + public registerSchemaContribution(contribution: KeybindingsSchemaContribution): void { + // noop + } } function isConfigurationOverrides(thing: any): thing is IConfigurationOverrides { @@ -648,7 +657,9 @@ export class SimpleBulkEditService implements IBulkEditService { let totalEdits = 0; let totalFiles = 0; edits.forEach((edits, model) => { - model.applyEdits(edits.map(edit => EditOperation.replaceMove(Range.lift(edit.range), edit.text))); + model.pushStackElement(); + model.pushEditOperations([], edits.map((e) => EditOperation.replaceMove(Range.lift(e.range), e.text)), () => []); + model.pushStackElement(); totalFiles += 1; totalEdits += edits.length; }); diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index 41faa97e62..34b4c74f66 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { OpenerService } from 'vs/editor/browser/services/openerService'; -import { DiffNavigator } from 'vs/editor/browser/widget/diffNavigator'; +import { DiffNavigator, IDiffNavigator } from 'vs/editor/browser/widget/diffNavigator'; import { ConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo'; import { Token } from 'vs/editor/common/core/token'; @@ -47,7 +47,7 @@ function withAllStandaloneServices(domElement: H let simpleEditorModelResolverService: SimpleEditorModelResolverService | null = null; if (!services.has(ITextModelService)) { - simpleEditorModelResolverService = new SimpleEditorModelResolverService(); + simpleEditorModelResolverService = new SimpleEditorModelResolverService(StaticServices.modelService.get()); services.set(ITextModelService, simpleEditorModelResolverService); } @@ -127,13 +127,6 @@ export function createDiffEditor(domElement: HTMLElement, options?: IDiffEditorC }); } -export interface IDiffNavigator { - canNavigate(): boolean; - next(): void; - previous(): void; - dispose(): void; -} - export interface IDiffNavigatorOptions { readonly followsCaret?: boolean; readonly ignoreCharChanges?: boolean; diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 3e9486a6e3..f1767ea51f 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -89,7 +89,7 @@ export module StaticServices { let _all: LazyStaticService[] = []; - function define(serviceId: ServiceIdentifier, factory: (overrides: IEditorOverrideServices) => T): LazyStaticService { + function define(serviceId: ServiceIdentifier, factory: (overrides: IEditorOverrideServices | undefined) => T): LazyStaticService { let r = new LazyStaticService(serviceId, factory); _all.push(r); return r; @@ -144,12 +144,12 @@ export module StaticServices { export const modeService = define(IModeService, (o) => new ModeServiceImpl()); - export const modelService = define(IModelService, (o) => new ModelServiceImpl(configurationService.get(o), resourcePropertiesService.get(o))); + export const standaloneThemeService = define(IStandaloneThemeService, () => new StandaloneThemeServiceImpl()); + + export const modelService = define(IModelService, (o) => new ModelServiceImpl(configurationService.get(o), resourcePropertiesService.get(o), standaloneThemeService.get(o))); export const markerDecorationsService = define(IMarkerDecorationsService, (o) => new MarkerDecorationsService(modelService.get(o), markerService.get(o))); - export const standaloneThemeService = define(IStandaloneThemeService, () => new StandaloneThemeServiceImpl()); - export const codeEditorService = define(ICodeEditorService, (o) => new StandaloneCodeEditorServiceImpl(standaloneThemeService.get(o))); export const editorProgressService = define(IEditorProgressService, () => new SimpleEditorProgressService()); @@ -194,7 +194,7 @@ export class DynamicStandaloneServices extends Disposable { ensure(IAccessibilityService, () => new BrowserAccessibilityService(contextKeyService, configurationService)); - ensure(IListService, () => new ListService(contextKeyService)); + ensure(IListService, () => new ListService(themeService)); let commandService = ensure(ICommandService, () => new StandaloneCommandService(this._instantiationService)); diff --git a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts index 0cc58b663a..2f184b926f 100644 --- a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts +++ b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts @@ -23,6 +23,7 @@ const colorRegistry = Registry.as(Extensions.ColorContribution); const themingRegistry = Registry.as(ThemingExtensions.ThemingContribution); class StandaloneTheme implements IStandaloneTheme { + public readonly id: string; public readonly themeName: string; @@ -128,6 +129,14 @@ class StandaloneTheme implements IStandaloneTheme { } return this._tokenTheme; } + + public getTokenStyleMetadata(type: string, modifiers: string[]): number | undefined { + return undefined; + } + + public get tokenColorMap(): string[] { + return []; + } } function isBuiltinTheme(themeName: string): themeName is BuiltinTheme { diff --git a/src/vs/editor/standalone/common/monarch/monarchLexer.ts b/src/vs/editor/standalone/common/monarch/monarchLexer.ts index 2aaad1bf06..ec4c87d1e5 100644 --- a/src/vs/editor/standalone/common/monarch/monarchLexer.ts +++ b/src/vs/editor/standalone/common/monarch/monarchLexer.ts @@ -421,24 +421,22 @@ export class MonarchTokenizer implements modes.ITokenizationSupport { public getLoadStatus(): ILoadStatus { let promises: Thenable[] = []; for (let nestedModeId in this._embeddedModes) { - if (this._embeddedModes.hasOwnProperty(nestedModeId)) { - const tokenizationSupport = modes.TokenizationRegistry.get(nestedModeId); - if (tokenizationSupport) { - // The nested mode is already loaded - if (tokenizationSupport instanceof MonarchTokenizer) { - const nestedModeStatus = tokenizationSupport.getLoadStatus(); - if (nestedModeStatus.loaded === false) { - promises.push(nestedModeStatus.promise); - } + const tokenizationSupport = modes.TokenizationRegistry.get(nestedModeId); + if (tokenizationSupport) { + // The nested mode is already loaded + if (tokenizationSupport instanceof MonarchTokenizer) { + const nestedModeStatus = tokenizationSupport.getLoadStatus(); + if (nestedModeStatus.loaded === false) { + promises.push(nestedModeStatus.promise); } - continue; } + continue; + } - const tokenizationSupportPromise = modes.TokenizationRegistry.getPromise(nestedModeId); - if (tokenizationSupportPromise) { - // The nested mode is in the process of being loaded - promises.push(tokenizationSupportPromise); - } + const tokenizationSupportPromise = modes.TokenizationRegistry.getPromise(nestedModeId); + if (tokenizationSupportPromise) { + // The nested mode is in the process of being loaded + promises.push(tokenizationSupportPromise); } } diff --git a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts index 6beba732e7..f054a393a7 100644 --- a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts +++ b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts @@ -54,9 +54,16 @@ suite('TokenizationSupport2Adapter', () => { defines: (color: ColorIdentifier): boolean => { throw new Error('Not implemented'); - } + }, + + getTokenStyleMetadata: (type: string, modifiers: string[]): number | undefined => { + return undefined; + }, + + tokenColorMap: [] }; } + public getIconTheme(): IIconTheme { return { hasFileIcons: false, diff --git a/src/vs/editor/test/browser/commands/shiftCommand.test.ts b/src/vs/editor/test/browser/commands/shiftCommand.test.ts index 265786c631..b709eb0a75 100644 --- a/src/vs/editor/test/browser/commands/shiftCommand.test.ts +++ b/src/vs/editor/test/browser/commands/shiftCommand.test.ts @@ -14,6 +14,7 @@ import { getEditOperation, testCommand } from 'vs/editor/test/browser/testComman import { withEditorModel } 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 { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions'; /** * Create single edit operation @@ -50,6 +51,7 @@ function testShiftCommand(lines: string[], languageIdentifier: LanguageIdentifie indentSize: 4, insertSpaces: false, useTabStops: useTabStops, + autoIndent: EditorAutoIndentStrategy.Full, }), expectedLines, expectedSelection); } @@ -60,6 +62,7 @@ function testUnshiftCommand(lines: string[], languageIdentifier: LanguageIdentif indentSize: 4, insertSpaces: false, useTabStops: useTabStops, + autoIndent: EditorAutoIndentStrategy.Full, }), expectedLines, expectedSelection); } @@ -672,7 +675,8 @@ suite('Editor Commands - ShiftCommand', () => { tabSize: 4, indentSize: 4, insertSpaces: true, - useTabStops: false + useTabStops: false, + autoIndent: EditorAutoIndentStrategy.Full, }), [ ' Written | Numeric', @@ -717,7 +721,8 @@ suite('Editor Commands - ShiftCommand', () => { tabSize: 4, indentSize: 4, insertSpaces: true, - useTabStops: false + useTabStops: false, + autoIndent: EditorAutoIndentStrategy.Full, }), [ ' Written | Numeric', @@ -762,7 +767,8 @@ suite('Editor Commands - ShiftCommand', () => { tabSize: 4, indentSize: 4, insertSpaces: false, - useTabStops: false + useTabStops: false, + autoIndent: EditorAutoIndentStrategy.Full, }), [ ' Written | Numeric', @@ -807,7 +813,8 @@ suite('Editor Commands - ShiftCommand', () => { tabSize: 4, indentSize: 4, insertSpaces: true, - useTabStops: false + useTabStops: false, + autoIndent: EditorAutoIndentStrategy.Full, }), [ ' Written | Numeric', @@ -841,7 +848,8 @@ suite('Editor Commands - ShiftCommand', () => { tabSize: 4, indentSize: 4, insertSpaces: false, - useTabStops: true + useTabStops: true, + autoIndent: EditorAutoIndentStrategy.Full, }), [ '\tHello world!', @@ -951,7 +959,8 @@ suite('Editor Commands - ShiftCommand', () => { tabSize: tabSize, indentSize: indentSize, insertSpaces: insertSpaces, - useTabStops: true + useTabStops: true, + autoIndent: EditorAutoIndentStrategy.Full, }); let actual = getEditOperation(model, op); assert.deepEqual(actual, expected); @@ -965,7 +974,8 @@ suite('Editor Commands - ShiftCommand', () => { tabSize: tabSize, indentSize: indentSize, insertSpaces: insertSpaces, - useTabStops: true + useTabStops: true, + autoIndent: EditorAutoIndentStrategy.Full, }); let actual = getEditOperation(model, op); assert.deepEqual(actual, expected); diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index 4aff37d49c..7dc1ba1213 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -726,7 +726,7 @@ suite('Editor Controller - Cursor', () => { }); }); - test('combining marks', () => { + test('grapheme breaking', () => { withTestCodeEditor([ 'abcabc', 'ãããããã', @@ -2834,7 +2834,7 @@ suite('Editor Controller - Indentation Rules', () => { ], languageIdentifier: mode.getLanguageIdentifier(), modelOpts: { insertSpaces: false }, - editorOpts: { autoIndent: true } + editorOpts: { autoIndent: 'full' } }, (model, cursor) => { moveTo(cursor, 1, 12, false); assertCursor(cursor, new Selection(1, 12, 1, 12)); @@ -2857,7 +2857,7 @@ suite('Editor Controller - Indentation Rules', () => { '\t' ], languageIdentifier: mode.getLanguageIdentifier(), - editorOpts: { autoIndent: true } + editorOpts: { autoIndent: 'full' } }, (model, cursor) => { moveTo(cursor, 2, 2, false); assertCursor(cursor, new Selection(2, 2, 2, 2)); @@ -2876,7 +2876,7 @@ suite('Editor Controller - Indentation Rules', () => { ], languageIdentifier: mode.getLanguageIdentifier(), modelOpts: { insertSpaces: false }, - editorOpts: { autoIndent: true } + editorOpts: { autoIndent: 'full' } }, (model, cursor) => { moveTo(cursor, 2, 15, false); assertCursor(cursor, new Selection(2, 15, 2, 15)); @@ -2896,7 +2896,7 @@ suite('Editor Controller - Indentation Rules', () => { ], languageIdentifier: mode.getLanguageIdentifier(), modelOpts: { insertSpaces: false }, - editorOpts: { autoIndent: true } + editorOpts: { autoIndent: 'full' } }, (model, cursor) => { moveTo(cursor, 2, 14, false); assertCursor(cursor, new Selection(2, 14, 2, 14)); @@ -2924,7 +2924,7 @@ suite('Editor Controller - Indentation Rules', () => { mode.getLanguageIdentifier() ); - withTestCodeEditor(null, { model: model, autoIndent: true }, (editor, cursor) => { + withTestCodeEditor(null, { model: model, autoIndent: 'full' }, (editor, cursor) => { moveTo(cursor, 2, 11, false); assertCursor(cursor, new Selection(2, 11, 2, 11)); @@ -2948,7 +2948,7 @@ suite('Editor Controller - Indentation Rules', () => { '}}' ], languageIdentifier: mode.getLanguageIdentifier(), - editorOpts: { autoIndent: true } + editorOpts: { autoIndent: 'full' } }, (model, cursor) => { moveTo(cursor, 3, 13, false); assertCursor(cursor, new Selection(3, 13, 3, 13)); @@ -3084,7 +3084,7 @@ suite('Editor Controller - Indentation Rules', () => { ], languageIdentifier: mode.getLanguageIdentifier(), modelOpts: { insertSpaces: false }, - editorOpts: { autoIndent: true } + editorOpts: { autoIndent: 'full' } }, (model, cursor) => { moveTo(cursor, 5, 4, false); assertCursor(cursor, new Selection(5, 4, 5, 4)); @@ -3554,7 +3554,7 @@ suite('Editor Controller - Indentation Rules', () => { rubyMode.getLanguageIdentifier() ); - withTestCodeEditor(null, { model: model, autoIndent: true }, (editor, cursor) => { + withTestCodeEditor(null, { model: model, autoIndent: 'full' }, (editor, cursor) => { moveTo(cursor, 4, 7, false); assertCursor(cursor, new Selection(4, 7, 4, 7)); @@ -3615,7 +3615,7 @@ suite('Editor Controller - Indentation Rules', () => { '\t\t' ], languageIdentifier: mode.getLanguageIdentifier(), - editorOpts: { autoIndent: true } + editorOpts: { autoIndent: 'full' } }, (model, cursor) => { moveTo(cursor, 3, 3, false); assertCursor(cursor, new Selection(3, 3, 3, 3)); @@ -3664,7 +3664,7 @@ suite('Editor Controller - Indentation Rules', () => { mode.getLanguageIdentifier() ); - withTestCodeEditor(null, { model: model, autoIndent: false }, (editor, cursor) => { + withTestCodeEditor(null, { model: model, autoIndent: 'advanced' }, (editor, cursor) => { moveTo(cursor, 7, 6, false); assertCursor(cursor, new Selection(7, 6, 7, 6)); @@ -3728,7 +3728,7 @@ suite('Editor Controller - Indentation Rules', () => { mode.getLanguageIdentifier() ); - withTestCodeEditor(null, { model: model, autoIndent: false }, (editor, cursor) => { + withTestCodeEditor(null, { model: model, autoIndent: 'advanced' }, (editor, cursor) => { moveTo(cursor, 8, 1, false); assertCursor(cursor, new Selection(8, 1, 8, 1)); @@ -3791,7 +3791,7 @@ suite('Editor Controller - Indentation Rules', () => { mode.getLanguageIdentifier() ); - withTestCodeEditor(null, { model: model, autoIndent: true }, (editor, cursor) => { + withTestCodeEditor(null, { model: model, autoIndent: 'full' }, (editor, cursor) => { moveTo(cursor, 3, 19, false); assertCursor(cursor, new Selection(3, 19, 3, 19)); @@ -4936,6 +4936,35 @@ suite('autoClosingPairs', () => { mode.dispose(); }); + test('issue #84998: Overtyping Brackets doesn\'t work after backslash', () => { + let mode = new AutoClosingMode(); + usingCursor({ + text: [ + '' + ], + languageIdentifier: mode.getLanguageIdentifier() + }, (model, cursor) => { + + cursor.setSelections('test', [new Selection(1, 1, 1, 1)]); + + cursorCommand(cursor, H.Type, { text: '\\' }, 'keyboard'); + assert.equal(model.getValue(), '\\'); + + cursorCommand(cursor, H.Type, { text: '(' }, 'keyboard'); + assert.equal(model.getValue(), '\\()'); + + cursorCommand(cursor, H.Type, { text: 'abc' }, 'keyboard'); + assert.equal(model.getValue(), '\\(abc)'); + + cursorCommand(cursor, H.Type, { text: '\\' }, 'keyboard'); + assert.equal(model.getValue(), '\\(abc\\)'); + + cursorCommand(cursor, H.Type, { text: ')' }, 'keyboard'); + assert.equal(model.getValue(), '\\(abc\\)'); + }); + mode.dispose(); + }); + test('issue #2773: Accents (´`¨^, others?) are inserted in the wrong position (Mac)', () => { let mode = new AutoClosingMode(); usingCursor({ diff --git a/src/vs/editor/test/browser/controller/imeTester.ts b/src/vs/editor/test/browser/controller/imeTester.ts index 67d317cdc5..43b3620abe 100644 --- a/src/vs/editor/test/browser/controller/imeTester.ts +++ b/src/vs/editor/test/browser/controller/imeTester.ts @@ -103,7 +103,7 @@ function doCreateTest(description: string, inputStr: string, expectedStr: string const selection = new Range(1, 1 + cursorOffset, 1, 1 + cursorOffset + cursorLength); - return PagedScreenReaderStrategy.fromEditorSelection(currentState, model, selection, true); + return PagedScreenReaderStrategy.fromEditorSelection(currentState, model, selection, 10, true); }, deduceModelPosition: (viewAnchorPosition: Position, deltaOffset: number, lineFeedCnt: number): Position => { return null!; diff --git a/src/vs/editor/test/browser/controller/textAreaState.test.ts b/src/vs/editor/test/browser/controller/textAreaState.test.ts index f8a8ec061f..2029e8fe7f 100644 --- a/src/vs/editor/test/browser/controller/textAreaState.test.ts +++ b/src/vs/editor/test/browser/controller/textAreaState.test.ts @@ -535,7 +535,7 @@ suite('TextAreaState', () => { function testPagedScreenReaderStrategy(lines: string[], selection: Selection, expected: TextAreaState): void { const model = TextModel.createFromString(lines.join('\n')); - const actual = PagedScreenReaderStrategy.fromEditorSelection(TextAreaState.EMPTY, model, selection, true); + const actual = PagedScreenReaderStrategy.fromEditorSelection(TextAreaState.EMPTY, model, selection, 10, true); assert.ok(equalsTextAreaState(actual, expected)); model.dispose(); } diff --git a/src/vs/editor/test/browser/services/openerService.test.ts b/src/vs/editor/test/browser/services/openerService.test.ts index c16eb4898b..332806420d 100644 --- a/src/vs/editor/test/browser/services/openerService.test.ts +++ b/src/vs/editor/test/browser/services/openerService.test.ts @@ -8,6 +8,7 @@ import { URI } from 'vs/base/common/uri'; import { OpenerService } from 'vs/editor/browser/services/openerService'; import { TestCodeEditorService } from 'vs/editor/test/browser/editorTestServices'; import { CommandsRegistry, ICommandService, NullCommandService } from 'vs/platform/commands/common/commands'; +import { matchesScheme } from 'vs/platform/opener/common/opener'; suite('OpenerService', function () { const editorService = new TestCodeEditorService(); @@ -28,27 +29,27 @@ suite('OpenerService', function () { lastCommand = undefined; }); - test('delegate to editorService, scheme:///fff', function () { + test('delegate to editorService, scheme:///fff', async function () { const openerService = new OpenerService(editorService, NullCommandService); - openerService.open(URI.parse('another:///somepath')); + await openerService.open(URI.parse('another:///somepath')); assert.equal(editorService.lastInput!.options!.selection, undefined); }); - test('delegate to editorService, scheme:///fff#L123', function () { + test('delegate to editorService, scheme:///fff#L123', async function () { const openerService = new OpenerService(editorService, NullCommandService); - openerService.open(URI.parse('file:///somepath#L23')); + await openerService.open(URI.parse('file:///somepath#L23')); assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23); assert.equal(editorService.lastInput!.options!.selection!.startColumn, 1); assert.equal(editorService.lastInput!.options!.selection!.endLineNumber, undefined); assert.equal(editorService.lastInput!.options!.selection!.endColumn, undefined); assert.equal(editorService.lastInput!.resource.fragment, ''); - openerService.open(URI.parse('another:///somepath#L23')); + await openerService.open(URI.parse('another:///somepath#L23')); assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23); assert.equal(editorService.lastInput!.options!.selection!.startColumn, 1); - openerService.open(URI.parse('another:///somepath#L23,45')); + await openerService.open(URI.parse('another:///somepath#L23,45')); assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23); assert.equal(editorService.lastInput!.options!.selection!.startColumn, 45); assert.equal(editorService.lastInput!.options!.selection!.endLineNumber, undefined); @@ -56,17 +57,17 @@ suite('OpenerService', function () { assert.equal(editorService.lastInput!.resource.fragment, ''); }); - test('delegate to editorService, scheme:///fff#123,123', function () { + test('delegate to editorService, scheme:///fff#123,123', async function () { const openerService = new OpenerService(editorService, NullCommandService); - openerService.open(URI.parse('file:///somepath#23')); + await openerService.open(URI.parse('file:///somepath#23')); assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23); assert.equal(editorService.lastInput!.options!.selection!.startColumn, 1); assert.equal(editorService.lastInput!.options!.selection!.endLineNumber, undefined); assert.equal(editorService.lastInput!.options!.selection!.endColumn, undefined); assert.equal(editorService.lastInput!.resource.fragment, ''); - openerService.open(URI.parse('file:///somepath#23,45')); + await openerService.open(URI.parse('file:///somepath#23,45')); assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23); assert.equal(editorService.lastInput!.options!.selection!.startColumn, 45); assert.equal(editorService.lastInput!.options!.selection!.endLineNumber, undefined); @@ -74,22 +75,22 @@ suite('OpenerService', function () { assert.equal(editorService.lastInput!.resource.fragment, ''); }); - test('delegate to commandsService, command:someid', function () { + test('delegate to commandsService, command:someid', async function () { const openerService = new OpenerService(editorService, commandService); const id = `aCommand${Math.random()}`; CommandsRegistry.registerCommand(id, function () { }); - openerService.open(URI.parse('command:' + id)); + await openerService.open(URI.parse('command:' + id)); assert.equal(lastCommand!.id, id); assert.equal(lastCommand!.args.length, 0); - openerService.open(URI.parse('command:' + id).with({ query: '123' })); + await openerService.open(URI.parse('command:' + id).with({ query: '123' })); assert.equal(lastCommand!.id, id); assert.equal(lastCommand!.args.length, 1); assert.equal(lastCommand!.args[0], '123'); - openerService.open(URI.parse('command:' + id).with({ query: JSON.stringify([12, true]) })); + await openerService.open(URI.parse('command:' + id).with({ query: JSON.stringify([12, true]) })); assert.equal(lastCommand!.id, id); assert.equal(lastCommand!.args.length, 2); assert.equal(lastCommand!.args[0], 12); @@ -199,4 +200,18 @@ suite('OpenerService', function () { assert.equal(v1, 2); assert.equal(v2, 0); }); + + test('matchesScheme', function () { + assert.ok(matchesScheme('https://microsoft.com', 'https')); + assert.ok(matchesScheme('http://microsoft.com', 'http')); + assert.ok(matchesScheme('hTTPs://microsoft.com', 'https')); + assert.ok(matchesScheme('httP://microsoft.com', 'http')); + assert.ok(matchesScheme(URI.parse('https://microsoft.com'), 'https')); + assert.ok(matchesScheme(URI.parse('http://microsoft.com'), 'http')); + assert.ok(matchesScheme(URI.parse('hTTPs://microsoft.com'), 'https')); + assert.ok(matchesScheme(URI.parse('httP://microsoft.com'), 'http')); + assert.ok(!matchesScheme(URI.parse('https://microsoft.com'), 'http')); + assert.ok(!matchesScheme(URI.parse('htt://microsoft.com'), 'http')); + assert.ok(!matchesScheme(URI.parse('z://microsoft.com'), 'http')); + }); }); diff --git a/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts b/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts index cba210f9c9..7728e2e483 100644 --- a/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts +++ b/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts @@ -86,7 +86,7 @@ suite('MinimapCharRenderer', () => { imageData.data[4 * i + 2] = background.b; imageData.data[4 * i + 3] = 255; } - renderer.renderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, false); + renderer.renderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, 2, false); let actual: number[] = []; for (let i = 0; i < imageData.data.length; i++) { @@ -116,7 +116,7 @@ suite('MinimapCharRenderer', () => { imageData.data[4 * i + 3] = 255; } - renderer.renderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, false); + renderer.renderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, 1, false); let actual: number[] = []; for (let i = 0; i < imageData.data.length; i++) { diff --git a/src/vs/editor/test/browser/view/minimapFontCreator.html b/src/vs/editor/test/browser/view/minimapFontCreator.html deleted file mode 100644 index 9ddd334797..0000000000 --- a/src/vs/editor/test/browser/view/minimapFontCreator.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/vs/editor/test/browser/view/minimapFontCreator.ts b/src/vs/editor/test/browser/view/minimapFontCreator.ts deleted file mode 100644 index b52adb977f..0000000000 --- a/src/vs/editor/test/browser/view/minimapFontCreator.ts +++ /dev/null @@ -1,119 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { RGBA8 } from 'vs/editor/common/core/rgba'; -import { MinimapCharRenderer } from 'vs/editor/browser/viewParts/minimap/minimapCharRenderer'; -import { Constants } from 'vs/editor/browser/viewParts/minimap/minimapCharSheet'; -import { MinimapCharRendererFactory } from 'vs/editor/browser/viewParts/minimap/minimapCharRendererFactory'; - -let sampleData = MinimapCharRendererFactory.createSampleData('monospace'); -let minimapCharRenderer1x = MinimapCharRendererFactory.createFromSampleData(sampleData.data, 1); -let minimapCharRenderer2x = MinimapCharRendererFactory.createFromSampleData(sampleData.data, 2); -let minimapCharRenderer4x = MinimapCharRendererFactory.createFromSampleData(sampleData.data, 4); -let minimapCharRenderer6x = MinimapCharRendererFactory.createFromSampleData(sampleData.data, 6); - -renderImageData(sampleData, 10, 100); -renderMinimapCharRenderer(minimapCharRenderer1x, 400, 1); -renderMinimapCharRenderer(minimapCharRenderer2x, 500, 2); -renderMinimapCharRenderer(minimapCharRenderer4x, 600, 4); -renderMinimapCharRenderer(minimapCharRenderer6x, 750, 8); - -function createFakeImageData(width: number, height: number): ImageData { - return { - width: width, - height: height, - data: new Uint8ClampedArray(width * height * Constants.RGBA_CHANNELS_CNT) - }; -} - -function renderMinimapCharRenderer(minimapCharRenderer: MinimapCharRenderer, y: number, scale: number): void { - let background = new RGBA8(0, 0, 0, 255); - let color = new RGBA8(255, 255, 255, 255); - - { - let x2 = createFakeImageData( - Constants.BASE_CHAR_WIDTH * scale * Constants.CHAR_COUNT, - Constants.BASE_CHAR_HEIGHT * scale - ); - // set the background color - for (let i = 0, len = x2.data.length / 4; i < len; i++) { - x2.data[4 * i + 0] = background.r; - x2.data[4 * i + 1] = background.g; - x2.data[4 * i + 2] = background.b; - x2.data[4 * i + 3] = 255; - } - let dx = 0; - for (let chCode = Constants.START_CH_CODE; chCode <= Constants.END_CH_CODE; chCode++) { - minimapCharRenderer.renderChar(x2, dx, 0, chCode, color, background, false); - dx += Constants.BASE_CHAR_WIDTH * scale; - } - renderImageData(x2, 10, y); - } -} - -(function () { - let r = 'let x2Data = [', - offset = 0; - for (let charIndex = 0; charIndex < Constants.CHAR_COUNT; charIndex++) { - let charCode = charIndex + Constants.START_CH_CODE; - r += '\n\n// ' + String.fromCharCode(charCode); - - for (let i = 0; i < Constants.BASE_CHAR_HEIGHT * 2; i++) { - if (i % 2 === 0) { - r += '\n'; - } - r += (minimapCharRenderer2x as any).charDataNormal[offset] + ','; - offset++; - } - } - r += '\n\n]'; - console.log(r); -})(); - -(function () { - let r = 'let x1Data = [', - offset = 0; - for (let charIndex = 0; charIndex < Constants.CHAR_COUNT; charIndex++) { - let charCode = charIndex + Constants.START_CH_CODE; - r += '\n\n// ' + String.fromCharCode(charCode); - - for (let i = 0; i < Constants.BASE_CHAR_HEIGHT * Constants.BASE_CHAR_WIDTH; i++) { - r += '\n'; - r += (minimapCharRenderer1x as any).charDataNormal[offset] + ','; - offset++; - } - } - r += '\n\n]'; - console.log(r); -})(); - -function renderImageData(imageData: ImageData, left: number, top: number): void { - let output = ''; - let offset = 0; - let PX_SIZE = 15; - for (let i = 0; i < imageData.height; i++) { - for (let j = 0; j < imageData.width; j++) { - let R = imageData.data[offset]; - let G = imageData.data[offset + 1]; - let B = imageData.data[offset + 2]; - let A = imageData.data[offset + 3]; - offset += 4; - - output += `
`; - } - } - - let domNode = document.createElement('div'); - domNode.style.position = 'absolute'; - domNode.style.top = top + 'px'; - domNode.style.left = left + 'px'; - domNode.style.width = imageData.width * PX_SIZE + 'px'; - domNode.style.height = imageData.height * PX_SIZE + 'px'; - domNode.style.border = '1px solid #ccc'; - domNode.style.background = '#000000'; - domNode.innerHTML = output; - document.body.appendChild(domNode); -} diff --git a/src/vs/editor/test/common/model/textModel.test.ts b/src/vs/editor/test/common/model/textModel.test.ts index 84021b31eb..07dd91eee3 100644 --- a/src/vs/editor/test/common/model/textModel.test.ts +++ b/src/vs/editor/test/common/model/textModel.test.ts @@ -755,7 +755,7 @@ suite('Editor Model - TextModel', () => { assert.deepEqual(actual, expected, `validateRange for ${input}, got ${actual}, expected ${expected}`); } - test('combining marks', () => { + test('grapheme breaking', () => { const m = TextModel.createFromString([ 'abcabc', 'ãããããã', diff --git a/src/vs/editor/test/common/modes/linkComputer.test.ts b/src/vs/editor/test/common/modes/linkComputer.test.ts index 5036e42728..165d1f6caa 100644 --- a/src/vs/editor/test/common/modes/linkComputer.test.ts +++ b/src/vs/editor/test/common/modes/linkComputer.test.ts @@ -202,4 +202,11 @@ suite('Editor Modes - Link Computer', () => { ' http://[::1]:5000/connect/token ' ); }); + + test('issue #70254: bold links dont open in markdown file using editor mode with ctrl + click', () => { + assertLink( + '2. Navigate to **https://portal.azure.com**', + ' https://portal.azure.com ' + ); + }); }); diff --git a/src/vs/editor/test/common/modes/supports/onEnter.test.ts b/src/vs/editor/test/common/modes/supports/onEnter.test.ts index cdef0c609f..e2407184f4 100644 --- a/src/vs/editor/test/common/modes/supports/onEnter.test.ts +++ b/src/vs/editor/test/common/modes/supports/onEnter.test.ts @@ -6,6 +6,7 @@ import * as assert from 'assert'; import { CharacterPair, IndentAction } from 'vs/editor/common/modes/languageConfiguration'; import { OnEnterSupport } from 'vs/editor/common/modes/supports/onEnter'; import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules'; +import { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions'; suite('OnEnter', () => { @@ -18,7 +19,7 @@ suite('OnEnter', () => { brackets: brackets }); let testIndentAction = (beforeText: string, afterText: string, expected: IndentAction) => { - let actual = support.onEnter('', beforeText, afterText); + let actual = support.onEnter(EditorAutoIndentStrategy.Advanced, '', beforeText, afterText); if (expected === IndentAction.None) { assert.equal(actual, null); } else { @@ -48,10 +49,10 @@ suite('OnEnter', () => { test('uses regExpRules', () => { let support = new OnEnterSupport({ - regExpRules: javascriptOnEnterRules + onEnterRules: javascriptOnEnterRules }); let testIndentAction = (oneLineAboveText: string, beforeText: string, afterText: string, expectedIndentAction: IndentAction | null, expectedAppendText: string | null, removeText: number = 0) => { - let actual = support.onEnter(oneLineAboveText, beforeText, afterText); + let actual = support.onEnter(EditorAutoIndentStrategy.Advanced, oneLineAboveText, beforeText, afterText); if (expectedIndentAction === null) { assert.equal(actual, null, 'isNull:' + beforeText); } else { @@ -132,4 +133,4 @@ suite('OnEnter', () => { testIndentAction('', ' * test() {', '', IndentAction.Indent, null, 0); testIndentAction(' ', ' * test() {', '', IndentAction.Indent, null, 0); }); -}); \ No newline at end of file +}); diff --git a/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts b/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts index 5409458a6d..7de605f9a3 100644 --- a/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts +++ b/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts @@ -109,9 +109,9 @@ suite('Editor Modes - textToHtmlTokenizer', () => { [ '
', 'Ciao', - ' ', + ' ', 'hello', - ' ', + ' ', 'world!', '
' ].join('') @@ -122,9 +122,9 @@ suite('Editor Modes - textToHtmlTokenizer', () => { [ '
', 'Ciao', - ' ', + ' ', 'hello', - ' ', + ' ', 'w', '
' ].join('') @@ -135,9 +135,9 @@ suite('Editor Modes - textToHtmlTokenizer', () => { [ '
', 'Ciao', - ' ', + ' ', 'hello', - ' ', + ' ', '
' ].join('') ); @@ -147,9 +147,9 @@ suite('Editor Modes - textToHtmlTokenizer', () => { [ '
', 'iao', - ' ', + ' ', 'hello', - ' ', + ' ', '
' ].join('') ); @@ -158,9 +158,9 @@ suite('Editor Modes - textToHtmlTokenizer', () => { tokenizeLineToHTML(text, lineTokens, colorMap, 4, 11, 4, true), [ '
', - ' ', + ' ', 'hello', - ' ', + ' ', '
' ].join('') ); @@ -170,7 +170,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { [ '
', 'hello', - ' ', + ' ', '
' ].join('') ); @@ -241,11 +241,11 @@ suite('Editor Modes - textToHtmlTokenizer', () => { tokenizeLineToHTML(text, lineTokens, colorMap, 0, 21, 4, true), [ '
', - '  ', + '  ', 'Ciao', - '   ', + '   ', 'hello', - ' ', + ' ', 'world!', '
' ].join('') @@ -255,11 +255,11 @@ suite('Editor Modes - textToHtmlTokenizer', () => { tokenizeLineToHTML(text, lineTokens, colorMap, 0, 17, 4, true), [ '
', - '  ', + '  ', 'Ciao', - '   ', + '   ', 'hello', - ' ', + ' ', 'wo', '
' ].join('') @@ -269,7 +269,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { tokenizeLineToHTML(text, lineTokens, colorMap, 0, 3, 4, true), [ '
', - '  ', + '  ', 'C', '
' ].join('') diff --git a/src/vs/editor/test/common/services/editorSimpleWorker.test.ts b/src/vs/editor/test/common/services/editorSimpleWorker.test.ts index 0c034204cb..93eb5e988d 100644 --- a/src/vs/editor/test/common/services/editorSimpleWorker.test.ts +++ b/src/vs/editor/test/common/services/editorSimpleWorker.test.ts @@ -167,9 +167,8 @@ suite('EditorSimpleWorker', () => { assert.ok(false); return; } - const { suggestions } = result; - assert.equal(suggestions.length, 1); - assert.equal(suggestions[0].label, 'foobar'); + assert.equal(result.length, 1); + assert.equal(result, 'foobar'); }); }); diff --git a/src/vs/editor/test/common/services/modelService.test.ts b/src/vs/editor/test/common/services/modelService.test.ts index 7d657c12c2..e01ff62ff1 100644 --- a/src/vs/editor/test/common/services/modelService.test.ts +++ b/src/vs/editor/test/common/services/modelService.test.ts @@ -16,6 +16,7 @@ import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; const GENERATE_TESTS = false; @@ -27,7 +28,7 @@ suite('ModelService', () => { configService.setUserConfiguration('files', { 'eol': '\n' }); configService.setUserConfiguration('files', { 'eol': '\r\n' }, URI.file(platform.isWindows ? 'c:\\myroot' : '/myroot')); - modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService)); + modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService()); }); teardown(() => { diff --git a/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts b/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts index 4a7b9fdbc7..516c85bf21 100644 --- a/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts +++ b/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts @@ -702,12 +702,12 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { decorationsHeight: 800, contentLeft: 10, - contentWidth: 901, + contentWidth: 893, contentHeight: 800, renderMinimap: RenderMinimap.Text, - minimapLeft: 911, - minimapWidth: 89, + minimapLeft: 903, + minimapWidth: 97, viewportColumn: 89, verticalScrollbarWidth: 0, @@ -760,12 +760,12 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { decorationsHeight: 800, contentLeft: 10, - contentWidth: 901, + contentWidth: 893, contentHeight: 800, renderMinimap: RenderMinimap.Text, - minimapLeft: 911, - minimapWidth: 89, + minimapLeft: 903, + minimapWidth: 97, viewportColumn: 89, verticalScrollbarWidth: 0, @@ -818,13 +818,13 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { decorationsHeight: 800, contentLeft: 10, - contentWidth: 943, + contentWidth: 935, contentHeight: 800, renderMinimap: RenderMinimap.Text, - minimapLeft: 953, - minimapWidth: 47, - viewportColumn: 94, + minimapLeft: 945, + minimapWidth: 55, + viewportColumn: 93, verticalScrollbarWidth: 0, horizontalScrollbarHeight: 0, @@ -863,26 +863,26 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { width: 1000, height: 800, - glyphMarginLeft: 47, + glyphMarginLeft: 55, glyphMarginWidth: 0, glyphMarginHeight: 800, - lineNumbersLeft: 47, + lineNumbersLeft: 55, lineNumbersWidth: 0, lineNumbersHeight: 800, - decorationsLeft: 47, + decorationsLeft: 55, decorationsWidth: 10, decorationsHeight: 800, - contentLeft: 57, - contentWidth: 943, + contentLeft: 65, + contentWidth: 935, contentHeight: 800, renderMinimap: RenderMinimap.Text, minimapLeft: 0, - minimapWidth: 47, - viewportColumn: 94, + minimapWidth: 55, + viewportColumn: 93, verticalScrollbarWidth: 0, horizontalScrollbarHeight: 0, @@ -934,12 +934,12 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { decorationsHeight: 422, contentLeft: 92, - contentWidth: 1026, + contentWidth: 1018, contentHeight: 422, renderMinimap: RenderMinimap.Text, - minimapLeft: 1104, - minimapWidth: 83, + minimapLeft: 1096, + minimapWidth: 91, viewportColumn: 83, verticalScrollbarWidth: 14, diff --git a/src/vs/editor/test/common/viewLayout/linesLayout.test.ts b/src/vs/editor/test/common/viewLayout/linesLayout.test.ts index 512b76254c..471b7853a0 100644 --- a/src/vs/editor/test/common/viewLayout/linesLayout.test.ts +++ b/src/vs/editor/test/common/viewLayout/linesLayout.test.ts @@ -3,10 +3,28 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { LinesLayout } from 'vs/editor/common/viewLayout/linesLayout'; +import { LinesLayout, EditorWhitespace } from 'vs/editor/common/viewLayout/linesLayout'; suite('Editor ViewLayout - LinesLayout', () => { + function insertWhitespace(linesLayout: LinesLayout, afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): string { + return linesLayout.changeWhitespace((accessor) => { + return accessor.insertWhitespace(afterLineNumber, ordinal, heightInPx, minWidth); + }); + } + + function changeOneWhitespace(linesLayout: LinesLayout, id: string, newAfterLineNumber: number, newHeight: number): void { + linesLayout.changeWhitespace((accessor) => { + accessor.changeOneWhitespace(id, newAfterLineNumber, newHeight); + }); + } + + function removeWhitespace(linesLayout: LinesLayout, id: string): void { + linesLayout.changeWhitespace((accessor) => { + accessor.removeWhitespace(id); + }); + } + test('LinesLayout 1', () => { // Start off with 10 lines @@ -39,7 +57,7 @@ suite('Editor ViewLayout - LinesLayout', () => { assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(29), 3); // Add whitespace of height 5px after 2nd line - linesLayout.insertWhitespace(2, 0, 5, 0); + insertWhitespace(linesLayout, 2, 0, 5, 0); // lines: [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] // whitespace: a(2,5) assert.equal(linesLayout.getLinesTotalHeight(), 105); @@ -63,8 +81,8 @@ suite('Editor ViewLayout - LinesLayout', () => { assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(105), 10); // Add two more whitespaces of height 5px - linesLayout.insertWhitespace(3, 0, 5, 0); - linesLayout.insertWhitespace(4, 0, 5, 0); + insertWhitespace(linesLayout, 3, 0, 5, 0); + insertWhitespace(linesLayout, 4, 0, 5, 0); // lines: [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] // whitespace: a(2,5), b(3, 5), c(4, 5) assert.equal(linesLayout.getLinesTotalHeight(), 115); @@ -120,7 +138,7 @@ suite('Editor ViewLayout - LinesLayout', () => { // Start off with 10 lines and one whitespace after line 2, of height 5 let linesLayout = new LinesLayout(10, 1); - let a = linesLayout.insertWhitespace(2, 0, 5, 0); + let a = insertWhitespace(linesLayout, 2, 0, 5, 0); // 10 lines // whitespace: - a(2,5) @@ -139,7 +157,7 @@ suite('Editor ViewLayout - LinesLayout', () => { // Change whitespace height // 10 lines // whitespace: - a(2,10) - linesLayout.changeWhitespace(a, 2, 10); + changeOneWhitespace(linesLayout, a, 2, 10); assert.equal(linesLayout.getLinesTotalHeight(), 20); assert.equal(linesLayout.getVerticalOffsetForLineNumber(1), 0); assert.equal(linesLayout.getVerticalOffsetForLineNumber(2), 1); @@ -155,7 +173,7 @@ suite('Editor ViewLayout - LinesLayout', () => { // Change whitespace position // 10 lines // whitespace: - a(5,10) - linesLayout.changeWhitespace(a, 5, 10); + changeOneWhitespace(linesLayout, a, 5, 10); assert.equal(linesLayout.getLinesTotalHeight(), 20); assert.equal(linesLayout.getVerticalOffsetForLineNumber(1), 0); assert.equal(linesLayout.getVerticalOffsetForLineNumber(2), 1); @@ -200,7 +218,7 @@ suite('Editor ViewLayout - LinesLayout', () => { // Remove whitespace // 10 lines - linesLayout.removeWhitespace(a); + removeWhitespace(linesLayout, a); assert.equal(linesLayout.getLinesTotalHeight(), 10); assert.equal(linesLayout.getVerticalOffsetForLineNumber(1), 0); assert.equal(linesLayout.getVerticalOffsetForLineNumber(2), 1); @@ -216,7 +234,7 @@ suite('Editor ViewLayout - LinesLayout', () => { test('LinesLayout getLineNumberAtOrAfterVerticalOffset', () => { let linesLayout = new LinesLayout(10, 1); - linesLayout.insertWhitespace(6, 0, 10, 0); + insertWhitespace(linesLayout, 6, 0, 10, 0); // 10 lines // whitespace: - a(6,10) @@ -265,7 +283,7 @@ suite('Editor ViewLayout - LinesLayout', () => { test('LinesLayout getCenteredLineInViewport', () => { let linesLayout = new LinesLayout(10, 1); - linesLayout.insertWhitespace(6, 0, 10, 0); + insertWhitespace(linesLayout, 6, 0, 10, 0); // 10 lines // whitespace: - a(6,10) @@ -348,7 +366,7 @@ suite('Editor ViewLayout - LinesLayout', () => { test('LinesLayout getLinesViewportData 1', () => { let linesLayout = new LinesLayout(10, 10); - linesLayout.insertWhitespace(6, 0, 100, 0); + insertWhitespace(linesLayout, 6, 0, 100, 0); // 10 lines // whitespace: - a(6,100) @@ -479,11 +497,10 @@ suite('Editor ViewLayout - LinesLayout', () => { assert.deepEqual(viewportData.relativeVerticalOffset, [160, 170, 180, 190]); }); - test('LinesLayout getLinesViewportData 2 & getWhitespaceViewportData', () => { let linesLayout = new LinesLayout(10, 10); - let a = linesLayout.insertWhitespace(6, 0, 100, 0); - let b = linesLayout.insertWhitespace(7, 0, 50, 0); + let a = insertWhitespace(linesLayout, 6, 0, 100, 0); + let b = insertWhitespace(linesLayout, 7, 0, 50, 0); // 10 lines // whitespace: - a(6,100), b(7, 50) @@ -553,8 +570,8 @@ suite('Editor ViewLayout - LinesLayout', () => { test('LinesLayout getWhitespaceAtVerticalOffset', () => { let linesLayout = new LinesLayout(10, 10); - let a = linesLayout.insertWhitespace(6, 0, 100, 0); - let b = linesLayout.insertWhitespace(7, 0, 50, 0); + let a = insertWhitespace(linesLayout, 6, 0, 100, 0); + let b = insertWhitespace(linesLayout, 7, 0, 50, 0); let whitespace = linesLayout.getWhitespaceAtVerticalOffset(0); assert.equal(whitespace, null); @@ -592,4 +609,536 @@ suite('Editor ViewLayout - LinesLayout', () => { whitespace = linesLayout.getWhitespaceAtVerticalOffset(220); assert.equal(whitespace, null); }); + + test('LinesLayout', () => { + + const linesLayout = new LinesLayout(100, 20); + + // Insert a whitespace after line number 2, of height 10 + const a = insertWhitespace(linesLayout, 2, 0, 10, 0); + // whitespaces: a(2, 10) + assert.equal(linesLayout.getWhitespacesCount(), 1); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2); + assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 10); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 10); + assert.equal(linesLayout.getWhitespacesTotalHeight(), 10); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 10); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 10); + + // Insert a whitespace again after line number 2, of height 20 + let b = insertWhitespace(linesLayout, 2, 0, 20, 0); + // whitespaces: a(2, 10), b(2, 20) + assert.equal(linesLayout.getWhitespacesCount(), 2); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2); + assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 10); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2); + assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 20); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 10); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 30); + assert.equal(linesLayout.getWhitespacesTotalHeight(), 30); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 30); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 30); + + // Change last inserted whitespace height to 30 + changeOneWhitespace(linesLayout, b, 2, 30); + // whitespaces: a(2, 10), b(2, 30) + assert.equal(linesLayout.getWhitespacesCount(), 2); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2); + assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 10); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2); + assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 30); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 10); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 40); + assert.equal(linesLayout.getWhitespacesTotalHeight(), 40); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 40); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 40); + + // Remove last inserted whitespace + removeWhitespace(linesLayout, b); + // whitespaces: a(2, 10) + assert.equal(linesLayout.getWhitespacesCount(), 1); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2); + assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 10); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 10); + assert.equal(linesLayout.getWhitespacesTotalHeight(), 10); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 10); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 10); + + // Add a whitespace before the first line of height 50 + b = insertWhitespace(linesLayout, 0, 0, 50, 0); + // whitespaces: b(0, 50), a(2, 10) + assert.equal(linesLayout.getWhitespacesCount(), 2); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0); + assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 50); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2); + assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 10); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 50); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 60); + assert.equal(linesLayout.getWhitespacesTotalHeight(), 60); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 60); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 60); + + // Add a whitespace after line 4 of height 20 + insertWhitespace(linesLayout, 4, 0, 20, 0); + // whitespaces: b(0, 50), a(2, 10), c(4, 20) + assert.equal(linesLayout.getWhitespacesCount(), 3); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0); + assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 50); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2); + assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 10); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 4); + assert.equal(linesLayout.getHeightForWhitespaceIndex(2), 20); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 50); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 60); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(2), 80); + assert.equal(linesLayout.getWhitespacesTotalHeight(), 80); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 60); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 60); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 80); + + // Add a whitespace after line 3 of height 30 + insertWhitespace(linesLayout, 3, 0, 30, 0); + // whitespaces: b(0, 50), a(2, 10), d(3, 30), c(4, 20) + assert.equal(linesLayout.getWhitespacesCount(), 4); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0); + assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 50); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2); + assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 10); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 3); + assert.equal(linesLayout.getHeightForWhitespaceIndex(2), 30); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(3), 4); + assert.equal(linesLayout.getHeightForWhitespaceIndex(3), 20); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 50); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 60); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(2), 90); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(3), 110); + assert.equal(linesLayout.getWhitespacesTotalHeight(), 110); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 60); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 90); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 110); + + // Change whitespace after line 2 to height of 100 + changeOneWhitespace(linesLayout, a, 2, 100); + // whitespaces: b(0, 50), a(2, 100), d(3, 30), c(4, 20) + assert.equal(linesLayout.getWhitespacesCount(), 4); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0); + assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 50); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2); + assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 100); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 3); + assert.equal(linesLayout.getHeightForWhitespaceIndex(2), 30); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(3), 4); + assert.equal(linesLayout.getHeightForWhitespaceIndex(3), 20); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 50); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 150); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(2), 180); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(3), 200); + assert.equal(linesLayout.getWhitespacesTotalHeight(), 200); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 150); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 180); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 200); + + // Remove whitespace after line 2 + removeWhitespace(linesLayout, a); + // whitespaces: b(0, 50), d(3, 30), c(4, 20) + assert.equal(linesLayout.getWhitespacesCount(), 3); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0); + assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 50); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); + assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 30); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 4); + assert.equal(linesLayout.getHeightForWhitespaceIndex(2), 20); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 50); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 80); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(2), 100); + assert.equal(linesLayout.getWhitespacesTotalHeight(), 100); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 80); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 100); + + // Remove whitespace before line 1 + removeWhitespace(linesLayout, b); + // whitespaces: d(3, 30), c(4, 20) + assert.equal(linesLayout.getWhitespacesCount(), 2); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3); + assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 30); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 4); + assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 20); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 30); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 50); + assert.equal(linesLayout.getWhitespacesTotalHeight(), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 30); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 50); + + // Delete line 1 + linesLayout.onLinesDeleted(1, 1); + // whitespaces: d(2, 30), c(3, 20) + assert.equal(linesLayout.getWhitespacesCount(), 2); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2); + assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 30); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); + assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 20); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 30); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 50); + assert.equal(linesLayout.getWhitespacesTotalHeight(), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 30); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 50); + + // Insert a line before line 1 + linesLayout.onLinesInserted(1, 1); + // whitespaces: d(3, 30), c(4, 20) + assert.equal(linesLayout.getWhitespacesCount(), 2); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3); + assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 30); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 4); + assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 20); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 30); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 50); + assert.equal(linesLayout.getWhitespacesTotalHeight(), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 30); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 50); + + // Delete line 4 + linesLayout.onLinesDeleted(4, 4); + // whitespaces: d(3, 30), c(3, 20) + assert.equal(linesLayout.getWhitespacesCount(), 2); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3); + assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 30); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); + assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 20); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 30); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 50); + assert.equal(linesLayout.getWhitespacesTotalHeight(), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 50); + }); + + test('LinesLayout findInsertionIndex', () => { + + const makeInternalWhitespace = (afterLineNumbers: number[], ordinal: number = 0) => { + return afterLineNumbers.map((afterLineNumber) => new EditorWhitespace('', afterLineNumber, ordinal, 0, 0)); + }; + + let arr: EditorWhitespace[]; + + arr = makeInternalWhitespace([]); + assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 0); + assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 0); + + arr = makeInternalWhitespace([1]); + assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1); + + arr = makeInternalWhitespace([1, 3]); + assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2); + + arr = makeInternalWhitespace([1, 3, 5]); + assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 5, 0), 3); + assert.equal(LinesLayout.findInsertionIndex(arr, 6, 0), 3); + + arr = makeInternalWhitespace([1, 3, 5], 3); + assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 0); + assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 5, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 6, 0), 3); + + arr = makeInternalWhitespace([1, 3, 5, 7]); + assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 5, 0), 3); + assert.equal(LinesLayout.findInsertionIndex(arr, 6, 0), 3); + assert.equal(LinesLayout.findInsertionIndex(arr, 7, 0), 4); + assert.equal(LinesLayout.findInsertionIndex(arr, 8, 0), 4); + + arr = makeInternalWhitespace([1, 3, 5, 7, 9]); + assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 5, 0), 3); + assert.equal(LinesLayout.findInsertionIndex(arr, 6, 0), 3); + assert.equal(LinesLayout.findInsertionIndex(arr, 7, 0), 4); + assert.equal(LinesLayout.findInsertionIndex(arr, 8, 0), 4); + assert.equal(LinesLayout.findInsertionIndex(arr, 9, 0), 5); + assert.equal(LinesLayout.findInsertionIndex(arr, 10, 0), 5); + + arr = makeInternalWhitespace([1, 3, 5, 7, 9, 11]); + assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 5, 0), 3); + assert.equal(LinesLayout.findInsertionIndex(arr, 6, 0), 3); + assert.equal(LinesLayout.findInsertionIndex(arr, 7, 0), 4); + assert.equal(LinesLayout.findInsertionIndex(arr, 8, 0), 4); + assert.equal(LinesLayout.findInsertionIndex(arr, 9, 0), 5); + assert.equal(LinesLayout.findInsertionIndex(arr, 10, 0), 5); + assert.equal(LinesLayout.findInsertionIndex(arr, 11, 0), 6); + assert.equal(LinesLayout.findInsertionIndex(arr, 12, 0), 6); + + arr = makeInternalWhitespace([1, 3, 5, 7, 9, 11, 13]); + assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 5, 0), 3); + assert.equal(LinesLayout.findInsertionIndex(arr, 6, 0), 3); + assert.equal(LinesLayout.findInsertionIndex(arr, 7, 0), 4); + assert.equal(LinesLayout.findInsertionIndex(arr, 8, 0), 4); + assert.equal(LinesLayout.findInsertionIndex(arr, 9, 0), 5); + assert.equal(LinesLayout.findInsertionIndex(arr, 10, 0), 5); + assert.equal(LinesLayout.findInsertionIndex(arr, 11, 0), 6); + assert.equal(LinesLayout.findInsertionIndex(arr, 12, 0), 6); + assert.equal(LinesLayout.findInsertionIndex(arr, 13, 0), 7); + assert.equal(LinesLayout.findInsertionIndex(arr, 14, 0), 7); + + arr = makeInternalWhitespace([1, 3, 5, 7, 9, 11, 13, 15]); + assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 5, 0), 3); + assert.equal(LinesLayout.findInsertionIndex(arr, 6, 0), 3); + assert.equal(LinesLayout.findInsertionIndex(arr, 7, 0), 4); + assert.equal(LinesLayout.findInsertionIndex(arr, 8, 0), 4); + assert.equal(LinesLayout.findInsertionIndex(arr, 9, 0), 5); + assert.equal(LinesLayout.findInsertionIndex(arr, 10, 0), 5); + assert.equal(LinesLayout.findInsertionIndex(arr, 11, 0), 6); + assert.equal(LinesLayout.findInsertionIndex(arr, 12, 0), 6); + assert.equal(LinesLayout.findInsertionIndex(arr, 13, 0), 7); + assert.equal(LinesLayout.findInsertionIndex(arr, 14, 0), 7); + assert.equal(LinesLayout.findInsertionIndex(arr, 15, 0), 8); + assert.equal(LinesLayout.findInsertionIndex(arr, 16, 0), 8); + }); + + test('LinesLayout changeWhitespaceAfterLineNumber & getFirstWhitespaceIndexAfterLineNumber', () => { + const linesLayout = new LinesLayout(100, 20); + + const a = insertWhitespace(linesLayout, 0, 0, 1, 0); + const b = insertWhitespace(linesLayout, 7, 0, 1, 0); + const c = insertWhitespace(linesLayout, 3, 0, 1, 0); + + assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0); + assert.equal(linesLayout.getIdForWhitespaceIndex(1), c); // 3 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); + assert.equal(linesLayout.getIdForWhitespaceIndex(2), b); // 7 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7); + + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 1); // c + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 1); // c + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 1); // c + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- + + // Do not really move a + changeOneWhitespace(linesLayout, a, 1, 1); + + assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 1 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 1); + assert.equal(linesLayout.getIdForWhitespaceIndex(1), c); // 3 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); + assert.equal(linesLayout.getIdForWhitespaceIndex(2), b); // 7 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7); + + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 0); // a + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 1); // c + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 1); // c + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- + + + // Do not really move a + changeOneWhitespace(linesLayout, a, 2, 1); + + assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 2 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2); + assert.equal(linesLayout.getIdForWhitespaceIndex(1), c); // 3 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); + assert.equal(linesLayout.getIdForWhitespaceIndex(2), b); // 7 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7); + + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 0); // a + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 0); // a + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 1); // c + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- + + + // Change a to conflict with c => a gets placed after c + changeOneWhitespace(linesLayout, a, 3, 1); + + assert.equal(linesLayout.getIdForWhitespaceIndex(0), c); // 3 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3); + assert.equal(linesLayout.getIdForWhitespaceIndex(1), a); // 3 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); + assert.equal(linesLayout.getIdForWhitespaceIndex(2), b); // 7 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7); + + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 0); // c + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 0); // c + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 0); // c + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- + + + // Make a no-op + changeOneWhitespace(linesLayout, c, 3, 1); + + assert.equal(linesLayout.getIdForWhitespaceIndex(0), c); // 3 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3); + assert.equal(linesLayout.getIdForWhitespaceIndex(1), a); // 3 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); + assert.equal(linesLayout.getIdForWhitespaceIndex(2), b); // 7 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7); + + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 0); // c + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 0); // c + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 0); // c + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- + + + + // Conflict c with b => c gets placed after b + changeOneWhitespace(linesLayout, c, 7, 1); + + assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 3 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3); + assert.equal(linesLayout.getIdForWhitespaceIndex(1), b); // 7 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 7); + assert.equal(linesLayout.getIdForWhitespaceIndex(2), c); // 7 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7); + + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 0); // a + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 0); // a + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 0); // a + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 1); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 1); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 1); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 1); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- + }); + + test('LinesLayout Bug', () => { + const linesLayout = new LinesLayout(100, 20); + + const a = insertWhitespace(linesLayout, 0, 0, 1, 0); + const b = insertWhitespace(linesLayout, 7, 0, 1, 0); + + assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0 + assert.equal(linesLayout.getIdForWhitespaceIndex(1), b); // 7 + + const c = insertWhitespace(linesLayout, 3, 0, 1, 0); + + assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0 + assert.equal(linesLayout.getIdForWhitespaceIndex(1), c); // 3 + assert.equal(linesLayout.getIdForWhitespaceIndex(2), b); // 7 + + const d = insertWhitespace(linesLayout, 2, 0, 1, 0); + assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0 + assert.equal(linesLayout.getIdForWhitespaceIndex(1), d); // 2 + assert.equal(linesLayout.getIdForWhitespaceIndex(2), c); // 3 + assert.equal(linesLayout.getIdForWhitespaceIndex(3), b); // 7 + + const e = insertWhitespace(linesLayout, 8, 0, 1, 0); + assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0 + assert.equal(linesLayout.getIdForWhitespaceIndex(1), d); // 2 + assert.equal(linesLayout.getIdForWhitespaceIndex(2), c); // 3 + assert.equal(linesLayout.getIdForWhitespaceIndex(3), b); // 7 + assert.equal(linesLayout.getIdForWhitespaceIndex(4), e); // 8 + + const f = insertWhitespace(linesLayout, 11, 0, 1, 0); + assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0 + assert.equal(linesLayout.getIdForWhitespaceIndex(1), d); // 2 + assert.equal(linesLayout.getIdForWhitespaceIndex(2), c); // 3 + assert.equal(linesLayout.getIdForWhitespaceIndex(3), b); // 7 + assert.equal(linesLayout.getIdForWhitespaceIndex(4), e); // 8 + assert.equal(linesLayout.getIdForWhitespaceIndex(5), f); // 11 + + const g = insertWhitespace(linesLayout, 10, 0, 1, 0); + assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0 + assert.equal(linesLayout.getIdForWhitespaceIndex(1), d); // 2 + assert.equal(linesLayout.getIdForWhitespaceIndex(2), c); // 3 + assert.equal(linesLayout.getIdForWhitespaceIndex(3), b); // 7 + assert.equal(linesLayout.getIdForWhitespaceIndex(4), e); // 8 + assert.equal(linesLayout.getIdForWhitespaceIndex(5), g); // 10 + assert.equal(linesLayout.getIdForWhitespaceIndex(6), f); // 11 + + const h = insertWhitespace(linesLayout, 0, 0, 1, 0); + assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0 + assert.equal(linesLayout.getIdForWhitespaceIndex(1), h); // 0 + assert.equal(linesLayout.getIdForWhitespaceIndex(2), d); // 2 + assert.equal(linesLayout.getIdForWhitespaceIndex(3), c); // 3 + assert.equal(linesLayout.getIdForWhitespaceIndex(4), b); // 7 + assert.equal(linesLayout.getIdForWhitespaceIndex(5), e); // 8 + assert.equal(linesLayout.getIdForWhitespaceIndex(6), g); // 10 + assert.equal(linesLayout.getIdForWhitespaceIndex(7), f); // 11 + }); }); diff --git a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts index 6e3fbb979e..a33a5ac526 100644 --- a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts +++ b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts @@ -189,8 +189,8 @@ suite('viewLineRenderer.renderLine', () => { createPart(48, 12), ]); let expectedOutput = [ - '\u2192\u00a0\u00a0\u00a0', - '\u00b7\u00b7\u00b7\u00b7', + '\u2192\u00a0\u00a0\u00a0', + '\u00b7\u00b7\u00b7\u00b7', 'export', '\u00a0', 'class', @@ -201,8 +201,8 @@ suite('viewLineRenderer.renderLine', () => { '\u00a0', '//\u00a0', 'http://test.com', - '\u00b7\u00b7', - '\u00b7\u00b7\u00b7' + '\u00b7\u00b7', + '\u00b7\u00b7\u00b7' ].join(''); let expectedOffsetsArr = [ [0], @@ -867,10 +867,10 @@ suite('viewLineRenderer.renderLine 2', () => { null, [ '', - '\u00b7\u00b7\u00b7\u00b7', + '\u00b7\u00b7\u00b7\u00b7', 'He', 'llo\u00a0world!', - '\u00b7\u00b7\u00b7\u00b7', + '\u00b7\u00b7\u00b7\u00b7', '', ].join('') ); @@ -889,12 +889,12 @@ suite('viewLineRenderer.renderLine 2', () => { null, [ '', - '\u00b7\u00b7\u00b7\u00b7', - '\u00b7\u00b7\u00b7\u00b7', + '\u00b7\u00b7\u00b7\u00b7', + '\u00b7\u00b7\u00b7\u00b7', 'He', 'llo\u00a0world!', - '\u00b7\u00b7\u00b7\u00b7', - '\u00b7\u00b7\u00b7\u00b7', + '\u00b7\u00b7\u00b7\u00b7', + '\u00b7\u00b7\u00b7\u00b7', '', ].join('') ); @@ -913,11 +913,11 @@ suite('viewLineRenderer.renderLine 2', () => { null, [ '', - '\u2192\u00a0\u00a0\u00a0', - '\u2192\u00a0\u00a0\u00a0', + '\u2192\u00a0\u00a0\u00a0', + '\u2192\u00a0\u00a0\u00a0', 'He', 'llo\u00a0world!', - '\u2192\u00a0\u00a0\u00a0', + '\u2192\u00a0\u00a0\u00a0', '', ].join('') ); @@ -936,15 +936,15 @@ suite('viewLineRenderer.renderLine 2', () => { null, [ '', - '\u00b7\u00b7\u2192\u00a0', - '\u2192\u00a0\u00a0\u00a0', - '\u00b7\u00b7', + '\u00b7\u00b7\u2192\u00a0', + '\u2192\u00a0\u00a0\u00a0', + '\u00b7\u00b7', 'He', 'llo\u00a0world!', - '\u00b7\uffeb', - '\u00b7\u00b7\u2192\u00a0', - '\u00b7\u00b7\u00b7\uffeb', - '\u00b7\u00b7\u00b7\u00b7', + '\u00b7\uffeb', + '\u00b7\u00b7\u2192\u00a0', + '\u00b7\u00b7\u00b7\uffeb', + '\u00b7\u00b7\u00b7\u00b7', '', ].join('') ); @@ -965,13 +965,13 @@ suite('viewLineRenderer.renderLine 2', () => { [ '', '\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0', - '\u00b7\u00b7', + '\u00b7\u00b7', 'He', 'llo\u00a0world!', - '\u00b7\uffeb', - '\u00b7\u00b7\u2192\u00a0', - '\u00b7\u00b7\u00b7\uffeb', - '\u00b7\u00b7\u00b7\u00b7', + '\u00b7\uffeb', + '\u00b7\u00b7\u2192\u00a0', + '\u00b7\u00b7\u00b7\uffeb', + '\u00b7\u00b7\u00b7\u00b7', '', ].join('') ); @@ -1016,11 +1016,11 @@ suite('viewLineRenderer.renderLine 2', () => { [ '', 'it', - '\u00b7\u00b7', + '\u00b7\u00b7', 'it', '\u00a0', 'it', - '\u00b7\u00b7', + '\u00b7\u00b7', 'it', '', ].join('') @@ -1041,12 +1041,12 @@ suite('viewLineRenderer.renderLine 2', () => { null, [ '', - '\u00b7', + '\u00b7', 'Hel', 'lo', - '\u00b7', + '\u00b7', 'world!', - '\u2192\u00a0\u00a0', + '\u2192\u00a0\u00a0', '', ].join('') ); @@ -1088,12 +1088,12 @@ suite('viewLineRenderer.renderLine 2', () => { [new LineRange(0, 14)], [ '', - '\u00b7', + '\u00b7', 'Hel', 'lo', - '\u00b7', + '\u00b7', 'world!', - '\u2192\u00a0\u00a0', + '\u2192\u00a0\u00a0', '', ].join('') ); @@ -1113,7 +1113,7 @@ suite('viewLineRenderer.renderLine 2', () => { [new LineRange(0, 5)], [ '', - '\u00b7', + '\u00b7', 'Hel', 'lo', '\u00a0world!\u00a0\u00a0\u00a0', @@ -1137,11 +1137,11 @@ suite('viewLineRenderer.renderLine 2', () => { [new LineRange(0, 5), new LineRange(9, 14)], [ '', - '\u00b7', + '\u00b7', 'Hel', 'lo', '\u00a0world!', - '\u2192\u00a0\u00a0', + '\u2192\u00a0\u00a0', '', ].join('') ); @@ -1162,11 +1162,11 @@ suite('viewLineRenderer.renderLine 2', () => { [new LineRange(9, 14), new LineRange(0, 5)], [ '', - '\u00b7', + '\u00b7', 'Hel', 'lo', '\u00a0world!', - '\u2192\u00a0\u00a0', + '\u2192\u00a0\u00a0', '', ].join('') ); @@ -1184,9 +1184,9 @@ suite('viewLineRenderer.renderLine 2', () => { [new LineRange(0, 1), new LineRange(1, 2), new LineRange(2, 3)], [ '', - '\u00b7', + '\u00b7', '*', - '\u00b7', + '\u00b7', 'S', '', ].join('') @@ -1293,7 +1293,7 @@ suite('viewLineRenderer.renderLine 2', () => { let expected = [ '', - '\u2192\u00a0\u00a0\u00a0', + '\u2192\u00a0\u00a0\u00a0', 'b', 'la', '' diff --git a/src/vs/editor/test/common/viewLayout/whitespaceComputer.test.ts b/src/vs/editor/test/common/viewLayout/whitespaceComputer.test.ts deleted file mode 100644 index 0a26122441..0000000000 --- a/src/vs/editor/test/common/viewLayout/whitespaceComputer.test.ts +++ /dev/null @@ -1,558 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; -import { WhitespaceComputer } from 'vs/editor/common/viewLayout/whitespaceComputer'; - -suite('Editor ViewLayout - WhitespaceComputer', () => { - - test('WhitespaceComputer', () => { - - let whitespaceComputer = new WhitespaceComputer(); - - // Insert a whitespace after line number 2, of height 10 - let a = whitespaceComputer.insertWhitespace(2, 0, 10, 0); - // whitespaces: a(2, 10) - assert.equal(whitespaceComputer.getCount(), 1); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 2); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 10); - assert.equal(whitespaceComputer.getAccumulatedHeight(0), 10); - assert.equal(whitespaceComputer.getTotalHeight(), 10); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 10); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 10); - - // Insert a whitespace again after line number 2, of height 20 - let b = whitespaceComputer.insertWhitespace(2, 0, 20, 0); - // whitespaces: a(2, 10), b(2, 20) - assert.equal(whitespaceComputer.getCount(), 2); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 2); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 10); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 2); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 20); - assert.equal(whitespaceComputer.getAccumulatedHeight(0), 10); - assert.equal(whitespaceComputer.getAccumulatedHeight(1), 30); - assert.equal(whitespaceComputer.getTotalHeight(), 30); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 30); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 30); - - // Change last inserted whitespace height to 30 - whitespaceComputer.changeWhitespaceHeight(b, 30); - // whitespaces: a(2, 10), b(2, 30) - assert.equal(whitespaceComputer.getCount(), 2); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 2); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 10); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 2); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 30); - assert.equal(whitespaceComputer.getAccumulatedHeight(0), 10); - assert.equal(whitespaceComputer.getAccumulatedHeight(1), 40); - assert.equal(whitespaceComputer.getTotalHeight(), 40); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 40); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 40); - - // Remove last inserted whitespace - whitespaceComputer.removeWhitespace(b); - // whitespaces: a(2, 10) - assert.equal(whitespaceComputer.getCount(), 1); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 2); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 10); - assert.equal(whitespaceComputer.getAccumulatedHeight(0), 10); - assert.equal(whitespaceComputer.getTotalHeight(), 10); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 10); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 10); - - // Add a whitespace before the first line of height 50 - b = whitespaceComputer.insertWhitespace(0, 0, 50, 0); - // whitespaces: b(0, 50), a(2, 10) - assert.equal(whitespaceComputer.getCount(), 2); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 0); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 50); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 2); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 10); - assert.equal(whitespaceComputer.getAccumulatedHeight(0), 50); - assert.equal(whitespaceComputer.getAccumulatedHeight(1), 60); - assert.equal(whitespaceComputer.getTotalHeight(), 60); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 60); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 60); - - // Add a whitespace after line 4 of height 20 - whitespaceComputer.insertWhitespace(4, 0, 20, 0); - // whitespaces: b(0, 50), a(2, 10), c(4, 20) - assert.equal(whitespaceComputer.getCount(), 3); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 0); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 50); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 2); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 10); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 4); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(2), 20); - assert.equal(whitespaceComputer.getAccumulatedHeight(0), 50); - assert.equal(whitespaceComputer.getAccumulatedHeight(1), 60); - assert.equal(whitespaceComputer.getAccumulatedHeight(2), 80); - assert.equal(whitespaceComputer.getTotalHeight(), 80); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 60); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 60); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(5), 80); - - // Add a whitespace after line 3 of height 30 - whitespaceComputer.insertWhitespace(3, 0, 30, 0); - // whitespaces: b(0, 50), a(2, 10), d(3, 30), c(4, 20) - assert.equal(whitespaceComputer.getCount(), 4); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 0); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 50); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 2); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 10); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 3); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(2), 30); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(3), 4); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(3), 20); - assert.equal(whitespaceComputer.getAccumulatedHeight(0), 50); - assert.equal(whitespaceComputer.getAccumulatedHeight(1), 60); - assert.equal(whitespaceComputer.getAccumulatedHeight(2), 90); - assert.equal(whitespaceComputer.getAccumulatedHeight(3), 110); - assert.equal(whitespaceComputer.getTotalHeight(), 110); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 60); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 90); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(5), 110); - - // Change whitespace after line 2 to height of 100 - whitespaceComputer.changeWhitespaceHeight(a, 100); - // whitespaces: b(0, 50), a(2, 100), d(3, 30), c(4, 20) - assert.equal(whitespaceComputer.getCount(), 4); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 0); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 50); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 2); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 100); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 3); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(2), 30); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(3), 4); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(3), 20); - assert.equal(whitespaceComputer.getAccumulatedHeight(0), 50); - assert.equal(whitespaceComputer.getAccumulatedHeight(1), 150); - assert.equal(whitespaceComputer.getAccumulatedHeight(2), 180); - assert.equal(whitespaceComputer.getAccumulatedHeight(3), 200); - assert.equal(whitespaceComputer.getTotalHeight(), 200); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 150); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 180); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(5), 200); - - // Remove whitespace after line 2 - whitespaceComputer.removeWhitespace(a); - // whitespaces: b(0, 50), d(3, 30), c(4, 20) - assert.equal(whitespaceComputer.getCount(), 3); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 0); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 50); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 3); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 30); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 4); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(2), 20); - assert.equal(whitespaceComputer.getAccumulatedHeight(0), 50); - assert.equal(whitespaceComputer.getAccumulatedHeight(1), 80); - assert.equal(whitespaceComputer.getAccumulatedHeight(2), 100); - assert.equal(whitespaceComputer.getTotalHeight(), 100); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 80); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(5), 100); - - // Remove whitespace before line 1 - whitespaceComputer.removeWhitespace(b); - // whitespaces: d(3, 30), c(4, 20) - assert.equal(whitespaceComputer.getCount(), 2); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 3); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 30); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 4); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 20); - assert.equal(whitespaceComputer.getAccumulatedHeight(0), 30); - assert.equal(whitespaceComputer.getAccumulatedHeight(1), 50); - assert.equal(whitespaceComputer.getTotalHeight(), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 30); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(5), 50); - - // Delete line 1 - whitespaceComputer.onLinesDeleted(1, 1); - // whitespaces: d(2, 30), c(3, 20) - assert.equal(whitespaceComputer.getCount(), 2); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 2); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 30); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 3); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 20); - assert.equal(whitespaceComputer.getAccumulatedHeight(0), 30); - assert.equal(whitespaceComputer.getAccumulatedHeight(1), 50); - assert.equal(whitespaceComputer.getTotalHeight(), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 30); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(5), 50); - - // Insert a line before line 1 - whitespaceComputer.onLinesInserted(1, 1); - // whitespaces: d(3, 30), c(4, 20) - assert.equal(whitespaceComputer.getCount(), 2); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 3); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 30); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 4); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 20); - assert.equal(whitespaceComputer.getAccumulatedHeight(0), 30); - assert.equal(whitespaceComputer.getAccumulatedHeight(1), 50); - assert.equal(whitespaceComputer.getTotalHeight(), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 30); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(5), 50); - - // Delete line 4 - whitespaceComputer.onLinesDeleted(4, 4); - // whitespaces: d(3, 30), c(3, 20) - assert.equal(whitespaceComputer.getCount(), 2); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 3); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 30); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 3); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 20); - assert.equal(whitespaceComputer.getAccumulatedHeight(0), 30); - assert.equal(whitespaceComputer.getAccumulatedHeight(1), 50); - assert.equal(whitespaceComputer.getTotalHeight(), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(5), 50); - }); - - test('WhitespaceComputer findInsertionIndex', () => { - - let makeArray = (size: number, fillValue: number) => { - let r: number[] = []; - for (let i = 0; i < size; i++) { - r[i] = fillValue; - } - return r; - }; - - let arr: number[]; - let ordinals: number[]; - - arr = []; - ordinals = makeArray(arr.length, 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 0); - - arr = [1]; - ordinals = makeArray(arr.length, 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 1); - - arr = [1, 3]; - ordinals = makeArray(arr.length, 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 3, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 4, ordinals, 0), 2); - - arr = [1, 3, 5]; - ordinals = makeArray(arr.length, 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 3, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 4, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 5, ordinals, 0), 3); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 6, ordinals, 0), 3); - - arr = [1, 3, 5]; - ordinals = makeArray(arr.length, 3); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 3, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 4, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 5, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 6, ordinals, 0), 3); - - arr = [1, 3, 5, 7]; - ordinals = makeArray(arr.length, 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 3, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 4, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 5, ordinals, 0), 3); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 6, ordinals, 0), 3); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 7, ordinals, 0), 4); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 8, ordinals, 0), 4); - - arr = [1, 3, 5, 7, 9]; - ordinals = makeArray(arr.length, 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 3, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 4, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 5, ordinals, 0), 3); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 6, ordinals, 0), 3); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 7, ordinals, 0), 4); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 8, ordinals, 0), 4); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 9, ordinals, 0), 5); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 10, ordinals, 0), 5); - - arr = [1, 3, 5, 7, 9, 11]; - ordinals = makeArray(arr.length, 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 3, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 4, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 5, ordinals, 0), 3); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 6, ordinals, 0), 3); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 7, ordinals, 0), 4); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 8, ordinals, 0), 4); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 9, ordinals, 0), 5); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 10, ordinals, 0), 5); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 11, ordinals, 0), 6); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 12, ordinals, 0), 6); - - arr = [1, 3, 5, 7, 9, 11, 13]; - ordinals = makeArray(arr.length, 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 3, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 4, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 5, ordinals, 0), 3); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 6, ordinals, 0), 3); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 7, ordinals, 0), 4); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 8, ordinals, 0), 4); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 9, ordinals, 0), 5); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 10, ordinals, 0), 5); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 11, ordinals, 0), 6); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 12, ordinals, 0), 6); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 13, ordinals, 0), 7); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 14, ordinals, 0), 7); - - arr = [1, 3, 5, 7, 9, 11, 13, 15]; - ordinals = makeArray(arr.length, 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 3, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 4, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 5, ordinals, 0), 3); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 6, ordinals, 0), 3); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 7, ordinals, 0), 4); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 8, ordinals, 0), 4); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 9, ordinals, 0), 5); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 10, ordinals, 0), 5); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 11, ordinals, 0), 6); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 12, ordinals, 0), 6); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 13, ordinals, 0), 7); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 14, ordinals, 0), 7); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 15, ordinals, 0), 8); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 16, ordinals, 0), 8); - }); - - test('WhitespaceComputer changeWhitespaceAfterLineNumber & getFirstWhitespaceIndexAfterLineNumber', () => { - let whitespaceComputer = new WhitespaceComputer(); - - let a = whitespaceComputer.insertWhitespace(0, 0, 1, 0); - let b = whitespaceComputer.insertWhitespace(7, 0, 1, 0); - let c = whitespaceComputer.insertWhitespace(3, 0, 1, 0); - - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 0 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 0); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), c); // 3 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 3); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), b); // 7 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 7); - - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(1), 1); // c - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(2), 1); // c - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(3), 1); // c - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- - - // Do not really move a - whitespaceComputer.changeWhitespaceAfterLineNumber(a, 1); - - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 1 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 1); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), c); // 3 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 3); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), b); // 7 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 7); - - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(1), 0); // a - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(2), 1); // c - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(3), 1); // c - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- - - - // Do not really move a - whitespaceComputer.changeWhitespaceAfterLineNumber(a, 2); - - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 2 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 2); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), c); // 3 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 3); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), b); // 7 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 7); - - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(1), 0); // a - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(2), 0); // a - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(3), 1); // c - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- - - - // Change a to conflict with c => a gets placed after c - whitespaceComputer.changeWhitespaceAfterLineNumber(a, 3); - - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), c); // 3 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 3); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), a); // 3 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 3); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), b); // 7 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 7); - - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(1), 0); // c - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(2), 0); // c - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(3), 0); // c - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- - - - // Make a no-op - whitespaceComputer.changeWhitespaceAfterLineNumber(c, 3); - - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), c); // 3 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 3); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), a); // 3 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 3); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), b); // 7 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 7); - - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(1), 0); // c - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(2), 0); // c - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(3), 0); // c - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- - - - - // Conflict c with b => c gets placed after b - whitespaceComputer.changeWhitespaceAfterLineNumber(c, 7); - - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 3 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 3); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), b); // 7 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 7); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), c); // 7 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 7); - - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(1), 0); // a - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(2), 0); // a - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(3), 0); // a - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(4), 1); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(5), 1); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(6), 1); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(7), 1); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- - }); - - - test('WhitespaceComputer Bug', () => { - let whitespaceComputer = new WhitespaceComputer(); - - let a = whitespaceComputer.insertWhitespace(0, 0, 1, 0); - let b = whitespaceComputer.insertWhitespace(7, 0, 1, 0); - - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 0 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), b); // 7 - - let c = whitespaceComputer.insertWhitespace(3, 0, 1, 0); - - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 0 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), c); // 3 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), b); // 7 - - let d = whitespaceComputer.insertWhitespace(2, 0, 1, 0); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 0 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), d); // 2 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), c); // 3 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(3), b); // 7 - - let e = whitespaceComputer.insertWhitespace(8, 0, 1, 0); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 0 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), d); // 2 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), c); // 3 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(3), b); // 7 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(4), e); // 8 - - let f = whitespaceComputer.insertWhitespace(11, 0, 1, 0); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 0 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), d); // 2 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), c); // 3 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(3), b); // 7 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(4), e); // 8 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(5), f); // 11 - - let g = whitespaceComputer.insertWhitespace(10, 0, 1, 0); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 0 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), d); // 2 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), c); // 3 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(3), b); // 7 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(4), e); // 8 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(5), g); // 10 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(6), f); // 11 - - let h = whitespaceComputer.insertWhitespace(0, 0, 1, 0); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 0 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), h); // 0 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), d); // 2 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(3), c); // 3 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(4), b); // 7 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(5), e); // 8 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(6), g); // 10 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(7), f); // 11 - }); -}); - diff --git a/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts b/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts index a09d7a668a..f2f57e34fa 100644 --- a/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts +++ b/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts @@ -103,16 +103,6 @@ suite('ViewModelDecorations', () => { // view line 2: (1,14 -> 1,24) assert.deepEqual(inlineDecorations1, [ - { - range: new Range(1, 2, 2, 1), - inlineClassName: 'i-dec2', - type: InlineDecorationType.Regular - }, - { - range: new Range(2, 1, 2, 1), - inlineClassName: 'a-dec2', - type: InlineDecorationType.After - }, { range: new Range(1, 2, 2, 2), inlineClassName: 'i-dec3', @@ -124,7 +114,7 @@ suite('ViewModelDecorations', () => { type: InlineDecorationType.After }, { - range: new Range(1, 2, 4, 1), + range: new Range(1, 2, 3, 13), inlineClassName: 'i-dec4', type: InlineDecorationType.Regular }, @@ -164,7 +154,7 @@ suite('ViewModelDecorations', () => { type: InlineDecorationType.After }, { - range: new Range(2, 1, 4, 1), + range: new Range(2, 1, 3, 13), inlineClassName: 'i-dec8', type: InlineDecorationType.Regular }, @@ -199,7 +189,7 @@ suite('ViewModelDecorations', () => { type: InlineDecorationType.After }, { - range: new Range(2, 3, 4, 1), + range: new Range(2, 3, 3, 13), inlineClassName: 'i-dec11', type: InlineDecorationType.Regular }, @@ -228,30 +218,45 @@ suite('ViewModelDecorations', () => { // view line 3 (24 -> 36) assert.deepEqual(inlineDecorations2, [ { - range: new Range(1, 2, 4, 1), + range: new Range(1, 2, 3, 13), inlineClassName: 'i-dec4', type: InlineDecorationType.Regular }, + { + range: new Range(3, 13, 3, 13), + inlineClassName: 'a-dec4', + type: InlineDecorationType.After + }, { range: new Range(1, 2, 5, 8), inlineClassName: 'i-dec5', type: InlineDecorationType.Regular }, { - range: new Range(2, 1, 4, 1), + range: new Range(2, 1, 3, 13), inlineClassName: 'i-dec8', type: InlineDecorationType.Regular }, + { + range: new Range(3, 13, 3, 13), + inlineClassName: 'a-dec8', + type: InlineDecorationType.After + }, { range: new Range(2, 1, 5, 8), inlineClassName: 'i-dec9', type: InlineDecorationType.Regular }, { - range: new Range(2, 3, 4, 1), + range: new Range(2, 3, 3, 13), inlineClassName: 'i-dec11', type: InlineDecorationType.Regular }, + { + range: new Range(3, 13, 3, 13), + inlineClassName: 'a-dec11', + type: InlineDecorationType.After + }, { range: new Range(2, 3, 5, 8), inlineClassName: 'i-dec12', diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 24a87d5874..626de975fd 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -131,7 +131,7 @@ declare namespace monaco { * * @param value A string which represents an Uri (see `Uri#toString`). */ - static parse(value: string): Uri; + static parse(value: string, _strict?: boolean): Uri; /** * Creates a new Uri from a file system path, e.g. `c:\my\files`, * `/usr/home`, or `\\server\share\some\path`. @@ -802,6 +802,12 @@ declare namespace monaco { declare namespace monaco.editor { + export interface IDiffNavigator { + canNavigate(): boolean; + next(): void; + previous(): void; + dispose(): void; + } /** * Create a new editor under `domElement`. @@ -824,13 +830,6 @@ declare namespace monaco.editor { */ export function createDiffEditor(domElement: HTMLElement, options?: IDiffEditorConstructionOptions, override?: IEditorOverrideServices): IStandaloneDiffEditor; - export interface IDiffNavigator { - canNavigate(): boolean; - next(): void; - previous(): void; - dispose(): void; - } - export interface IDiffNavigatorOptions { readonly followsCaret?: boolean; readonly ignoreCharChanges?: boolean; @@ -1193,7 +1192,8 @@ declare namespace monaco.editor { * Position in the minimap to render the decoration. */ export enum MinimapPosition { - Inline = 1 + Inline = 1, + Gutter = 2 } export interface IDecorationOptions { @@ -2396,6 +2396,18 @@ declare namespace monaco.editor { * The secondary selections. */ readonly secondarySelections: Selection[]; + /** + * The model version id. + */ + readonly modelVersionId: number; + /** + * The old selections. + */ + readonly oldSelections: Selection[] | null; + /** + * The model version id the that `oldSelections` refer to. + */ + readonly oldModelVersionId: number; /** * Source of the call that caused the event. */ @@ -2470,6 +2482,12 @@ declare namespace monaco.editor { * Defaults to 0. */ cursorSurroundingLines?: number; + /** + * Controls when `cursorSurroundingLines` should be enforced + * Defaults to `default`, `cursorSurroundingLines` is not enforced when cursor position is changed + * by mouse. + */ + cursorSurroundingLinesStyle?: 'default' | 'all'; /** * Render last line number when the file ends with a newline. * Defaults to true. @@ -2536,7 +2554,7 @@ declare namespace monaco.editor { fixedOverflowWidgets?: boolean; /** * The number of vertical lanes the overview ruler should render. - * Defaults to 2. + * Defaults to 3. */ overviewRulerLanes?: number; /** @@ -2579,8 +2597,8 @@ declare namespace monaco.editor { */ fontLigatures?: boolean | string; /** - * Disable the use of `will-change` for the editor margin and lines layers. - * The usage of `will-change` acts as a hint for browsers to create an extra layer. + * Disable the use of `transform: translate3d(0px, 0px, 0px)` for the editor margin and lines layers. + * The usage of `transform: translate3d(0px, 0px, 0px)` acts as a hint for browsers to create an extra layer. * Defaults to false. */ disableLayerHinting?: boolean; @@ -2712,6 +2730,10 @@ declare namespace monaco.editor { * Defaults to 'auto'. It is best to leave this to 'auto'. */ accessibilitySupport?: 'auto' | 'off' | 'on'; + /** + * Controls the number of lines in the editor that can be read out by a screen reader + */ + accessibilityPageSize?: number; /** * Suggest options. */ @@ -2757,7 +2779,7 @@ declare namespace monaco.editor { * Enable auto indentation adjustment. * Defaults to false. */ - autoIndent?: boolean; + autoIndent?: 'none' | 'keep' | 'brackets' | 'advanced' | 'full'; /** * Enable format on type. * Defaults to false. @@ -3038,22 +3060,31 @@ declare namespace monaco.editor { */ seedSearchStringFromSelection?: boolean; /** - * Controls if Find in Selection flag is turned on when multiple lines of text are selected in the editor. + * Controls if Find in Selection flag is turned on in the editor. */ - autoFindInSelection?: boolean; + autoFindInSelection?: 'never' | 'always' | 'multiline'; addExtraSpaceOnTop?: boolean; } export type EditorFindOptions = Readonly>; + export type GoToLocationValues = 'peek' | 'gotoAndPeek' | 'goto'; + /** * Configuration options for go to location */ export interface IGotoLocationOptions { - /** - * Control how goto-command work when having multiple results. - */ - multiple?: 'peek' | 'gotoAndPeek' | 'goto'; + multiple?: GoToLocationValues; + multipleDefinitions?: GoToLocationValues; + multipleTypeDefinitions?: GoToLocationValues; + multipleDeclarations?: GoToLocationValues; + multipleImplementations?: GoToLocationValues; + multipleReferences?: GoToLocationValues; + alternativeDefinitionCommand?: string; + alternativeTypeDefinitionCommand?: string; + alternativeDeclarationCommand?: string; + alternativeImplementationCommand?: string; + alternativeReferenceCommand?: string; } export type GoToLocationOptions = Readonly>; @@ -3375,7 +3406,11 @@ declare namespace monaco.editor { /** * Overwrite word ends on accept. Default to false. */ - overwriteOnAccept?: boolean; + insertMode?: 'insert' | 'replace'; + /** + * Show a highlight when suggestion replaces or keep text after the cursor. Defaults to false. + */ + insertHighlight?: boolean; /** * Enable graceful matching. Defaults to true. */ @@ -3401,9 +3436,105 @@ declare namespace monaco.editor { */ maxVisibleSuggestions?: number; /** - * Names of suggestion types to filter. + * Show method-suggestions. */ - filteredTypes?: Record; + showMethods?: boolean; + /** + * Show function-suggestions. + */ + showFunctions?: boolean; + /** + * Show constructor-suggestions. + */ + showConstructors?: boolean; + /** + * Show field-suggestions. + */ + showFields?: boolean; + /** + * Show variable-suggestions. + */ + showVariables?: boolean; + /** + * Show class-suggestions. + */ + showClasses?: boolean; + /** + * Show struct-suggestions. + */ + showStructs?: boolean; + /** + * Show interface-suggestions. + */ + showInterfaces?: boolean; + /** + * Show module-suggestions. + */ + showModules?: boolean; + /** + * Show property-suggestions. + */ + showProperties?: boolean; + /** + * Show event-suggestions. + */ + showEvents?: boolean; + /** + * Show operator-suggestions. + */ + showOperators?: boolean; + /** + * Show unit-suggestions. + */ + showUnits?: boolean; + /** + * Show value-suggestions. + */ + showValues?: boolean; + /** + * Show constant-suggestions. + */ + showConstants?: boolean; + /** + * Show enum-suggestions. + */ + showEnums?: boolean; + /** + * Show enumMember-suggestions. + */ + showEnumMembers?: boolean; + /** + * Show keyword-suggestions. + */ + showKeywords?: boolean; + /** + * Show text-suggestions. + */ + showWords?: boolean; + /** + * Show color-suggestions. + */ + showColors?: boolean; + /** + * Show file-suggestions. + */ + showFiles?: boolean; + /** + * Show reference-suggestions. + */ + showReferences?: boolean; + /** + * Show folder-suggestions. + */ + showFolders?: boolean; + /** + * Show typeParameter-suggestions. + */ + showTypeParameters?: boolean; + /** + * Show snippet-suggestions. + */ + showSnippets?: boolean; } export type InternalSuggestOptions = Readonly>; @@ -4116,6 +4247,10 @@ declare namespace monaco.editor { * If the diff computation is not finished or the model is missing, will return null. */ getDiffLineInformationForModified(lineNumber: number): IDiffLineInformation | null; + /** + * Update the editor's options after the editor has been created. + */ + updateOptions(newOptions: IDiffEditorOptions): void; } export class FontInfo extends BareFontInfo { @@ -4769,7 +4904,10 @@ declare namespace monaco.languages { * *Note:* The range must be a [single line](#Range.isSingleLine) and it must * [contain](#Range.contains) the position at which completion has been [requested](#CompletionItemProvider.provideCompletionItems). */ - range: IRange; + range: IRange | { + insert: IRange; + replace: IRange; + }; /** * An optional set of characters that when pressed while this completion is active will accept it first and * then type that character. *Note* that all commit characters should have `length=1` and that superfluous @@ -4853,6 +4991,7 @@ declare namespace monaco.languages { diagnostics?: editor.IMarkerData[]; kind?: string; isPreferred?: boolean; + disabled?: string; } export interface CodeActionList extends IDisposable { @@ -5440,6 +5579,33 @@ declare namespace monaco.languages { resolveCodeLens?(model: editor.ITextModel, codeLens: CodeLens, token: CancellationToken): ProviderResult; } + export interface SemanticTokensLegend { + readonly tokenTypes: string[]; + readonly tokenModifiers: string[]; + } + + export interface SemanticTokens { + readonly resultId?: string; + readonly data: Uint32Array; + } + + export interface SemanticTokensEdit { + readonly start: number; + readonly deleteCount: number; + readonly data?: Uint32Array; + } + + export interface SemanticTokensEdits { + readonly resultId?: string; + readonly edits: SemanticTokensEdit[]; + } + + export interface SemanticTokensProvider { + getLegend(): SemanticTokensLegend; + provideSemanticTokens(model: editor.ITextModel, lastResultId: string | null, ranges: Range[] | null, token: CancellationToken): ProviderResult; + releaseSemanticTokens(resultId: string | undefined): void; + } + export interface ILanguageExtensionPoint { id: string; extensions?: string[]; diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index c7bbf4f26e..210bcdefe6 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -16,6 +16,7 @@ import { ICommandAction, IMenu, IMenuActionOptions, MenuItemAction, SubmenuItemA import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; // The alternative key on all platforms is alt. On windows we also support shift as an alternative key #44136 class AlternativeKeyEmitter extends Emitter { @@ -148,7 +149,7 @@ export class MenuEntryActionViewItem extends ActionViewItem { @INotificationService protected _notificationService: INotificationService, @IContextMenuService _contextMenuService: IContextMenuService ) { - super(undefined, _action, { icon: !!(_action.class || _action.item.iconLocation), label: !_action.class && !_action.item.iconLocation }); + super(undefined, _action, { icon: !!(_action.class || _action.item.icon), label: !_action.class && !_action.item.icon }); this._altKey = AlternativeKeyEmitter.getInstance(_contextMenuService); } @@ -237,28 +238,45 @@ export class MenuEntryActionViewItem extends ActionViewItem { _updateItemClass(item: ICommandAction): void { this._itemClassDispose.value = undefined; - if (item.iconLocation) { - let iconClass: string; - - const iconPathMapKey = item.iconLocation.dark.toString(); - - if (MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) { - iconClass = MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!; - } else { - iconClass = ids.nextId(); - createCSSRule(`.icon.${iconClass}`, `background-image: ${asCSSUrl(item.iconLocation.light || item.iconLocation.dark)}`); - createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: ${asCSSUrl(item.iconLocation.dark)}`); - MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, iconClass); - } - - if (this.label) { - addClasses(this.label, 'icon', iconClass); + if (ThemeIcon.isThemeIcon(item.icon)) { + // theme icons + const iconClass = ThemeIcon.asClassName(item.icon); + if (this.label && iconClass) { + addClasses(this.label, iconClass); this._itemClassDispose.value = toDisposable(() => { if (this.label) { - removeClasses(this.label, 'icon', iconClass); + removeClasses(this.label, iconClass); } }); } + + } else if (item.icon) { + // icon path + let iconClass: string; + + if (item.icon?.dark?.scheme) { + + const iconPathMapKey = item.icon.dark.toString(); + + if (MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) { + iconClass = MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!; + } else { + iconClass = ids.nextId(); + createCSSRule(`.icon.${iconClass}`, `background-image: ${asCSSUrl(item.icon.light || item.icon.dark)}`); + createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: ${asCSSUrl(item.icon.dark)}`); + MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, iconClass); + } + + if (this.label) { + + addClasses(this.label, 'icon', iconClass); + this._itemClassDispose.value = toDisposable(() => { + if (this.label) { + removeClasses(this.label, 'icon', iconClass); + } + }); + } + } } } } @@ -304,17 +322,19 @@ export class LabeledMenuItemActionItem extends MenuEntryActionViewItem { dispose(this._labeledItemClassDispose); this._labeledItemClassDispose = undefined; - if (item.iconLocation) { + if (ThemeIcon.isThemeIcon(item.icon)) { + // TODO + } else if (item.icon) { let iconClass: string; - const iconPathMapKey = item.iconLocation.dark.toString(); + const iconPathMapKey = item.icon.dark.toString(); if (MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) { iconClass = MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!; } else { iconClass = ids.nextId(); - createCSSRule(`.icon.${iconClass}`, `background-image: ${asCSSUrl(item.iconLocation.light || item.iconLocation.dark)}`); - createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: ${asCSSUrl(item.iconLocation.dark)}`); + createCSSRule(`.icon.${iconClass}`, `background-image: ${asCSSUrl(item.icon.light || item.icon.dark)}`); + createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: ${asCSSUrl(item.icon.dark)}`); MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, iconClass); } diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 3849589d38..af1fcb1710 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -5,13 +5,14 @@ import { Action } from 'vs/base/common/actions'; import { SyncDescriptor0, createSyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { IConstructorSignature2, createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IConstructorSignature2, createDecorator, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindings, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ICommandService, ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import { URI, UriComponents } from 'vs/base/common/uri'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; export interface ILocalizedString { value: string; @@ -22,7 +23,7 @@ export interface ICommandAction { id: string; title: string | ILocalizedString; category?: string | ILocalizedString; - iconLocation?: { dark: URI; light?: URI; }; + icon?: { dark?: URI; light?: URI; } | ThemeIcon; precondition?: ContextKeyExpr; toggled?: ContextKeyExpr; } @@ -64,6 +65,7 @@ export const enum MenuId { DebugWatchContext, DebugToolBar, EditorContext, + EditorContextPeek, EditorTitle, EditorTitleContext, EmptyEditorGroupContext, @@ -95,6 +97,9 @@ export const enum MenuId { StatusBarWindowIndicatorMenu, TouchBarContext, TitleBarContext, + TunnelContext, + TunnelInline, + TunnelTitle, ViewItemContext, ViewTitle, ObjectExplorerItemContext, // {{SQL CARBON EDIT}} @@ -300,7 +305,13 @@ export class SyncActionDescriptor { private readonly _keybindingContext: ContextKeyExpr | undefined; private readonly _keybindingWeight: number | undefined; - constructor(ctor: IConstructorSignature2, + public static create(ctor: { new(id: string, label: string, ...services: Services): Action }, + id: string, label: string | undefined, keybindings?: IKeybindings, keybindingContext?: ContextKeyExpr, keybindingWeight?: number + ): SyncActionDescriptor { + return new SyncActionDescriptor(ctor as IConstructorSignature2, id, label, keybindings, keybindingContext, keybindingWeight); + } + + private constructor(ctor: IConstructorSignature2, id: string, label: string | undefined, keybindings?: IKeybindings, keybindingContext?: ContextKeyExpr, keybindingWeight?: number ) { this._id = id; diff --git a/src/vs/platform/actions/common/menuService.ts b/src/vs/platform/actions/common/menuService.ts index 9329a30998..2368d78c9a 100644 --- a/src/vs/platform/actions/common/menuService.ts +++ b/src/vs/platform/actions/common/menuService.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { IMenu, IMenuActionOptions, IMenuItem, IMenuService, isIMenuItem, ISubmenuItem, MenuId, MenuItemAction, MenuRegistry, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IMenu, IMenuActionOptions, IMenuItem, IMenuService, isIMenuItem, ISubmenuItem, MenuId, MenuItemAction, MenuRegistry, SubmenuItemAction, ILocalizedString } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr, IContextKeyService, IContextKeyChangeEvent } from 'vs/platform/contextkey/common/contextkey'; @@ -27,24 +27,24 @@ export class MenuService implements IMenuService { type MenuItemGroup = [string, Array]; -class Menu extends Disposable implements IMenu { +class Menu implements IMenu { - private readonly _onDidChange = this._register(new Emitter()); + private readonly _onDidChange = new Emitter(); + private readonly _dispoables = new DisposableStore(); - private _menuGroups!: MenuItemGroup[]; - private _contextKeys!: Set; + private _menuGroups: MenuItemGroup[] = []; + private _contextKeys: Set = new Set(); constructor( private readonly _id: MenuId, @ICommandService private readonly _commandService: ICommandService, @IContextKeyService private readonly _contextKeyService: IContextKeyService ) { - super(); this._build(); // rebuild this menu whenever the menu registry reports an // event for this MenuId - this._register(Event.debounce( + this._dispoables.add(Event.debounce( Event.filter(MenuRegistry.onDidChangeMenu, menuId => menuId === this._id), () => { }, 50 @@ -52,18 +52,23 @@ class Menu extends Disposable implements IMenu { // when context keys change we need to check if the menu also // has changed - this._register(Event.debounce( + this._dispoables.add(Event.debounce( this._contextKeyService.onDidChangeContext, (last, event) => last || event.affectsSome(this._contextKeys), 50 )(e => e && this._onDidChange.fire(undefined), this)); } + dispose(): void { + this._dispoables.dispose(); + this._onDidChange.dispose(); + } + private _build(): void { // reset - this._menuGroups = []; - this._contextKeys = new Set(); + this._menuGroups.length = 0; + this._contextKeys.clear(); const menuItems = MenuRegistry.getMenuItems(this._id); @@ -106,7 +111,10 @@ class Menu extends Disposable implements IMenu { const activeActions: Array = []; for (const item of items) { if (this._contextKeyService.contextMatchesRules(item.when)) { - const action = isIMenuItem(item) ? new MenuItemAction(item.command, item.alt, options, this._contextKeyService, this._commandService) : new SubmenuItemAction(item); + const action = isIMenuItem(item) + ? new MenuItemAction(item.command, item.alt, options, this._contextKeyService, this._commandService) + : new SubmenuItemAction(item); + activeActions.push(action); } } @@ -125,7 +133,7 @@ class Menu extends Disposable implements IMenu { } } - private static _compareMenuItems(a: IMenuItem, b: IMenuItem): number { + private static _compareMenuItems(a: IMenuItem | ISubmenuItem, b: IMenuItem | ISubmenuItem): number { let aGroup = a.group; let bGroup = b.group; @@ -163,8 +171,15 @@ class Menu extends Disposable implements IMenu { } // sort on titles - const aTitle = typeof a.command.title === 'string' ? a.command.title : a.command.title.value; - const bTitle = typeof b.command.title === 'string' ? b.command.title : b.command.title.value; - return aTitle.localeCompare(bTitle); + return Menu._compareTitles( + isIMenuItem(a) ? a.command.title : a.title, + isIMenuItem(b) ? b.command.title : b.title + ); + } + + private static _compareTitles(a: string | ILocalizedString, b: string | ILocalizedString) { + const aStr = typeof a === 'string' ? a : a.value; + const bStr = typeof b === 'string' ? b : b.value; + return aStr.localeCompare(bStr); } } diff --git a/src/vs/platform/auth/common/auth.css b/src/vs/platform/auth/common/auth.css new file mode 100644 index 0000000000..09f75d9883 --- /dev/null +++ b/src/vs/platform/auth/common/auth.css @@ -0,0 +1,100 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +html { + height: 100%; +} + +body { + box-sizing: border-box; + min-height: 100%; + margin: 0; + padding: 15px 30px; + display: flex; + flex-direction: column; + color: white; + font-family: "Segoe UI","Helvetica Neue","Helvetica",Arial,sans-serif; + background-color: #373277; +} + +.branding { + background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PGRlZnM+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtmaWxsOiNmNmY2ZjY7b3BhY2l0eTowO30uaWNvbi13aGl0ZXtmaWxsOiNmZmY7fTwvc3R5bGU+PC9kZWZzPjx0aXRsZT5CcmFuZFZpc3VhbFN0dWRpb0NvZGUyMDE3UlRXXzI0eF93aGl0ZV8yNHg8L3RpdGxlPjxwYXRoIGNsYXNzPSJpY29uLWNhbnZhcy10cmFuc3BhcmVudCIgZD0iTTI0LDBWMjRIMFYwWiIvPjxwYXRoIGNsYXNzPSJpY29uLXdoaXRlIiBkPSJNMjQsMi41VjIxLjVMMTgsMjQsMCwxOC41di0uNTYxbDE4LDEuNTQ1VjBaTTEsMTMuMTExLDQuMzg1LDEwLDEsNi44ODlsMS40MTgtLjgyN0w1Ljg1Myw4LjY1LDEyLDNsMywxLjQ1NlYxNS41NDRMMTIsMTcsNS44NTMsMTEuMzUsMi40MTksMTMuOTM5Wk03LjY0NCwxMCwxMiwxMy4yODNWNi43MTdaIi8+PC9zdmc+"); + background-size: 24px; + background-repeat: no-repeat; + background-position: left 50%; + padding-left: 36px; + font-size: 20px; + letter-spacing: -0.04rem; + font-weight: 400; + color: white; + text-decoration: none; +} + +.message-container { + flex-grow: 1; + display: flex; + align-items: center; + justify-content: center; + margin: 0 30px; +} + +.message { + font-weight: 300; + font-size: 1.3rem; +} + +body.error .message { + display: none; +} + +body.error .error-message { + display: block; +} + +.error-message { + display: none; + font-weight: 300; + font-size: 1.3rem; +} + +.error-text { + color: red; + font-size: 1rem; +} + +@font-face { + font-family: 'Segoe UI'; + src: url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/light/latest.eot"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/light/latest.eot?#iefix") format("embedded-opentype"); + src: local("Segoe UI Light"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/light/latest.woff2") format("woff2"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/light/latest.woff") format("woff"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/light/latest.ttf") format("truetype"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/light/latest.svg#web") format("svg"); + font-weight: 200 +} + +@font-face { + font-family: 'Segoe UI'; + src: url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semilight/latest.eot"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semilight/latest.eot?#iefix") format("embedded-opentype"); + src: local("Segoe UI Semilight"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semilight/latest.woff2") format("woff2"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semilight/latest.woff") format("woff"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semilight/latest.ttf") format("truetype"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semilight/latest.svg#web") format("svg"); + font-weight: 300 +} + +@font-face { + font-family: 'Segoe UI'; + src: url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/normal/latest.eot"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/normal/latest.eot?#iefix") format("embedded-opentype"); + src: local("Segoe UI"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/normal/latest.woff2") format("woff"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/normal/latest.woff") format("woff"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/normal/latest.ttf") format("truetype"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/normal/latest.svg#web") format("svg"); + font-weight: 400 +} + +@font-face { + font-family: 'Segoe UI'; + src: url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semibold/latest.eot"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semibold/latest.eot?#iefix") format("embedded-opentype"); + src: local("Segoe UI Semibold"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semibold/latest.woff2") format("woff"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semibold/latest.woff") format("woff"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semibold/latest.ttf") format("truetype"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semibold/latest.svg#web") format("svg"); + font-weight: 600 +} + +@font-face { + font-family: 'Segoe UI'; + src: url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/bold/latest.eot"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/bold/latest.eot?#iefix") format("embedded-opentype"); + src: local("Segoe UI Bold"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/bold/latest.woff2") format("woff"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/bold/latest.woff") format("woff"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/bold/latest.ttf") format("truetype"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/bold/latest.svg#web") format("svg"); + font-weight: 700 +} diff --git a/src/vs/platform/auth/common/auth.html b/src/vs/platform/auth/common/auth.html new file mode 100644 index 0000000000..8fe3e50e7b --- /dev/null +++ b/src/vs/platform/auth/common/auth.html @@ -0,0 +1,35 @@ + + + + + + + Azure Account - Sign In + + + + + + Visual Studio Code + +
+
+ You are signed in now and can close this page. +
+
+ An error occurred while signing in: +
+
+
+ + + diff --git a/src/vs/platform/auth/common/auth.ts b/src/vs/platform/auth/common/auth.ts index 466357e293..7ca970155d 100644 --- a/src/vs/platform/auth/common/auth.ts +++ b/src/vs/platform/auth/common/auth.ts @@ -4,12 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { Event } from 'vs/base/common/event'; +import { Event, Emitter } from 'vs/base/common/event'; +import { URI } from 'vs/base/common/uri'; export const enum AuthTokenStatus { - Disabled = 'Disabled', - Inactive = 'Inactive', - Active = 'Active' + Initializing = 'Initializing', + SignedOut = 'SignedOut', + SignedIn = 'SignedIn', + SigningIn = 'SigningIn', + RefreshingToken = 'RefreshingToken' } export const IAuthTokenService = createDecorator('IAuthTokenService'); @@ -19,11 +22,10 @@ export interface IAuthTokenService { readonly status: AuthTokenStatus; readonly onDidChangeStatus: Event; + readonly _onDidGetCallback: Emitter; - getToken(): Promise; - updateToken(token: string): Promise; + getToken(): Promise; refreshToken(): Promise; - deleteToken(): Promise; - + login(): Promise; + logout(): Promise; } - diff --git a/src/vs/platform/auth/common/authTokenIpc.ts b/src/vs/platform/auth/common/authTokenIpc.ts index e6c0a16250..7e6323b192 100644 --- a/src/vs/platform/auth/common/authTokenIpc.ts +++ b/src/vs/platform/auth/common/authTokenIpc.ts @@ -22,9 +22,9 @@ export class AuthTokenChannel implements IServerChannel { switch (command) { case '_getInitialStatus': return Promise.resolve(this.service.status); case 'getToken': return this.service.getToken(); - case 'updateToken': return this.service.updateToken(args[0]); case 'refreshToken': return this.service.refreshToken(); - case 'deleteToken': return this.service.deleteToken(); + case 'login': return this.service.login(); + case 'logout': return this.service.logout(); } throw new Error('Invalid call'); } diff --git a/src/vs/platform/auth/electron-browser/authServer.ts b/src/vs/platform/auth/electron-browser/authServer.ts new file mode 100644 index 0000000000..aed2ccf204 --- /dev/null +++ b/src/vs/platform/auth/electron-browser/authServer.ts @@ -0,0 +1,165 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as http from 'http'; +import * as url from 'url'; +import * as fs from 'fs'; +import * as net from 'net'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; +import { assertIsDefined } from 'vs/base/common/types'; + +interface Deferred { + resolve: (result: T | Promise) => void; + reject: (reason: any) => void; +} + +export function createTerminateServer(server: http.Server) { + const sockets: Record = {}; + let socketCount = 0; + server.on('connection', socket => { + const id = socketCount++; + sockets[id] = socket; + socket.on('close', () => { + delete sockets[id]; + }); + }); + return async () => { + const result = new Promise(resolve => server.close(resolve)); + for (const id in sockets) { + sockets[id].destroy(); + } + return result; + }; +} + +export async function startServer(server: http.Server): Promise { + let portTimer: NodeJS.Timer; + + function cancelPortTimer() { + clearTimeout(portTimer); + } + + const port = new Promise((resolve, reject) => { + portTimer = setTimeout(() => { + reject(new Error('Timeout waiting for port')); + }, 5000); + + server.on('listening', () => { + const address = server.address(); + if (typeof address === 'string') { + resolve(address); + } else { + resolve(assertIsDefined(address).port.toString()); + } + }); + + server.on('error', err => { + reject(err); + }); + + server.on('close', () => { + reject(new Error('Closed')); + }); + + server.listen(0); + }); + + port.then(cancelPortTimer, cancelPortTimer); + return port; +} + +function sendFile(res: http.ServerResponse, filepath: string, contentType: string) { + fs.readFile(filepath, (err, body) => { + if (err) { + console.error(err); + res.writeHead(404); + res.end(); + } else { + res.writeHead(200, { + 'Content-Length': body.length, + 'Content-Type': contentType + }); + res.end(body); + } + }); +} + +async function callback(nonce: string, reqUrl: url.Url): Promise { + const query = reqUrl.query; + if (!query || typeof query === 'string') { + throw new Error('No query received.'); + } + + let error = query.error_description || query.error; + + if (!error) { + const state = (query.state as string) || ''; + const receivedNonce = (state.split(',')[1] || '').replace(/ /g, '+'); + if (receivedNonce !== nonce) { + error = 'Nonce does not match.'; + } + } + + const code = query.code as string; + if (!error && code) { + return code; + } + + throw new Error((error as string) || 'No code received.'); +} + +export function createServer(nonce: string) { + type RedirectResult = { req: http.IncomingMessage; res: http.ServerResponse; } | { err: any; res: http.ServerResponse; }; + let deferredRedirect: Deferred; + const redirectPromise = new Promise((resolve, reject) => deferredRedirect = { resolve, reject }); + + type CodeResult = { code: string; res: http.ServerResponse; } | { err: any; res: http.ServerResponse; }; + let deferredCode: Deferred; + const codePromise = new Promise((resolve, reject) => deferredCode = { resolve, reject }); + + const codeTimer = setTimeout(() => { + deferredCode.reject(new Error('Timeout waiting for code')); + }, 5 * 60 * 1000); + + function cancelCodeTimer() { + clearTimeout(codeTimer); + } + + const server = http.createServer(function (req, res) { + const reqUrl = url.parse(req.url!, /* parseQueryString */ true); + switch (reqUrl.pathname) { + case '/signin': + const receivedNonce = ((reqUrl.query.nonce as string) || '').replace(/ /g, '+'); + if (receivedNonce === nonce) { + deferredRedirect.resolve({ req, res }); + } else { + const err = new Error('Nonce does not match.'); + deferredRedirect.resolve({ err, res }); + } + break; + case '/': + sendFile(res, getPathFromAmdModule(require, '../common/auth.html'), 'text/html; charset=utf-8'); + break; + case '/auth.css': + sendFile(res, getPathFromAmdModule(require, '../common/auth.css'), 'text/css; charset=utf-8'); + break; + case '/callback': + deferredCode.resolve(callback(nonce, reqUrl) + .then(code => ({ code, res }), err => ({ err, res }))); + break; + default: + res.writeHead(404); + res.end(); + break; + } + }); + + codePromise.then(cancelCodeTimer, cancelCodeTimer); + return { + server, + redirectPromise, + codePromise + }; +} diff --git a/src/vs/platform/auth/electron-browser/authTokenService.ts b/src/vs/platform/auth/electron-browser/authTokenService.ts new file mode 100644 index 0000000000..e299ea0f4f --- /dev/null +++ b/src/vs/platform/auth/electron-browser/authTokenService.ts @@ -0,0 +1,276 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as crypto from 'crypto'; +import * as https from 'https'; +import { Event, Emitter } from 'vs/base/common/event'; +import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth'; +import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { generateUuid } from 'vs/base/common/uuid'; +import { shell } from 'electron'; +import { createServer, startServer } from 'vs/platform/auth/electron-browser/authServer'; +import { IProductService } from 'vs/platform/product/common/productService'; + +const SERVICE_NAME = 'VS Code'; +const ACCOUNT = 'MyAccount'; + +const activeDirectoryResourceId = 'https://management.core.windows.net/'; + +function toQuery(obj: any): string { + return Object.keys(obj).map(key => `${key}=${obj[key]}`).join('&'); +} + +function toBase64UrlEncoding(base64string: string) { + return base64string.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); // Need to use base64url encoding +} + +export interface IToken { + expiresIn: string; // How long access token is valid, in seconds + expiresOn: string; // When the access token expires in epoch time + accessToken: string; + refreshToken: string; +} + +export class AuthTokenService extends Disposable implements IAuthTokenService { + _serviceBrand: undefined; + + private _status: AuthTokenStatus = AuthTokenStatus.Initializing; + get status(): AuthTokenStatus { return this._status; } + private _onDidChangeStatus: Emitter = this._register(new Emitter()); + readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; + + public readonly _onDidGetCallback: Emitter = this._register(new Emitter()); + readonly onDidGetCallback: Event = this._onDidGetCallback.event; + + private _activeToken: IToken | undefined; + + constructor( + @ICredentialsService private readonly credentialsService: ICredentialsService, + @IProductService private readonly productService: IProductService + ) { + super(); + if (!this.productService.auth) { + return; + } + + this.credentialsService.getPassword(SERVICE_NAME, ACCOUNT).then(storedRefreshToken => { + if (storedRefreshToken) { + this.refresh(storedRefreshToken); + } else { + this.setStatus(AuthTokenStatus.SignedOut); + } + }); + } + + public async login(): Promise { + if (!this.productService.auth) { + throw new Error('Authentication is not configured.'); + } + + this.setStatus(AuthTokenStatus.SigningIn); + + const nonce = generateUuid(); + const { server, redirectPromise, codePromise } = createServer(nonce); + + try { + const port = await startServer(server); + shell.openExternal(`http://localhost:${port}/signin?nonce=${encodeURIComponent(nonce)}`); + + const redirectReq = await redirectPromise; + if ('err' in redirectReq) { + const { err, res } = redirectReq; + res.writeHead(302, { Location: `/?error=${encodeURIComponent(err && err.message || 'Unkown error')}` }); + res.end(); + throw err; + } + + const host = redirectReq.req.headers.host || ''; + const updatedPortStr = (/^[^:]+:(\d+)$/.exec(Array.isArray(host) ? host[0] : host) || [])[1]; + const updatedPort = updatedPortStr ? parseInt(updatedPortStr, 10) : port; + + const state = `${updatedPort},${encodeURIComponent(nonce)}`; + + const codeVerifier = toBase64UrlEncoding(crypto.randomBytes(32).toString('base64')); + const codeChallenge = toBase64UrlEncoding(crypto.createHash('sha256').update(codeVerifier).digest('base64')); + + let uri = URI.parse(this.productService.auth.loginUrl); + uri = uri.with({ + query: `response_type=code&client_id=${encodeURIComponent(this.productService.auth.clientId)}&redirect_uri=${this.productService.auth.redirectUrl}&state=${encodeURIComponent(state)}&resource=${activeDirectoryResourceId}&prompt=select_account&code_challenge_method=S256&code_challenge=${codeChallenge}` + }); + + await redirectReq.res.writeHead(302, { Location: uri.toString(true) }); + redirectReq.res.end(); + + const codeRes = await codePromise; + const res = codeRes.res; + + try { + if ('err' in codeRes) { + throw codeRes.err; + } + const token = await this.exchangeCodeForToken(codeRes.code, codeVerifier); + this.setToken(token); + res.writeHead(302, { Location: '/' }); + res.end(); + } catch (err) { + res.writeHead(302, { Location: `/?error=${encodeURIComponent(err && err.message || 'Unkown error')}` }); + res.end(); + } + } finally { + setTimeout(() => { + server.close(); + }, 5000); + } + + } + + public getToken(): Promise { + return Promise.resolve(this._activeToken?.accessToken); + } + + public async refreshToken(): Promise { + if (!this._activeToken) { + throw new Error('No token to refresh'); + } + + this.refresh(this._activeToken.refreshToken); + } + + private setToken(token: IToken) { + this._activeToken = token; + this.credentialsService.setPassword(SERVICE_NAME, ACCOUNT, token.refreshToken); + this.setStatus(AuthTokenStatus.SignedIn); + } + + private exchangeCodeForToken(code: string, codeVerifier: string): Promise { + return new Promise((resolve: (value: IToken) => void, reject) => { + try { + if (!this.productService.auth) { + throw new Error('Authentication is not configured.'); + } + + const postData = toQuery({ + grant_type: 'authorization_code', + code: code, + client_id: this.productService.auth?.clientId, + code_verifier: codeVerifier, + redirect_uri: this.productService.auth?.redirectUrl + }); + + const tokenUrl = URI.parse(this.productService.auth.tokenUrl); + + const post = https.request({ + host: tokenUrl.authority, + path: tokenUrl.path, + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': postData.length + } + }, result => { + const buffer: Buffer[] = []; + result.on('data', (chunk: Buffer) => { + buffer.push(chunk); + }); + result.on('end', () => { + if (result.statusCode === 200) { + const json = JSON.parse(Buffer.concat(buffer).toString()); + resolve({ + expiresIn: json.access_token, + expiresOn: json.expires_on, + accessToken: json.access_token, + refreshToken: json.refresh_token + }); + } else { + reject(new Error('Bad!')); + } + }); + }); + + post.write(postData); + + post.end(); + post.on('error', err => { + reject(err); + }); + + } catch (e) { + reject(e); + } + }); + } + + private async refresh(refreshToken: string): Promise { + return new Promise((resolve, reject) => { + if (!this.productService.auth) { + throw new Error('Authentication is not configured.'); + } + + this.setStatus(AuthTokenStatus.RefreshingToken); + const postData = toQuery({ + refresh_token: refreshToken, + client_id: this.productService.auth?.clientId, + grant_type: 'refresh_token', + resource: activeDirectoryResourceId + }); + + const tokenUrl = URI.parse(this.productService.auth.tokenUrl); + + const post = https.request({ + host: tokenUrl.authority, + path: tokenUrl.path, + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': postData.length + } + }, result => { + const buffer: Buffer[] = []; + result.on('data', (chunk: Buffer) => { + buffer.push(chunk); + }); + result.on('end', () => { + if (result.statusCode === 200) { + const json = JSON.parse(Buffer.concat(buffer).toString()); + this.setToken({ + expiresIn: json.access_token, + expiresOn: json.expires_on, + accessToken: json.access_token, + refreshToken: json.refresh_token + }); + resolve(); + } else { + reject(new Error('Refreshing token failed.')); + } + }); + }); + + post.write(postData); + + post.end(); + post.on('error', err => { + this.setStatus(AuthTokenStatus.SignedOut); + reject(err); + }); + }); + } + + async logout(): Promise { + await this.credentialsService.deletePassword(SERVICE_NAME, ACCOUNT); + this._activeToken = undefined; + this.setStatus(AuthTokenStatus.SignedOut); + } + + private setStatus(status: AuthTokenStatus): void { + if (this._status !== status) { + this._status = status; + this._onDidChangeStatus.fire(status); + } + } + +} + diff --git a/src/vs/platform/configuration/test/node/configurationService.test.ts b/src/vs/platform/configuration/test/node/configurationService.test.ts index 565e7851d1..70c333e69a 100644 --- a/src/vs/platform/configuration/test/node/configurationService.test.ts +++ b/src/vs/platform/configuration/test/node/configurationService.test.ts @@ -14,6 +14,8 @@ import * as uuid from 'vs/base/common/uuid'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { testFile } from 'vs/base/test/node/utils'; import { URI } from 'vs/base/common/uri'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { Event } from 'vs/base/common/event'; suite('ConfigurationService - Node', () => { @@ -94,10 +96,11 @@ suite('ConfigurationService - Node', () => { const service = new ConfigurationService(URI.file(res.testFile)); await service.initialize(); return new Promise((c, e) => { - const disposable = service.onDidChangeConfiguration(() => { + const disposable = Event.filter(service.onDidChangeConfiguration, e => e.source === ConfigurationTarget.USER)(async (e) => { disposable.dispose(); assert.equal(service.getValue('foo'), 'bar'); service.dispose(); + await res.cleanUp(); c(); }); fs.writeFileSync(res.testFile, '{ "foo": "bar" }'); diff --git a/src/vs/platform/contextkey/common/contextkey.ts b/src/vs/platform/contextkey/common/contextkey.ts index abe9e168aa..df7ad84d1d 100644 --- a/src/vs/platform/contextkey/common/contextkey.ts +++ b/src/vs/platform/contextkey/common/contextkey.ts @@ -640,7 +640,7 @@ export class ContextKeyAndExpr implements ContextKeyExpr { if (e instanceof ContextKeyOrExpr) { // Not allowed, because we don't have parens! - throw new Error(`It is not allowed to have an or expression here due to lack of parens!`); + throw new Error(`It is not allowed to have an or expression here due to lack of parens! For example "a && (b||c)" is not supported, use "(a&&b) || (a&&c)" instead.`); } expr.push(e); diff --git a/src/vs/platform/contextview/browser/contextView.ts b/src/vs/platform/contextview/browser/contextView.ts index c3e3f463f3..64981ed584 100644 --- a/src/vs/platform/contextview/browser/contextView.ts +++ b/src/vs/platform/contextview/browser/contextView.ts @@ -7,11 +7,11 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IContextMenuDelegate } from 'vs/base/browser/contextmenu'; -import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; +import { AnchorAlignment, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; export const IContextViewService = createDecorator('contextViewService'); -export interface IContextViewService { +export interface IContextViewService extends IContextViewProvider { _serviceBrand: undefined; @@ -41,4 +41,4 @@ export interface IContextMenuService { showContextMenu(delegate: IContextMenuDelegate): void; onDidContextMenu: Event; // TODO@isidor these event should be removed once we get async context menus -} \ No newline at end of file +} diff --git a/src/vs/platform/debug/common/extensionHostDebugIpc.ts b/src/vs/platform/debug/common/extensionHostDebugIpc.ts index 1da4777adc..89cf5f446e 100644 --- a/src/vs/platform/debug/common/extensionHostDebugIpc.ts +++ b/src/vs/platform/debug/common/extensionHostDebugIpc.ts @@ -102,8 +102,6 @@ export class ExtensionHostDebugChannelClient extends Disposable implements IExte } openExtensionDevelopmentHostWindow(args: string[], env: IProcessEnvironment): Promise { - // TODO@Isidor - //return this.channel.call('openExtensionDevelopmentHostWindow', [args, env]); - return Promise.resolve(); + return this.channel.call('openExtensionDevelopmentHostWindow', [args, env]); } } diff --git a/src/vs/platform/diagnostics/node/diagnosticsService.ts b/src/vs/platform/diagnostics/node/diagnosticsService.ts index 19f00a3fce..7bd6075d32 100644 --- a/src/vs/platform/diagnostics/node/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/node/diagnosticsService.ts @@ -7,7 +7,7 @@ import { virtualMachineHint } from 'vs/base/node/id'; import { IMachineInfo, WorkspaceStats, WorkspaceStatItem, PerformanceInfo, SystemInfo, IRemoteDiagnosticInfo, IRemoteDiagnosticError, isRemoteDiagnosticError, IWorkspaceInformation } from 'vs/platform/diagnostics/common/diagnostics'; import { readdir, stat, exists, readFile } from 'fs'; import { join, basename } from 'vs/base/common/path'; -import { parse, ParseError } from 'vs/base/common/json'; +import { parse, ParseError, getNodeType } from 'vs/base/common/json'; import { listProcesses } from 'vs/base/node/ps'; import product from 'vs/platform/product/common/product'; import { repeat, pad } from 'vs/base/common/strings'; @@ -223,7 +223,7 @@ export function collectLaunchConfigs(folder: string): Promise('dialogService'); export interface IDialogOptions { @@ -240,23 +239,34 @@ export interface IFileDialogService { */ showSaveDialog(options: ISaveDialogOptions): Promise; + /** + * Shows a confirm dialog for saving 1-N files. + */ + showSaveConfirm(fileNamesOrResources: (string | URI)[]): Promise; + /** * Shows a open file dialog and returns the chosen file URI. */ showOpenDialog(options: IOpenDialogOptions): Promise; } +export const enum ConfirmResult { + SAVE, + DONT_SAVE, + CANCEL +} + const MAX_CONFIRM_FILES = 10; -export function getConfirmMessage(start: string, resourcesToConfirm: readonly URI[]): string { +export function getConfirmMessage(start: string, fileNamesOrResources: readonly (string | URI)[]): string { const message = [start]; message.push(''); - message.push(...resourcesToConfirm.slice(0, MAX_CONFIRM_FILES).map(r => basename(r))); + message.push(...fileNamesOrResources.slice(0, MAX_CONFIRM_FILES).map(fileNameOrResource => typeof fileNameOrResource === 'string' ? fileNameOrResource : basename(fileNameOrResource))); - if (resourcesToConfirm.length > MAX_CONFIRM_FILES) { - if (resourcesToConfirm.length - MAX_CONFIRM_FILES === 1) { + if (fileNamesOrResources.length > MAX_CONFIRM_FILES) { + if (fileNamesOrResources.length - MAX_CONFIRM_FILES === 1) { message.push(localize('moreFile', "...1 additional file not shown")); } else { - message.push(localize('moreFiles', "...{0} additional files not shown", resourcesToConfirm.length - MAX_CONFIRM_FILES)); + message.push(localize('moreFiles', "...{0} additional files not shown", fileNamesOrResources.length - MAX_CONFIRM_FILES)); } } diff --git a/src/vs/platform/download/common/downloadService.ts b/src/vs/platform/download/common/downloadService.ts index 3da98d5e1a..2553aa5429 100644 --- a/src/vs/platform/download/common/downloadService.ts +++ b/src/vs/platform/download/common/downloadService.ts @@ -30,7 +30,7 @@ export class DownloadService implements IDownloadService { await this.fileService.writeFile(target, context.stream); } else { const message = await asText(context); - return Promise.reject(new Error(`Expected 200, got back ${context.res.statusCode} instead.\n\n${message}`)); + throw new Error(`Expected 200, got back ${context.res.statusCode} instead.\n\n${message}`); } } } diff --git a/src/vs/platform/driver/electron-main/driver.ts b/src/vs/platform/driver/electron-main/driver.ts index 85c811d7f5..f5d4c92476 100644 --- a/src/vs/platform/driver/electron-main/driver.ts +++ b/src/vs/platform/driver/electron-main/driver.ts @@ -39,7 +39,7 @@ export class Driver implements IDriver, IWindowDriverRegistry { private options: IDriverOptions, @IWindowsMainService private readonly windowsMainService: IWindowsMainService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, - @IElectronMainService private readonly electronMainService: any // {{SQL CARBON EDIT}} remove interface, naster work around + @IElectronMainService private readonly electronMainService: IElectronMainService ) { } async registerWindowDriver(windowId: number): Promise { @@ -212,7 +212,7 @@ export async function serve( instantiationService: IInstantiationService ): Promise { const verbose = environmentService.driverVerbose; - const driver = instantiationService.createInstance(Driver, windowServer, { verbose }); + const driver = instantiationService.createInstance(Driver as any, windowServer, { verbose }) as Driver; // {{SQL CARBON EDIT}} strict-null-check...i guess? const windowDriverRegistryChannel = new WindowDriverRegistryChannel(driver); windowServer.registerChannel('windowDriverRegistry', windowDriverRegistryChannel); diff --git a/src/vs/platform/electron/electron-main/electronMainService.ts b/src/vs/platform/electron/electron-main/electronMainService.ts index fe42cd5672..b50795b045 100644 --- a/src/vs/platform/electron/electron-main/electronMainService.ts +++ b/src/vs/platform/electron/electron-main/electronMainService.ts @@ -10,7 +10,7 @@ import { INativeOpenWindowOptions } from 'vs/platform/windows/node/window'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IOpenedWindow, OpenContext, IWindowOpenable, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; -import { isMacintosh, IProcessEnvironment } from 'vs/base/common/platform'; +import { isMacintosh } from 'vs/base/common/platform'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -18,7 +18,6 @@ import { AddFirstParameterToFunctions } from 'vs/base/common/types'; import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; import { dirExists } from 'vs/base/node/pfs'; import { URI } from 'vs/base/common/uri'; -import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { ITelemetryData, ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -100,6 +99,7 @@ export class ElectronMainService implements IElectronMainService { cli: this.environmentService.args, forceNewWindow: options.forceNewWindow, forceReuseWindow: options.forceReuseWindow, + preferNewWindow: options.preferNewWindow, diffMode: options.diffMode, addMode: options.addMode, gotoLineMode: options.gotoLineMode, @@ -407,24 +407,6 @@ export class ElectronMainService implements IElectronMainService { //#endregion - //#region Debug - - // TODO@Isidor move into debug IPC channel (https://github.com/microsoft/vscode/issues/81060) - - async openExtensionDevelopmentHostWindow(windowId: number, args: string[], env: IProcessEnvironment): Promise { - const pargs = parseArgs(args, OPTIONS); - const extDevPaths = pargs.extensionDevelopmentPath; - if (extDevPaths) { - this.windowsMainService.openExtensionDevelopmentHostWindow(extDevPaths, { - context: OpenContext.API, - cli: pargs, - userEnv: Object.keys(env).length > 0 ? env : undefined - }); - } - } - - //#endregion - private windowById(windowId: number | undefined): ICodeWindow | undefined { if (typeof windowId !== 'number') { return undefined; diff --git a/src/vs/platform/electron/node/electron.ts b/src/vs/platform/electron/node/electron.ts index 32f10149ee..2002a64955 100644 --- a/src/vs/platform/electron/node/electron.ts +++ b/src/vs/platform/electron/node/electron.ts @@ -9,7 +9,6 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { IWindowOpenable, IOpenEmptyWindowOptions, IOpenedWindow } from 'vs/platform/windows/common/windows'; import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; -import { IProcessEnvironment } from 'vs/base/common/platform'; import { INativeOpenWindowOptions } from 'vs/platform/windows/node/window'; export const IElectronService = createDecorator('electronService'); @@ -85,7 +84,4 @@ export interface IElectronService { // Connectivity resolveProxy(url: string): Promise; - - // Debug (TODO@Isidor move into debug IPC channel (https://github.com/microsoft/vscode/issues/81060) - openExtensionDevelopmentHostWindow(args: string[], env: IProcessEnvironment): Promise; } diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 4a7dcf5109..c6136d7eef 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -5,6 +5,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; +import { IUserHomeProvider } from 'vs/base/common/labels'; export interface ParsedArgs { _: string[]; @@ -91,6 +92,8 @@ export interface ParsedArgs { 'js-flags'?: string; 'disable-gpu'?: boolean; 'nolazy'?: boolean; + 'force-device-scale-factor'?: string; + 'force-renderer-accessibility'?: boolean; } export const IEnvironmentService = createDecorator('environmentService'); @@ -106,7 +109,7 @@ export interface IExtensionHostDebugParams extends IDebugParams { export const BACKUPS = 'Backups'; -export interface IEnvironmentService { +export interface IEnvironmentService extends IUserHomeProvider { _serviceBrand: undefined; @@ -119,8 +122,6 @@ export interface IEnvironmentService { userHome: string; userDataPath: string; - appNameLong: string; - appQuality?: string; appSettingsHome: URI; // user roaming data @@ -133,6 +134,7 @@ export interface IEnvironmentService { // sync resources userDataSyncLogResource: URI; settingsSyncPreviewResource: URI; + keybindingsSyncPreviewResource: URI; machineSettingsHome: URI; machineSettingsResource: URI; @@ -151,6 +153,7 @@ export interface IEnvironmentService { extensionsPath?: string; extensionDevelopmentLocationURI?: URI[]; extensionTestsLocationURI?: URI; + logExtensionHostCommunication?: boolean; debugExtensionHost: IExtensionHostDebugParams; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index f8c031d794..0eee4a872a 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -7,13 +7,10 @@ import * as minimist from 'vscode-minimist'; import * as os from 'os'; import { localize } from 'vs/nls'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; -import { join } from 'vs/base/common/path'; -import { writeFileSync } from 'vs/base/node/pfs'; /** * This code is also used by standalone cli's. Avoid adding any other dependencies. */ - const helpCategories = { o: localize('optionsUpperCase', "Options"), e: localize('extensionsManagement', "Extensions Management"), @@ -129,6 +126,8 @@ export const OPTIONS: OptionDescriptions> = { 'inspect': { type: 'string' }, 'inspect-brk': { type: 'string' }, 'nolazy': { type: 'boolean' }, // node inspect + 'force-device-scale-factor': { type: 'string' }, + 'force-renderer-accessibility': { type: 'boolean' }, '_urls': { type: 'string[]' }, _: { type: 'string[]' } // main arguments @@ -170,7 +169,7 @@ export function parseArgs(args: string[], options: OptionDescriptions, err } } } - // remote aliases to avoid confusion + // remove aliases to avoid confusion const parsedArgs = minimist(args, { string, boolean, alias }); const cleanedArgs: any = {}; @@ -193,7 +192,7 @@ export function parseArgs(args: string[], options: OptionDescriptions, err delete parsedArgs[o.deprecates]; } - if (val) { + if (typeof val !== 'undefined') { if (o.type === 'string[]') { if (val && !Array.isArray(val)) { val = [val]; @@ -319,33 +318,3 @@ export function buildVersionMessage(version: string | undefined, commit: string return `${version || localize('unknownVersion', "Unknown version")}\n${commit || localize('unknownCommit', "Unknown commit")}\n${process.arch}`; } -export function addArg(argv: string[], ...args: string[]): string[] { - const endOfArgsMarkerIndex = argv.indexOf('--'); - if (endOfArgsMarkerIndex === -1) { - argv.push(...args); - } else { - // if the we have an argument "--" (end of argument marker) - // we cannot add arguments at the end. rather, we add - // arguments before the "--" marker. - argv.splice(endOfArgsMarkerIndex, 0, ...args); - } - - return argv; -} - -export function createWaitMarkerFile(verbose?: boolean): string | undefined { - const randomWaitMarkerPath = join(os.tmpdir(), Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 10)); - - try { - writeFileSync(randomWaitMarkerPath, ''); - if (verbose) { - console.log(`Marker file for --wait created: ${randomWaitMarkerPath}`); - } - return randomWaitMarkerPath; - } catch (err) { - if (verbose) { - console.error(`Failed to create marker file for --wait: ${err}`); - } - return undefined; - } -} diff --git a/src/vs/platform/environment/node/argvHelper.ts b/src/vs/platform/environment/node/argvHelper.ts index c215d1b996..98f36be451 100644 --- a/src/vs/platform/environment/node/argvHelper.ts +++ b/src/vs/platform/environment/node/argvHelper.ts @@ -69,3 +69,17 @@ export function parseCLIProcessArgv(processArgv: string[]): ParsedArgs { return parseAndValidate(args, true); } + +export function addArg(argv: string[], ...args: string[]): string[] { + const endOfArgsMarkerIndex = argv.indexOf('--'); + if (endOfArgsMarkerIndex === -1) { + argv.push(...args); + } else { + // if the we have an argument "--" (end of argument marker) + // we cannot add arguments at the end. rather, we add + // arguments before the "--" marker. + argv.splice(endOfArgsMarkerIndex, 0, ...args); + } + + return argv; +} diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index 7c168b9fd4..da6a392b87 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -102,10 +102,6 @@ export class EnvironmentService implements IEnvironmentService { return parseUserDataDir(this._args, process); } - get appNameLong(): string { return product.nameLong; } - - get appQuality(): string | undefined { return product.quality; } - @memoize get appSettingsHome(): URI { return URI.file(path.join(this.userDataPath, 'User')); } @@ -118,6 +114,9 @@ export class EnvironmentService implements IEnvironmentService { @memoize get settingsSyncPreviewResource(): URI { return resources.joinPath(this.userRoamingDataHome, '.settings.json'); } + @memoize + get keybindingsSyncPreviewResource(): URI { return resources.joinPath(this.userRoamingDataHome, '.keybindings.json'); } + @memoize get userDataSyncLogResource(): URI { return URI.file(path.join(this.logsPath, 'userDataSync.log')); } @@ -239,6 +238,8 @@ export class EnvironmentService implements IEnvironmentService { @memoize get debugExtensionHost(): IExtensionHostDebugParams { return parseExtensionHostPort(this._args, this.isBuilt); } + @memoize + get logExtensionHostCommunication(): boolean { return !!this.args.logExtensionHostCommunication; } get isBuilt(): boolean { return !process.env['VSCODE_DEV']; } get verbose(): boolean { return !!this._args.verbose; } diff --git a/src/vs/platform/environment/node/waitMarkerFile.ts b/src/vs/platform/environment/node/waitMarkerFile.ts new file mode 100644 index 0000000000..015468a874 --- /dev/null +++ b/src/vs/platform/environment/node/waitMarkerFile.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * This code is also used by standalone cli's. Avoid adding dependencies to keep the size of the cli small. + */ +import * as path from 'vs/base/common/path'; +import * as os from 'os'; +import * as fs from 'fs'; + +export function createWaitMarkerFile(verbose?: boolean): string | undefined { + const randomWaitMarkerPath = path.join(os.tmpdir(), Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 10)); + + try { + fs.writeFileSync(randomWaitMarkerPath, ''); // use built-in fs to avoid dragging in more dependencies + if (verbose) { + console.log(`Marker file for --wait created: ${randomWaitMarkerPath}`); + } + return randomWaitMarkerPath; + } catch (err) { + if (verbose) { + console.error(`Failed to create marker file for --wait: ${err}`); + } + return undefined; + } +} diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 3dc618b9d6..acf92c6165 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -746,7 +746,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { if (extension.assets.manifest) { return this.getAsset(extension.assets.manifest, {}, token) .then(asText) - .then(JSON.parse); + .then(text => text ? JSON.parse(text) : null); } return Promise.resolve(null); } @@ -756,7 +756,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { if (asset) { return this.getAsset(asset[1]) .then(asText) - .then(JSON.parse); + .then(text => text ? JSON.parse(text) : null); } return Promise.resolve(null); } @@ -823,17 +823,6 @@ export class ExtensionGalleryService implements IExtensionGalleryService { } const message = getErrorMessage(err); - type GalleryServiceRequestErrorClassification = { - url: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - cdn: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; - message: { classification: 'CallstackOrException', purpose: 'FeatureInsight' }; - }; - type GalleryServiceRequestErrorEvent = { - url: string; - cdn: boolean; - message: string; - }; - this.telemetryService.publicLog2('galleryService:requestError', { url, cdn: true, message }); type GalleryServiceCDNFallbackClassification = { url: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; message: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; @@ -845,15 +834,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { this.telemetryService.publicLog2('galleryService:cdnFallback', { url, message }); const fallbackOptions = assign({}, options, { url: fallbackUrl }); - return this.requestService.request(fallbackOptions, token).then(undefined, err => { - if (isPromiseCanceledError(err)) { - return Promise.reject(err); - } - - const message = getErrorMessage(err); - this.telemetryService.publicLog2('galleryService:requestError', { url: fallbackUrl, cdn: false, message }); - return Promise.reject(err); - }); + return this.requestService.request(fallbackOptions, token); }); }); } diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 157f7aae98..6c622ea14e 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -186,6 +186,7 @@ export interface DidUninstallExtensionEvent { error?: string; } +export const INSTALL_ERROR_NOT_SUPPORTED = 'notsupported'; export const INSTALL_ERROR_MALICIOUS = 'malicious'; export const INSTALL_ERROR_INCOMPATIBLE = 'incompatible'; diff --git a/src/vs/platform/extensionManagement/node/extensionLifecycle.ts b/src/vs/platform/extensionManagement/node/extensionLifecycle.ts index 1bf0c68798..01f8997409 100644 --- a/src/vs/platform/extensionManagement/node/extensionLifecycle.ts +++ b/src/vs/platform/extensionManagement/node/extensionLifecycle.ts @@ -98,11 +98,11 @@ export class ExtensionsLifecycle extends Disposable { // Catch all output coming from the process type Output = { data: string, format: string[] }; - extensionUninstallProcess.stdout.setEncoding('utf8'); - extensionUninstallProcess.stderr.setEncoding('utf8'); + extensionUninstallProcess.stdout!.setEncoding('utf8'); + extensionUninstallProcess.stderr!.setEncoding('utf8'); - const onStdout = Event.fromNodeEventEmitter(extensionUninstallProcess.stdout, 'data'); - const onStderr = Event.fromNodeEventEmitter(extensionUninstallProcess.stderr, 'data'); + const onStdout = Event.fromNodeEventEmitter(extensionUninstallProcess.stdout!, 'data'); + const onStderr = Event.fromNodeEventEmitter(extensionUninstallProcess.stderr!, 'data'); // Log output onStdout(data => this.logService.info(extension.identifier.id, extension.manifest.version, `post-${lifecycleType}`, data)); diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 95bf99c477..bb8babc467 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -153,7 +153,7 @@ export class ExtensionManagementService extends Disposable implements IExtension this.logService.trace('ExtensionManagementService#zip', extension.identifier.id); return this.collectFiles(extension) .then(files => zip(path.join(tmpdir(), generateUuid()), files)) - .then(path => URI.file(path)); + .then(path => URI.file(path)); } unzip(zipLocation: URI, type: ExtensionType): Promise { @@ -222,6 +222,16 @@ export class ExtensionManagementService extends Disposable implements IExtension } else if (semver.gt(existing.manifest.version, manifest.version)) { return this.uninstall(existing, true); } + } else { + // Remove the extension with same version if it is already uninstalled. + // Installing a VSIX extension shall replace the existing extension always. + return this.unsetUninstalledAndGetLocal(identifierWithVersion) + .then(existing => { + if (existing) { + return this.removeExtension(existing, 'existing').then(null, e => Promise.reject(new Error(nls.localize('restartCode', "Please restart Azure Data Studio before reinstalling {0}.", manifest.displayName || manifest.name)))); + } + return undefined; + }); } return undefined; }) @@ -390,7 +400,7 @@ export class ExtensionManagementService extends Disposable implements IExtension return this.setUninstalled(extension) .then(() => this.removeUninstalledExtension(extension) .then( - () => this.installFromGallery(galleryExtension), + () => this.installFromGallery(galleryExtension).then(), e => Promise.reject(new Error(nls.localize('removeError', "Error while removing the extension: {0}. Please Quit and Start VS Code before trying again.", toErrorMessage(e)))))); } return Promise.reject(new Error(nls.localize('Not a Marketplace extension', "Only Marketplace Extensions can be reinstalled"))); @@ -541,10 +551,10 @@ export class ExtensionManagementService extends Disposable implements IExtension .then(galleryResult => { const extensionsToInstall = galleryResult.firstPage; return Promise.all(extensionsToInstall.map(e => this.installFromGallery(e))) - .then(() => null, errors => this.rollback(extensionsToInstall).then(() => Promise.reject(errors), () => Promise.reject(errors))); + .then(undefined, errors => this.rollback(extensionsToInstall).then(() => Promise.reject(errors), () => Promise.reject(errors))); }); } - return null; + return undefined; // {{SQL CARBON EDIT}} strict-null-checks }); } } @@ -565,7 +575,7 @@ export class ExtensionManagementService extends Disposable implements IExtension .then(installed => { const extensionToUninstall = installed.filter(e => areSameExtensions(e.identifier, extension.identifier))[0]; if (extensionToUninstall) { - return this.checkForDependenciesAndUninstall(extensionToUninstall, installed).then(() => null, error => Promise.reject(this.joinErrors(error))); + return this.checkForDependenciesAndUninstall(extensionToUninstall, installed).then(undefined, error => Promise.reject(this.joinErrors(error))); } else { return Promise.reject(new Error(nls.localize('notInstalled', "Extension '{0}' is not installed.", extension.manifest.displayName || extension.manifest.name))); } diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index cf41128794..f1e3b860ed 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -103,6 +103,17 @@ export interface IWebviewEditor { }[]; } +export interface ICodeActionContributionAction { + readonly kind: string; + readonly title: string; + readonly description?: string; +} + +export interface ICodeActionContribution { + readonly languages: readonly string[]; + readonly actions: readonly ICodeActionContributionAction[]; +} + export interface IExtensionContributions { commands?: ICommand[]; configuration?: IConfiguration | IConfiguration[]; @@ -120,6 +131,7 @@ export interface IExtensionContributions { colors?: IColor[]; localizations?: ILocalization[]; readonly webviewEditors?: readonly IWebviewEditor[]; + readonly codeActions?: readonly ICodeActionContribution[]; } export type ExtensionKind = 'ui' | 'workspace' | 'web'; @@ -151,7 +163,7 @@ export interface IExtensionManifest { readonly activationEvents?: string[]; readonly extensionDependencies?: string[]; readonly extensionPack?: string[]; - readonly extensionKind?: ExtensionKind; + readonly extensionKind?: ExtensionKind | ExtensionKind[]; readonly contributes?: IExtensionContributions; readonly repository?: { url: string; }; readonly bugs?: { url: string; }; diff --git a/src/vs/platform/files/common/fileService.ts b/src/vs/platform/files/common/fileService.ts index 25f0036e31..f962be772c 100644 --- a/src/vs/platform/files/common/fileService.ts +++ b/src/vs/platform/files/common/fileService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, IDisposable, toDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; -import { IFileService, IResolveFileOptions, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata, IWatchOptions, IWriteFileOptions, IReadFileOptions, IFileStreamContent, IFileContent, ETAG_DISABLED } from 'vs/platform/files/common/files'; +import { IFileService, IResolveFileOptions, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata, IWatchOptions, IWriteFileOptions, IReadFileOptions, IFileStreamContent, IFileContent, ETAG_DISABLED, hasFileReadStreamCapability, IFileSystemProviderWithFileReadStreamCapability, ensureFileSystemProviderError } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { isAbsolutePath, dirname, basename, joinPath, isEqual, isEqualOrParent } from 'vs/base/common/resources'; @@ -13,10 +13,13 @@ import { TernarySearchTree } from 'vs/base/common/map'; import { isNonEmptyArray, coalesce } from 'vs/base/common/arrays'; import { getBaseLabel } from 'vs/base/common/labels'; import { ILogService } from 'vs/platform/log/common/log'; -import { VSBuffer, VSBufferReadable, readableToBuffer, bufferToReadable, streamToBuffer, bufferToStream, VSBufferReadableStream, writeableBufferStream, VSBufferWriteableStream, isVSBufferReadableStream } from 'vs/base/common/buffer'; +import { VSBuffer, VSBufferReadable, readableToBuffer, bufferToReadable, streamToBuffer, bufferToStream, VSBufferReadableStream } from 'vs/base/common/buffer'; +import { isReadableStream, transform, ReadableStreamEvents, consumeReadableWithLimit, consumeStreamWithLimit } from 'vs/base/common/stream'; import { Queue } from 'vs/base/common/async'; import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; import { Schemas } from 'vs/base/common/network'; +import { assign } from 'vs/base/common/objects'; +import { createReadStream } from 'vs/platform/files/common/io'; export class FileService extends Disposable implements IFileService { @@ -118,14 +121,24 @@ export class FileService extends Disposable implements IFileService { return provider; } - private async withReadWriteProvider(resource: URI): Promise { + private async withReadProvider(resource: URI): Promise { + const provider = await this.withProvider(resource); + + if (hasOpenReadWriteCloseCapability(provider) || hasReadWriteCapability(provider) || hasFileReadStreamCapability(provider)) { + return provider; + } + + throw new Error('Provider neither has FileReadWrite, FileReadStream nor FileOpenReadWriteClose capability which is needed for the read operation.'); + } + + private async withWriteProvider(resource: URI): Promise { const provider = await this.withProvider(resource); if (hasOpenReadWriteCloseCapability(provider) || hasReadWriteCapability(provider)) { return provider; } - throw new Error('Provider neither has FileReadWrite nor FileOpenReadWriteClose capability which is needed for the operation.'); + throw new Error('Provider neither has FileReadWrite nor FileOpenReadWriteClose capability which is needed for the write operation.'); } //#endregion @@ -154,7 +167,7 @@ export class FileService extends Disposable implements IFileService { } // Bubble up any other error as is - throw this.ensureError(error); + throw ensureFileSystemProviderError(error); } } @@ -196,16 +209,19 @@ export class FileService extends Disposable implements IFileService { }); } + private async toFileStat(provider: IFileSystemProvider, resource: URI, stat: IStat | { type: FileType } & Partial, siblings: number | undefined, resolveMetadata: boolean, recurse: (stat: IFileStat, siblings?: number) => boolean): Promise; + private async toFileStat(provider: IFileSystemProvider, resource: URI, stat: IStat, siblings: number | undefined, resolveMetadata: true, recurse: (stat: IFileStat, siblings?: number) => boolean): Promise; private async toFileStat(provider: IFileSystemProvider, resource: URI, stat: IStat | { type: FileType } & Partial, siblings: number | undefined, resolveMetadata: boolean, recurse: (stat: IFileStat, siblings?: number) => boolean): Promise { // convert to file stat const fileStat: IFileStat = { resource, name: getBaseLabel(resource), + isFile: (stat.type & FileType.File) !== 0, isDirectory: (stat.type & FileType.Directory) !== 0, isSymbolicLink: (stat.type & FileType.SymbolicLink) !== 0, - isReadonly: !!(provider.capabilities & FileSystemProviderCapabilities.Readonly), mtime: stat.mtime, + ctime: stat.ctime, size: stat.size, etag: etag({ mtime: stat.mtime, size: stat.size }) }; @@ -288,7 +304,7 @@ export class FileService extends Disposable implements IFileService { } async writeFile(resource: URI, bufferOrReadableOrStream: VSBuffer | VSBufferReadable | VSBufferReadableStream, options?: IWriteFileOptions): Promise { - const provider = this.throwIfFileSystemIsReadonly(await this.withReadWriteProvider(resource)); + const provider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(resource)); try { @@ -300,17 +316,29 @@ export class FileService extends Disposable implements IFileService { await this.mkdirp(provider, dirname(resource)); } - // write file: buffered - if (hasOpenReadWriteCloseCapability(provider)) { - await this.doWriteBuffered(provider, resource, bufferOrReadableOrStream instanceof VSBuffer ? bufferToReadable(bufferOrReadableOrStream) : bufferOrReadableOrStream); + // optimization: if the provider has unbuffered write capability and the data + // to write is a Readable, we consume up to 3 chunks and try to write the data + // unbuffered to reduce the overhead. If the Readable has more data to provide + // we continue to write buffered. + if (hasReadWriteCapability(provider) && !(bufferOrReadableOrStream instanceof VSBuffer)) { + if (isReadableStream(bufferOrReadableOrStream)) { + bufferOrReadableOrStream = await consumeStreamWithLimit(bufferOrReadableOrStream, data => VSBuffer.concat(data), 3); + } else { + bufferOrReadableOrStream = consumeReadableWithLimit(bufferOrReadableOrStream, data => VSBuffer.concat(data), 3); + } } - // write file: unbuffered - else { + // write file: unbuffered (only if data to write is a buffer, or the provider has no buffered write capability) + if (!hasOpenReadWriteCloseCapability(provider) || (hasReadWriteCapability(provider) && bufferOrReadableOrStream instanceof VSBuffer)) { await this.doWriteUnbuffered(provider, resource, bufferOrReadableOrStream); } + + // write file: buffered + else { + await this.doWriteBuffered(provider, resource, bufferOrReadableOrStream instanceof VSBuffer ? bufferToReadable(bufferOrReadableOrStream) : bufferOrReadableOrStream); + } } catch (error) { - throw new FileOperationError(localize('err.write', "Unable to write file ({0})", this.ensureError(error).toString()), toFileOperationResult(error), options); + throw new FileOperationError(localize('err.write', "Unable to write file ({0})", ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options); } return this.resolve(resource, { resolveMetadata: true }); @@ -333,7 +361,7 @@ export class FileService extends Disposable implements IFileService { // mtime and etag, we bail out to prevent dirty writing. // // First, we check for a mtime that is in the future before we do more checks. The assumption is - // that only the mtime is an indicator for a file that has changd on disk. + // that only the mtime is an indicator for a file that has changed on disk. // // Second, if the mtime has advanced, we compare the size of the file on disk with our previous // one using the etag() function. Relying only on the mtime check has prooven to produce false @@ -353,7 +381,16 @@ export class FileService extends Disposable implements IFileService { } async readFile(resource: URI, options?: IReadFileOptions): Promise { - const stream = await this.readFileStream(resource, options); + const provider = await this.withReadProvider(resource); + + const stream = await this.doReadAsFileStream(provider, resource, assign({ + // optimization: since we know that the caller does not + // care about buffering, we indicate this to the reader. + // this reduces all the overhead the buffered reading + // has (open, read, close) if the provider supports + // unbuffered reading. + preferUnbuffered: true + }, options || Object.create(null))); return { ...stream, @@ -362,7 +399,12 @@ export class FileService extends Disposable implements IFileService { } async readFileStream(resource: URI, options?: IReadFileOptions): Promise { - const provider = await this.withReadWriteProvider(resource); + const provider = await this.withReadProvider(resource); + + return this.doReadAsFileStream(provider, resource, options); + } + + private async doReadAsFileStream(provider: IFileSystemProviderWithFileReadWriteCapability | IFileSystemProviderWithOpenReadWriteCloseCapability | IFileSystemProviderWithFileReadStreamCapability, resource: URI, options?: IReadFileOptions & { preferUnbuffered?: boolean }): Promise { // install a cancellation token that gets cancelled // when any error occurs. this allows us to resolve @@ -389,14 +431,19 @@ export class FileService extends Disposable implements IFileService { let fileStreamPromise: Promise; - // read buffered - if (hasOpenReadWriteCloseCapability(provider)) { - fileStreamPromise = Promise.resolve(this.readFileBuffered(provider, resource, cancellableSource.token, options)); + // read unbuffered (only if either preferred, or the provider has no buffered read capability) + if (!(hasOpenReadWriteCloseCapability(provider) || hasFileReadStreamCapability(provider)) || (hasReadWriteCapability(provider) && options?.preferUnbuffered)) { + fileStreamPromise = this.readFileUnbuffered(provider, resource, options); } - // read unbuffered + // read streamed (always prefer over primitive buffered read) + else if (hasFileReadStreamCapability(provider)) { + fileStreamPromise = Promise.resolve(this.readFileStreamed(provider, resource, cancellableSource.token, options)); + } + + // read buffered else { - fileStreamPromise = this.readFileUnbuffered(provider, resource, options); + fileStreamPromise = Promise.resolve(this.readFileBuffered(provider, resource, cancellableSource.token, options)); } const [fileStat, fileStream] = await Promise.all([statPromise, fileStreamPromise]); @@ -406,74 +453,30 @@ export class FileService extends Disposable implements IFileService { value: fileStream }; } catch (error) { - throw new FileOperationError(localize('err.read', "Unable to read file ({0})", this.ensureError(error).toString()), toFileOperationResult(error), options); + throw new FileOperationError(localize('err.read', "Unable to read file ({0})", ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options); } } - private readFileBuffered(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, token: CancellationToken, options?: IReadFileOptions): VSBufferReadableStream { - const stream = writeableBufferStream(); + private readFileStreamed(provider: IFileSystemProviderWithFileReadStreamCapability, resource: URI, token: CancellationToken, options: IReadFileOptions = Object.create(null)): VSBufferReadableStream { + const fileStream = provider.readFileStream(resource, options, token); - // do not await reading but simply return - // the stream directly since it operates - // via events. finally end the stream and - // send through the possible error - let error: Error | undefined = undefined; - this.doReadFileBuffered(provider, resource, stream, token, options).then(undefined, err => error = err).finally(() => stream.end(error)); - - return stream; + return this.transformFileReadStream(fileStream, options); } - private async doReadFileBuffered(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, stream: VSBufferWriteableStream, token: CancellationToken, options?: IReadFileOptions): Promise { + private readFileBuffered(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, token: CancellationToken, options: IReadFileOptions = Object.create(null)): VSBufferReadableStream { + const fileStream = createReadStream(provider, resource, { + ...options, + bufferSize: this.BUFFER_SIZE + }, token); - // open handle through provider - const handle = await provider.open(resource, { create: false }); + return this.transformFileReadStream(fileStream, options); + } - try { - let totalBytesRead = 0; - let bytesRead = 0; - let allowedRemainingBytes = (options && typeof options.length === 'number') ? options.length : undefined; - - let buffer = VSBuffer.alloc(Math.min(this.BUFFER_SIZE, typeof allowedRemainingBytes === 'number' ? allowedRemainingBytes : this.BUFFER_SIZE)); - - let posInFile = options && typeof options.position === 'number' ? options.position : 0; - let posInBuffer = 0; - do { - // read from source (handle) at current position (pos) into buffer (buffer) at - // buffer position (posInBuffer) up to the size of the buffer (buffer.byteLength). - bytesRead = await provider.read(handle, posInFile, buffer.buffer, posInBuffer, buffer.byteLength - posInBuffer); - - posInFile += bytesRead; - posInBuffer += bytesRead; - totalBytesRead += bytesRead; - - if (typeof allowedRemainingBytes === 'number') { - allowedRemainingBytes -= bytesRead; - } - - // when buffer full, create a new one and emit it through stream - if (posInBuffer === buffer.byteLength) { - stream.write(buffer); - - buffer = VSBuffer.alloc(Math.min(this.BUFFER_SIZE, typeof allowedRemainingBytes === 'number' ? allowedRemainingBytes : this.BUFFER_SIZE)); - - posInBuffer = 0; - } - } while (bytesRead > 0 && (typeof allowedRemainingBytes !== 'number' || allowedRemainingBytes > 0) && this.throwIfCancelled(token) && this.throwIfTooLarge(totalBytesRead, options)); - - // wrap up with last buffer (also respect maxBytes if provided) - if (posInBuffer > 0) { - let lastChunkLength = posInBuffer; - if (typeof allowedRemainingBytes === 'number') { - lastChunkLength = Math.min(posInBuffer, allowedRemainingBytes); - } - - stream.write(buffer.slice(0, lastChunkLength)); - } - } catch (error) { - throw this.ensureError(error); - } finally { - await provider.close(handle); - } + private transformFileReadStream(stream: ReadableStreamEvents, options: IReadFileOptions): VSBufferReadableStream { + return transform(stream, { + data: data => data instanceof VSBuffer ? data : VSBuffer.wrap(data), + error: error => new FileOperationError(localize('err.read', "Unable to read file ({0})", ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options) + }, data => VSBuffer.concat(data)); } private async readFileUnbuffered(provider: IFileSystemProviderWithFileReadWriteCapability, resource: URI, options?: IReadFileOptions): Promise { @@ -489,43 +492,56 @@ export class FileService extends Disposable implements IFileService { buffer = buffer.slice(0, options.length); } + // Throw if file is too large to load + this.validateReadFileLimits(buffer.byteLength, options); + return bufferToStream(VSBuffer.wrap(buffer)); } private async validateReadFile(resource: URI, options?: IReadFileOptions): Promise { const stat = await this.resolve(resource, { resolveMetadata: true }); - // Return early if resource is a directory + // Throw if resource is a directory if (stat.isDirectory) { throw new FileOperationError(localize('fileIsDirectoryError', "Expected file {0} is actually a directory", this.resourceForError(resource)), FileOperationResult.FILE_IS_DIRECTORY, options); } - // Return early if file not modified since (unless disabled) + // Throw if file not modified since (unless disabled) if (options && typeof options.etag === 'string' && options.etag !== ETAG_DISABLED && options.etag === stat.etag) { throw new FileOperationError(localize('fileNotModifiedError', "File not modified since"), FileOperationResult.FILE_NOT_MODIFIED_SINCE, options); } - // Return early if file is too large to load - if (options?.limits) { - if (typeof options.limits.memory === 'number' && stat.size > options.limits.memory) { - throw new FileOperationError(localize('fileTooLargeForHeapError', "To open a file of this size, you need to restart and allow it to use more memory"), FileOperationResult.FILE_EXCEED_MEMORY_LIMIT); - } - - if (typeof options.limits.size === 'number' && stat.size > options.limits.size) { - throw new FileOperationError(localize('fileTooLargeError', "File is too large to open"), FileOperationResult.FILE_TOO_LARGE); - } - } + // Throw if file is too large to load + this.validateReadFileLimits(stat.size, options); return stat; } + private validateReadFileLimits(size: number, options?: IReadFileOptions): void { + if (options?.limits) { + let tooLargeErrorResult: FileOperationResult | undefined = undefined; + + if (typeof options.limits.memory === 'number' && size > options.limits.memory) { + tooLargeErrorResult = FileOperationResult.FILE_EXCEEDS_MEMORY_LIMIT; + } + + if (typeof options.limits.size === 'number' && size > options.limits.size) { + tooLargeErrorResult = FileOperationResult.FILE_TOO_LARGE; + } + + if (typeof tooLargeErrorResult === 'number') { + throw new FileOperationError(localize('fileTooLargeError', "File is too large to open"), tooLargeErrorResult); + } + } + } + //#endregion //#region Move/Copy/Delete/Create Folder async move(source: URI, target: URI, overwrite?: boolean): Promise { - const sourceProvider = this.throwIfFileSystemIsReadonly(await this.withReadWriteProvider(source)); - const targetProvider = this.throwIfFileSystemIsReadonly(await this.withReadWriteProvider(target)); + const sourceProvider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(source)); + const targetProvider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(target)); // move const mode = await this.doMoveCopy(sourceProvider, source, targetProvider, target, 'move', !!overwrite); @@ -538,8 +554,8 @@ export class FileService extends Disposable implements IFileService { } async copy(source: URI, target: URI, overwrite?: boolean): Promise { - const sourceProvider = await this.withReadWriteProvider(source); - const targetProvider = this.throwIfFileSystemIsReadonly(await this.withReadWriteProvider(target)); + const sourceProvider = await this.withReadProvider(source); + const targetProvider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(target)); // copy const mode = await this.doMoveCopy(sourceProvider, source, targetProvider, target, 'copy', !!overwrite); @@ -551,7 +567,7 @@ export class FileService extends Disposable implements IFileService { return fileStat; } - private async doMoveCopy(sourceProvider: IFileSystemProviderWithFileReadWriteCapability | IFileSystemProviderWithOpenReadWriteCloseCapability, source: URI, targetProvider: IFileSystemProviderWithFileReadWriteCapability | IFileSystemProviderWithOpenReadWriteCloseCapability, target: URI, mode: 'move' | 'copy', overwrite: boolean): Promise<'move' | 'copy'> { + private async doMoveCopy(sourceProvider: IFileSystemProvider, source: URI, targetProvider: IFileSystemProvider, target: URI, mode: 'move' | 'copy', overwrite: boolean): Promise<'move' | 'copy'> { if (source.toString() === target.toString()) { return mode; // simulate node.js behaviour here and do a no-op if paths match } @@ -666,7 +682,7 @@ export class FileService extends Disposable implements IFileService { } if (!isSameResourceWithDifferentPathCase && isEqualOrParent(target, source, !isPathCaseSensitive)) { - throw new Error(localize('unableToMoveCopyError2', "Unable to move/copy when source is parent of target")); + throw new Error(localize('unableToMoveCopyError2', "Unable to move/copy when source is parent of target.")); } } @@ -676,7 +692,7 @@ export class FileService extends Disposable implements IFileService { // Bail out if target exists and we are not about to overwrite if (!overwrite) { - throw new FileOperationError(localize('unableToMoveCopyError3', "Unable to move/copy. File already exists at destination."), FileOperationResult.FILE_MOVE_CONFLICT); + throw new FileOperationError(localize('unableToMoveCopyError3', "Unable to move/copy since a file already exists at destination."), FileOperationResult.FILE_MOVE_CONFLICT); } // Special case: if the target is a parent of the source, we cannot delete @@ -684,7 +700,7 @@ export class FileService extends Disposable implements IFileService { if (sourceProvider === targetProvider) { const isPathCaseSensitive = !!(sourceProvider.capabilities & FileSystemProviderCapabilities.PathCaseSensitive); if (isEqualOrParent(source, target, !isPathCaseSensitive)) { - throw new Error(localize('unableToMoveCopyError4', "Unable to move/copy. File would replace folder it is contained in.")); + throw new Error(localize('unableToMoveCopyError4', "Unable to move/copy since a file would replace the folder it is contained in.")); } } } @@ -871,13 +887,13 @@ export class FileService extends Disposable implements IFileService { // write into handle until all bytes from buffer have been written try { - if (isVSBufferReadableStream(readableOrStream)) { + if (isReadableStream(readableOrStream)) { await this.doWriteStreamBufferedQueued(provider, handle, readableOrStream); } else { await this.doWriteReadableBufferedQueued(provider, handle, readableOrStream); } } catch (error) { - throw this.ensureError(error); + throw ensureFileSystemProviderError(error); } finally { // close handle always @@ -919,7 +935,7 @@ export class FileService extends Disposable implements IFileService { let posInFile = 0; let chunk: VSBuffer | null; - while (chunk = readable.read()) { + while ((chunk = readable.read()) !== null) { await this.doWriteBuffer(provider, handle, chunk, chunk.byteLength, posInFile, 0); posInFile += chunk.byteLength; @@ -942,7 +958,7 @@ export class FileService extends Disposable implements IFileService { let buffer: VSBuffer; if (bufferOrReadableOrStream instanceof VSBuffer) { buffer = bufferOrReadableOrStream; - } else if (isVSBufferReadableStream(bufferOrReadableOrStream)) { + } else if (isReadableStream(bufferOrReadableOrStream)) { buffer = await streamToBuffer(bufferOrReadableOrStream); } else { buffer = readableToBuffer(bufferOrReadableOrStream); @@ -988,7 +1004,7 @@ export class FileService extends Disposable implements IFileService { } } while (bytesRead > 0); } catch (error) { - throw this.ensureError(error); + throw ensureFileSystemProviderError(error); } finally { await Promise.all([ typeof sourceHandle === 'number' ? sourceProvider.close(sourceHandle) : Promise.resolve(), @@ -1019,7 +1035,7 @@ export class FileService extends Disposable implements IFileService { const buffer = await sourceProvider.readFile(source); await this.doWriteBuffer(targetProvider, targetHandle, VSBuffer.wrap(buffer), buffer.byteLength, 0, 0); } catch (error) { - throw this.ensureError(error); + throw ensureFileSystemProviderError(error); } finally { await targetProvider.close(targetHandle); } @@ -1042,38 +1058,6 @@ export class FileService extends Disposable implements IFileService { return provider; } - private throwIfCancelled(token: CancellationToken): boolean { - if (token.isCancellationRequested) { - throw new Error('cancelled'); - } - - return true; - } - - private ensureError(error?: Error): Error { - if (!error) { - return new Error(localize('unknownError', "Unknown Error")); // https://github.com/Microsoft/vscode/issues/72798 - } - - return error; - } - - private throwIfTooLarge(totalBytesRead: number, options?: IReadFileOptions): boolean { - - // Return early if file is too large to load - if (options?.limits) { - if (typeof options.limits.memory === 'number' && totalBytesRead > options.limits.memory) { - throw new FileOperationError(localize('fileTooLargeForHeapError', "To open a file of this size, you need to restart and allow it to use more memory"), FileOperationResult.FILE_EXCEED_MEMORY_LIMIT); - } - - if (typeof options.limits.size === 'number' && totalBytesRead > options.limits.size) { - throw new FileOperationError(localize('fileTooLargeError', "File is too large to open"), FileOperationResult.FILE_TOO_LARGE); - } - } - - return true; - } - private resourceForError(resource: URI): string { if (resource.scheme === Schemas.file) { return resource.fsPath; diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 176f0799ba..23a2a5fb4b 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; import { sep } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import * as glob from 'vs/base/common/glob'; @@ -13,6 +14,8 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { isEqualOrParent, isEqual } from 'vs/base/common/resources'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer'; +import { ReadableStreamEvents } from 'vs/base/common/stream'; +import { CancellationToken } from 'vs/base/common/cancellation'; export const IFileService = createDecorator('fileService'); @@ -158,6 +161,29 @@ export interface FileOverwriteOptions { overwrite: boolean; } +export interface FileReadStreamOptions { + + /** + * Is an integer specifying where to begin reading from in the file. If position is undefined, + * data will be read from the current file position. + */ + readonly position?: number; + + /** + * Is an integer specifying how many bytes to read from the file. By default, all bytes + * will be read. + */ + readonly length?: number; + + /** + * If provided, the size of the file will be checked against the limits. + */ + limits?: { + readonly size?: number; + readonly memory?: number; + }; +} + export interface FileWriteOptions { overwrite: boolean; create: boolean; @@ -181,8 +207,17 @@ export enum FileType { export interface IStat { type: FileType; + + /** + * The last modification date represented as millis from unix epoch. + */ mtime: number; + + /** + * The creation date represented as millis from unix epoch. + */ ctime: number; + size: number; } @@ -194,6 +229,8 @@ export interface IWatchOptions { export const enum FileSystemProviderCapabilities { FileReadWrite = 1 << 1, FileOpenReadWriteClose = 1 << 2, + FileReadStream = 1 << 4, + FileFolderCopy = 1 << 3, PathCaseSensitive = 1 << 10, @@ -223,6 +260,8 @@ export interface IFileSystemProvider { readFile?(resource: URI): Promise; writeFile?(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise; + readFileStream?(resource: URI, opts: FileReadStreamOptions, token?: CancellationToken): ReadableStreamEvents; + open?(resource: URI, opts: FileOpenOptions): Promise; close?(fd: number): Promise; read?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise; @@ -257,11 +296,21 @@ export function hasOpenReadWriteCloseCapability(provider: IFileSystemProvider): return !!(provider.capabilities & FileSystemProviderCapabilities.FileOpenReadWriteClose); } +export interface IFileSystemProviderWithFileReadStreamCapability extends IFileSystemProvider { + readFileStream(resource: URI, opts: FileReadStreamOptions, token?: CancellationToken): ReadableStreamEvents; +} + +export function hasFileReadStreamCapability(provider: IFileSystemProvider): provider is IFileSystemProviderWithFileReadStreamCapability { + return !!(provider.capabilities & FileSystemProviderCapabilities.FileReadStream); +} + export enum FileSystemProviderErrorCode { FileExists = 'EntryExists', FileNotFound = 'EntryNotFound', FileNotADirectory = 'EntryNotADirectory', FileIsADirectory = 'EntryIsADirectory', + FileExceedsMemoryLimit = 'EntryExceedsMemoryLimit', + FileTooLarge = 'EntryTooLarge', NoPermissions = 'NoPermissions', Unavailable = 'Unavailable', Unknown = 'Unknown' @@ -274,13 +323,21 @@ export class FileSystemProviderError extends Error { } } -export function createFileSystemProviderError(error: Error, code: FileSystemProviderErrorCode): FileSystemProviderError { +export function createFileSystemProviderError(error: Error | string, code: FileSystemProviderErrorCode): FileSystemProviderError { const providerError = new FileSystemProviderError(error.toString(), code); markAsFileSystemProviderError(providerError, code); return providerError; } +export function ensureFileSystemProviderError(error?: Error): Error { + if (!error) { + return createFileSystemProviderError(localize('unknownError', "Unknown Error"), FileSystemProviderErrorCode.Unknown); // https://github.com/Microsoft/vscode/issues/72798 + } + + return error; +} + export function markAsFileSystemProviderError(error: Error, code: FileSystemProviderErrorCode): Error { error.name = code ? `${code} (FileSystemError)` : `FileSystemError`; @@ -311,6 +368,8 @@ export function toFileSystemProviderErrorCode(error: Error | undefined | null): case FileSystemProviderErrorCode.FileIsADirectory: return FileSystemProviderErrorCode.FileIsADirectory; case FileSystemProviderErrorCode.FileNotADirectory: return FileSystemProviderErrorCode.FileNotADirectory; case FileSystemProviderErrorCode.FileNotFound: return FileSystemProviderErrorCode.FileNotFound; + case FileSystemProviderErrorCode.FileExceedsMemoryLimit: return FileSystemProviderErrorCode.FileExceedsMemoryLimit; + case FileSystemProviderErrorCode.FileTooLarge: return FileSystemProviderErrorCode.FileTooLarge; case FileSystemProviderErrorCode.NoPermissions: return FileSystemProviderErrorCode.NoPermissions; case FileSystemProviderErrorCode.Unavailable: return FileSystemProviderErrorCode.Unavailable; } @@ -335,7 +394,10 @@ export function toFileOperationResult(error: Error): FileOperationResult { return FileOperationResult.FILE_PERMISSION_DENIED; case FileSystemProviderErrorCode.FileExists: return FileOperationResult.FILE_MOVE_CONFLICT; - case FileSystemProviderErrorCode.FileNotADirectory: + case FileSystemProviderErrorCode.FileExceedsMemoryLimit: + return FileOperationResult.FILE_EXCEEDS_MEMORY_LIMIT; + case FileSystemProviderErrorCode.FileTooLarge: + return FileOperationResult.FILE_TOO_LARGE; default: return FileOperationResult.FILE_OTHER_ERROR; } @@ -530,14 +592,21 @@ interface IBaseStat { size?: number; /** - * The last modification date represented - * as millis from unix epoch. + * The last modification date represented as millis from unix epoch. * * The value may or may not be resolved as * it is optional. */ mtime?: number; + /** + * The creation date represented as millis from unix epoch. + * + * The value may or may not be resolved as + * it is optional. + */ + ctime?: number; + /** * A unique identifier thet represents the * current state of the file or directory. @@ -546,15 +615,11 @@ interface IBaseStat { * it is optional. */ etag?: string; - - /** - * The resource is readonly. - */ - isReadonly?: boolean; } export interface IBaseStatWithMetadata extends IBaseStat { mtime: number; + ctime: number; etag: string; size: number; } @@ -565,14 +630,19 @@ export interface IBaseStatWithMetadata extends IBaseStat { export interface IFileStat extends IBaseStat { /** - * The resource is a directory + * The resource is a file. + */ + isFile: boolean; + + /** + * The resource is a directory. */ isDirectory: boolean; /** * The resource is a symbolic link. */ - isSymbolicLink?: boolean; + isSymbolicLink: boolean; /** * The children of the file stat or undefined if none. @@ -582,6 +652,7 @@ export interface IFileStat extends IBaseStat { export interface IFileStatWithMetadata extends IFileStat, IBaseStatWithMetadata { mtime: number; + ctime: number; etag: string; size: number; children?: IFileStatWithMetadata[]; @@ -612,7 +683,7 @@ export interface IFileStreamContent extends IBaseStatWithMetadata { value: VSBufferReadableStream; } -export interface IReadFileOptions { +export interface IReadFileOptions extends FileReadStreamOptions { /** * The optional etag parameter allows to return early from resolving the resource if @@ -621,26 +692,6 @@ export interface IReadFileOptions { * It is the task of the caller to makes sure to handle this error case from the promise. */ readonly etag?: string; - - /** - * Is an integer specifying where to begin reading from in the file. If position is null, - * data will be read from the current file position. - */ - readonly position?: number; - - /** - * Is an integer specifying how many bytes to read from the file. By default, all bytes - * will be read. - */ - readonly length?: number; - - /** - * If provided, the size of the file will be checked against the limits. - */ - limits?: { - readonly size?: number; - readonly memory?: number; - }; } export interface IWriteFileOptions { @@ -670,7 +721,7 @@ export interface IResolveFileOptions { readonly resolveSingleChildDescendants?: boolean; /** - * Will resolve mtime, size and etag of files if enabled. This can have a negative impact + * Will resolve mtime, ctime, size and etag of files if enabled. This can have a negative impact * on performance and thus should only be used when these values are required. */ readonly resolveMetadata?: boolean; @@ -709,7 +760,7 @@ export const enum FileOperationResult { FILE_PERMISSION_DENIED, FILE_TOO_LARGE, FILE_INVALID_PATH, - FILE_EXCEED_MEMORY_LIMIT, + FILE_EXCEEDS_MEMORY_LIMIT, FILE_OTHER_ERROR } diff --git a/src/vs/platform/files/common/io.ts b/src/vs/platform/files/common/io.ts new file mode 100644 index 0000000000..fba64799b2 --- /dev/null +++ b/src/vs/platform/files/common/io.ts @@ -0,0 +1,114 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { URI } from 'vs/base/common/uri'; +import { VSBuffer, VSBufferWriteableStream, newWriteableBufferStream, VSBufferReadableStream } from 'vs/base/common/buffer'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IFileSystemProviderWithOpenReadWriteCloseCapability, FileReadStreamOptions, createFileSystemProviderError, FileSystemProviderErrorCode, ensureFileSystemProviderError } from 'vs/platform/files/common/files'; +import { canceled } from 'vs/base/common/errors'; + +export interface ICreateReadStreamOptions extends FileReadStreamOptions { + + /** + * The size of the buffer to use before sending to the stream. + */ + bufferSize: number; +} + +export function createReadStream(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, options: ICreateReadStreamOptions, token?: CancellationToken): VSBufferReadableStream { + const stream = newWriteableBufferStream(); + + // do not await reading but simply return the stream directly since it operates + // via events. finally end the stream and send through the possible error + let error: Error | undefined = undefined; + + doReadFileIntoStream(provider, resource, stream, options, token).then(undefined, err => error = err).finally(() => stream.end(error)); + + return stream; +} + +async function doReadFileIntoStream(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, stream: VSBufferWriteableStream, options: ICreateReadStreamOptions, token?: CancellationToken): Promise { + + // Check for cancellation + throwIfCancelled(token); + + // open handle through provider + const handle = await provider.open(resource, { create: false }); + + // Check for cancellation + throwIfCancelled(token); + + try { + let totalBytesRead = 0; + let bytesRead = 0; + let allowedRemainingBytes = (options && typeof options.length === 'number') ? options.length : undefined; + + let buffer = VSBuffer.alloc(Math.min(options.bufferSize, typeof allowedRemainingBytes === 'number' ? allowedRemainingBytes : options.bufferSize)); + + let posInFile = options && typeof options.position === 'number' ? options.position : 0; + let posInBuffer = 0; + do { + // read from source (handle) at current position (pos) into buffer (buffer) at + // buffer position (posInBuffer) up to the size of the buffer (buffer.byteLength). + bytesRead = await provider.read(handle, posInFile, buffer.buffer, posInBuffer, buffer.byteLength - posInBuffer); + + posInFile += bytesRead; + posInBuffer += bytesRead; + totalBytesRead += bytesRead; + + if (typeof allowedRemainingBytes === 'number') { + allowedRemainingBytes -= bytesRead; + } + + // when buffer full, create a new one and emit it through stream + if (posInBuffer === buffer.byteLength) { + stream.write(buffer); + + buffer = VSBuffer.alloc(Math.min(options.bufferSize, typeof allowedRemainingBytes === 'number' ? allowedRemainingBytes : options.bufferSize)); + + posInBuffer = 0; + } + } while (bytesRead > 0 && (typeof allowedRemainingBytes !== 'number' || allowedRemainingBytes > 0) && throwIfCancelled(token) && throwIfTooLarge(totalBytesRead, options)); + + // wrap up with last buffer (also respect maxBytes if provided) + if (posInBuffer > 0) { + let lastChunkLength = posInBuffer; + if (typeof allowedRemainingBytes === 'number') { + lastChunkLength = Math.min(posInBuffer, allowedRemainingBytes); + } + + stream.write(buffer.slice(0, lastChunkLength)); + } + } catch (error) { + throw ensureFileSystemProviderError(error); + } finally { + await provider.close(handle); + } +} + +function throwIfCancelled(token?: CancellationToken): boolean { + if (token && token.isCancellationRequested) { + throw canceled(); + } + + return true; +} + +function throwIfTooLarge(totalBytesRead: number, options: ICreateReadStreamOptions): boolean { + + // Return early if file is too large to load and we have configured limits + if (options?.limits) { + if (typeof options.limits.memory === 'number' && totalBytesRead > options.limits.memory) { + throw createFileSystemProviderError(localize('fileTooLargeForHeapError', "To open a file of this size, you need to restart and allow it to use more memory"), FileSystemProviderErrorCode.FileExceedsMemoryLimit); + } + + if (typeof options.limits.size === 'number' && totalBytesRead > options.limits.size) { + throw createFileSystemProviderError(localize('fileTooLargeError', "File is too large to open"), FileSystemProviderErrorCode.FileTooLarge); + } + } + + return true; +} diff --git a/src/vs/platform/files/node/diskFileSystemProvider.ts b/src/vs/platform/files/node/diskFileSystemProvider.ts index a0e8c1059c..338c095afa 100644 --- a/src/vs/platform/files/node/diskFileSystemProvider.ts +++ b/src/vs/platform/files/node/diskFileSystemProvider.ts @@ -6,7 +6,7 @@ import { mkdir, open, close, read, write, fdatasync, Dirent, Stats } from 'fs'; import { promisify } from 'util'; import { IDisposable, Disposable, toDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle'; -import { IFileSystemProvider, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileOpenOptions, FileSystemProviderErrorCode, createFileSystemProviderError, FileSystemProviderError } from 'vs/platform/files/common/files'; +import { FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileOpenOptions, FileSystemProviderErrorCode, createFileSystemProviderError, FileSystemProviderError, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, FileReadStreamOptions, IFileSystemProviderWithFileFolderCopyCapability } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { isLinux, isWindows } from 'vs/base/common/platform'; @@ -22,15 +22,30 @@ import { FileWatcher as UnixWatcherService } from 'vs/platform/files/node/watche import { FileWatcher as WindowsWatcherService } from 'vs/platform/files/node/watcher/win32/watcherService'; import { FileWatcher as NsfwWatcherService } from 'vs/platform/files/node/watcher/nsfw/watcherService'; import { FileWatcher as NodeJSWatcherService } from 'vs/platform/files/node/watcher/nodejs/watcherService'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { ReadableStreamEvents, transform } from 'vs/base/common/stream'; +import { createReadStream } from 'vs/platform/files/common/io'; export interface IWatcherOptions { pollingInterval?: number; usePolling: boolean; } -export class DiskFileSystemProvider extends Disposable implements IFileSystemProvider { +export interface IDiskFileSystemProviderOptions { + bufferSize?: number; + watcher?: IWatcherOptions; +} - constructor(private logService: ILogService, private watcherOptions?: IWatcherOptions) { +export class DiskFileSystemProvider extends Disposable implements + IFileSystemProviderWithFileReadWriteCapability, + IFileSystemProviderWithOpenReadWriteCloseCapability, + IFileSystemProviderWithFileReadStreamCapability, + IFileSystemProviderWithFileFolderCopyCapability { + + private readonly BUFFER_SIZE = this.options?.bufferSize || 64 * 1024; + + constructor(private logService: ILogService, private options?: IDiskFileSystemProviderOptions) { super(); } @@ -44,6 +59,7 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro this._capabilities = FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.FileOpenReadWriteClose | + FileSystemProviderCapabilities.FileReadStream | FileSystemProviderCapabilities.FileFolderCopy; if (isLinux) { @@ -64,7 +80,7 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro return { type: this.toType(stat, isSymbolicLink), - ctime: stat.ctime.getTime(), + ctime: stat.birthtime.getTime(), // intentionally not using ctime here, we want the creation time mtime: stat.mtime.getTime(), size: stat.size }; @@ -121,17 +137,32 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro } } + readFileStream(resource: URI, opts: FileReadStreamOptions, token?: CancellationToken): ReadableStreamEvents { + const fileStream = createReadStream(this, resource, { + ...opts, + bufferSize: this.BUFFER_SIZE + }, token); + + return transform(fileStream, { data: data => data.buffer }, data => VSBuffer.concat(data.map(data => VSBuffer.wrap(data))).buffer); + } + async writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise { let handle: number | undefined = undefined; try { const filePath = this.toFilePath(resource); - // Validate target - const fileExists = await exists(filePath); - if (fileExists && !opts.overwrite) { - throw createFileSystemProviderError(new Error(localize('fileExists', "File already exists")), FileSystemProviderErrorCode.FileExists); - } else if (!fileExists && !opts.create) { - throw createFileSystemProviderError(new Error(localize('fileNotExists', "File does not exist")), FileSystemProviderErrorCode.FileNotFound); + // Validate target unless { create: true, overwrite: true } + if (!opts.create || !opts.overwrite) { + const fileExists = await exists(filePath); + if (fileExists) { + if (!opts.overwrite) { + throw createFileSystemProviderError(localize('fileExists', "File already exists"), FileSystemProviderErrorCode.FileExists); + } + } else { + if (!opts.create) { + throw createFileSystemProviderError(localize('fileNotExists', "File does not exist"), FileSystemProviderErrorCode.FileNotFound); + } + } } // Open @@ -435,13 +466,13 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro } if (isSameResourceWithDifferentPathCase && mode === 'copy') { - throw createFileSystemProviderError(new Error('File cannot be copied to same path with different path case'), FileSystemProviderErrorCode.FileExists); + throw createFileSystemProviderError(localize('fileCopyErrorPathCase', "'File cannot be copied to same path with different path case"), FileSystemProviderErrorCode.FileExists); } // handle existing target (unless this is a case change) if (!isSameResourceWithDifferentPathCase && await exists(toFilePath)) { if (!overwrite) { - throw createFileSystemProviderError(new Error('File at target already exists'), FileSystemProviderErrorCode.FileExists); + throw createFileSystemProviderError(localize('fileCopyErrorExists', "File at target already exists"), FileSystemProviderErrorCode.FileExists); } // Delete target @@ -532,9 +563,9 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro let watcherOptions: IWatcherOptions | undefined = undefined; // requires a polling watcher - if (this.watcherOptions && this.watcherOptions.usePolling) { + if (this.options?.watcher?.usePolling) { watcherImpl = UnixWatcherService; - watcherOptions = this.watcherOptions; + watcherOptions = this.options?.watcher; } // Single Folder Watcher diff --git a/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts b/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts index 7ad5efcdf5..cf5324e194 100644 --- a/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts +++ b/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts @@ -8,7 +8,7 @@ import * as extpath from 'vs/base/common/extpath'; import * as path from 'vs/base/common/path'; import * as platform from 'vs/base/common/platform'; import { IDiskFileChange, normalizeFileChanges, ILogMessage } from 'vs/platform/files/node/watcher/watcher'; -import * as nsfw from 'nsfw'; +import * as nsfw from 'vscode-nsfw'; import { IWatcherService, IWatcherRequest, IWatcherOptions } from 'vs/platform/files/node/watcher/nsfw/watcher'; import { ThrottledDelayer } from 'vs/base/common/async'; import { FileChangeType } from 'vs/platform/files/common/files'; diff --git a/src/vs/platform/files/node/watcher/win32/csharpWatcherService.ts b/src/vs/platform/files/node/watcher/win32/csharpWatcherService.ts index 77d4251922..801f852077 100644 --- a/src/vs/platform/files/node/watcher/win32/csharpWatcherService.ts +++ b/src/vs/platform/files/node/watcher/win32/csharpWatcherService.ts @@ -55,7 +55,7 @@ export class OutOfProcessWin32FolderWatcher { const stdoutLineDecoder = new decoder.LineDecoder(); // Events over stdout - this.handle.stdout.on('data', (data: Buffer) => { + this.handle.stdout!.on('data', (data: Buffer) => { // Collect raw events from output const rawEvents: IDiskFileChange[] = []; @@ -99,7 +99,7 @@ export class OutOfProcessWin32FolderWatcher { // Errors this.handle.on('error', (error: Error) => this.onError(error)); - this.handle.stderr.on('data', (data: Buffer) => this.onError(data)); + this.handle.stderr!.on('data', (data: Buffer) => this.onError(data)); // Exit this.handle.on('exit', (code: number, signal: string) => this.onExit(code, signal)); diff --git a/src/vs/platform/files/test/node/diskFileService.test.ts b/src/vs/platform/files/test/node/diskFileService.test.ts index 4ef1b3d20b..9b0a27b91f 100644 --- a/src/vs/platform/files/test/node/diskFileService.test.ts +++ b/src/vs/platform/files/test/node/diskFileService.test.ts @@ -20,13 +20,14 @@ import { NullLogService } from 'vs/platform/log/common/log'; import { isLinux, isWindows } from 'vs/base/common/platform'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { isEqual } from 'vs/base/common/resources'; -import { VSBuffer, VSBufferReadable, toVSBufferReadableStream, VSBufferReadableStream, bufferToReadable, bufferToStream } from 'vs/base/common/buffer'; +import { VSBuffer, VSBufferReadable, streamToBufferReadableStream, VSBufferReadableStream, bufferToReadable, bufferToStream, streamToBuffer } from 'vs/base/common/buffer'; import { find } from 'vs/base/common/arrays'; function getByName(root: IFileStat, name: string): IFileStat | undefined { if (root.children === undefined) { return undefined; } + return find(root.children, child => child.name === name); } @@ -57,6 +58,7 @@ export class TestDiskFileSystemProvider extends DiskFileSystemProvider { totalBytesRead: number = 0; private invalidStatSize: boolean = false; + private smallStatSize: boolean = false; private _testCapabilities!: FileSystemProviderCapabilities; get capabilities(): FileSystemProviderCapabilities { @@ -64,6 +66,7 @@ export class TestDiskFileSystemProvider extends DiskFileSystemProvider { this._testCapabilities = FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.FileOpenReadWriteClose | + FileSystemProviderCapabilities.FileReadStream | FileSystemProviderCapabilities.FileFolderCopy; if (isLinux) { @@ -78,8 +81,12 @@ export class TestDiskFileSystemProvider extends DiskFileSystemProvider { this._testCapabilities = capabilities; } - setInvalidStatSize(disabled: boolean): void { - this.invalidStatSize = disabled; + setInvalidStatSize(enabled: boolean): void { + this.invalidStatSize = enabled; + } + + setSmallStatSize(enabled: boolean): void { + this.smallStatSize = enabled; } async stat(resource: URI): Promise { @@ -87,6 +94,8 @@ export class TestDiskFileSystemProvider extends DiskFileSystemProvider { if (this.invalidStatSize) { res.size = String(res.size) as any; // for https://github.com/Microsoft/vscode/issues/72909 + } else if (this.smallStatSize) { + res.size = 1; } return res; @@ -174,7 +183,7 @@ suite('Disk File Service', function () { assert.equal(event!.target!.isDirectory, true); }); - test('createFolder: creating multiple folders at once', async function () { + test('createFolder: creating multiple folders at once', async () => { let event: FileOperationEvent; disposables.add(service.onAfterOperation(e => event = e)); @@ -204,23 +213,35 @@ suite('Disk File Service', function () { assert.equal(exists, false); }); - test('resolve', async () => { - const resolved = await service.resolve(URI.file(testDir), { resolveTo: [URI.file(join(testDir, 'deep'))] }); - assert.equal(resolved.children!.length, 8); + test('resolve - file', async () => { + const resource = URI.file(getPathFromAmdModule(require, './fixtures/resolver/index.html')); + const resolved = await service.resolve(resource); - const deep = (getByName(resolved, 'deep')!); - assert.equal(deep.children!.length, 4); + assert.equal(resolved.name, 'index.html'); + assert.equal(resolved.isFile, true); + assert.equal(resolved.isDirectory, false); + assert.equal(resolved.isSymbolicLink, false); + assert.equal(resolved.resource.toString(), resource.toString()); + assert.equal(resolved.children, undefined); + assert.ok(resolved.mtime! > 0); + assert.ok(resolved.ctime! > 0); + assert.ok(resolved.size! > 0); }); test('resolve - directory', async () => { const testsElements = ['examples', 'other', 'index.html', 'site.css']; - const result = await service.resolve(URI.file(getPathFromAmdModule(require, './fixtures/resolver'))); + const resource = URI.file(getPathFromAmdModule(require, './fixtures/resolver')); + const result = await service.resolve(resource); assert.ok(result); + assert.equal(result.resource.toString(), resource.toString()); + assert.equal(result.name, 'resolver'); assert.ok(result.children); assert.ok(result.children!.length > 0); assert.ok(result!.isDirectory); + assert.ok(result.mtime! > 0); + assert.ok(result.ctime! > 0); assert.equal(result.children!.length, testsElements.length); assert.ok(result.children!.every(entry => { @@ -233,12 +254,18 @@ suite('Disk File Service', function () { assert.ok(basename(value.resource.fsPath)); if (['examples', 'other'].indexOf(basename(value.resource.fsPath)) >= 0) { assert.ok(value.isDirectory); + assert.equal(value.mtime, undefined); + assert.equal(value.ctime, undefined); } else if (basename(value.resource.fsPath) === 'index.html') { assert.ok(!value.isDirectory); assert.ok(!value.children); + assert.equal(value.mtime, undefined); + assert.equal(value.ctime, undefined); } else if (basename(value.resource.fsPath) === 'site.css') { assert.ok(!value.isDirectory); assert.ok(!value.children); + assert.equal(value.mtime, undefined); + assert.equal(value.ctime, undefined); } else { assert.ok(!'Unexpected value ' + basename(value.resource.fsPath)); } @@ -251,9 +278,12 @@ suite('Disk File Service', function () { const result = await service.resolve(URI.file(getPathFromAmdModule(require, './fixtures/resolver')), { resolveMetadata: true }); assert.ok(result); + assert.equal(result.name, 'resolver'); assert.ok(result.children); assert.ok(result.children!.length > 0); assert.ok(result!.isDirectory); + assert.ok(result.mtime! > 0); + assert.ok(result.ctime! > 0); assert.equal(result.children!.length, testsElements.length); assert.ok(result.children!.every(entry => { @@ -268,18 +298,32 @@ suite('Disk File Service', function () { assert.ok(basename(value.resource.fsPath)); if (['examples', 'other'].indexOf(basename(value.resource.fsPath)) >= 0) { assert.ok(value.isDirectory); + assert.ok(value.mtime! > 0); + assert.ok(value.ctime! > 0); } else if (basename(value.resource.fsPath) === 'index.html') { assert.ok(!value.isDirectory); assert.ok(!value.children); + assert.ok(value.mtime! > 0); + assert.ok(value.ctime! > 0); } else if (basename(value.resource.fsPath) === 'site.css') { assert.ok(!value.isDirectory); assert.ok(!value.children); + assert.ok(value.mtime! > 0); + assert.ok(value.ctime! > 0); } else { assert.ok(!'Unexpected value ' + basename(value.resource.fsPath)); } }); }); + test('resolve - directory with resolveTo', async () => { + const resolved = await service.resolve(URI.file(testDir), { resolveTo: [URI.file(join(testDir, 'deep'))] }); + assert.equal(resolved.children!.length, 8); + + const deep = (getByName(resolved, 'deep')!); + assert.equal(deep.children!.length, 4); + }); + test('resolve - directory - resolveTo single directory', async () => { const resolverFixturesPath = getPathFromAmdModule(require, './fixtures/resolver'); const result = await service.resolve(URI.file(resolverFixturesPath), { resolveTo: [URI.file(join(resolverFixturesPath, 'other/deep'))] }); @@ -419,6 +463,7 @@ suite('Disk File Service', function () { await service.del(source.resource); assert.equal(existsSync(source.resource.fsPath), false); + assert.ok(event!); assert.equal(event!.resource.fsPath, resource.fsPath); assert.equal(event!.operation, FileOperation.DELETE); @@ -481,56 +526,56 @@ suite('Disk File Service', function () { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); setCapabilities(testProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - await testMoveAcrossProviders(); + return testMoveAcrossProviders(); }); test('move - across providers (unbuffered => unbuffered)', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); setCapabilities(testProvider, FileSystemProviderCapabilities.FileReadWrite); - await testMoveAcrossProviders(); + return testMoveAcrossProviders(); }); test('move - across providers (buffered => unbuffered)', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); setCapabilities(testProvider, FileSystemProviderCapabilities.FileReadWrite); - await testMoveAcrossProviders(); + return testMoveAcrossProviders(); }); test('move - across providers (unbuffered => buffered)', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); setCapabilities(testProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - await testMoveAcrossProviders(); + return testMoveAcrossProviders(); }); test('move - across providers - large (buffered => buffered)', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); setCapabilities(testProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - await testMoveAcrossProviders('lorem.txt'); + return testMoveAcrossProviders('lorem.txt'); }); test('move - across providers - large (unbuffered => unbuffered)', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); setCapabilities(testProvider, FileSystemProviderCapabilities.FileReadWrite); - await testMoveAcrossProviders('lorem.txt'); + return testMoveAcrossProviders('lorem.txt'); }); test('move - across providers - large (buffered => unbuffered)', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); setCapabilities(testProvider, FileSystemProviderCapabilities.FileReadWrite); - await testMoveAcrossProviders('lorem.txt'); + return testMoveAcrossProviders('lorem.txt'); }); test('move - across providers - large (unbuffered => buffered)', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); setCapabilities(testProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - await testMoveAcrossProviders('lorem.txt'); + return testMoveAcrossProviders('lorem.txt'); }); async function testMoveAcrossProviders(sourceFile = 'index.html'): Promise { @@ -596,28 +641,28 @@ suite('Disk File Service', function () { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); setCapabilities(testProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - await testMoveFolderAcrossProviders(); + return testMoveFolderAcrossProviders(); }); test('move - directory - across providers (unbuffered => unbuffered)', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); setCapabilities(testProvider, FileSystemProviderCapabilities.FileReadWrite); - await testMoveFolderAcrossProviders(); + return testMoveFolderAcrossProviders(); }); test('move - directory - across providers (buffered => unbuffered)', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); setCapabilities(testProvider, FileSystemProviderCapabilities.FileReadWrite); - await testMoveFolderAcrossProviders(); + return testMoveFolderAcrossProviders(); }); - test('move - directory - across providers (unbuffered => buffered)', async function () { + test('move - directory - across providers (unbuffered => buffered)', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); setCapabilities(testProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - await testMoveFolderAcrossProviders(); + return testMoveFolderAcrossProviders(); }); async function testMoveFolderAcrossProviders(): Promise { @@ -992,6 +1037,10 @@ suite('Disk File Service', function () { assert.equal(source.size, copied.size); }); + test('readFile - small file - default', () => { + return testReadFile(URI.file(join(testDir, 'small.txt'))); + }); + test('readFile - small file - buffered', () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); @@ -1016,6 +1065,22 @@ suite('Disk File Service', function () { return testReadFile(URI.file(join(testDir, 'small.txt'))); }); + test('readFile - small file - streamed', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadStream); + + return testReadFile(URI.file(join(testDir, 'small.txt'))); + }); + + test('readFile - small file - streamed / readonly', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadStream | FileSystemProviderCapabilities.Readonly); + + return testReadFile(URI.file(join(testDir, 'small.txt'))); + }); + + test('readFile - large file - default', async () => { + return testReadFile(URI.file(join(testDir, 'lorem.txt'))); + }); + test('readFile - large file - buffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); @@ -1028,35 +1093,69 @@ suite('Disk File Service', function () { return testReadFile(URI.file(join(testDir, 'lorem.txt'))); }); + test('readFile - large file - streamed', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadStream); + + return testReadFile(URI.file(join(testDir, 'lorem.txt'))); + }); + async function testReadFile(resource: URI): Promise { const content = await service.readFile(resource); assert.equal(content.value.toString(), readFileSync(resource.fsPath)); } + test('readFileStream - small file - default', () => { + return testReadFileStream(URI.file(join(testDir, 'small.txt'))); + }); + + test('readFileStream - small file - buffered', () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); + + return testReadFileStream(URI.file(join(testDir, 'small.txt'))); + }); + + test('readFileStream - small file - unbuffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + + return testReadFileStream(URI.file(join(testDir, 'small.txt'))); + }); + + test('readFileStream - small file - streamed', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadStream); + + return testReadFileStream(URI.file(join(testDir, 'small.txt'))); + }); + + async function testReadFileStream(resource: URI): Promise { + const content = await service.readFileStream(resource); + + assert.equal((await streamToBuffer(content.value)).toString(), readFileSync(resource.fsPath)); + } + + test('readFile - Files are intermingled #38331 - default', async () => { + return testFilesNotIntermingled(); + }); + test('readFile - Files are intermingled #38331 - buffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - let resource1 = URI.file(join(testDir, 'lorem.txt')); - let resource2 = URI.file(join(testDir, 'some_utf16le.css')); - - // load in sequence and keep data - const value1 = await service.readFile(resource1); - const value2 = await service.readFile(resource2); - - // load in parallel in expect the same result - const result = await Promise.all([ - service.readFile(resource1), - service.readFile(resource2) - ]); - - assert.equal(result[0].value.toString(), value1.value.toString()); - assert.equal(result[1].value.toString(), value2.value.toString()); + return testFilesNotIntermingled(); }); test('readFile - Files are intermingled #38331 - unbuffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + return testFilesNotIntermingled(); + }); + + test('readFile - Files are intermingled #38331 - streamed', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadStream); + + return testFilesNotIntermingled(); + }); + + async function testFilesNotIntermingled() { let resource1 = URI.file(join(testDir, 'lorem.txt')); let resource2 = URI.file(join(testDir, 'some_utf16le.css')); @@ -1072,108 +1171,149 @@ suite('Disk File Service', function () { assert.equal(result[0].value.toString(), value1.value.toString()); assert.equal(result[1].value.toString(), value2.value.toString()); + } + + test('readFile - from position (ASCII) - default', async () => { + return testReadFileFromPositionAscii(); }); test('readFile - from position (ASCII) - buffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - const resource = URI.file(join(testDir, 'small.txt')); - - const contents = await service.readFile(resource, { position: 6 }); - - assert.equal(contents.value.toString(), 'File'); - }); - - test('readFile - from position (with umlaut) - buffered', async () => { - setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - - const resource = URI.file(join(testDir, 'small_umlaut.txt')); - - const contents = await service.readFile(resource, { position: Buffer.from('Small File with Ü').length }); - - assert.equal(contents.value.toString(), 'mlaut'); + return testReadFileFromPositionAscii(); }); test('readFile - from position (ASCII) - unbuffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + return testReadFileFromPositionAscii(); + }); + + test('readFile - from position (ASCII) - streamed', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadStream); + + return testReadFileFromPositionAscii(); + }); + + async function testReadFileFromPositionAscii() { const resource = URI.file(join(testDir, 'small.txt')); const contents = await service.readFile(resource, { position: 6 }); assert.equal(contents.value.toString(), 'File'); + } + + test('readFile - from position (with umlaut) - default', async () => { + return testReadFileFromPositionUmlaut(); + }); + + test('readFile - from position (with umlaut) - buffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); + + return testReadFileFromPositionUmlaut(); }); test('readFile - from position (with umlaut) - unbuffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + return testReadFileFromPositionUmlaut(); + }); + + test('readFile - from position (with umlaut) - streamed', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadStream); + + return testReadFileFromPositionUmlaut(); + }); + + async function testReadFileFromPositionUmlaut() { const resource = URI.file(join(testDir, 'small_umlaut.txt')); const contents = await service.readFile(resource, { position: Buffer.from('Small File with Ü').length }); assert.equal(contents.value.toString(), 'mlaut'); - }); + } + test('readFile - 3 bytes (ASCII) - default', async () => { + return testReadThreeBytesFromFile(); + }); test('readFile - 3 bytes (ASCII) - buffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - const resource = URI.file(join(testDir, 'small.txt')); - - const contents = await service.readFile(resource, { length: 3 }); - - assert.equal(contents.value.toString(), 'Sma'); + return testReadThreeBytesFromFile(); }); test('readFile - 3 bytes (ASCII) - unbuffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + return testReadThreeBytesFromFile(); + }); + + test('readFile - 3 bytes (ASCII) - streamed', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadStream); + + return testReadThreeBytesFromFile(); + }); + + async function testReadThreeBytesFromFile() { const resource = URI.file(join(testDir, 'small.txt')); const contents = await service.readFile(resource, { length: 3 }); assert.equal(contents.value.toString(), 'Sma'); + } + + test('readFile - 20000 bytes (large) - default', async () => { + return readLargeFileWithLength(20000); }); test('readFile - 20000 bytes (large) - buffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - const resource = URI.file(join(testDir, 'lorem.txt')); - - const contents = await service.readFile(resource, { length: 20000 }); - - assert.equal(contents.value.byteLength, 20000); + return readLargeFileWithLength(20000); }); test('readFile - 20000 bytes (large) - unbuffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); - const resource = URI.file(join(testDir, 'lorem.txt')); + return readLargeFileWithLength(20000); + }); - const contents = await service.readFile(resource, { length: 20000 }); + test('readFile - 20000 bytes (large) - streamed', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadStream); - assert.equal(contents.value.byteLength, 20000); + return readLargeFileWithLength(20000); + }); + + test('readFile - 80000 bytes (large) - default', async () => { + return readLargeFileWithLength(80000); }); test('readFile - 80000 bytes (large) - buffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - const resource = URI.file(join(testDir, 'lorem.txt')); - - const contents = await service.readFile(resource, { length: 80000 }); - - assert.equal(contents.value.byteLength, 80000); + return readLargeFileWithLength(80000); }); test('readFile - 80000 bytes (large) - unbuffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + return readLargeFileWithLength(80000); + }); + + test('readFile - 80000 bytes (large) - streamed', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadStream); + + return readLargeFileWithLength(80000); + }); + + async function readLargeFileWithLength(length: number) { const resource = URI.file(join(testDir, 'lorem.txt')); - const contents = await service.readFile(resource, { length: 80000 }); + const contents = await service.readFile(resource, { length }); - assert.equal(contents.value.byteLength, 80000); - }); + assert.equal(contents.value.byteLength, length); + } test('readFile - FILE_IS_DIRECTORY', async () => { const resource = URI.file(join(testDir, 'deep')); @@ -1203,9 +1343,29 @@ suite('Disk File Service', function () { assert.equal(error!.fileOperationResult, FileOperationResult.FILE_NOT_FOUND); }); + test('readFile - FILE_NOT_MODIFIED_SINCE - default', async () => { + return testNotModifiedSince(); + }); + test('readFile - FILE_NOT_MODIFIED_SINCE - buffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); + return testNotModifiedSince(); + }); + + test('readFile - FILE_NOT_MODIFIED_SINCE - unbuffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + + return testNotModifiedSince(); + }); + + test('readFile - FILE_NOT_MODIFIED_SINCE - streamed', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadStream); + + return testNotModifiedSince(); + }); + + async function testNotModifiedSince() { const resource = URI.file(join(testDir, 'index.html')); const contents = await service.readFile(resource); @@ -1221,10 +1381,9 @@ suite('Disk File Service', function () { assert.ok(error); assert.equal(error!.fileOperationResult, FileOperationResult.FILE_NOT_MODIFIED_SINCE); assert.equal(fileProvider.totalBytesRead, 0); - }); + } test('readFile - FILE_NOT_MODIFIED_SINCE does not fire wrongly - https://github.com/Microsoft/vscode/issues/72909', async () => { - setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); fileProvider.setInvalidStatSize(true); const resource = URI.file(join(testDir, 'index.html')); @@ -1241,45 +1400,37 @@ suite('Disk File Service', function () { assert.ok(!error); }); - test('readFile - FILE_NOT_MODIFIED_SINCE - unbuffered', async () => { - setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); - - const resource = URI.file(join(testDir, 'index.html')); - - const contents = await service.readFile(resource); - fileProvider.totalBytesRead = 0; - - let error: FileOperationError | undefined = undefined; - try { - await service.readFile(resource, { etag: contents.etag }); - } catch (err) { - error = err; - } - - assert.ok(error); - assert.equal(error!.fileOperationResult, FileOperationResult.FILE_NOT_MODIFIED_SINCE); - assert.equal(fileProvider.totalBytesRead, 0); + test('readFile - FILE_EXCEEDS_MEMORY_LIMIT - default', async () => { + return testFileExceedsMemoryLimit(); }); - test('readFile - FILE_EXCEED_MEMORY_LIMIT - buffered', async () => { + test('readFile - FILE_EXCEEDS_MEMORY_LIMIT - buffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - const resource = URI.file(join(testDir, 'index.html')); - - let error: FileOperationError | undefined = undefined; - try { - await service.readFile(resource, { limits: { memory: 10 } }); - } catch (err) { - error = err; - } - - assert.ok(error); - assert.equal(error!.fileOperationResult, FileOperationResult.FILE_EXCEED_MEMORY_LIMIT); + return testFileExceedsMemoryLimit(); }); - test('readFile - FILE_EXCEED_MEMORY_LIMIT - unbuffered', async () => { + test('readFile - FILE_EXCEEDS_MEMORY_LIMIT - unbuffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + return testFileExceedsMemoryLimit(); + }); + + test('readFile - FILE_EXCEEDS_MEMORY_LIMIT - streamed', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadStream); + + return testFileExceedsMemoryLimit(); + }); + + async function testFileExceedsMemoryLimit() { + await doTestFileExceedsMemoryLimit(); + + // Also test when the stat size is wrong + fileProvider.setSmallStatSize(true); + return doTestFileExceedsMemoryLimit(false); + } + + async function doTestFileExceedsMemoryLimit(testTotalBytesRead = true) { const resource = URI.file(join(testDir, 'index.html')); let error: FileOperationError | undefined = undefined; @@ -1290,28 +1441,44 @@ suite('Disk File Service', function () { } assert.ok(error); - assert.equal(error!.fileOperationResult, FileOperationResult.FILE_EXCEED_MEMORY_LIMIT); + assert.equal(error!.fileOperationResult, FileOperationResult.FILE_EXCEEDS_MEMORY_LIMIT); + + if (testTotalBytesRead) { + assert.equal(fileProvider.totalBytesRead, 0); + } + } + + test('readFile - FILE_TOO_LARGE - default', async () => { + return testFileTooLarge(); }); test('readFile - FILE_TOO_LARGE - buffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - const resource = URI.file(join(testDir, 'index.html')); - - let error: FileOperationError | undefined = undefined; - try { - await service.readFile(resource, { limits: { size: 10 } }); - } catch (err) { - error = err; - } - - assert.ok(error); - assert.equal(error!.fileOperationResult, FileOperationResult.FILE_TOO_LARGE); + return testFileTooLarge(); }); test('readFile - FILE_TOO_LARGE - unbuffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + return testFileTooLarge(); + }); + + test('readFile - FILE_TOO_LARGE - streamed', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadStream); + + return testFileTooLarge(); + }); + + async function testFileTooLarge() { + await doTestFileExceedsMemoryLimit(); + + // Also test when the stat size is wrong + fileProvider.setSmallStatSize(true); + return doTestFileTooLarge(); + } + + async function doTestFileTooLarge() { const resource = URI.file(join(testDir, 'index.html')); let error: FileOperationError | undefined = undefined; @@ -1323,18 +1490,18 @@ suite('Disk File Service', function () { assert.ok(error); assert.equal(error!.fileOperationResult, FileOperationResult.FILE_TOO_LARGE); - }); + } test('createFile', async () => { - assertCreateFile(contents => VSBuffer.fromString(contents)); + return assertCreateFile(contents => VSBuffer.fromString(contents)); }); test('createFile (readable)', async () => { - assertCreateFile(contents => bufferToReadable(VSBuffer.fromString(contents))); + return assertCreateFile(contents => bufferToReadable(VSBuffer.fromString(contents))); }); test('createFile (stream)', async () => { - assertCreateFile(contents => bufferToStream(VSBuffer.fromString(contents))); + return assertCreateFile(contents => bufferToStream(VSBuffer.fromString(contents))); }); async function assertCreateFile(converter: (content: string) => VSBuffer | VSBufferReadable | VSBufferReadableStream): Promise { @@ -1390,57 +1557,23 @@ suite('Disk File Service', function () { assert.equal(event!.target!.resource.fsPath, resource.fsPath); }); + test('writeFile - default', async () => { + return testWriteFile(); + }); + test('writeFile - buffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - const resource = URI.file(join(testDir, 'small.txt')); - - const content = readFileSync(resource.fsPath); - assert.equal(content, 'Small File'); - - const newContent = 'Updates to the small file'; - await service.writeFile(resource, VSBuffer.fromString(newContent)); - - assert.equal(readFileSync(resource.fsPath), newContent); - }); - - test('writeFile (large file) - buffered', async () => { - setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - - const resource = URI.file(join(testDir, 'lorem.txt')); - - const content = readFileSync(resource.fsPath); - const newContent = content.toString() + content.toString(); - - const fileStat = await service.writeFile(resource, VSBuffer.fromString(newContent)); - assert.equal(fileStat.name, 'lorem.txt'); - - assert.equal(readFileSync(resource.fsPath), newContent); - }); - - test('writeFile - buffered - readonly throws', async () => { - setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose | FileSystemProviderCapabilities.Readonly); - - const resource = URI.file(join(testDir, 'small.txt')); - - const content = readFileSync(resource.fsPath); - assert.equal(content, 'Small File'); - - const newContent = 'Updates to the small file'; - - let error: Error; - try { - await service.writeFile(resource, VSBuffer.fromString(newContent)); - } catch (err) { - error = err; - } - - assert.ok(error!); + return testWriteFile(); }); test('writeFile - unbuffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + return testWriteFile(); + }); + + async function testWriteFile() { const resource = URI.file(join(testDir, 'small.txt')); const content = readFileSync(resource.fsPath); @@ -1450,11 +1583,25 @@ suite('Disk File Service', function () { await service.writeFile(resource, VSBuffer.fromString(newContent)); assert.equal(readFileSync(resource.fsPath), newContent); + } + + test('writeFile (large file) - default', async () => { + return testWriteFileLarge(); + }); + + test('writeFile (large file) - buffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); + + return testWriteFileLarge(); }); test('writeFile (large file) - unbuffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + return testWriteFileLarge(); + }); + + async function testWriteFileLarge() { const resource = URI.file(join(testDir, 'lorem.txt')); const content = readFileSync(resource.fsPath); @@ -1464,11 +1611,21 @@ suite('Disk File Service', function () { assert.equal(fileStat.name, 'lorem.txt'); assert.equal(readFileSync(resource.fsPath), newContent); + } + + test('writeFile - buffered - readonly throws', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose | FileSystemProviderCapabilities.Readonly); + + return testWriteFileReadonlyThrows(); }); test('writeFile - unbuffered - readonly throws', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.Readonly); + return testWriteFileReadonlyThrows(); + }); + + async function testWriteFileReadonlyThrows() { const resource = URI.file(join(testDir, 'small.txt')); const content = readFileSync(resource.fsPath); @@ -1484,7 +1641,7 @@ suite('Disk File Service', function () { } assert.ok(error!); - }); + } test('writeFile (large file) - multiple parallel writes queue up', async () => { const resource = URI.file(join(testDir, 'lorem.txt')); @@ -1501,37 +1658,23 @@ suite('Disk File Service', function () { assert.ok(['0', '00', '000', '0000', '00000'].some(offset => fileContent === offset + newContent)); }); + test('writeFile (readable) - default', async () => { + return testWriteFileReadable(); + }); + test('writeFile (readable) - buffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - const resource = URI.file(join(testDir, 'small.txt')); - - const content = readFileSync(resource.fsPath); - assert.equal(content, 'Small File'); - - const newContent = 'Updates to the small file'; - await service.writeFile(resource, toLineByLineReadable(newContent)); - - assert.equal(readFileSync(resource.fsPath), newContent); - }); - - test('writeFile (large file - readable) - buffered', async () => { - setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - - const resource = URI.file(join(testDir, 'lorem.txt')); - - const content = readFileSync(resource.fsPath); - const newContent = content.toString() + content.toString(); - - const fileStat = await service.writeFile(resource, toLineByLineReadable(newContent)); - assert.equal(fileStat.name, 'lorem.txt'); - - assert.equal(readFileSync(resource.fsPath), newContent); + return testWriteFileReadable(); }); test('writeFile (readable) - unbuffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + return testWriteFileReadable(); + }); + + async function testWriteFileReadable() { const resource = URI.file(join(testDir, 'small.txt')); const content = readFileSync(resource.fsPath); @@ -1541,11 +1684,25 @@ suite('Disk File Service', function () { await service.writeFile(resource, toLineByLineReadable(newContent)); assert.equal(readFileSync(resource.fsPath), newContent); + } + + test('writeFile (large file - readable) - default', async () => { + return testWriteFileLargeReadable(); + }); + + test('writeFile (large file - readable) - buffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); + + return testWriteFileLargeReadable(); }); test('writeFile (large file - readable) - unbuffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + return testWriteFileLargeReadable(); + }); + + async function testWriteFileLargeReadable() { const resource = URI.file(join(testDir, 'lorem.txt')); const content = readFileSync(resource.fsPath); @@ -1555,55 +1712,59 @@ suite('Disk File Service', function () { assert.equal(fileStat.name, 'lorem.txt'); assert.equal(readFileSync(resource.fsPath), newContent); + } + + test('writeFile (stream) - default', async () => { + return testWriteFileStream(); }); test('writeFile (stream) - buffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - const source = URI.file(join(testDir, 'small.txt')); - const target = URI.file(join(testDir, 'small-copy.txt')); - - const fileStat = await service.writeFile(target, toVSBufferReadableStream(createReadStream(source.fsPath))); - assert.equal(fileStat.name, 'small-copy.txt'); - - assert.equal(readFileSync(source.fsPath).toString(), readFileSync(target.fsPath).toString()); - }); - - test('writeFile (large file - stream) - buffered', async () => { - setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - - const source = URI.file(join(testDir, 'lorem.txt')); - const target = URI.file(join(testDir, 'lorem-copy.txt')); - - const fileStat = await service.writeFile(target, toVSBufferReadableStream(createReadStream(source.fsPath))); - assert.equal(fileStat.name, 'lorem-copy.txt'); - - assert.equal(readFileSync(source.fsPath).toString(), readFileSync(target.fsPath).toString()); + return testWriteFileStream(); }); test('writeFile (stream) - unbuffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + return testWriteFileStream(); + }); + + async function testWriteFileStream() { const source = URI.file(join(testDir, 'small.txt')); const target = URI.file(join(testDir, 'small-copy.txt')); - const fileStat = await service.writeFile(target, toVSBufferReadableStream(createReadStream(source.fsPath))); + const fileStat = await service.writeFile(target, streamToBufferReadableStream(createReadStream(source.fsPath))); assert.equal(fileStat.name, 'small-copy.txt'); assert.equal(readFileSync(source.fsPath).toString(), readFileSync(target.fsPath).toString()); + } + + test('writeFile (large file - stream) - default', async () => { + return testWriteFileLargeStream(); + }); + + test('writeFile (large file - stream) - buffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); + + return testWriteFileLargeStream(); }); test('writeFile (large file - stream) - unbuffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + return testWriteFileLargeStream(); + }); + + async function testWriteFileLargeStream() { const source = URI.file(join(testDir, 'lorem.txt')); const target = URI.file(join(testDir, 'lorem-copy.txt')); - const fileStat = await service.writeFile(target, toVSBufferReadableStream(createReadStream(source.fsPath))); + const fileStat = await service.writeFile(target, streamToBufferReadableStream(createReadStream(source.fsPath))); assert.equal(fileStat.name, 'lorem-copy.txt'); assert.equal(readFileSync(source.fsPath).toString(), readFileSync(target.fsPath).toString()); - }); + } test('writeFile (file is created including parents)', async () => { const resource = URI.file(join(testDir, 'other', 'newfile.txt')); diff --git a/src/vs/platform/files/test/node/fixtures/service/lorem.txt b/src/vs/platform/files/test/node/fixtures/service/lorem.txt index 9d348ac090..88c7aaee6b 100644 --- a/src/vs/platform/files/test/node/fixtures/service/lorem.txt +++ b/src/vs/platform/files/test/node/fixtures/service/lorem.txt @@ -280,4 +280,856 @@ Cras aliquet metus ut purus sagittis, vel venenatis ante consectetur. Pellentesq Donec vehicula mauris eget lacus mollis venenatis et sed nibh. Nam sodales ligula ipsum, scelerisque lacinia ligula sagittis in. Nam sit amet ipsum at erat malesuada congue. Aenean ut sollicitudin sapien. Etiam at tempor odio. Mauris vitae purus ut magna suscipit consequat. Vivamus quis sapien neque. Nulla vulputate sem sit amet massa pellentesque, eleifend tristique ligula egestas. Suspendisse tincidunt gravida mi, in pulvinar lectus egestas non. Aenean imperdiet ex sit amet nunc sollicitudin porta. Integer justo odio, ultricies at interdum in, rhoncus vitae sem. Sed porttitor arcu quis purus aliquet hendrerit. Praesent tempor tortor at dolor dictum pulvinar. Nulla aliquet nunc non ligula scelerisque accumsan. Donec nulla justo, congue vitae massa in, faucibus hendrerit magna. Donec non egestas purus. -Vivamus iaculis, lacus efficitur faucibus porta, dui nulla facilisis ligula, ut sodales odio nunc id sapien. Cras viverra auctor ipsum, dapibus mattis neque dictum sed. Sed convallis fermentum molestie. Nulla facilisi turpis duis. \ No newline at end of file +Vivamus iaculis, lacus efficitur faucibus porta, dui nulla facilisis ligula, ut sodales odio nunc id sapien. Cras viverra auctor ipsum, dapibu + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vulputate, ipsum quis interdum fermentum, lorem sem fermentum eros, vitae auctor neque lacus in nisi. Suspendisse potenti. Maecenas et scelerisque elit, in tincidunt quam. Sed eu tincidunt quam. Nullam justo ex, imperdiet a imperdiet et, fermentum sit amet eros. Aenean quis tempus sem. Pellentesque accumsan magna mi, ut mollis velit sagittis id. Etiam quis ipsum orci. Fusce purus ante, accumsan a lobortis at, venenatis eu nisl. Praesent ornare sed ante placerat accumsan. Suspendisse tempus dignissim fermentum. Nunc a leo ac lacus sodales iaculis eu vitae mi. In feugiat ante at massa finibus cursus. Suspendisse posuere fringilla ornare. Mauris elementum ac quam id convallis. Vestibulum non elit quis urna volutpat aliquam a eu lacus. + +Aliquam vestibulum imperdiet neque, suscipit aliquam elit ultrices bibendum. Suspendisse ultrices pulvinar cursus. Morbi risus nisi, cursus consequat rutrum vitae, molestie sed dui. Fusce posuere, augue quis dignissim aliquam, nisi ipsum porttitor ante, quis fringilla nisl turpis ac nisi. Nulla varius enim eget lorem vehicula gravida. Donec finibus malesuada leo nec semper. Proin ac enim eros. Vivamus non tincidunt nisi, vel tristique lorem. + +Nunc consequat ex id eros dignissim, id rutrum risus laoreet. Sed euismod non erat eu ultricies. Etiam vehicula gravida lacus ut porta. Vestibulum eu eros quis nunc aliquet luctus. Cras quis semper ligula. Nullam gravida vehicula quam sed porta. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In porta cursus vulputate. Quisque porta a nisi eget cursus. Aliquam risus leo, luctus ac magna in, efficitur cursus magna. In condimentum non mi id semper. Donec interdum ante eget commodo maximus. + +Vivamus sit amet vestibulum lectus. Fusce tincidunt mi sapien, dictum sollicitudin diam vulputate in. Integer fringilla consequat mollis. Cras aliquet consequat felis eget feugiat. Nunc tempor cursus arcu, vitae ornare nunc varius et. Vestibulum et tortor vel ante viverra porttitor. Nam at tortor ullamcorper, facilisis augue quis, tristique erat. Aenean ut euismod nibh. Quisque eu tincidunt est, nec euismod eros. + +Proin vehicula nibh non viverra egestas. Phasellus sem dolor, ultricies ac sagittis tristique, lacinia a purus. Vestibulum in ante eros. Pellentesque lacus nulla, tristique vitae interdum vel, malesuada ac diam. Aenean bibendum posuere turpis in accumsan. Ut est nulla, ullamcorper quis turpis at, viverra sagittis mauris. Sed in interdum purus. Praesent scelerisque nibh eget sem euismod, ut imperdiet mi venenatis. Vivamus pulvinar orci sed dapibus auctor. Nulla facilisi. Vestibulum tincidunt erat nec porttitor egestas. Mauris quis risus ante. Nulla facilisi. + +Aliquam ullamcorper ornare lobortis. Phasellus quis sem et ipsum mollis malesuada sed in ex. Ut aliquam ex eget metus finibus maximus. Proin suscipit mauris eu nibh lacinia, quis feugiat dui dapibus. Nam sed libero est. Aenean vulputate orci sit amet diam faucibus, eu sagittis sapien volutpat. Nam imperdiet felis turpis, at pretium odio pulvinar in. Sed vestibulum id eros nec ultricies. Sed quis aliquam tortor, vitae ullamcorper tellus. Donec egestas laoreet eros, id suscipit est rutrum nec. Sed auctor nulla eget metus aliquam, ut condimentum enim elementum. + +Aliquam suscipit non turpis sit amet bibendum. Fusce velit ligula, euismod et maximus at, luctus sed neque. Quisque pretium, nisl at ullamcorper finibus, lectus leo mattis sapien, vel euismod mauris diam ullamcorper ex. Nulla ut risus finibus, lacinia ligula at, auctor erat. Mauris consectetur sagittis ligula vel dapibus. Nullam libero libero, lobortis aliquam libero vel, venenatis ultricies leo. Duis porttitor, nibh congue fermentum posuere, erat libero pulvinar tortor, a pellentesque nunc ipsum vel sem. Nullam volutpat, eros sit amet facilisis consectetur, ipsum est vehicula massa, non vestibulum neque elit in mauris. Nunc hendrerit ipsum non enim bibendum, vitae rhoncus mi egestas. Etiam ullamcorper massa vel nisl sagittis, nec bibendum arcu malesuada. Aenean aliquet turpis justo, a consectetur arcu mollis convallis. Etiam tellus ipsum, ultricies vitae lorem et, ornare facilisis orci. Praesent fringilla justo urna, vel mollis neque pulvinar vestibulum. + +Donec non iaculis erat. Aliquam et mi sed nunc pulvinar ultricies in ut ipsum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Praesent feugiat lacus ac dignissim semper. Phasellus vitae quam nisi. Morbi vel diam ultricies risus lobortis ornare. Fusce maximus et ligula quis iaculis. Sed congue ex eget felis convallis, sit amet hendrerit elit tempor. Donec vehicula blandit ante eget commodo. Vestibulum eleifend diam at feugiat euismod. Etiam magna tellus, dignissim eget fermentum vel, vestibulum vitae mauris. Nam accumsan et erat id sagittis. Donec lacinia, odio ut ornare ultricies, dolor velit accumsan tortor, non finibus erat tellus quis ligula. Nunc quis metus in leo volutpat ornare vulputate eu nisl. + +Donec quis viverra ex. Nullam id feugiat mauris, eu fringilla nulla. Vestibulum id maximus elit. Cras elementum elit sed felis lobortis, eget sagittis nisi hendrerit. Vivamus vitae elit neque. Donec vulputate lacus ut libero ultrices accumsan. Vivamus accumsan nulla orci, in dignissim est laoreet sagittis. Proin at commodo velit. Curabitur in velit felis. Aliquam erat volutpat. Sed consequat, nulla et cursus sodales, nisi lacus mattis risus, quis eleifend erat ex nec turpis. Sed suscipit ultrices lorem in hendrerit. + +Morbi vitae lacus nec libero ornare tempus eu et diam. Suspendisse magna ipsum, fermentum vel odio quis, molestie aliquam urna. Fusce mollis turpis a eros accumsan porttitor. Pellentesque rhoncus dolor sit amet magna rutrum, et dapibus justo tempor. Sed purus nisi, maximus vitae fringilla eu, molestie nec urna. Fusce malesuada finibus pretium. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec sed aliquet eros. Pellentesque luctus diam ante, eget euismod nisl aliquet eu. Sed accumsan elit purus, tempor varius ligula tempus nec. Curabitur ornare leo suscipit suscipit fermentum. Morbi eget nulla est. Maecenas faucibus interdum tristique. + +Etiam ut elit eros. Nulla pharetra suscipit molestie. Nulla facilisis bibendum nisl non molestie. Curabitur turpis lectus, facilisis vel diam non, vulputate ultrices mauris. Aenean placerat aliquam convallis. Suspendisse sed scelerisque tellus. Vivamus lacinia neque eget risus cursus suscipit. Proin consequat dolor vel neque tempor, eu aliquam sem scelerisque. Duis non eros a purus malesuada pharetra non et nulla. Suspendisse potenti. Mauris libero eros, finibus vel nulla id, sagittis dapibus ante. Proin iaculis sed nunc et cursus. + +Quisque accumsan lorem sit amet lorem aliquet euismod. Curabitur fermentum rutrum posuere. Etiam ultricies, sem id pellentesque suscipit, urna magna lacinia eros, quis efficitur risus nisl at lacus. Nulla quis lacus tortor. Mauris placerat ex in dolor tincidunt, vel aliquet nisi pretium. Cras iaculis risus vitae pellentesque aliquet. Quisque a enim imperdiet, ullamcorper arcu vitae, rutrum risus. Nullam consectetur libero at felis fringilla, nec congue nibh dignissim. Nam et lobortis felis, eu pellentesque ligula. Aenean facilisis, ligula non imperdiet maximus, massa orci gravida sapien, at sagittis lacus nisl in lacus. Nulla quis mauris luctus, scelerisque felis consequat, tempus risus. Fusce auctor nisl non nulla luctus molestie. Maecenas sapien nisl, auctor non dolor et, iaculis scelerisque lorem. Suspendisse egestas enim aliquet, accumsan mauris nec, posuere quam. Nulla iaculis dui dui, sit amet vestibulum erat ultricies ac. + +Cras eget dolor erat. Proin at nisl ut leo consectetur ultricies vel ut arcu. Nulla in felis malesuada, ullamcorper tortor et, convallis massa. Nunc urna justo, ornare in nibh vitae, hendrerit condimentum libero. Etiam vitae libero in purus venenatis fringilla. Nullam velit nulla, consequat ut turpis non, egestas hendrerit nibh. Duis tortor turpis, interdum non ante ac, cursus accumsan lectus. Cras pharetra bibendum augue quis dictum. Sed euismod vestibulum justo. Proin porta lobortis purus. Duis venenatis diam tortor, sit amet condimentum eros rhoncus a. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc at magna nec diam lobortis efficitur sit amet ut lacus. Nulla quis orci tortor. Pellentesque tempus velit a odio finibus porta. + +Proin feugiat mauris a tellus scelerisque convallis. Maecenas libero magna, blandit nec ultrices id, congue vel mi. Aliquam lacinia, quam vel condimentum convallis, tortor turpis aliquam odio, sed blandit libero lacus et eros. In eleifend iaculis magna ac finibus. Praesent auctor facilisis tellus in congue. Sed molestie lobortis dictum. Nam quis dignissim augue, vel euismod lorem. Curabitur posuere dapibus luctus. Donec ultricies dictum lectus, quis blandit arcu commodo ac. Aenean tincidunt ligula in nunc imperdiet dignissim. Curabitur egestas sollicitudin sapien ut semper. Aenean nec dignissim lacus. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec aliquam dictum vehicula. Donec tortor est, volutpat non nisi nec, varius gravida ex. Nunc vel tristique nunc, vitae mattis nisi. Nunc nec luctus ex, vitae tincidunt lectus. In hac habitasse platea dictumst. Curabitur lobortis ex eget tincidunt tempor. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut a vehicula mi. + +Fusce eu libero finibus, interdum nulla a, placerat neque. Cras bibendum tempor libero nec feugiat. Cras ut sodales eros. Proin viverra, massa sit amet viverra egestas, neque nisl porta ex, sit amet hendrerit libero ligula vel urna. Mauris suscipit lacus id justo rhoncus suscipit. Etiam vel libero tellus. Maecenas non diam molestie, condimentum tellus a, bibendum enim. Mauris aliquet imperdiet tellus, eget sagittis dolor. Sed blandit in neque et luctus. Cras elementum sagittis nunc, vel mollis lorem euismod et. Donec posuere at lacus eget suscipit. + +Nulla nunc mi, pretium non massa vel, tempor semper magna. Nunc a leo pulvinar, tincidunt nunc at, dignissim mi. Aliquam erat volutpat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut viverra nulla a nisl finibus, at hendrerit ligula ullamcorper. Donec a lorem semper, tempor magna et, lobortis libero. Mauris id sapien leo. Donec dignissim, quam vitae porttitor dignissim, quam justo mattis dui, vel consequat odio elit quis orci. Etiam nec pretium neque, sit amet pretium orci. Duis ac tortor venenatis, feugiat purus non, feugiat nunc. Proin scelerisque nisl in turpis aliquam vulputate. + +Praesent sed est semper, fringilla lorem vitae, tincidunt nibh. Cras eros metus, auctor at mauris sit amet, sodales semper orci. Nunc a ornare ex. Curabitur bibendum arcu congue urna vulputate egestas. Vestibulum finibus id risus et accumsan. Aenean ut volutpat tellus. Aenean tincidunt malesuada urna sit amet vestibulum. Mauris vel tellus dictum, varius lacus quis, dictum arcu. + +Aenean quis metus eu erat feugiat cursus vel at ligula. Proin dapibus sodales urna, id euismod lectus tempus id. Pellentesque ex ligula, convallis et erat vel, vulputate condimentum nisl. Pellentesque pharetra nulla quis massa eleifend hendrerit. Praesent sed massa ipsum. Maecenas vehicula dolor massa, id sodales urna faucibus et. Mauris ac quam non massa tincidunt feugiat et at lacus. Fusce libero massa, vulputate vel scelerisque non, mollis in leo. Ut sit amet ultricies odio. Suspendisse in sapien viverra, facilisis purus ut, pretium libero. + +Vivamus tristique pharetra molestie. Nam a volutpat purus. Praesent consequat gravida nisi, ac blandit nisi suscipit ut. Quisque posuere, ligula a ultrices laoreet, ligula nunc vulputate libero, ut rutrum erat odio tincidunt justo. Sed vitae leo at leo fringilla bibendum. Vestibulum ut augue nec dolor auctor accumsan. Praesent laoreet id eros pulvinar commodo. Suspendisse potenti. Ut pharetra, mauris vitae blandit fringilla, odio ante tincidunt lorem, sit amet tempor metus diam ut turpis. + +Praesent quis egestas arcu. Nullam at porta arcu. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi vulputate ligula malesuada ligula luctus, vulputate tempus erat bibendum. Nunc ullamcorper non lectus at euismod. Etiam nibh felis, tincidunt a metus vel, pellentesque rhoncus neque. Etiam at diam in erat luctus interdum. Nunc vel ipsum pulvinar, sollicitudin lacus ac, tempus urna. Etiam vel lacinia sapien. Pellentesque sagittis velit vel mi efficitur iaculis. Integer euismod sit amet urna in sagittis. Cras eleifend ut nibh in facilisis. Donec et lacus vitae nunc placerat sodales. Nulla sed hendrerit ligula, at dapibus sapien. + +Praesent at iaculis ex. Curabitur est purus, cursus a faucibus quis, dictum id velit. Donec dignissim fringilla viverra. Nunc mauris felis, laoreet sit amet sagittis at, vestibulum in libero. Maecenas quis orci turpis. Quisque ut nibh vitae magna mollis consequat id at mauris. Aliquam eu odio eget nulla bibendum sodales. Quisque vel orci eleifend nisi pretium lacinia. Suspendisse eget risus eget mi volutpat molestie eget quis lacus. Duis nisi libero, tincidunt nec nulla id, faucibus cursus felis. + +Donec tempor eget risus pellentesque molestie. Phasellus porta neque vel arcu egestas, nec blandit velit fringilla. Nullam porta faucibus justo vitae laoreet. Pellentesque viverra id nunc eu varius. Nulla pulvinar lobortis iaculis. Etiam vestibulum odio nec velit tristique, a tristique nisi mattis. In sed fringilla orci, vitae efficitur odio. Quisque dui odio, ornare eget velit at, lacinia consequat libero. Quisque lectus nulla, aliquet eu leo in, porta rutrum diam. Donec nec mattis neque. Nam rutrum, odio ac eleifend bibendum, dolor arcu rutrum neque, eget porta elit tellus a lacus. Sed massa metus, sollicitudin et sapien eu, finibus tempus orci. Proin et sapien sit amet erat molestie interdum. In quis rutrum velit, faucibus ultrices tellus. + +Sed sagittis sed justo eget tincidunt. Maecenas ut leo sagittis, feugiat magna et, viverra velit. Maecenas ex arcu, feugiat at consequat vitae, auctor eu massa. Integer egestas, enim vitae maximus convallis, est lectus pretium mauris, ac posuere lectus nisl quis quam. Aliquam tempus laoreet mi, vitae dapibus dolor varius dapibus. Suspendisse potenti. Donec sit amet purus nec libero dapibus tristique. Pellentesque viverra bibendum ligula. Donec sed felis et ex lobortis laoreet. Phasellus a fringilla libero, vitae malesuada nulla. Pellentesque blandit mattis lacus, et blandit tortor laoreet consequat. Suspendisse libero nunc, viverra sed fermentum in, accumsan egestas arcu. Proin in placerat elit. Sed interdum imperdiet malesuada. Suspendisse aliquet quis mauris eget sollicitudin. + +Vivamus accumsan tellus non erat volutpat, quis dictum dolor feugiat. Praesent rutrum nunc ac est mollis cursus. Fusce semper volutpat dui ut egestas. Curabitur sit amet posuere massa. Cras tincidunt nulla et mi mollis imperdiet. Suspendisse scelerisque ex id sodales vulputate. In nunc augue, pharetra in placerat eu, mattis id tellus. Vivamus cursus efficitur vehicula. Nulla aliquet vehicula aliquet. + +Sed cursus tellus sed porta pulvinar. Sed vitae nisi neque. Nullam aliquet, lorem et efficitur scelerisque, arcu diam aliquam felis, sed pulvinar lorem odio et turpis. Praesent convallis pulvinar turpis eu iaculis. Aliquam nec gravida mi. Curabitur eu nibh tempor, blandit justo in, ultrices felis. Fusce placerat metus non mi sagittis rutrum. Morbi sed dui fringilla, sagittis mauris eget, imperdiet nunc. Phasellus hendrerit sem elit, id hendrerit libero auctor sit amet. Integer sodales elit sit amet consequat cursus. + +Nam semper est eget nunc mollis, in pellentesque lectus fringilla. In finibus vel diam id semper. Nunc mattis quis erat eu consectetur. In hac habitasse platea dictumst. Nullam et ipsum vestibulum ex pulvinar ultricies sit amet id velit. Aenean suscipit mi tortor, a lobortis magna viverra non. Nulla condimentum aliquet ante et ullamcorper. Pellentesque porttitor arcu a posuere tempus. Aenean lacus quam, imperdiet eu justo vitae, pretium efficitur ex. Duis id purus id magna rhoncus ultrices id eu risus. Nunc dignissim et libero id dictum. + +Quisque a tincidunt neque. Phasellus commodo mi sit amet tempor fringilla. Ut rhoncus, neque non porttitor elementum, libero nulla egestas augue, sed fringilla sapien felis ac velit. Phasellus viverra rhoncus mollis. Nam ullamcorper leo vel erat laoreet luctus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus semper a metus a cursus. Nulla sed orci egestas, efficitur purus ac, malesuada tellus. Aenean rutrum velit at tellus fermentum mollis. Aliquam eleifend euismod metus. + +In hac habitasse platea dictumst. Vestibulum volutpat neque vitae porttitor laoreet. Nam at tellus consequat, sodales quam in, pulvinar arcu. Maecenas varius convallis diam, ac lobortis tellus pellentesque quis. Maecenas eget augue massa. Nullam volutpat nibh ac justo rhoncus, ut iaculis tellus rutrum. Fusce efficitur efficitur libero quis condimentum. Curabitur congue neque non tincidunt tristique. Fusce eget tempor ex, at pellentesque odio. Praesent luctus dictum vestibulum. Etiam non orci nunc. Vivamus vitae laoreet purus, a lobortis velit. Curabitur tincidunt purus ac lectus elementum pellentesque. Quisque sed tincidunt est. + +Sed vel ultrices massa, vitae ultricies justo. Cras finibus mauris nec lacus tempus dignissim. Cras faucibus maximus velit, eget faucibus orci luctus vehicula. Nulla massa nunc, porta ac consequat eget, rhoncus non tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce sed maximus metus, vel imperdiet ipsum. Ut scelerisque lectus at blandit porttitor. Ut vulputate nunc pharetra, aliquet sapien ac, sollicitudin sapien. Aenean eget ante lorem. Nam accumsan venenatis tellus id dignissim. + +Curabitur fringilla, magna non maximus dapibus, nulla sapien vestibulum lectus, sit amet semper dolor neque vitae nisl. Nunc ultrices vehicula augue sed iaculis. Maecenas nec diam mollis, suscipit orci et, vestibulum ante. Pellentesque eu nisl tortor. Nunc eleifend, lacus quis volutpat volutpat, nisi mi molestie sem, quis mollis ipsum libero a tellus. Ut viverra dolor mattis convallis interdum. Sed tempus nisl at nunc scelerisque aliquet. Quisque tempor tempor lorem id feugiat. Nullam blandit lectus velit, vitae porta lacus tincidunt a. Vivamus sit amet arcu ultrices, tincidunt mi quis, viverra quam. Aenean fringilla libero elementum lorem semper, quis pulvinar eros gravida. Nullam sodales blandit mauris, sed fermentum velit fermentum sit amet. Donec malesuada mauris in augue sodales vulputate. Vestibulum gravida turpis id elit rhoncus dignissim. Integer non congue lorem, eu viverra orci. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec at dolor magna. Aliquam consectetur erat augue, id iaculis velit pharetra ac. Integer rutrum venenatis dignissim. Integer non sodales elit. Curabitur ut magna ut nibh feugiat aliquam ac ut risus. Morbi nibh quam, aliquam id placerat nec, vestibulum eget velit. Suspendisse at dignissim quam. Vivamus aliquet sem sed nisl volutpat, ut cursus orci ultrices. Aliquam ultrices lacinia enim, vitae aliquet neque. + +Quisque scelerisque finibus diam in mattis. Cras cursus auctor velit. Aliquam sem leo, fermentum et maximus et, molestie a libero. Aenean justo elit, rutrum a ornare id, egestas eget enim. Aenean auctor tristique erat. Curabitur condimentum libero lacus, nec consequat orci vestibulum sed. Fusce elit ligula, blandit vitae sapien vitae, dictum ultrices risus. Nam laoreet suscipit sapien, at interdum velit faucibus sit amet. Duis quis metus egestas lectus elementum posuere non nec libero. Aliquam a dolor bibendum, facilisis nunc a, maximus diam. Vestibulum suscipit tristique magna, non dignissim turpis sodales sed. Nunc ornare, velit ac facilisis fringilla, dolor mi consectetur lorem, vitae finibus erat justo suscipit urna. Maecenas sit amet eros erat. Nunc non arcu ornare, suscipit lorem eget, sodales mauris. Aliquam tincidunt, quam nec mollis lacinia, nisi orci fermentum libero, consequat eleifend lectus quam et sapien. Vestibulum a quam urna. + +Cras arcu leo, euismod ac ullamcorper at, faucibus sed massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus porttitor velit in enim interdum, non commodo metus ornare. Morbi vel lorem quis nisl luctus tristique quis vitae nisl. Suspendisse condimentum tortor enim, nec eleifend ipsum euismod et. Sed gravida quam ut tristique lacinia. Mauris eu interdum ipsum, ac ultrices odio. Nullam auctor tellus a risus porttitor vehicula. Nulla blandit euismod dictum. In pharetra, enim iaculis pulvinar interdum, dui nunc placerat nunc, sit amet pretium lectus nulla vitae quam. Phasellus quis enim sollicitudin, varius nulla id, ornare purus. Donec quam lacus, vestibulum quis nunc ac, mollis dictum nisi. Cras ut mollis elit. Maecenas ultrices ligula at risus faucibus scelerisque. Etiam vitae porttitor purus. Curabitur blandit lectus urna, ut hendrerit tortor feugiat ut. + +Phasellus fringilla, sapien pellentesque commodo pharetra, ante libero aliquam tellus, ut consectetur augue libero a sapien. Maecenas blandit luctus nisl eget aliquet. Maecenas vitae porta dolor, faucibus laoreet sapien. Suspendisse lobortis, ipsum sed vehicula aliquam, elit purus scelerisque dui, rutrum consectetur diam odio et lorem. In nec lacinia metus. Donec viverra libero est, vel bibendum erat condimentum quis. Donec feugiat purus leo. In laoreet vitae felis a porttitor. Mauris ullamcorper, lacus id condimentum suscipit, neque magna pellentesque arcu, eget cursus neque tellus id metus. Curabitur volutpat ac orci vel ultricies. + +Sed ut finibus erat. Sed diam purus, varius non tincidunt quis, ultrices sit amet ipsum. Donec et egestas nulla. Suspendisse placerat nisi at dui laoreet iaculis. Aliquam aliquet leo at augue faucibus molestie. Nullam lacus augue, hendrerit sed nisi eu, faucibus porta est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam ut leo aliquet sem fermentum rutrum quis ac justo. Integer placerat aliquam nisl ut sagittis. Proin erat orci, lobortis et sem eget, eleifend fringilla augue. Mauris varius laoreet arcu, sed tincidunt felis. Pellentesque venenatis lorem odio, id pulvinar velit molestie feugiat. Donec mattis lacus sed eleifend pulvinar. + +Sed condimentum ex in tincidunt hendrerit. Etiam eget risus lacinia, euismod nibh eu, pellentesque quam. Proin elit eros, convallis id mauris ac, bibendum ultrices lectus. Morbi venenatis, purus id fermentum consequat, nunc libero tincidunt ligula, non dictum ligula orci nec quam. Nulla nec ultrices lorem. Aenean maximus augue vel dictum pharetra. Etiam turpis urna, pellentesque quis malesuada eu, molestie faucibus felis. + +Vestibulum pharetra augue ut quam blandit congue in nec risus. Proin eu nibh eu dui eleifend porta vitae id lectus. Proin lacus nibh, lobortis sed ligula vitae, interdum lobortis erat. Suspendisse potenti. In sollicitudin quis sapien ut aliquet. Mauris ac nulla arcu. Fusce tristique justo quis lectus mollis, eu volutpat lectus finibus. Vivamus venenatis facilisis ex ut vestibulum. + +Etiam varius lobortis purus, in hendrerit elit tristique at. In tempus, augue vestibulum fermentum gravida, ligula tellus vulputate arcu, eu molestie ex sapien at purus. Vestibulum nec egestas metus. Duis pulvinar quam nec consequat interdum. Aenean non dapibus lacus. Aliquam sit amet aliquet nulla. Sed venenatis volutpat purus nec convallis. Phasellus aliquet semper sodales. Cras risus sapien, condimentum auctor urna a, pulvinar ornare nisl. Sed tincidunt felis elit, ut elementum est bibendum ac. Morbi interdum justo vel dui faucibus condimentum. + +Sed convallis eu sem at tincidunt. Nullam at auctor est, et ullamcorper ipsum. Pellentesque eget ante ante. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer euismod, sapien sed dapibus ornare, nibh enim maximus lacus, lacinia placerat urna quam quis felis. Morbi accumsan id nisl ut condimentum. Donec bibendum nisi est, sed volutpat lorem rhoncus in. Vestibulum ac lacinia nunc, eget volutpat magna. Integer aliquam pharetra ipsum, id placerat nunc volutpat quis. Etiam urna diam, rhoncus sit amet varius vel, euismod vel sem. Nullam vel molestie urna. Vivamus ornare erat at venenatis euismod. Suspendisse potenti. Fusce diam justo, tincidunt vel sem at, commodo faucibus nisl. Duis gravida efficitur diam, vel sagittis erat pulvinar ut. + +Quisque vel pharetra felis. Duis efficitur tortor dolor, vitae porttitor erat fermentum sed. Sed eu mi purus. Etiam dignissim tortor eu tempus molestie. Aenean pretium erat enim, in hendrerit ante hendrerit at. Sed ut risus vel nunc venenatis ultricies quis in lacus. Pellentesque vitae purus euismod, placerat risus non, ullamcorper augue. Quisque varius quam ligula, nec aliquet ex faucibus vitae. Quisque rhoncus sit amet leo tincidunt mattis. Cras id mauris eget purus pretium gravida sit amet eu augue. Aliquam dapibus odio augue, id lacinia velit pulvinar eu. + +Mauris fringilla, tellus nec pharetra iaculis, neque nisi ultrices massa, et tincidunt sem dui sed mi. Curabitur erat lorem, venenatis quis tempus lacinia, tempus sit amet nunc. Aliquam at neque ac metus commodo dictum quis vitae justo. Phasellus eget lacus tempus, blandit lorem vel, rutrum est. Aenean pharetra sem ut augue lobortis dignissim. Sed rhoncus at nulla id ultrices. Cras id condimentum felis. In suscipit luctus vulputate. Donec tincidunt lacus nec enim tincidunt sollicitudin ut quis enim. Nam at libero urna. Praesent sit amet massa vitae massa ullamcorper vehicula. + +Nullam bibendum augue ut turpis condimentum bibendum. Proin sit amet urna hendrerit, sodales tortor a, lobortis lectus. Integer sagittis velit turpis, et tincidunt nisi commodo eget. Duis tincidunt elit finibus accumsan cursus. Aenean dignissim scelerisque felis vel lacinia. Nunc lacinia maximus luctus. In hac habitasse platea dictumst. Vestibulum eget urna et enim tempor tempor. Nam feugiat, felis vel vestibulum tempus, orci justo viverra diam, id dapibus lorem justo in ligula. + +Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In ac pellentesque sem. Vestibulum lacinia magna dui, eu lacinia augue placerat et. Maecenas pulvinar congue est. Pellentesque commodo dui non pulvinar scelerisque. Etiam interdum est posuere sem bibendum, ac commodo magna dictum. Cras ipsum turpis, rhoncus nec posuere vitae, laoreet a arcu. Integer ac massa sit amet enim placerat lacinia sed ultrices arcu. Suspendisse sem nibh, luctus sit amet volutpat in, pellentesque eu metus. Ut gravida neque eget mi accumsan tempus. Nam sit amet aliquet nibh. + +Pellentesque a purus cursus nulla hendrerit congue quis et odio. Aenean hendrerit, leo ullamcorper sagittis hendrerit, erat dui molestie quam, sed condimentum lacus risus sed tellus. Morbi a dapibus lectus, ut feugiat ex. Phasellus pretium quam et sapien mollis, vel iaculis dui dignissim. Sed ullamcorper est turpis, a viverra lorem consectetur in. Aenean aliquet nibh non cursus rutrum. Suspendisse at tristique urna, id lobortis urna. In hac habitasse platea dictumst. Phasellus libero velit, rutrum sed tellus nec, dapibus tincidunt ligula. Quisque vel dui venenatis, consequat nisl ut, lacinia ipsum. Phasellus vitae magna pellentesque, lobortis est id, faucibus quam. Nam eleifend faucibus dui vel pellentesque. + +Etiam ut est non lacus tincidunt interdum. Maecenas sed massa urna. Quisque ut nibh tortor. Pellentesque felis ipsum, tempor finibus ipsum et, euismod pretium metus. Donec sit amet est ipsum. Quisque rhoncus justo non finibus elementum. Nulla nec lectus ac tortor placerat fringilla. Phasellus ac ultrices nunc, eu efficitur nisl. Nulla rhoncus nunc vitae ante dictum tincidunt. Nunc ultrices, massa sit amet malesuada dignissim, lectus lacus consequat sapien, non eleifend metus sem in eros. Phasellus mauris ante, dictum sit amet suscipit ac, rhoncus eget nisi. Phasellus at orci mollis, imperdiet neque eget, faucibus nulla. In at purus massa. Pellentesque quis rutrum lectus. + +Integer eu faucibus turpis, sit amet mollis massa. Vestibulum id nulla commodo, rutrum ipsum sed, semper ante. Phasellus condimentum orci nec nibh convallis, ac maximus orci ullamcorper. Maecenas vitae sollicitudin mi. Integer et finibus lectus, et condimentum ligula. Donec elementum tristique quam vitae dapibus. Morbi euismod ipsum in tristique ullamcorper. + +Duis fermentum non enim eu auctor. Quisque lacinia nibh vehicula nibh posuere, eu volutpat turpis facilisis. Ut ac faucibus nulla. Sed eleifend quis ex et pellentesque. Vestibulum sollicitudin in libero id fringilla. Phasellus dignissim purus consequat, condimentum dui sit amet, condimentum ante. Pellentesque ac consectetur massa, quis sagittis est. Nulla maximus tristique risus accumsan convallis. Curabitur imperdiet ac lacus a ultrices. Nulla facilisi. Sed quis quam quis lectus placerat lobortis vel sed turpis. In mollis dui id neque iaculis, ut aliquet tellus malesuada. Proin at luctus odio, vel blandit sapien. Praesent dignissim tortor vehicula libero fringilla, nec ultrices erat suscipit. Maecenas scelerisque purus in dapibus fermentum. + +Curabitur magna odio, mattis in tortor ut, porttitor congue est. Vestibulum mollis lacinia elementum. Fusce maximus erat vitae nunc rutrum lobortis. Integer ligula eros, auctor vel elit non, posuere luctus lacus. Maecenas quis auctor massa. Ut ipsum lacus, efficitur posuere euismod et, hendrerit efficitur est. Phasellus fringilla, quam id tincidunt pretium, nunc dui sollicitudin orci, eu dignissim nisi metus ut magna. Integer lobortis interdum dolor, non bibendum purus posuere et. Donec non lectus aliquet, pretium dolor eu, cursus massa. Sed ut dui sapien. In sed vestibulum massa. Pellentesque blandit, dui non sodales vehicula, orci metus mollis nunc, non pharetra ex tellus ac est. Mauris sagittis metus et fermentum pretium. Nulla facilisi. Quisque quis ante ut nulla placerat mattis ut quis nisi. + +Sed quis nulla ligula. Quisque dignissim ligula urna, sed aliquam purus semper at. Suspendisse potenti. Nunc massa lectus, pharetra vehicula arcu bibendum, imperdiet sodales ipsum. Nam ac sapien diam. Mauris iaculis fringilla mattis. Pellentesque tempus eros sit amet justo volutpat mollis. Phasellus ac turpis ipsum. Morbi vel ante elit. Aenean posuere quam consequat velit varius suscipit. Donec tempor quam ut nibh cursus efficitur. + +Morbi molestie dolor nec sem egestas suscipit. Etiam placerat pharetra lectus, et ullamcorper risus tristique in. Sed faucibus ullamcorper lectus eget fringilla. Maecenas malesuada hendrerit congue. Sed eget neque a erat placerat tincidunt. Aliquam vitae dignissim turpis. Fusce at placerat magna, a laoreet lectus. Maecenas a purus nec diam gravida fringilla. Nam malesuada euismod ante non vehicula. In faucibus bibendum leo, faucibus posuere nisl pretium quis. Fusce finibus bibendum finibus. Vestibulum eu justo maximus, hendrerit diam nec, dignissim sapien. Aenean dolor lacus, malesuada quis vestibulum ac, venenatis ac ipsum. Cras a est id nunc finibus facilisis. Cras lacinia neque et interdum vehicula. Suspendisse vulputate tellus elit, eget tempor dui finibus vel. + +Cras sed pretium odio. Proin hendrerit elementum felis in tincidunt. Nam sed turpis vel justo molestie accumsan condimentum eu nunc. Praesent lobortis euismod rhoncus. Nulla vitae euismod nibh, quis mattis mi. Fusce ultrices placerat porttitor. Duis sem ipsum, pellentesque sit amet odio a, molestie vulputate mauris. + +Duis blandit mollis ligula, sit amet mattis ligula finibus sit amet. Nunc a leo molestie, placerat diam et, vestibulum leo. Suspendisse facilisis neque purus, nec pellentesque ligula fermentum nec. Aenean malesuada mauris lorem, eu blandit arcu pulvinar quis. Duis laoreet urna lacus, non maximus arcu rutrum ultricies. Nulla augue dolor, suscipit eu mollis eu, aliquam condimentum diam. Ut semper orci luctus, pharetra turpis at, euismod mi. Nulla leo diam, finibus sit amet purus sed, maximus dictum lorem. Integer eu mi id turpis laoreet rhoncus. + +Integer a mauris tincidunt, finibus orci ut, pretium mauris. Nulla molestie nunc mi, id finibus lorem elementum sed. Proin quis laoreet ante. Integer nulla augue, commodo id molestie quis, rutrum ut turpis. Suspendisse et tortor turpis. Sed ut pharetra massa. Pellentesque elementum blandit sem, ut elementum tellus egestas a. Fusce eu purus nibh. + +Cras dignissim ligula scelerisque magna faucibus ullamcorper. Proin at condimentum risus, auctor malesuada quam. Nullam interdum interdum egestas. Nulla aliquam nisi vitae felis mollis dictum. Suspendisse dapibus consectetur tortor. Ut ut nisi non sem bibendum tincidunt. Vivamus suscipit leo quis gravida dignissim. + +Aliquam interdum, leo id vehicula mollis, eros eros rhoncus diam, non mollis ligula mi eu mauris. Sed ultrices vel velit sollicitudin tincidunt. Nunc auctor metus at ligula gravida elementum. Praesent interdum eu elit et mollis. Duis egestas quam sit amet velit dignissim consequat. Aliquam ac turpis nec nunc convallis sagittis. Fusce blandit, erat ac fringilla consectetur, dolor eros sodales leo, vel aliquet risus nisl et diam. Aliquam luctus felis vitae est eleifend euismod facilisis et lacus. Sed leo tellus, auctor eu arcu in, volutpat sagittis nisl. Pellentesque nisl ligula, placerat vel ullamcorper at, vulputate ac odio. Morbi ac faucibus orci, et tempus nulla. Proin rhoncus rutrum dolor, in venenatis mauris. Suspendisse a fermentum augue, non semper mi. Nunc eget pretium neque. Phasellus augue erat, feugiat ac aliquam congue, rutrum non sapien. Pellentesque ac diam gravida, consectetur felis at, ornare neque. + +Nullam interdum mattis sapien quis porttitor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Phasellus aliquet rutrum ipsum id euismod. Maecenas consectetur massa et mi porta viverra. Nunc quam nibh, dignissim vitae maximus et, ullamcorper nec lorem. Nunc vitae justo dapibus, luctus lacus vitae, pretium elit. Maecenas et efficitur leo. Curabitur mauris lectus, placerat quis vehicula vitae, auctor ut urna. Quisque rhoncus pharetra luctus. In hac habitasse platea dictumst. Integer sit amet metus nec eros malesuada aliquam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi hendrerit mi ac leo aliquam, sit amet ultricies libero commodo. Mauris dapibus purus metus, sit amet viverra nibh imperdiet et. Nullam porta nulla tellus, quis vehicula diam imperdiet non. Vivamus enim massa, bibendum in fermentum in, ultrices at ex. + +Suspendisse fermentum id nibh eget accumsan. Duis dapibus bibendum erat ut sollicitudin. Aliquam nec felis risus. Pellentesque rhoncus ligula id sem maximus mollis sed nec massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus ipsum ipsum, sodales sed enim id, convallis faucibus eros. Donec ultricies dictum tincidunt. Cras vitae nibh arcu. Pellentesque cursus, sapien nec consequat fermentum, ipsum ante suscipit dui, imperdiet hendrerit est nisl eu massa. Quisque vitae sem ligula. Aenean iaculis metus ut mauris interdum laoreet. Vivamus sed gravida dolor. + +Morbi nulla metus, porttitor sed eros sit amet, efficitur efficitur est. In vel nisl urna. Ut aliquet tellus at congue convallis. Phasellus imperdiet lobortis sollicitudin. Integer sodales, sem eu ultricies pharetra, erat erat porttitor odio, eget dapibus libero ipsum eget velit. Phasellus gravida nulla nisl, eu pharetra mi auctor vel. Sed blandit pharetra velit, ut egestas libero placerat non. Aliquam a interdum quam. Proin at tortor nec dui sollicitudin tempus sed vestibulum elit. Nunc non sollicitudin velit. + +Aenean consequat diam velit, sed rutrum tortor faucibus dictum. Quisque at semper augue. Duis ut est eget mi ornare bibendum id et ligula. Phasellus consequat tortor non leo pulvinar posuere. Proin vestibulum eleifend felis, in hendrerit tortor sollicitudin eu. Phasellus hendrerit, lacus vel laoreet interdum, dui tortor consequat justo, commodo ultricies arcu felis vitae enim. Vivamus eu sapien at leo suscipit rutrum eu at justo. Aenean et dolor a libero ullamcorper posuere. Integer laoreet placerat nisi in vulputate. Mauris laoreet eget risus sed cursus. Donec scelerisque neque a libero eleifend hendrerit. Nulla varius condimentum nunc sit amet fermentum. Aliquam lorem ex, varius nec mollis ut, ultrices in neque. Morbi sit amet porta leo. Integer iaculis fermentum lacus in vestibulum. + +Ut gravida, tellus ut maximus ultrices, erat est venenatis nisl, vitae pretium massa ex ac magna. Sed non purus eget ligula aliquet volutpat non quis arcu. Nam aliquam tincidunt risus, sit amet fringilla sapien vulputate ut. Mauris luctus suscipit pellentesque. Nunc porttitor dapibus ex quis tempus. Ut ullamcorper metus a eros vulputate, vitae viverra lectus convallis. Mauris semper imperdiet augue quis tincidunt. Integer porta pretium magna, sed cursus sem scelerisque sollicitudin. Nam efficitur, nibh pretium eleifend vestibulum, purus diam posuere sem, in egestas mauris augue sit amet urna. + +Vestibulum tincidunt euismod massa in congue. Duis interdum metus non laoreet fringilla. Donec at ligula congue, tincidunt nunc non, scelerisque nunc. Donec bibendum magna non est scelerisque feugiat at nec neque. Ut orci tortor, tempus eget massa non, dignissim faucibus dolor. Nam odio risus, accumsan pretium neque eget, accumsan dignissim dui. In ut neque auctor, scelerisque tellus sed, ullamcorper nisi. Suspendisse varius cursus quam at hendrerit. Vivamus elit libero, sagittis vitae sem ac, vulputate iaculis ligula. + +Sed lobortis laoreet purus sit amet rutrum. Pellentesque feugiat non leo vel lacinia. Quisque feugiat nisl a orci bibendum vestibulum. In et sollicitudin urna. Morbi a arcu ac metus faucibus tempus. Nam eu imperdiet sapien, suscipit mattis tortor. Aenean blandit ipsum nisi, a eleifend ligula euismod at. Integer tincidunt pharetra felis, mollis placerat mauris hendrerit at. Curabitur convallis, est sit amet luctus volutpat, massa lacus cursus augue, sed eleifend magna quam et risus. Aliquam lobortis tincidunt metus vitae porttitor. Suspendisse potenti. Aenean ullamcorper, neque id commodo luctus, nulla nunc lobortis quam, id dapibus neque dui nec mauris. Etiam quis lorem quis elit commodo ornare. Ut pharetra purus ultricies enim ultrices efficitur. Proin vehicula tincidunt molestie. Mauris et placerat sem. + +Aliquam erat volutpat. Suspendisse velit turpis, posuere ac lacus eu, lacinia laoreet velit. Sed interdum felis neque, id blandit sem malesuada sit amet. Ut sagittis justo erat, efficitur semper orci tempor sed. Donec enim massa, posuere varius lectus egestas, pellentesque posuere mi. Cras tincidunt ut libero sed mattis. Suspendisse quis magna et tellus posuere interdum vel at purus. Pellentesque fringilla tristique neque, id aliquet tellus ultricies non. Duis ut tellus vel odio lobortis vulputate. + +Integer at magna ac erat convallis vestibulum. Sed lobortis porttitor mauris. Fusce varius lorem et volutpat pulvinar. Aenean ac vulputate lectus, vitae consequat velit. Suspendisse ex dui, varius ut risus ut, dictum scelerisque sem. Vivamus urna orci, volutpat ut convallis ac, venenatis vitae urna. In hac habitasse platea dictumst. Etiam eu purus arcu. Aenean vulputate leo urna, vel tristique dui sagittis euismod. Suspendisse non tellus efficitur ante rhoncus volutpat at et sapien. + +Sed dapibus accumsan porttitor. Phasellus facilisis lectus finibus ligula dignissim, id pulvinar lectus feugiat. Nullam egestas commodo nisi posuere aliquet. Morbi sit amet tortor sagittis, rutrum dui nec, dapibus sapien. Sed posuere tortor tortor, interdum auctor magna varius vitae. Vestibulum id sagittis augue. Curabitur fermentum arcu sem, eu condimentum quam rutrum non. Phasellus rutrum nibh quis lectus rhoncus pretium. Curabitur dictum interdum elit. Vestibulum maximus sodales imperdiet. Mauris auctor nec purus sed venenatis. In in urna purus. + +Duis placerat molestie suscipit. Morbi a elit id purus efficitur consequat. Nunc ac commodo turpis. Etiam sit amet lacus a ipsum tempus venenatis sed vel nibh. Duis elementum aliquam mi sed tristique. Morbi ligula tortor, semper ac est vel, lobortis maximus erat. Curabitur ipsum felis, laoreet vel condimentum eget, ullamcorper sit amet mauris. Nulla facilisi. Nam at purus sed mi egestas placerat vitae vel magna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse at dignissim diam. Phasellus consectetur eget neque vel viverra. Donec sollicitudin mattis dolor vel malesuada. Vivamus vehicula leo neque, vitae fermentum leo posuere et. Praesent dui est, finibus sit amet tristique quis, pharetra vel nibh. + +Duis nulla leo, accumsan eu odio eget, sagittis semper orci. Quisque ullamcorper ligula quam, commodo porttitor mauris ullamcorper eu. Cras varius sagittis felis in aliquam. Duis sodales risus ac justo vehicula, nec mattis diam lacinia. Cras eget lectus ipsum. Ut commodo, enim vitae malesuada hendrerit, ex dolor egestas lectus, sit amet hendrerit metus diam nec est. Vestibulum tortor metus, lobortis sit amet ante eget, tempor molestie lacus. In molestie et urna et semper. Mauris mollis, sem non hendrerit condimentum, sapien nisi cursus est, non suscipit quam justo non metus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam enim est, porta ac feugiat vitae, rutrum in lorem. Duis vehicula tortor ut posuere maximus. + +Nullam vestibulum non tellus sed commodo. Quisque mattis elit sit amet sapien sollicitudin, ut condimentum nisl congue. Aenean sagittis massa vel elit faucibus fermentum. Donec tincidunt nisi nec nisl sodales pellentesque. Mauris congue congue ligula ut suscipit. Vivamus velit tortor, tempor et gravida eget, fermentum sit amet ante. Nullam fringilla, lorem at ultrices cursus, urna neque ornare dolor, eu lacinia orci enim sed nibh. Ut a ullamcorper lectus, id mattis purus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean maximus sollicitudin posuere. Nunc at augue lacus. Aenean efficitur leo sit amet lacinia efficitur. + +Quisque venenatis quam mi, in pharetra odio vulputate eu. In vel nisl pulvinar, pulvinar ligula ut, sodales risus. Sed efficitur lectus at vestibulum tincidunt. Vestibulum eu ullamcorper elit. Fusce vestibulum magna enim, et tempor lacus posuere vitae. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer leo elit, luctus nec mattis sit amet, sollicitudin in turpis. + +Proin convallis venenatis leo, vitae tristique erat iaculis nec. Nulla facilisi. Duis porttitor, sapien et bibendum vulputate, sem libero sodales lacus, non malesuada felis erat ut libero. Nam non felis semper, finibus est a, mattis mauris. Praesent nec eros quam. Nulla hendrerit, augue consectetur eleifend ultricies, purus mi condimentum nulla, eget dapibus est nunc sed libero. Nullam elementum dui erat, vitae luctus libero sollicitudin et. Nulla odio magna, placerat in augue eu, dapibus imperdiet odio. Suspendisse imperdiet metus sit amet rhoncus dapibus. Cras at enim et urna vehicula cursus eu a mauris. Integer magna ante, eleifend ac placerat vitae, porta at nisi. Cras eget malesuada orci. Curabitur nunc est, vulputate id viverra et, dignissim sed odio. Curabitur non mattis sem. Sed bibendum, turpis vitae vehicula faucibus, nunc quam ultricies lectus, vitae viverra felis turpis at libero. + +Nullam ut egestas ligula. Proin hendrerit justo a lectus commodo venenatis. Nulla facilisi. Ut cursus lorem quis est bibendum condimentum. Aenean in tristique odio. Fusce tempor hendrerit ipsum. Curabitur mollis felis justo, quis dapibus erat auctor vel. Sed augue lectus, finibus ut urna quis, ullamcorper vestibulum dui. Etiam molestie aliquam tempor. Integer mattis sollicitudin erat, et tristique elit varius vel. Mauris a ex justo. + +Nam eros est, imperdiet non volutpat rutrum, pellentesque accumsan ligula. Duis sit amet turpis metus. Aenean in rhoncus metus, ac fringilla ex. Suspendisse condimentum egestas purus, ut pharetra odio vulputate vel. Duis tincidunt massa a placerat ultrices. Mauris ultricies nibh sit amet condimentum malesuada. Duis tincidunt id ipsum sed congue. + +Praesent eu ex augue. Nullam in porta ligula. In tincidunt accumsan arcu, in pellentesque magna tristique in. Mauris eleifend libero ac nisl viverra faucibus. Nam sollicitudin dolor in commodo hendrerit. Cras at orci metus. Ut quis laoreet orci. Vivamus ultrices leo pellentesque tempor aliquet. Maecenas ut eros vitae purus placerat vestibulum. Etiam vitae gravida dolor, quis rhoncus diam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. + +Suspendisse fringilla lacinia sagittis. Integer tincidunt consectetur tristique. Morbi non orci convallis, congue sapien quis, vulputate nunc. Donec a libero vel magna elementum facilisis non quis mi. Mauris posuere tellus non ipsum ultrices elementum. Vivamus massa velit, facilisis quis placerat aliquet, aliquet nec leo. Praesent a maximus sem. Sed neque elit, feugiat vel quam non, molestie sagittis nunc. Etiam luctus nunc ac mauris scelerisque, nec rhoncus lacus convallis. Nunc pharetra, nunc ac pulvinar aliquam, ex ipsum euismod augue, nec porttitor lacus turpis vitae neque. Fusce bibendum odio id tortor faucibus pellentesque. Sed ac porta nibh, eu gravida erat. + +Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam quis ullamcorper felis. Nulla mattis sagittis ante ac tincidunt. Integer ac felis efficitur, viverra libero et, facilisis ligula. Suspendisse a metus a massa rhoncus posuere. Phasellus suscipit ligula ut lacus facilisis, ac pellentesque ex tempor. Quisque consectetur massa mi, ac molestie libero dictum quis. Proin porttitor ligula quis erat tincidunt venenatis. Proin congue nunc sed elit gravida, nec consectetur lectus sodales. Etiam tincidunt convallis ipsum at vestibulum. Quisque maximus enim et mauris porttitor, et molestie magna tristique. Morbi vitae metus elit. Maecenas sed volutpat turpis. Aliquam vitae dolor vestibulum, elementum purus eget, dapibus nibh. Nullam egestas dui ac rutrum semper. + +Etiam hendrerit est metus, et condimentum metus aliquam ac. Pellentesque id neque id ipsum rhoncus vulputate. Aliquam erat nisl, posuere sit amet ligula ac, fermentum blandit felis. Vivamus fermentum mi risus, non lacinia purus viverra id. Aenean ac sapien consequat, finibus mauris nec, porta sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed quis consectetur ex, dignissim bibendum nulla. Phasellus ac libero at quam vehicula euismod non eu leo. Phasellus a sapien augue. + +Maecenas ligula dui, bibendum vitae mauris et, auctor laoreet felis. Duis non libero a mi semper mattis. Quisque consequat luctus massa, quis tristique eros auctor feugiat. Maecenas sodales euismod neque vitae facilisis. Nullam laoreet imperdiet velit at pellentesque. Etiam massa odio, facilisis a consequat vitae, placerat vel magna. Nunc sagittis eros nec urna fringilla, pulvinar vestibulum nibh scelerisque. Sed magna metus, cursus eu consequat et, pharetra a est. Suspendisse elementum neque a dui malesuada lacinia. Donec sed ipsum volutpat, cursus urna id, ullamcorper arcu. Maecenas laoreet nisl eget velit egestas sollicitudin. Etiam nisl turpis, mollis id dignissim vitae, tristique vehicula ante. Maecenas eget placerat est, at rutrum augue. Vivamus faucibus lacinia ullamcorper. Sed pulvinar urna sodales ante sodales, at gravida leo dictum. + +Morbi maximus, quam a lobortis bibendum, enim felis varius elit, ac vehicula elit nisl ut lacus. Quisque ut arcu augue. Praesent id turpis quam. Sed sed arcu eros. Maecenas at cursus lorem, ac eleifend nisi. Fusce mattis felis at commodo pharetra. Praesent ac commodo ipsum. Quisque finibus et eros vitae tincidunt. In hac habitasse platea dictumst. Praesent purus ipsum, luctus lobortis ornare quis, auctor eget justo. Nam vel enim sollicitudin, faucibus tortor eu, sagittis eros. Ut nec consectetur erat. Donec ultricies malesuada ligula, a hendrerit sapien volutpat in. Maecenas sed enim vitae sapien pulvinar faucibus. + +Proin semper nunc nibh, non consequat neque ullamcorper vel. Maecenas lobortis sagittis blandit. Aenean et arcu ultricies turpis malesuada malesuada. Ut quam ex, laoreet ut blandit cursus, feugiat vitae dolor. Etiam ex lacus, scelerisque vel erat vel, efficitur tincidunt magna. Morbi tristique lacinia dolor, in egestas magna ultrices vitae. Integer ultrices leo ac tempus venenatis. Praesent ac porta tortor. Vivamus ornare blandit tristique. Nulla rutrum finibus pellentesque. In non dui elementum, fermentum ipsum vel, varius magna. Pellentesque euismod tortor risus, ac pellentesque nisl faucibus eget. + +Vivamus eu enim purus. Cras ultrices rutrum egestas. Sed mollis erat nibh, at posuere nisl luctus nec. Nunc vulputate, sapien id auctor molestie, nisi diam tristique ante, non convallis tellus nibh at orci. Morbi a posuere purus, in ullamcorper ligula. Etiam elementum sit amet dui imperdiet iaculis. Proin vitae tincidunt ipsum, sit amet placerat lectus. Curabitur commodo sapien quam, et accumsan lectus fringilla non. Nullam eget accumsan enim, ac pharetra mauris. Sed quis tristique velit, vitae commodo nisi. Duis turpis dui, maximus ut risus at, finibus consequat nunc. Maecenas sed est accumsan, aliquet diam in, facilisis risus. Curabitur vehicula rutrum auctor. Nam iaculis risus pulvinar maximus viverra. Nulla vel augue et ex sagittis blandit. + +Ut sem nulla, porta ac ante ac, posuere laoreet eros. Donec sodales posuere justo a auctor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras mollis at orci hendrerit porta. Nullam sodales tortor tortor, non lacinia diam finibus id. Duis libero orci, suscipit ac odio et, dictum consequat ipsum. Pellentesque eu ligula sagittis, volutpat eros at, lacinia lorem. Cras euismod tellus in iaculis tempor. Quisque accumsan, magna a congue venenatis, ante ipsum aliquam lectus, at egestas enim nunc at justo. Quisque sem purus, viverra ut tristique ut, maximus id enim. Etiam quis placerat sem. In sollicitudin, lacus eu rutrum mollis, nulla eros luctus elit, vel dapibus urna purus nec urna. Phasellus egestas massa quam, ac molestie erat hendrerit a. Praesent ultrices neque ut turpis molestie auctor. Etiam molestie placerat purus, et euismod erat aliquam in. Morbi id suscipit justo. + +Proin est ante, consequat at varius a, mattis quis felis. Sed accumsan nibh sit amet ipsum elementum posuere. Vestibulum bibendum id diam sit amet gravida. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi nec dolor vel ipsum dignissim hendrerit vel non ipsum. Praesent facilisis orci quis elit auctor lobortis. Phasellus cursus risus lectus, vel lobortis libero dapibus in. Quisque tristique tempus leo a pulvinar. Pellentesque a magna tincidunt, pellentesque massa nec, laoreet orci. Morbi congue ornare dolor quis commodo. Phasellus massa nisi, tincidunt at eros dictum, hendrerit lobortis urna. Maecenas porta, magna id mattis molestie, nibh tellus lobortis sem, eget tincidunt ipsum quam eu turpis. + +Ut gravida orci risus, vel rutrum mauris vehicula id. Etiam bibendum, neque a placerat condimentum, ex orci imperdiet lectus, quis dapibus arcu lacus eget lectus. Sed consequat non mi sit amet venenatis. Fusce vestibulum erat libero, eget hendrerit risus vulputate sollicitudin. Integer sed eleifend felis. Donec commodo, sem eu mattis placerat, urna odio aliquam tellus, et laoreet justo tellus eget erat. Fusce sed suscipit tortor. Nam hendrerit nibh ac nunc auctor lacinia. Pellentesque placerat condimentum ipsum, eget semper tortor hendrerit vel. Nullam non urna eu lacus pellentesque congue ut id eros. + +Nunc finibus leo in rhoncus tristique. Sed eu ipsum nec nisl egestas faucibus eget a felis. Pellentesque vitae nisi in nulla accumsan fermentum. Sed venenatis feugiat eleifend. Fusce porttitor varius placerat. Aliquam aliquet lacus sit amet mattis mollis. Sed vel nulla quis dolor suscipit vehicula ac viverra lorem. Duis viverra ipsum eget nulla ullamcorper fermentum. Mauris tincidunt arcu quis quam fringilla ornare. Donec et iaculis tortor. Nam ultricies libero vel ipsum aliquet efficitur. Morbi eget dolor aliquam, tempus sapien eget, viverra ante. Donec varius mollis ex, sed efficitur purus euismod interdum. Quisque vel sapien non neque tincidunt semper. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. + +Suspendisse sit amet purus leo. Fusce lectus lorem, aliquam ac nulla eget, imperdiet ornare eros. Nullam sem augue, varius in nisi non, sollicitudin pellentesque ante. Etiam eu odio condimentum, tempor libero et, egestas arcu. Cras pellentesque eleifend aliquet. Pellentesque non blandit ligula. Ut congue viverra rhoncus. Phasellus mattis mi ac eros placerat, eu feugiat tellus ultrices. Aenean mollis laoreet libero eu imperdiet. Cras sed pulvinar mi, ac vehicula ligula. Vestibulum sit amet ex massa. In a egestas eros. + +Mauris pretium ipsum risus, venenatis cursus ante imperdiet id. Praesent eu turpis nec risus feugiat maximus ullamcorper ac lectus. Integer placerat at mi vel dapibus. Vestibulum fermentum turpis sit amet turpis viverra, id aliquet diam suscipit. Nam nec ex sed ante ullamcorper pharetra quis sit amet risus. Sed ac faucibus velit, id feugiat nibh. Nullam eget ipsum ex. Vivamus tincidunt non nunc non faucibus. Quisque bibendum viverra facilisis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at nisi hendrerit quam suscipit egestas. Curabitur laoreet maximus ultricies. Duis ut tellus ac augue molestie dictum. + +Suspendisse rhoncus iaculis erat, ut ullamcorper est tristique eget. Donec auctor nec risus at gravida. Vivamus volutpat vulputate tellus, vel ultricies eros suscipit eget. Ut pulvinar id mi eu tempus. Morbi malesuada augue in dui varius, nec blandit neque vehicula. Donec ornare nec nisl in mollis. Morbi enim nisi, rhoncus nec est id, dapibus tempus urna. Ut id elit a felis vestibulum consectetur. Duis lectus quam, pharetra sit amet diam sed, posuere vestibulum erat. Fusce vitae maximus massa. Nullam id metus tempus, iaculis risus eu, lobortis urna. Quisque in congue urna. Pellentesque placerat neque in augue dapibus, non varius ex malesuada. Curabitur ut eleifend libero. Fusce vitae ligula luctus, fermentum enim vitae, ultrices erat. + +Sed viverra augue turpis, scelerisque egestas sapien mattis eu. Duis laoreet magna at ex pharetra dapibus. Praesent eget odio vel quam venenatis dictum. Nulla in sollicitudin dolor. Mauris lobortis nec eros vel rhoncus. Vestibulum porta viverra venenatis. Curabitur vel scelerisque quam, a egestas velit. Praesent volutpat tincidunt magna at laoreet. + +Cras nec lorem odio. Pellentesque quis dui urna. Praesent at tellus ac lectus scelerisque placerat nec eu risus. Vestibulum sit amet mattis ligula. Vivamus sed nisi at leo elementum accumsan at sit amet arcu. Aenean mattis tellus nec leo gravida, eget hendrerit nisl faucibus. Mauris pellentesque luctus condimentum. Maecenas pretium sapien nunc, eget commodo dolor maximus id. Mauris vestibulum accumsan massa a dictum. Phasellus interdum quam ligula, ut maximus diam blandit aliquam. Nunc vitae ex eu erat condimentum consectetur. Maecenas interdum condimentum volutpat. + +Donec et enim a libero rutrum laoreet. Praesent a condimentum sem, at tincidunt quam. In vel molestie risus. Sed urna dui, molestie vitae mollis laoreet, tempor quis lectus. Praesent vitae auctor est, et aliquet nunc. Curabitur vulputate blandit nulla, at gravida metus. Maecenas gravida dui eu iaculis tristique. Pellentesque posuere turpis nec auctor eleifend. Suspendisse bibendum diam eu tellus lobortis, et laoreet quam congue. In hac habitasse platea dictumst. Morbi dictum neque velit, eget rutrum eros ultrices sit amet. + +Phasellus fermentum risus pharetra consectetur bibendum. Donec magna tortor, lacinia vitae nibh quis, aliquet pretium lorem. Donec turpis nisi, pretium eu enim volutpat, mattis malesuada augue. Nullam vel tellus iaculis, sollicitudin elit eget, tincidunt lacus. Fusce elementum elementum felis et iaculis. Suspendisse porta eros nec neque malesuada, in malesuada ante sollicitudin. Vivamus bibendum viverra molestie. + +Integer feugiat, erat nec convallis aliquam, velit felis congue erat, molestie eleifend tellus erat in tellus. Nunc et justo purus. Donec egestas fermentum dui non feugiat. Quisque in sapien sagittis, gravida quam id, iaculis lectus. Cras sagittis rhoncus bibendum. Fusce quis metus in velit scelerisque tincidunt at non ipsum. Vivamus efficitur ante eu odio vulputate, vitae ultricies risus vehicula. Proin eget odio eu sem tincidunt feugiat vel id lorem. + +Vestibulum sit amet nulla dignissim, euismod mi in, fermentum tortor. Donec ut aliquet libero, lacinia accumsan velit. Donec et nulla quam. Nullam laoreet odio nec nunc imperdiet, a congue eros venenatis. Quisque nec tellus sit amet neque interdum posuere. Duis quis mi gravida, tincidunt diam convallis, ultricies augue. Mauris consequat risus non porttitor congue. Ut in ligula consequat, viverra nunc a, eleifend enim. Duis ligula urna, imperdiet nec facilisis et, ornare eu ex. Proin lobortis lectus a lobortis porttitor. Nulla leo metus, egestas eu libero sed, pretium faucibus felis. Vestibulum non sem tortor. Nam cursus est leo. Vivamus luctus enim odio, non interdum sem dapibus a. Aenean accumsan consequat lectus in imperdiet. + +Donec vehicula laoreet ipsum in posuere. Quisque vel quam imperdiet, sollicitudin nisi quis, suscipit velit. Morbi id sodales mauris. Curabitur tellus arcu, feugiat sed dui sit amet, sodales sagittis libero. Aenean vel suscipit metus, non placerat leo. Vestibulum quis nulla elit. Proin scelerisque non ante ut commodo. Interdum et malesuada fames ac ante ipsum primis in faucibus. + +Sed non urna dolor. Suspendisse convallis mi porta pulvinar ultrices. Suspendisse quam ipsum, hendrerit non scelerisque molestie, interdum dictum nunc. Morbi condimentum condimentum turpis eu luctus. Pellentesque sagittis sollicitudin odio, sed ultricies felis ornare sit amet. Sed ultrices ex leo, a tincidunt nisl gravida sed. Nullam ornare accumsan porta. Praesent consectetur id est nec sollicitudin. + +In hac habitasse platea dictumst. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed sed ultrices nibh. Duis accumsan suscipit eros, a dictum odio tempus sit amet. Aenean imperdiet erat ac lacus finibus, scelerisque cursus massa imperdiet. Mauris molestie risus ut lacinia posuere. Nulla et sodales purus. Maecenas orci erat, placerat in tristique quis, placerat in mi. + +Donec sollicitudin pellentesque odio in feugiat. Morbi eu dolor ut mauris congue sollicitudin. Aliquam erat volutpat. Nulla id varius dui. Curabitur finibus urna ante, consectetur interdum nisi volutpat a. Quisque quis mi tristique, consequat tellus eget, rutrum sapien. Vivamus vitae tellus vulputate, rutrum ex eu, vulputate sem. Suspendisse viverra lorem tellus, vel interdum orci gravida quis. Ut laoreet arcu at mi ullamcorper finibus. Duis porta sagittis vestibulum. Sed commodo nisl vitae urna sollicitudin, nec lacinia est sodales. Curabitur imperdiet sodales dui sed iaculis. Sed ac tellus maximus, eleifend quam sit amet, feugiat elit. Aenean viverra, dui at mattis varius, est odio vestibulum sapien, sit amet mollis libero massa nec velit. Etiam quis sodales justo. + +Ut ultricies, sem eget sodales feugiat, nunc arcu congue elit, ac tempor justo massa nec purus. Maecenas enim nunc, pharetra eget dictum sit amet, tempus pellentesque velit. Suspendisse venenatis ligula in nulla mattis, et imperdiet ex tincidunt. Etiam vulputate, tellus et ultrices suscipit, enim velit laoreet massa, vitae congue odio enim ac urna. Morbi quam lorem, iaculis ac varius sagittis, euismod quis dolor. In ut dui eu purus feugiat consectetur. Vestibulum cursus velit quis lacus pellentesque iaculis. Cras in risus sed mauris porta rutrum. Nulla facilisi. Nullam eu bibendum est, non pellentesque lectus. Sed imperdiet feugiat lorem, quis convallis ante auctor in. Maecenas justo magna, scelerisque sit amet tellus eget, varius elementum risus. Duis placerat et quam sed varius. + +Duis nec nibh vitae nibh dignissim mollis quis sed felis. Curabitur vitae quam placerat, venenatis purus ut, euismod nisl. Curabitur porttitor nibh eu pulvinar ullamcorper. Suspendisse posuere nec ipsum ac dapibus. Cras convallis consectetur urna. Phasellus a nibh in dolor lacinia posuere id eget augue. In eu pharetra lorem, vitae cursus lacus. Aliquam tincidunt nibh lectus. Aenean facilisis ultricies posuere. Sed ut placerat orci. Curabitur scelerisque gravida blandit. Maecenas placerat ligula eget suscipit fringilla. Mauris a tortor justo. Aliquam hendrerit semper mollis. Phasellus et tincidunt libero. Etiam vel quam libero. + +Quisque aliquet tempor ex. Ut ante sem, vehicula at enim vel, gravida porta elit. Etiam vitae lacus a neque lobortis consectetur. Mauris sed interdum odio. Mauris elementum ex blandit tempor cursus. Integer in enim in leo viverra elementum. Fusce consectetur metus et sem rutrum, mattis euismod diam semper. Nunc sed ipsum vel urna consequat vehicula. Donec cursus pretium lorem, vestibulum pretium felis commodo sit amet. Nam blandit felis enim, eget gravida ex faucibus a. In nec neque massa. Etiam laoreet posuere ipsum. Praesent volutpat nunc dolor, ac vulputate magna facilisis non. Aenean congue turpis vel lectus sollicitudin tristique. Sed nec consequat purus, non vehicula quam. Etiam ultricies, est ac dictum tincidunt, turpis turpis pretium massa, a vulputate libero justo at nibh. + +Aliquam erat volutpat. Cras ultrices augue ac sollicitudin lobortis. Curabitur et aliquet purus. Duis feugiat semper facilisis. Phasellus lobortis cursus velit, a sollicitudin tortor. Nam feugiat sapien non dapibus condimentum. Morbi at mi bibendum, commodo quam at, laoreet enim. Integer eu ultrices enim. Sed vestibulum eu urna ut dictum. Curabitur at mattis leo, sed cursus massa. Aliquam porttitor, felis quis fermentum porttitor, justo velit feugiat nulla, eget condimentum sem dui ut sapien. + +In fringilla elit eu orci aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut eget fringilla tellus. Curabitur fermentum, mi et condimentum suscipit, elit neque bibendum dui, et hendrerit nunc metus id ipsum. Morbi placerat mi in hendrerit congue. Ut feugiat mauris eget scelerisque viverra. Vivamus sit amet erat dictum, sagittis lectus nec, pulvinar lorem. Sed non enim ac dui sollicitudin aliquet. Quisque ut lacus dolor. Fusce hendrerit malesuada euismod. Nulla faucibus vel mauris eu mollis. Mauris est diam, fringilla ac arcu feugiat, efficitur volutpat turpis. Aliquam venenatis cursus massa sed porttitor. Ut ac finibus enim, in tincidunt sapien. + +Nunc faucibus semper turpis a lacinia. Phasellus gravida, libero vel pulvinar ornare, ex sem tincidunt lectus, sit amet convallis augue risus at tortor. Quisque sit amet ipsum id nulla posuere vestibulum. Pellentesque scelerisque mauris vel leo viverra sodales. Nulla viverra aliquam ex, ut rutrum enim fermentum venenatis. Aenean eget dapibus ex, eget faucibus metus. Vestibulum volutpat leo in diam semper, eget porta magna suscipit. Sed sit amet nulla blandit, aliquam dolor ac, gravida velit. Sed vel velit viverra, maximus est id, convallis justo. + +Curabitur nulla ante, vulputate at libero vel, ullamcorper rutrum nibh. Pellentesque porttitor eu mauris id mattis. Duis vulputate augue elit, eget interdum justo pretium vel. Maecenas eu vulputate arcu, eget posuere purus. Suspendisse viverra a velit dictum eleifend. Suspendisse vitae dapibus diam. Donec vehicula justo in ante interdum, eu luctus diam placerat. Vivamus convallis ipsum eu orci suscipit, sed fermentum enim euismod. Maecenas faucibus elit vitae ex ornare tristique. Donec vestibulum nec elit sit amet porttitor. Aenean tempor lectus eget tortor hendrerit luctus. Nullam interdum vitae lectus vel feugiat. Cras in risus non magna consectetur lobortis. Sed faucibus enim quis gravida convallis. + +Phasellus eget massa sit amet libero ultrices suscipit. Vivamus at risus sapien. Nam mollis nunc eget velit dictum maximus. Sed pellentesque, nunc ac fringilla lacinia, quam enim mattis ex, sed euismod tortor metus eu neque. Ut mattis nisl ut lectus rhoncus, sodales bibendum eros porta. Nulla porttitor enim nec diam sagittis, eget porta velit efficitur. Vestibulum ultricies eros neque. Phasellus rutrum suscipit enim, in interdum ante gravida vitae. Sed in sagittis diam, non commodo velit. + +Morbi hendrerit odio orci, nec tincidunt odio rhoncus nec. Mauris neque velit, vehicula a lorem at, suscipit tristique dui. Sed finibus, nisl in mattis convallis, turpis neque sodales lacus, eu porta enim magna non diam. Nam commodo sodales risus consectetur malesuada. In eget elementum justo. Phasellus sit amet massa imperdiet, dapibus nunc sit amet, suscipit orci. Fusce condimentum laoreet feugiat. Ut ut viverra ante. Praesent bibendum interdum commodo. Nulla mollis nisi a est ornare volutpat. Sed at ligula eu nisi dapibus tempus. Proin cursus vestibulum justo, nec efficitur justo dignissim vel. Nunc quis maximus eros. + +Cras viverra, diam a tristique mattis, libero felis vulputate tellus, a ornare felis leo a dui. Nulla ante nulla, finibus ut tellus ut, blandit pharetra nibh. Proin eleifend fermentum ex, eget auctor libero vulputate in. Nullam ultricies, mauris placerat pretium placerat, leo urna lobortis leo, vel placerat arcu libero sed mauris. Aliquam mauris ligula, ornare at urna at, eleifend gravida ligula. Vestibulum consectetur ut nulla non scelerisque. Donec ornare, sem nec elementum aliquam, urna nulla bibendum metus, eu euismod dui ligula ac est. Fusce laoreet erat eu ex lobortis, quis bibendum ligula interdum. Sed vel mi erat. Vivamus id lacus ac enim mattis tempor. Nunc ultricies pellentesque enim sed euismod. Fusce tincidunt convallis elit quis aliquam. Mauris nulla ipsum, sollicitudin quis diam ac, feugiat volutpat tellus. In nibh nibh, vulputate quis tincidunt quis, pulvinar eget magna. Pellentesque quis finibus dolor. Suspendisse viverra vitae lectus non eleifend. + +Nunc ut orci et sapien maximus semper. Nulla dignissim sem urna, ac varius lectus ultricies id. Quisque aliquet pulvinar pretium. In ultricies molestie tellus vehicula porta. Nam enim lorem, aliquam eget ex et, hendrerit volutpat quam. Maecenas diam lacus, pellentesque eget tempus ac, pharetra eu elit. Donec vel eros a sem facilisis vulputate. Nullam ac nisi vulputate, laoreet nisl ac, eleifend sem. Nullam mi massa, rhoncus sed pharetra interdum, tincidunt eget nunc. Aliquam viverra mattis posuere. Mauris et dui sed nisl sollicitudin fermentum quis ut arcu. Nam placerat eget orci at tincidunt. Curabitur vel turpis metus. Phasellus nibh nulla, fermentum scelerisque sem vel, gravida tincidunt velit. Pellentesque vel quam tempor, finibus massa pellentesque, condimentum dui. + +Donec at mattis neque. Etiam velit diam, consequat auctor mauris id, hendrerit faucibus metus. Maecenas ullamcorper eros a est sodales, ac consectetur odio scelerisque. Donec leo metus, imperdiet at pellentesque vel, feugiat id erat. Suspendisse at magna enim. Vestibulum placerat sodales lorem id sollicitudin. Aenean at euismod ligula, eget mollis diam. Phasellus pulvinar, orci nec pretium condimentum, est erat facilisis purus, quis feugiat augue elit aliquam nulla. Aenean vitae tortor id risus congue tincidunt. Sed dolor enim, mattis a ullamcorper id, volutpat ac leo. + +Proin vehicula feugiat augue, id feugiat quam sodales quis. Donec et ultricies massa, a lacinia nulla. Duis aliquam augue ornare euismod viverra. Ut lectus risus, rutrum sit amet efficitur a, luctus nec nisl. Cras volutpat ullamcorper congue. Sed vitae odio metus. Phasellus aliquet euismod varius. + +Nullam sem ex, malesuada ut magna ut, pretium mollis arcu. Nam porttitor eros cursus mi lacinia faucibus. Suspendisse aliquet eleifend iaculis. Maecenas sit amet viverra tortor. Nunc a mollis risus. Etiam tempus dolor in tortor malesuada mattis. Ut tincidunt venenatis est sit amet dignissim. Vestibulum massa enim, tristique sed scelerisque eu, fringilla ac velit. Donec efficitur quis urna sit amet malesuada. Vestibulum consequat ac ligula in dapibus. Maecenas massa massa, molestie non posuere nec, elementum ut magna. In nisi erat, mollis non venenatis eu, faucibus in justo. Morbi gravida non ex non egestas. Pellentesque finibus laoreet diam, eu commodo augue congue vitae. + +Aenean sem mi, ullamcorper dapibus lobortis vitae, interdum tincidunt tortor. Vivamus eget vulputate libero. Ut bibendum posuere lectus, vel tincidunt tortor aliquet at. Phasellus malesuada orci et bibendum accumsan. Aliquam quis libero vel leo mollis porta. Sed sagittis leo ac lacus dictum, ac malesuada elit finibus. Suspendisse pharetra luctus commodo. Vivamus ultricies a odio non interdum. Vivamus scelerisque tincidunt turpis quis tempor. Pellentesque tortor ligula, varius non nunc eu, blandit sollicitudin neque. Nunc imperdiet, diam et tristique luctus, ipsum ex condimentum nunc, sit amet aliquam justo velit sed libero. Duis vel suscipit ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed tincidunt neque vel massa ultricies, id dictum leo consequat. Curabitur lobortis ultricies tellus, eget mattis nisl aliquam sit amet. + +Proin at suscipit justo. Vivamus ut vestibulum nisl. Pellentesque enim odio, pharetra non magna sed, efficitur auctor magna. Praesent tincidunt ante quis ante hendrerit viverra. Pellentesque vel ipsum id magna vulputate efficitur. Sed nec neque accumsan, pulvinar sapien quis, euismod mauris. Donec condimentum laoreet sapien quis gravida. Quisque sed mattis purus. Vestibulum placerat vel neque maximus scelerisque. + +Vestibulum mattis quam quis efficitur elementum. Duis dictum dolor ac scelerisque commodo. Fusce sollicitudin nisi sit amet dictum placerat. Suspendisse euismod pharetra eleifend. In eros nisl, porttitor sed mauris at, consectetur aliquet mauris. Donec euismod viverra neque sed fermentum. Phasellus libero magna, accumsan ut ultricies vitae, dignissim eget metus. Donec tellus turpis, interdum eget maximus nec, hendrerit eget massa. Curabitur auctor ligula in iaculis auctor. In ultrices quam suscipit cursus finibus. Aenean id mi at dolor interdum iaculis vitae ut lorem. Nullam sed nibh fringilla, lacinia odio nec, placerat erat. In dui libero, viverra ac viverra ac, pellentesque sit amet turpis. + +Nulla in enim ex. Sed feugiat est et consectetur venenatis. Cras varius facilisis dui vel convallis. Vestibulum et elit eget tellus feugiat pellentesque. In ut ante eu purus aliquet posuere. Nulla nec ornare sem, sed luctus lorem. Nam varius iaculis odio, eget faucibus nisl ullamcorper in. Sed eget cursus felis, nec efficitur nisi. + +Vivamus commodo et sem quis pulvinar. Pellentesque libero ante, venenatis vitae ligula sit amet, ornare sollicitudin nulla. Mauris eget tellus hendrerit, pulvinar metus quis, tempor nisi. Proin magna ex, laoreet sed tortor quis, varius fermentum enim. Integer eu dolor dictum, vulputate tortor et, aliquet ligula. Vestibulum vitae justo id mauris luctus sollicitudin. Suspendisse eget auctor neque, sodales egestas lorem. Vestibulum lacinia egestas metus vitae euismod. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus ex tellus, volutpat nec pulvinar sit amet, condimentum vitae dui. Curabitur vel felis sodales, lacinia nunc iaculis, ullamcorper augue. Pellentesque consequat dolor quis eros efficitur malesuada. Nulla ut malesuada lectus. + +Morbi et tristique ante. Aliquam erat volutpat. Vivamus vitae dui nec turpis pellentesque fermentum. Quisque eget velit massa. Pellentesque tristique aliquam nisl, eu sollicitudin justo venenatis sed. Duis eleifend sem eros, ut aliquam libero porttitor id. Sed non nunc consequat, rhoncus diam eu, commodo erat. Praesent fermentum in lectus id blandit. Donec quis ipsum at justo volutpat finibus. Nulla blandit justo nulla, at mollis lacus consequat eget. Aenean sollicitudin quis eros ut ullamcorper. + +Pellentesque venenatis nulla ut mi aliquet feugiat. Cras semper vel magna nec pharetra. Integer mattis felis et sapien commodo imperdiet. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis quis luctus felis. Vestibulum justo nibh, aliquam non lectus vitae, molestie placerat justo. Donec lorem nibh, gravida sit amet hendrerit ac, maximus id ipsum. Nunc ac libero sodales risus eleifend sagittis. Phasellus est massa, lobortis elementum ex sed, scelerisque consectetur neque. Nunc faucibus neque id lorem malesuada, eget convallis ex mattis. + +Sed turpis tortor, fermentum non turpis id, posuere varius nibh. Donec iaculis lorem dui. Etiam eros ante, sodales eget venenatis at, consectetur eget risus. Curabitur non aliquam ante, a pretium justo. Maecenas tempor nisl tortor, vitae dictum nisi ultrices eu. Duis eget dui ultrices, porttitor lacus sed, lobortis purus. Quisque mattis elit nec neque sagittis, sed commodo leo blandit. Mauris sodales interdum eleifend. Vestibulum condimentum consectetur augue, id luctus diam convallis et. + +Nunc suscipit risus in justo accumsan, a placerat magna tincidunt. Proin a nisl ipsum. Sed libero dui, tristique in augue quis, auctor tristique risus. Sed porttitor ex augue, eu porta augue molestie a. Duis rhoncus purus libero, eu tempus turpis condimentum at. Sed mollis nisi id lectus placerat tincidunt. Maecenas non scelerisque elit, quis rutrum orci. Donec in tellus pharetra urna ornare lobortis. Phasellus id risus at nisi varius rutrum eu ut turpis. + +Duis dictum justo quis nisl porta, eget tincidunt magna suscipit. Sed velit massa, ullamcorper eu sodales ac, pretium a massa. Duis et rutrum tortor. Nulla accumsan hendrerit sapien, cursus volutpat eros egestas eget. Donec sollicitudin at ante quis sollicitudin. Aenean blandit feugiat diam, id feugiat eros faucibus eget. Donec viverra dolor vel justo scelerisque dignissim. Nulla semper sem nunc, rhoncus semper tellus ultricies sed. Duis in ornare diam. Donec vehicula feugiat varius. Maecenas ut suscipit est. Vivamus sem sem, finibus at dolor sit amet, euismod dapibus ligula. Vestibulum fringilla odio dapibus, congue massa eget, congue sem. Donec feugiat magna eget tortor lacinia scelerisque non et ipsum. + +Suspendisse potenti. Nunc convallis sollicitudin ex eget venenatis. Sed iaculis nibh ex, vel ornare ligula congue dignissim. Quisque sollicitudin dolor ac dui vestibulum, sit amet molestie nisi aliquet. Donec at risus felis. Aenean sollicitudin metus a feugiat porta. Aenean a tortor ut dolor cursus sagittis. Vivamus consectetur porttitor nunc in facilisis. Proin sit amet mi vel lectus consectetur ultrices. + +Sed cursus lectus vitae nunc tristique, nec commodo turpis dapibus. Pellentesque luctus ex id facilisis ornare. Morbi quis placerat dolor. Donec in lectus in arcu mattis porttitor ac sit amet metus. Cras congue mauris non risus sodales, vitae feugiat ipsum bibendum. Nulla venenatis urna sed libero elementum, a cursus lorem commodo. Mauris faucibus lobortis eros nec commodo. + +Nullam suscipit ligula ullamcorper lorem commodo blandit. Nulla porta nibh quis pulvinar placerat. Vivamus eu arcu justo. Vestibulum imperdiet est ut fermentum porttitor. Pellentesque consectetur libero in sapien efficitur scelerisque. Curabitur ac erat sit amet odio aliquet dignissim. Pellentesque mi sem, rhoncus et luctus at, porttitor rutrum lectus. Vestibulum sollicitudin sollicitudin suscipit. Aenean efficitur dolor non ultrices imperdiet. Donec vel sem ex. + +Sed convallis mauris aliquam rutrum cursus. Ut tempor porttitor sodales. Etiam eu risus ac augue gravida egestas et eu dolor. Proin id magna ex. Suspendisse quis lectus quis lorem ultricies tempus. Donec porttitor velit vitae tincidunt faucibus. Aliquam vitae semper nisi. Morbi ultrices, leo non pretium dapibus, dui libero pellentesque ex, vel placerat enim ante vitae dui. Nunc varius, sem sit amet sagittis lobortis, lectus odio scelerisque mauris, ut vestibulum orci magna quis neque. Sed id congue justo. Interdum et malesuada fames ac ante ipsum primis in faucibus. Mauris congue nisi est, malesuada mollis elit tincidunt sed. Curabitur sed ex sit amet felis tristique elementum vitae vel nibh. + +Etiam mollis pretium lobortis. Mauris augue lacus, efficitur at lacus sed, mollis tincidunt lectus. Aliquam erat volutpat. Donec at euismod elit, et mattis felis. Sed id lobortis urna. Morbi imperdiet vestibulum leo, sed maximus leo blandit eu. Aliquam semper lorem neque, nec euismod turpis mattis mollis. Quisque lobortis urna ultrices odio pretium, ac venenatis orci faucibus. Suspendisse bibendum odio ligula, sed lobortis massa pharetra nec. Donec turpis justo, iaculis at dictum ac, finibus eu libero. Maecenas quis porttitor mi, sit amet aliquet neque. + +Vivamus auctor vulputate ante, at egestas lorem. Donec eu risus in nulla mollis ultricies at et urna. Duis accumsan porta egestas. Ut vel euismod augue. Fusce convallis nulla ante, nec fringilla velit aliquet at. Nam malesuada dapibus ligula, a aliquam nibh scelerisque ac. Praesent malesuada neque et pellentesque interdum. Curabitur volutpat at turpis vitae tristique. Vivamus porttitor semper congue. Quisque suscipit lacus mi, rhoncus ultrices tortor auctor quis. Maecenas neque neque, molestie ac facilisis eget, luctus ac lorem. In ut odio ut lacus suscipit pulvinar vitae sed elit. Nulla imperdiet, sem quis euismod sagittis, dui erat luctus dolor, faucibus faucibus erat sem eget nunc. Nam accumsan placerat malesuada. Maecenas convallis finibus pulvinar. + +Cras at placerat tortor. Morbi facilisis auctor felis sit amet molestie. Donec sodales sed lorem vitae suscipit. Etiam fermentum pharetra ipsum, nec luctus orci gravida eu. Pellentesque gravida, est non condimentum tempus, mauris ligula molestie est, in congue dolor nisl vel sapien. Duis congue tempor augue, id rutrum eros porta dapibus. Etiam rutrum eget est eget vestibulum. Aenean mollis arcu vel consequat varius. Praesent at condimentum felis. Duis nec interdum nisl. Donec commodo lorem sed sapien scelerisque malesuada non eu urna. In blandit non ipsum at porta. Nam lobortis leo vitae dui auctor, non feugiat quam bibendum. Donec auctor lectus sagittis laoreet maximus. Maecenas rhoncus laoreet porttitor. Vestibulum porttitor augue ut lectus hendrerit, eget posuere mi gravida. + +Sed mattis ex in erat pulvinar, eu imperdiet magna dapibus. Etiam nisi nibh, tempus non tellus sit amet, mattis tempor odio. Quisque nec lorem feugiat, lobortis odio et, commodo nunc. Maecenas semper purus nisi, nec vehicula nibh eleifend vitae. Nulla fermentum a lectus at maximus. Phasellus finibus metus non euismod ultrices. Etiam a pulvinar ante. Quisque convallis nec metus sit amet facilisis. Praesent laoreet massa et sollicitudin laoreet. Vestibulum in mauris aliquet, convallis mi ut, elementum purus. Nulla purus nulla, sodales at hendrerit quis, tempus sed lectus. + +Nam ut laoreet neque, ut maximus nibh. Maecenas quis justo pellentesque, sollicitudin elit at, venenatis velit. Aenean nunc velit, vehicula scelerisque odio at, consectetur laoreet purus. Duis dui purus, malesuada quis ipsum sit amet, tempor interdum libero. Curabitur porta scelerisque sapien, vitae cursus diam condimentum eu. Phasellus sed orci quam. Nullam vitae dui quis purus tincidunt vestibulum. Curabitur quis nulla porta, cursus arcu non, auctor enim. Etiam sollicitudin ex id sem vehicula mollis. Morbi viverra laoreet tincidunt. Praesent ut semper dui. Nam sit amet pretium neque. Mauris vitae luctus diam, in lacinia purus. Maecenas ut placerat justo, ut porta felis. Integer eu mauris ante. + +Aenean porttitor tellus diam, tempor consequat metus efficitur id. Suspendisse ut felis at erat tempor dictum at nec sapien. Sed vestibulum interdum felis, ac mattis mauris porta in. Nunc et condimentum massa. Sed cursus dictum justo et luctus. Integer convallis enim nisl, a rutrum lectus ultricies in. Donec dapibus lacus at nulla dapibus, id sollicitudin velit hendrerit. Fusce a magna at orci mollis rutrum ac a dolor. Aliquam erat volutpat. Morbi varius porta nunc, sit amet sodales ex hendrerit commodo. Donec tincidunt tortor sapien, vitae egestas sapien vehicula eget. + +Suspendisse potenti. Donec pulvinar felis nec leo malesuada interdum. Integer posuere placerat maximus. Donec nibh ipsum, tincidunt vitae luctus vitae, bibendum at leo. Sed cursus nisl ut ex faucibus aliquet sed nec eros. Curabitur molestie posuere felis. Integer faucibus velit eget consequat iaculis. Mauris sed vulputate odio. Phasellus maximus, elit a pharetra egestas, lorem magna semper tellus, vestibulum semper diam felis at sapien. Suspendisse facilisis, nisl sit amet euismod vehicula, libero nulla vehicula dolor, quis fermentum nibh elit sit amet diam. + +Morbi lorem enim, euismod eu varius ut, scelerisque quis odio. Nam tempus vitae eros id molestie. Nunc pretium in nulla eget accumsan. Quisque mattis est ut semper aliquet. Maecenas eget diam elementum, fermentum ipsum a, euismod sapien. Duis quam ligula, cursus et velit nec, ullamcorper tincidunt magna. Donec vulputate nisl est, et ullamcorper urna tempor sit amet. + +Proin lacinia dui non turpis congue pretium. Morbi posuere metus vel purus imperdiet interdum. Morbi venenatis vel eros non ultricies. Nulla vel semper elit. Ut quis purus tincidunt, auctor justo ut, faucibus turpis. Proin quis mattis erat, at faucibus ligula. Mauris in mauris enim. Donec facilisis enim at est feugiat hendrerit. Nam vel nisi lorem. Fusce ultricies convallis diam, in feugiat tortor luctus quis. Donec tempor, leo vitae volutpat aliquam, magna elit feugiat leo, quis placerat sapien felis eget arcu. Donec ornare fermentum eleifend. Integer a est orci. + +Proin rhoncus egestas leo. Nulla ultricies porta elit quis ornare. Nunc fermentum interdum vehicula. In in ligula lorem. Donec nec arcu sit amet orci lobortis iaculis. Mauris at mollis erat, sit amet mollis tortor. Mauris laoreet justo ullamcorper porttitor auctor. Aenean sit amet aliquam lectus, id fermentum eros. Praesent urna sem, vehicula ac fermentum id, dapibus ut purus. Vestibulum vitae tempus nunc. Donec at nunc ornare metus volutpat porta at eget magna. Donec varius aliquet metus, eu lobortis risus aliquam sed. Ut dapibus fermentum velit, ac tincidunt libero faucibus at. + +In in purus auctor, feugiat massa quis, facilisis nisi. Donec dolor purus, gravida eget dolor ac, porttitor imperdiet urna. Donec faucibus placerat erat, a sagittis ante finibus ac. Sed venenatis dignissim elit, in iaculis felis posuere faucibus. Praesent sed viverra dolor. Mauris sed nulla consectetur nunc laoreet molestie in ut metus. Proin ac ex sit amet magna vulputate hendrerit ac condimentum urna. Proin ligula metus, gravida et sollicitudin facilisis, iaculis ut odio. Cras tincidunt urna et augue varius, ut facilisis urna consequat. Aenean vehicula finibus quam. Ut iaculis eu diam ac mollis. Nam mi lorem, tristique eget varius at, sodales at urna. + +Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vitae dictum erat, et auctor ipsum. Nullam nunc nunc, sollicitudin quis magna a, vestibulum fermentum mauris. Praesent at erat dolor. Proin laoreet tristique nulla vel efficitur. Nam sed ultrices nibh, id rutrum nunc. Curabitur eleifend a erat sit amet sollicitudin. Nullam metus quam, laoreet vitae dapibus id, placerat sed leo. Aliquam erat volutpat. Donec turpis nisl, cursus eu ex sit amet, lacinia pellentesque nisl. Sed id ipsum massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec interdum scelerisque lorem eu mattis. + +Vivamus ac tristique massa, nec facilisis nisl. Nam ipsum neque, tincidunt vel urna in, cursus imperdiet enim. Nam pellentesque egestas tempus. Morbi facilisis imperdiet libero vitae fringilla. Nam lacinia ligula at sapien facilisis malesuada. Nullam accumsan pulvinar sem, et cursus libero porta sit amet. Curabitur vulputate erat elit, ut pulvinar erat maximus vel. + +Cras aliquet metus ut purus sagittis, vel venenatis ante consectetur. Pellentesque nulla lacus, viverra viverra mattis non, placerat vitae nibh. Donec enim turpis, accumsan sit amet tincidunt eu, imperdiet non metus. Morbi ipsum eros, tincidunt vel est ac, tristique porttitor nibh. Praesent ut ullamcorper mauris. Sed laoreet sit amet diam congue venenatis. Integer porta purus nec orci sagittis posuere. + +Donec vehicula mauris eget lacus mollis venenatis et sed nibh. Nam sodales ligula ipsum, scelerisque lacinia ligula sagittis in. Nam sit amet ipsum at erat malesuada congue. Aenean ut sollicitudin sapien. Etiam at tempor odio. Mauris vitae purus ut magna suscipit consequat. Vivamus quis sapien neque. Nulla vulputate sem sit amet massa pellentesque, eleifend tristique ligula egestas. Suspendisse tincidunt gravida mi, in pulvinar lectus egestas non. Aenean imperdiet ex sit amet nunc sollicitudin porta. Integer justo odio, ultricies at interdum in, rhoncus vitae sem. Sed porttitor arcu quis purus aliquet hendrerit. Praesent tempor tortor at dolor dictum pulvinar. Nulla aliquet nunc non ligula scelerisque accumsan. Donec nulla justo, congue vitae massa in, faucibus hendrerit magna. Donec non egestas purus. + +Vivamus iaculis, lacus efficitur faucibus porta, dui nulla facilisis ligula, ut sodales odio nunc id sapien. Cras viverra auctor ipsum, dapibus mattis neque dictum sed. Sed convallis fermentum molestie. Nulla facilisi turpis duis. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vulputate, ipsum quis interdum fermentum, lorem sem fermentum eros, vitae auctor neque lacus in nisi. Suspendisse potenti. Maecenas et scelerisque elit, in tincidunt quam. Sed eu tincidunt quam. Nullam justo ex, imperdiet a imperdiet et, fermentum sit amet eros. Aenean quis tempus sem. Pellentesque accumsan magna mi, ut mollis velit sagittis id. Etiam quis ipsum orci. Fusce purus ante, accumsan a lobortis at, venenatis eu nisl. Praesent ornare sed ante placerat accumsan. Suspendisse tempus dignissim fermentum. Nunc a leo ac lacus sodales iaculis eu vitae mi. In feugiat ante at massa finibus cursus. Suspendisse posuere fringilla ornare. Mauris elementum ac quam id convallis. Vestibulum non elit quis urna volutpat aliquam a eu lacus. + +Aliquam vestibulum imperdiet neque, suscipit aliquam elit ultrices bibendum. Suspendisse ultrices pulvinar cursus. Morbi risus nisi, cursus consequat rutrum vitae, molestie sed dui. Fusce posuere, augue quis dignissim aliquam, nisi ipsum porttitor ante, quis fringilla nisl turpis ac nisi. Nulla varius enim eget lorem vehicula gravida. Donec finibus malesuada leo nec semper. Proin ac enim eros. Vivamus non tincidunt nisi, vel tristique lorem. + +Nunc consequat ex id eros dignissim, id rutrum risus laoreet. Sed euismod non erat eu ultricies. Etiam vehicula gravida lacus ut porta. Vestibulum eu eros quis nunc aliquet luctus. Cras quis semper ligula. Nullam gravida vehicula quam sed porta. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In porta cursus vulputate. Quisque porta a nisi eget cursus. Aliquam risus leo, luctus ac magna in, efficitur cursus magna. In condimentum non mi id semper. Donec interdum ante eget commodo maximus. + +Vivamus sit amet vestibulum lectus. Fusce tincidunt mi sapien, dictum sollicitudin diam vulputate in. Integer fringilla consequat mollis. Cras aliquet consequat felis eget feugiat. Nunc tempor cursus arcu, vitae ornare nunc varius et. Vestibulum et tortor vel ante viverra porttitor. Nam at tortor ullamcorper, facilisis augue quis, tristique erat. Aenean ut euismod nibh. Quisque eu tincidunt est, nec euismod eros. + +Proin vehicula nibh non viverra egestas. Phasellus sem dolor, ultricies ac sagittis tristique, lacinia a purus. Vestibulum in ante eros. Pellentesque lacus nulla, tristique vitae interdum vel, malesuada ac diam. Aenean bibendum posuere turpis in accumsan. Ut est nulla, ullamcorper quis turpis at, viverra sagittis mauris. Sed in interdum purus. Praesent scelerisque nibh eget sem euismod, ut imperdiet mi venenatis. Vivamus pulvinar orci sed dapibus auctor. Nulla facilisi. Vestibulum tincidunt erat nec porttitor egestas. Mauris quis risus ante. Nulla facilisi. + +Aliquam ullamcorper ornare lobortis. Phasellus quis sem et ipsum mollis malesuada sed in ex. Ut aliquam ex eget metus finibus maximus. Proin suscipit mauris eu nibh lacinia, quis feugiat dui dapibus. Nam sed libero est. Aenean vulputate orci sit amet diam faucibus, eu sagittis sapien volutpat. Nam imperdiet felis turpis, at pretium odio pulvinar in. Sed vestibulum id eros nec ultricies. Sed quis aliquam tortor, vitae ullamcorper tellus. Donec egestas laoreet eros, id suscipit est rutrum nec. Sed auctor nulla eget metus aliquam, ut condimentum enim elementum. + +Aliquam suscipit non turpis sit amet bibendum. Fusce velit ligula, euismod et maximus at, luctus sed neque. Quisque pretium, nisl at ullamcorper finibus, lectus leo mattis sapien, vel euismod mauris diam ullamcorper ex. Nulla ut risus finibus, lacinia ligula at, auctor erat. Mauris consectetur sagittis ligula vel dapibus. Nullam libero libero, lobortis aliquam libero vel, venenatis ultricies leo. Duis porttitor, nibh congue fermentum posuere, erat libero pulvinar tortor, a pellentesque nunc ipsum vel sem. Nullam volutpat, eros sit amet facilisis consectetur, ipsum est vehicula massa, non vestibulum neque elit in mauris. Nunc hendrerit ipsum non enim bibendum, vitae rhoncus mi egestas. Etiam ullamcorper massa vel nisl sagittis, nec bibendum arcu malesuada. Aenean aliquet turpis justo, a consectetur arcu mollis convallis. Etiam tellus ipsum, ultricies vitae lorem et, ornare facilisis orci. Praesent fringilla justo urna, vel mollis neque pulvinar vestibulum. + +Donec non iaculis erat. Aliquam et mi sed nunc pulvinar ultricies in ut ipsum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Praesent feugiat lacus ac dignissim semper. Phasellus vitae quam nisi. Morbi vel diam ultricies risus lobortis ornare. Fusce maximus et ligula quis iaculis. Sed congue ex eget felis convallis, sit amet hendrerit elit tempor. Donec vehicula blandit ante eget commodo. Vestibulum eleifend diam at feugiat euismod. Etiam magna tellus, dignissim eget fermentum vel, vestibulum vitae mauris. Nam accumsan et erat id sagittis. Donec lacinia, odio ut ornare ultricies, dolor velit accumsan tortor, non finibus erat tellus quis ligula. Nunc quis metus in leo volutpat ornare vulputate eu nisl. + +Donec quis viverra ex. Nullam id feugiat mauris, eu fringilla nulla. Vestibulum id maximus elit. Cras elementum elit sed felis lobortis, eget sagittis nisi hendrerit. Vivamus vitae elit neque. Donec vulputate lacus ut libero ultrices accumsan. Vivamus accumsan nulla orci, in dignissim est laoreet sagittis. Proin at commodo velit. Curabitur in velit felis. Aliquam erat volutpat. Sed consequat, nulla et cursus sodales, nisi lacus mattis risus, quis eleifend erat ex nec turpis. Sed suscipit ultrices lorem in hendrerit. + +Morbi vitae lacus nec libero ornare tempus eu et diam. Suspendisse magna ipsum, fermentum vel odio quis, molestie aliquam urna. Fusce mollis turpis a eros accumsan porttitor. Pellentesque rhoncus dolor sit amet magna rutrum, et dapibus justo tempor. Sed purus nisi, maximus vitae fringilla eu, molestie nec urna. Fusce malesuada finibus pretium. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec sed aliquet eros. Pellentesque luctus diam ante, eget euismod nisl aliquet eu. Sed accumsan elit purus, tempor varius ligula tempus nec. Curabitur ornare leo suscipit suscipit fermentum. Morbi eget nulla est. Maecenas faucibus interdum tristique. + +Etiam ut elit eros. Nulla pharetra suscipit molestie. Nulla facilisis bibendum nisl non molestie. Curabitur turpis lectus, facilisis vel diam non, vulputate ultrices mauris. Aenean placerat aliquam convallis. Suspendisse sed scelerisque tellus. Vivamus lacinia neque eget risus cursus suscipit. Proin consequat dolor vel neque tempor, eu aliquam sem scelerisque. Duis non eros a purus malesuada pharetra non et nulla. Suspendisse potenti. Mauris libero eros, finibus vel nulla id, sagittis dapibus ante. Proin iaculis sed nunc et cursus. + +Quisque accumsan lorem sit amet lorem aliquet euismod. Curabitur fermentum rutrum posuere. Etiam ultricies, sem id pellentesque suscipit, urna magna lacinia eros, quis efficitur risus nisl at lacus. Nulla quis lacus tortor. Mauris placerat ex in dolor tincidunt, vel aliquet nisi pretium. Cras iaculis risus vitae pellentesque aliquet. Quisque a enim imperdiet, ullamcorper arcu vitae, rutrum risus. Nullam consectetur libero at felis fringilla, nec congue nibh dignissim. Nam et lobortis felis, eu pellentesque ligula. Aenean facilisis, ligula non imperdiet maximus, massa orci gravida sapien, at sagittis lacus nisl in lacus. Nulla quis mauris luctus, scelerisque felis consequat, tempus risus. Fusce auctor nisl non nulla luctus molestie. Maecenas sapien nisl, auctor non dolor et, iaculis scelerisque lorem. Suspendisse egestas enim aliquet, accumsan mauris nec, posuere quam. Nulla iaculis dui dui, sit amet vestibulum erat ultricies ac. + +Cras eget dolor erat. Proin at nisl ut leo consectetur ultricies vel ut arcu. Nulla in felis malesuada, ullamcorper tortor et, convallis massa. Nunc urna justo, ornare in nibh vitae, hendrerit condimentum libero. Etiam vitae libero in purus venenatis fringilla. Nullam velit nulla, consequat ut turpis non, egestas hendrerit nibh. Duis tortor turpis, interdum non ante ac, cursus accumsan lectus. Cras pharetra bibendum augue quis dictum. Sed euismod vestibulum justo. Proin porta lobortis purus. Duis venenatis diam tortor, sit amet condimentum eros rhoncus a. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc at magna nec diam lobortis efficitur sit amet ut lacus. Nulla quis orci tortor. Pellentesque tempus velit a odio finibus porta. + +Proin feugiat mauris a tellus scelerisque convallis. Maecenas libero magna, blandit nec ultrices id, congue vel mi. Aliquam lacinia, quam vel condimentum convallis, tortor turpis aliquam odio, sed blandit libero lacus et eros. In eleifend iaculis magna ac finibus. Praesent auctor facilisis tellus in congue. Sed molestie lobortis dictum. Nam quis dignissim augue, vel euismod lorem. Curabitur posuere dapibus luctus. Donec ultricies dictum lectus, quis blandit arcu commodo ac. Aenean tincidunt ligula in nunc imperdiet dignissim. Curabitur egestas sollicitudin sapien ut semper. Aenean nec dignissim lacus. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec aliquam dictum vehicula. Donec tortor est, volutpat non nisi nec, varius gravida ex. Nunc vel tristique nunc, vitae mattis nisi. Nunc nec luctus ex, vitae tincidunt lectus. In hac habitasse platea dictumst. Curabitur lobortis ex eget tincidunt tempor. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut a vehicula mi. + +Fusce eu libero finibus, interdum nulla a, placerat neque. Cras bibendum tempor libero nec feugiat. Cras ut sodales eros. Proin viverra, massa sit amet viverra egestas, neque nisl porta ex, sit amet hendrerit libero ligula vel urna. Mauris suscipit lacus id justo rhoncus suscipit. Etiam vel libero tellus. Maecenas non diam molestie, condimentum tellus a, bibendum enim. Mauris aliquet imperdiet tellus, eget sagittis dolor. Sed blandit in neque et luctus. Cras elementum sagittis nunc, vel mollis lorem euismod et. Donec posuere at lacus eget suscipit. + +Nulla nunc mi, pretium non massa vel, tempor semper magna. Nunc a leo pulvinar, tincidunt nunc at, dignissim mi. Aliquam erat volutpat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut viverra nulla a nisl finibus, at hendrerit ligula ullamcorper. Donec a lorem semper, tempor magna et, lobortis libero. Mauris id sapien leo. Donec dignissim, quam vitae porttitor dignissim, quam justo mattis dui, vel consequat odio elit quis orci. Etiam nec pretium neque, sit amet pretium orci. Duis ac tortor venenatis, feugiat purus non, feugiat nunc. Proin scelerisque nisl in turpis aliquam vulputate. + +Praesent sed est semper, fringilla lorem vitae, tincidunt nibh. Cras eros metus, auctor at mauris sit amet, sodales semper orci. Nunc a ornare ex. Curabitur bibendum arcu congue urna vulputate egestas. Vestibulum finibus id risus et accumsan. Aenean ut volutpat tellus. Aenean tincidunt malesuada urna sit amet vestibulum. Mauris vel tellus dictum, varius lacus quis, dictum arcu. + +Aenean quis metus eu erat feugiat cursus vel at ligula. Proin dapibus sodales urna, id euismod lectus tempus id. Pellentesque ex ligula, convallis et erat vel, vulputate condimentum nisl. Pellentesque pharetra nulla quis massa eleifend hendrerit. Praesent sed massa ipsum. Maecenas vehicula dolor massa, id sodales urna faucibus et. Mauris ac quam non massa tincidunt feugiat et at lacus. Fusce libero massa, vulputate vel scelerisque non, mollis in leo. Ut sit amet ultricies odio. Suspendisse in sapien viverra, facilisis purus ut, pretium libero. + +Vivamus tristique pharetra molestie. Nam a volutpat purus. Praesent consequat gravida nisi, ac blandit nisi suscipit ut. Quisque posuere, ligula a ultrices laoreet, ligula nunc vulputate libero, ut rutrum erat odio tincidunt justo. Sed vitae leo at leo fringilla bibendum. Vestibulum ut augue nec dolor auctor accumsan. Praesent laoreet id eros pulvinar commodo. Suspendisse potenti. Ut pharetra, mauris vitae blandit fringilla, odio ante tincidunt lorem, sit amet tempor metus diam ut turpis. + +Praesent quis egestas arcu. Nullam at porta arcu. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi vulputate ligula malesuada ligula luctus, vulputate tempus erat bibendum. Nunc ullamcorper non lectus at euismod. Etiam nibh felis, tincidunt a metus vel, pellentesque rhoncus neque. Etiam at diam in erat luctus interdum. Nunc vel ipsum pulvinar, sollicitudin lacus ac, tempus urna. Etiam vel lacinia sapien. Pellentesque sagittis velit vel mi efficitur iaculis. Integer euismod sit amet urna in sagittis. Cras eleifend ut nibh in facilisis. Donec et lacus vitae nunc placerat sodales. Nulla sed hendrerit ligula, at dapibus sapien. + +Praesent at iaculis ex. Curabitur est purus, cursus a faucibus quis, dictum id velit. Donec dignissim fringilla viverra. Nunc mauris felis, laoreet sit amet sagittis at, vestibulum in libero. Maecenas quis orci turpis. Quisque ut nibh vitae magna mollis consequat id at mauris. Aliquam eu odio eget nulla bibendum sodales. Quisque vel orci eleifend nisi pretium lacinia. Suspendisse eget risus eget mi volutpat molestie eget quis lacus. Duis nisi libero, tincidunt nec nulla id, faucibus cursus felis. + +Donec tempor eget risus pellentesque molestie. Phasellus porta neque vel arcu egestas, nec blandit velit fringilla. Nullam porta faucibus justo vitae laoreet. Pellentesque viverra id nunc eu varius. Nulla pulvinar lobortis iaculis. Etiam vestibulum odio nec velit tristique, a tristique nisi mattis. In sed fringilla orci, vitae efficitur odio. Quisque dui odio, ornare eget velit at, lacinia consequat libero. Quisque lectus nulla, aliquet eu leo in, porta rutrum diam. Donec nec mattis neque. Nam rutrum, odio ac eleifend bibendum, dolor arcu rutrum neque, eget porta elit tellus a lacus. Sed massa metus, sollicitudin et sapien eu, finibus tempus orci. Proin et sapien sit amet erat molestie interdum. In quis rutrum velit, faucibus ultrices tellus. + +Sed sagittis sed justo eget tincidunt. Maecenas ut leo sagittis, feugiat magna et, viverra velit. Maecenas ex arcu, feugiat at consequat vitae, auctor eu massa. Integer egestas, enim vitae maximus convallis, est lectus pretium mauris, ac posuere lectus nisl quis quam. Aliquam tempus laoreet mi, vitae dapibus dolor varius dapibus. Suspendisse potenti. Donec sit amet purus nec libero dapibus tristique. Pellentesque viverra bibendum ligula. Donec sed felis et ex lobortis laoreet. Phasellus a fringilla libero, vitae malesuada nulla. Pellentesque blandit mattis lacus, et blandit tortor laoreet consequat. Suspendisse libero nunc, viverra sed fermentum in, accumsan egestas arcu. Proin in placerat elit. Sed interdum imperdiet malesuada. Suspendisse aliquet quis mauris eget sollicitudin. + +Vivamus accumsan tellus non erat volutpat, quis dictum dolor feugiat. Praesent rutrum nunc ac est mollis cursus. Fusce semper volutpat dui ut egestas. Curabitur sit amet posuere massa. Cras tincidunt nulla et mi mollis imperdiet. Suspendisse scelerisque ex id sodales vulputate. In nunc augue, pharetra in placerat eu, mattis id tellus. Vivamus cursus efficitur vehicula. Nulla aliquet vehicula aliquet. + +Sed cursus tellus sed porta pulvinar. Sed vitae nisi neque. Nullam aliquet, lorem et efficitur scelerisque, arcu diam aliquam felis, sed pulvinar lorem odio et turpis. Praesent convallis pulvinar turpis eu iaculis. Aliquam nec gravida mi. Curabitur eu nibh tempor, blandit justo in, ultrices felis. Fusce placerat metus non mi sagittis rutrum. Morbi sed dui fringilla, sagittis mauris eget, imperdiet nunc. Phasellus hendrerit sem elit, id hendrerit libero auctor sit amet. Integer sodales elit sit amet consequat cursus. + +Nam semper est eget nunc mollis, in pellentesque lectus fringilla. In finibus vel diam id semper. Nunc mattis quis erat eu consectetur. In hac habitasse platea dictumst. Nullam et ipsum vestibulum ex pulvinar ultricies sit amet id velit. Aenean suscipit mi tortor, a lobortis magna viverra non. Nulla condimentum aliquet ante et ullamcorper. Pellentesque porttitor arcu a posuere tempus. Aenean lacus quam, imperdiet eu justo vitae, pretium efficitur ex. Duis id purus id magna rhoncus ultrices id eu risus. Nunc dignissim et libero id dictum. + +Quisque a tincidunt neque. Phasellus commodo mi sit amet tempor fringilla. Ut rhoncus, neque non porttitor elementum, libero nulla egestas augue, sed fringilla sapien felis ac velit. Phasellus viverra rhoncus mollis. Nam ullamcorper leo vel erat laoreet luctus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus semper a metus a cursus. Nulla sed orci egestas, efficitur purus ac, malesuada tellus. Aenean rutrum velit at tellus fermentum mollis. Aliquam eleifend euismod metus. + +In hac habitasse platea dictumst. Vestibulum volutpat neque vitae porttitor laoreet. Nam at tellus consequat, sodales quam in, pulvinar arcu. Maecenas varius convallis diam, ac lobortis tellus pellentesque quis. Maecenas eget augue massa. Nullam volutpat nibh ac justo rhoncus, ut iaculis tellus rutrum. Fusce efficitur efficitur libero quis condimentum. Curabitur congue neque non tincidunt tristique. Fusce eget tempor ex, at pellentesque odio. Praesent luctus dictum vestibulum. Etiam non orci nunc. Vivamus vitae laoreet purus, a lobortis velit. Curabitur tincidunt purus ac lectus elementum pellentesque. Quisque sed tincidunt est. + +Sed vel ultrices massa, vitae ultricies justo. Cras finibus mauris nec lacus tempus dignissim. Cras faucibus maximus velit, eget faucibus orci luctus vehicula. Nulla massa nunc, porta ac consequat eget, rhoncus non tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce sed maximus metus, vel imperdiet ipsum. Ut scelerisque lectus at blandit porttitor. Ut vulputate nunc pharetra, aliquet sapien ac, sollicitudin sapien. Aenean eget ante lorem. Nam accumsan venenatis tellus id dignissim. + +Curabitur fringilla, magna non maximus dapibus, nulla sapien vestibulum lectus, sit amet semper dolor neque vitae nisl. Nunc ultrices vehicula augue sed iaculis. Maecenas nec diam mollis, suscipit orci et, vestibulum ante. Pellentesque eu nisl tortor. Nunc eleifend, lacus quis volutpat volutpat, nisi mi molestie sem, quis mollis ipsum libero a tellus. Ut viverra dolor mattis convallis interdum. Sed tempus nisl at nunc scelerisque aliquet. Quisque tempor tempor lorem id feugiat. Nullam blandit lectus velit, vitae porta lacus tincidunt a. Vivamus sit amet arcu ultrices, tincidunt mi quis, viverra quam. Aenean fringilla libero elementum lorem semper, quis pulvinar eros gravida. Nullam sodales blandit mauris, sed fermentum velit fermentum sit amet. Donec malesuada mauris in augue sodales vulputate. Vestibulum gravida turpis id elit rhoncus dignissim. Integer non congue lorem, eu viverra orci. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec at dolor magna. Aliquam consectetur erat augue, id iaculis velit pharetra ac. Integer rutrum venenatis dignissim. Integer non sodales elit. Curabitur ut magna ut nibh feugiat aliquam ac ut risus. Morbi nibh quam, aliquam id placerat nec, vestibulum eget velit. Suspendisse at dignissim quam. Vivamus aliquet sem sed nisl volutpat, ut cursus orci ultrices. Aliquam ultrices lacinia enim, vitae aliquet neque. + +Quisque scelerisque finibus diam in mattis. Cras cursus auctor velit. Aliquam sem leo, fermentum et maximus et, molestie a libero. Aenean justo elit, rutrum a ornare id, egestas eget enim. Aenean auctor tristique erat. Curabitur condimentum libero lacus, nec consequat orci vestibulum sed. Fusce elit ligula, blandit vitae sapien vitae, dictum ultrices risus. Nam laoreet suscipit sapien, at interdum velit faucibus sit amet. Duis quis metus egestas lectus elementum posuere non nec libero. Aliquam a dolor bibendum, facilisis nunc a, maximus diam. Vestibulum suscipit tristique magna, non dignissim turpis sodales sed. Nunc ornare, velit ac facilisis fringilla, dolor mi consectetur lorem, vitae finibus erat justo suscipit urna. Maecenas sit amet eros erat. Nunc non arcu ornare, suscipit lorem eget, sodales mauris. Aliquam tincidunt, quam nec mollis lacinia, nisi orci fermentum libero, consequat eleifend lectus quam et sapien. Vestibulum a quam urna. + +Cras arcu leo, euismod ac ullamcorper at, faucibus sed massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus porttitor velit in enim interdum, non commodo metus ornare. Morbi vel lorem quis nisl luctus tristique quis vitae nisl. Suspendisse condimentum tortor enim, nec eleifend ipsum euismod et. Sed gravida quam ut tristique lacinia. Mauris eu interdum ipsum, ac ultrices odio. Nullam auctor tellus a risus porttitor vehicula. Nulla blandit euismod dictum. In pharetra, enim iaculis pulvinar interdum, dui nunc placerat nunc, sit amet pretium lectus nulla vitae quam. Phasellus quis enim sollicitudin, varius nulla id, ornare purus. Donec quam lacus, vestibulum quis nunc ac, mollis dictum nisi. Cras ut mollis elit. Maecenas ultrices ligula at risus faucibus scelerisque. Etiam vitae porttitor purus. Curabitur blandit lectus urna, ut hendrerit tortor feugiat ut. + +Phasellus fringilla, sapien pellentesque commodo pharetra, ante libero aliquam tellus, ut consectetur augue libero a sapien. Maecenas blandit luctus nisl eget aliquet. Maecenas vitae porta dolor, faucibus laoreet sapien. Suspendisse lobortis, ipsum sed vehicula aliquam, elit purus scelerisque dui, rutrum consectetur diam odio et lorem. In nec lacinia metus. Donec viverra libero est, vel bibendum erat condimentum quis. Donec feugiat purus leo. In laoreet vitae felis a porttitor. Mauris ullamcorper, lacus id condimentum suscipit, neque magna pellentesque arcu, eget cursus neque tellus id metus. Curabitur volutpat ac orci vel ultricies. + +Sed ut finibus erat. Sed diam purus, varius non tincidunt quis, ultrices sit amet ipsum. Donec et egestas nulla. Suspendisse placerat nisi at dui laoreet iaculis. Aliquam aliquet leo at augue faucibus molestie. Nullam lacus augue, hendrerit sed nisi eu, faucibus porta est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam ut leo aliquet sem fermentum rutrum quis ac justo. Integer placerat aliquam nisl ut sagittis. Proin erat orci, lobortis et sem eget, eleifend fringilla augue. Mauris varius laoreet arcu, sed tincidunt felis. Pellentesque venenatis lorem odio, id pulvinar velit molestie feugiat. Donec mattis lacus sed eleifend pulvinar. + +Sed condimentum ex in tincidunt hendrerit. Etiam eget risus lacinia, euismod nibh eu, pellentesque quam. Proin elit eros, convallis id mauris ac, bibendum ultrices lectus. Morbi venenatis, purus id fermentum consequat, nunc libero tincidunt ligula, non dictum ligula orci nec quam. Nulla nec ultrices lorem. Aenean maximus augue vel dictum pharetra. Etiam turpis urna, pellentesque quis malesuada eu, molestie faucibus felis. + +Vestibulum pharetra augue ut quam blandit congue in nec risus. Proin eu nibh eu dui eleifend porta vitae id lectus. Proin lacus nibh, lobortis sed ligula vitae, interdum lobortis erat. Suspendisse potenti. In sollicitudin quis sapien ut aliquet. Mauris ac nulla arcu. Fusce tristique justo quis lectus mollis, eu volutpat lectus finibus. Vivamus venenatis facilisis ex ut vestibulum. + +Etiam varius lobortis purus, in hendrerit elit tristique at. In tempus, augue vestibulum fermentum gravida, ligula tellus vulputate arcu, eu molestie ex sapien at purus. Vestibulum nec egestas metus. Duis pulvinar quam nec consequat interdum. Aenean non dapibus lacus. Aliquam sit amet aliquet nulla. Sed venenatis volutpat purus nec convallis. Phasellus aliquet semper sodales. Cras risus sapien, condimentum auctor urna a, pulvinar ornare nisl. Sed tincidunt felis elit, ut elementum est bibendum ac. Morbi interdum justo vel dui faucibus condimentum. + +Sed convallis eu sem at tincidunt. Nullam at auctor est, et ullamcorper ipsum. Pellentesque eget ante ante. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer euismod, sapien sed dapibus ornare, nibh enim maximus lacus, lacinia placerat urna quam quis felis. Morbi accumsan id nisl ut condimentum. Donec bibendum nisi est, sed volutpat lorem rhoncus in. Vestibulum ac lacinia nunc, eget volutpat magna. Integer aliquam pharetra ipsum, id placerat nunc volutpat quis. Etiam urna diam, rhoncus sit amet varius vel, euismod vel sem. Nullam vel molestie urna. Vivamus ornare erat at venenatis euismod. Suspendisse potenti. Fusce diam justo, tincidunt vel sem at, commodo faucibus nisl. Duis gravida efficitur diam, vel sagittis erat pulvinar ut. + +Quisque vel pharetra felis. Duis efficitur tortor dolor, vitae porttitor erat fermentum sed. Sed eu mi purus. Etiam dignissim tortor eu tempus molestie. Aenean pretium erat enim, in hendrerit ante hendrerit at. Sed ut risus vel nunc venenatis ultricies quis in lacus. Pellentesque vitae purus euismod, placerat risus non, ullamcorper augue. Quisque varius quam ligula, nec aliquet ex faucibus vitae. Quisque rhoncus sit amet leo tincidunt mattis. Cras id mauris eget purus pretium gravida sit amet eu augue. Aliquam dapibus odio augue, id lacinia velit pulvinar eu. + +Mauris fringilla, tellus nec pharetra iaculis, neque nisi ultrices massa, et tincidunt sem dui sed mi. Curabitur erat lorem, venenatis quis tempus lacinia, tempus sit amet nunc. Aliquam at neque ac metus commodo dictum quis vitae justo. Phasellus eget lacus tempus, blandit lorem vel, rutrum est. Aenean pharetra sem ut augue lobortis dignissim. Sed rhoncus at nulla id ultrices. Cras id condimentum felis. In suscipit luctus vulputate. Donec tincidunt lacus nec enim tincidunt sollicitudin ut quis enim. Nam at libero urna. Praesent sit amet massa vitae massa ullamcorper vehicula. + +Nullam bibendum augue ut turpis condimentum bibendum. Proin sit amet urna hendrerit, sodales tortor a, lobortis lectus. Integer sagittis velit turpis, et tincidunt nisi commodo eget. Duis tincidunt elit finibus accumsan cursus. Aenean dignissim scelerisque felis vel lacinia. Nunc lacinia maximus luctus. In hac habitasse platea dictumst. Vestibulum eget urna et enim tempor tempor. Nam feugiat, felis vel vestibulum tempus, orci justo viverra diam, id dapibus lorem justo in ligula. + +Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In ac pellentesque sem. Vestibulum lacinia magna dui, eu lacinia augue placerat et. Maecenas pulvinar congue est. Pellentesque commodo dui non pulvinar scelerisque. Etiam interdum est posuere sem bibendum, ac commodo magna dictum. Cras ipsum turpis, rhoncus nec posuere vitae, laoreet a arcu. Integer ac massa sit amet enim placerat lacinia sed ultrices arcu. Suspendisse sem nibh, luctus sit amet volutpat in, pellentesque eu metus. Ut gravida neque eget mi accumsan tempus. Nam sit amet aliquet nibh. + +Pellentesque a purus cursus nulla hendrerit congue quis et odio. Aenean hendrerit, leo ullamcorper sagittis hendrerit, erat dui molestie quam, sed condimentum lacus risus sed tellus. Morbi a dapibus lectus, ut feugiat ex. Phasellus pretium quam et sapien mollis, vel iaculis dui dignissim. Sed ullamcorper est turpis, a viverra lorem consectetur in. Aenean aliquet nibh non cursus rutrum. Suspendisse at tristique urna, id lobortis urna. In hac habitasse platea dictumst. Phasellus libero velit, rutrum sed tellus nec, dapibus tincidunt ligula. Quisque vel dui venenatis, consequat nisl ut, lacinia ipsum. Phasellus vitae magna pellentesque, lobortis est id, faucibus quam. Nam eleifend faucibus dui vel pellentesque. + +Etiam ut est non lacus tincidunt interdum. Maecenas sed massa urna. Quisque ut nibh tortor. Pellentesque felis ipsum, tempor finibus ipsum et, euismod pretium metus. Donec sit amet est ipsum. Quisque rhoncus justo non finibus elementum. Nulla nec lectus ac tortor placerat fringilla. Phasellus ac ultrices nunc, eu efficitur nisl. Nulla rhoncus nunc vitae ante dictum tincidunt. Nunc ultrices, massa sit amet malesuada dignissim, lectus lacus consequat sapien, non eleifend metus sem in eros. Phasellus mauris ante, dictum sit amet suscipit ac, rhoncus eget nisi. Phasellus at orci mollis, imperdiet neque eget, faucibus nulla. In at purus massa. Pellentesque quis rutrum lectus. + +Integer eu faucibus turpis, sit amet mollis massa. Vestibulum id nulla commodo, rutrum ipsum sed, semper ante. Phasellus condimentum orci nec nibh convallis, ac maximus orci ullamcorper. Maecenas vitae sollicitudin mi. Integer et finibus lectus, et condimentum ligula. Donec elementum tristique quam vitae dapibus. Morbi euismod ipsum in tristique ullamcorper. + +Duis fermentum non enim eu auctor. Quisque lacinia nibh vehicula nibh posuere, eu volutpat turpis facilisis. Ut ac faucibus nulla. Sed eleifend quis ex et pellentesque. Vestibulum sollicitudin in libero id fringilla. Phasellus dignissim purus consequat, condimentum dui sit amet, condimentum ante. Pellentesque ac consectetur massa, quis sagittis est. Nulla maximus tristique risus accumsan convallis. Curabitur imperdiet ac lacus a ultrices. Nulla facilisi. Sed quis quam quis lectus placerat lobortis vel sed turpis. In mollis dui id neque iaculis, ut aliquet tellus malesuada. Proin at luctus odio, vel blandit sapien. Praesent dignissim tortor vehicula libero fringilla, nec ultrices erat suscipit. Maecenas scelerisque purus in dapibus fermentum. + +Curabitur magna odio, mattis in tortor ut, porttitor congue est. Vestibulum mollis lacinia elementum. Fusce maximus erat vitae nunc rutrum lobortis. Integer ligula eros, auctor vel elit non, posuere luctus lacus. Maecenas quis auctor massa. Ut ipsum lacus, efficitur posuere euismod et, hendrerit efficitur est. Phasellus fringilla, quam id tincidunt pretium, nunc dui sollicitudin orci, eu dignissim nisi metus ut magna. Integer lobortis interdum dolor, non bibendum purus posuere et. Donec non lectus aliquet, pretium dolor eu, cursus massa. Sed ut dui sapien. In sed vestibulum massa. Pellentesque blandit, dui non sodales vehicula, orci metus mollis nunc, non pharetra ex tellus ac est. Mauris sagittis metus et fermentum pretium. Nulla facilisi. Quisque quis ante ut nulla placerat mattis ut quis nisi. + +Sed quis nulla ligula. Quisque dignissim ligula urna, sed aliquam purus semper at. Suspendisse potenti. Nunc massa lectus, pharetra vehicula arcu bibendum, imperdiet sodales ipsum. Nam ac sapien diam. Mauris iaculis fringilla mattis. Pellentesque tempus eros sit amet justo volutpat mollis. Phasellus ac turpis ipsum. Morbi vel ante elit. Aenean posuere quam consequat velit varius suscipit. Donec tempor quam ut nibh cursus efficitur. + +Morbi molestie dolor nec sem egestas suscipit. Etiam placerat pharetra lectus, et ullamcorper risus tristique in. Sed faucibus ullamcorper lectus eget fringilla. Maecenas malesuada hendrerit congue. Sed eget neque a erat placerat tincidunt. Aliquam vitae dignissim turpis. Fusce at placerat magna, a laoreet lectus. Maecenas a purus nec diam gravida fringilla. Nam malesuada euismod ante non vehicula. In faucibus bibendum leo, faucibus posuere nisl pretium quis. Fusce finibus bibendum finibus. Vestibulum eu justo maximus, hendrerit diam nec, dignissim sapien. Aenean dolor lacus, malesuada quis vestibulum ac, venenatis ac ipsum. Cras a est id nunc finibus facilisis. Cras lacinia neque et interdum vehicula. Suspendisse vulputate tellus elit, eget tempor dui finibus vel. + +Cras sed pretium odio. Proin hendrerit elementum felis in tincidunt. Nam sed turpis vel justo molestie accumsan condimentum eu nunc. Praesent lobortis euismod rhoncus. Nulla vitae euismod nibh, quis mattis mi. Fusce ultrices placerat porttitor. Duis sem ipsum, pellentesque sit amet odio a, molestie vulputate mauris. + +Duis blandit mollis ligula, sit amet mattis ligula finibus sit amet. Nunc a leo molestie, placerat diam et, vestibulum leo. Suspendisse facilisis neque purus, nec pellentesque ligula fermentum nec. Aenean malesuada mauris lorem, eu blandit arcu pulvinar quis. Duis laoreet urna lacus, non maximus arcu rutrum ultricies. Nulla augue dolor, suscipit eu mollis eu, aliquam condimentum diam. Ut semper orci luctus, pharetra turpis at, euismod mi. Nulla leo diam, finibus sit amet purus sed, maximus dictum lorem. Integer eu mi id turpis laoreet rhoncus. + +Integer a mauris tincidunt, finibus orci ut, pretium mauris. Nulla molestie nunc mi, id finibus lorem elementum sed. Proin quis laoreet ante. Integer nulla augue, commodo id molestie quis, rutrum ut turpis. Suspendisse et tortor turpis. Sed ut pharetra massa. Pellentesque elementum blandit sem, ut elementum tellus egestas a. Fusce eu purus nibh. + +Cras dignissim ligula scelerisque magna faucibus ullamcorper. Proin at condimentum risus, auctor malesuada quam. Nullam interdum interdum egestas. Nulla aliquam nisi vitae felis mollis dictum. Suspendisse dapibus consectetur tortor. Ut ut nisi non sem bibendum tincidunt. Vivamus suscipit leo quis gravida dignissim. + +Aliquam interdum, leo id vehicula mollis, eros eros rhoncus diam, non mollis ligula mi eu mauris. Sed ultrices vel velit sollicitudin tincidunt. Nunc auctor metus at ligula gravida elementum. Praesent interdum eu elit et mollis. Duis egestas quam sit amet velit dignissim consequat. Aliquam ac turpis nec nunc convallis sagittis. Fusce blandit, erat ac fringilla consectetur, dolor eros sodales leo, vel aliquet risus nisl et diam. Aliquam luctus felis vitae est eleifend euismod facilisis et lacus. Sed leo tellus, auctor eu arcu in, volutpat sagittis nisl. Pellentesque nisl ligula, placerat vel ullamcorper at, vulputate ac odio. Morbi ac faucibus orci, et tempus nulla. Proin rhoncus rutrum dolor, in venenatis mauris. Suspendisse a fermentum augue, non semper mi. Nunc eget pretium neque. Phasellus augue erat, feugiat ac aliquam congue, rutrum non sapien. Pellentesque ac diam gravida, consectetur felis at, ornare neque. + +Nullam interdum mattis sapien quis porttitor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Phasellus aliquet rutrum ipsum id euismod. Maecenas consectetur massa et mi porta viverra. Nunc quam nibh, dignissim vitae maximus et, ullamcorper nec lorem. Nunc vitae justo dapibus, luctus lacus vitae, pretium elit. Maecenas et efficitur leo. Curabitur mauris lectus, placerat quis vehicula vitae, auctor ut urna. Quisque rhoncus pharetra luctus. In hac habitasse platea dictumst. Integer sit amet metus nec eros malesuada aliquam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi hendrerit mi ac leo aliquam, sit amet ultricies libero commodo. Mauris dapibus purus metus, sit amet viverra nibh imperdiet et. Nullam porta nulla tellus, quis vehicula diam imperdiet non. Vivamus enim massa, bibendum in fermentum in, ultrices at ex. + +Suspendisse fermentum id nibh eget accumsan. Duis dapibus bibendum erat ut sollicitudin. Aliquam nec felis risus. Pellentesque rhoncus ligula id sem maximus mollis sed nec massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus ipsum ipsum, sodales sed enim id, convallis faucibus eros. Donec ultricies dictum tincidunt. Cras vitae nibh arcu. Pellentesque cursus, sapien nec consequat fermentum, ipsum ante suscipit dui, imperdiet hendrerit est nisl eu massa. Quisque vitae sem ligula. Aenean iaculis metus ut mauris interdum laoreet. Vivamus sed gravida dolor. + +Morbi nulla metus, porttitor sed eros sit amet, efficitur efficitur est. In vel nisl urna. Ut aliquet tellus at congue convallis. Phasellus imperdiet lobortis sollicitudin. Integer sodales, sem eu ultricies pharetra, erat erat porttitor odio, eget dapibus libero ipsum eget velit. Phasellus gravida nulla nisl, eu pharetra mi auctor vel. Sed blandit pharetra velit, ut egestas libero placerat non. Aliquam a interdum quam. Proin at tortor nec dui sollicitudin tempus sed vestibulum elit. Nunc non sollicitudin velit. + +Aenean consequat diam velit, sed rutrum tortor faucibus dictum. Quisque at semper augue. Duis ut est eget mi ornare bibendum id et ligula. Phasellus consequat tortor non leo pulvinar posuere. Proin vestibulum eleifend felis, in hendrerit tortor sollicitudin eu. Phasellus hendrerit, lacus vel laoreet interdum, dui tortor consequat justo, commodo ultricies arcu felis vitae enim. Vivamus eu sapien at leo suscipit rutrum eu at justo. Aenean et dolor a libero ullamcorper posuere. Integer laoreet placerat nisi in vulputate. Mauris laoreet eget risus sed cursus. Donec scelerisque neque a libero eleifend hendrerit. Nulla varius condimentum nunc sit amet fermentum. Aliquam lorem ex, varius nec mollis ut, ultrices in neque. Morbi sit amet porta leo. Integer iaculis fermentum lacus in vestibulum. + +Ut gravida, tellus ut maximus ultrices, erat est venenatis nisl, vitae pretium massa ex ac magna. Sed non purus eget ligula aliquet volutpat non quis arcu. Nam aliquam tincidunt risus, sit amet fringilla sapien vulputate ut. Mauris luctus suscipit pellentesque. Nunc porttitor dapibus ex quis tempus. Ut ullamcorper metus a eros vulputate, vitae viverra lectus convallis. Mauris semper imperdiet augue quis tincidunt. Integer porta pretium magna, sed cursus sem scelerisque sollicitudin. Nam efficitur, nibh pretium eleifend vestibulum, purus diam posuere sem, in egestas mauris augue sit amet urna. + +Vestibulum tincidunt euismod massa in congue. Duis interdum metus non laoreet fringilla. Donec at ligula congue, tincidunt nunc non, scelerisque nunc. Donec bibendum magna non est scelerisque feugiat at nec neque. Ut orci tortor, tempus eget massa non, dignissim faucibus dolor. Nam odio risus, accumsan pretium neque eget, accumsan dignissim dui. In ut neque auctor, scelerisque tellus sed, ullamcorper nisi. Suspendisse varius cursus quam at hendrerit. Vivamus elit libero, sagittis vitae sem ac, vulputate iaculis ligula. + +Sed lobortis laoreet purus sit amet rutrum. Pellentesque feugiat non leo vel lacinia. Quisque feugiat nisl a orci bibendum vestibulum. In et sollicitudin urna. Morbi a arcu ac metus faucibus tempus. Nam eu imperdiet sapien, suscipit mattis tortor. Aenean blandit ipsum nisi, a eleifend ligula euismod at. Integer tincidunt pharetra felis, mollis placerat mauris hendrerit at. Curabitur convallis, est sit amet luctus volutpat, massa lacus cursus augue, sed eleifend magna quam et risus. Aliquam lobortis tincidunt metus vitae porttitor. Suspendisse potenti. Aenean ullamcorper, neque id commodo luctus, nulla nunc lobortis quam, id dapibus neque dui nec mauris. Etiam quis lorem quis elit commodo ornare. Ut pharetra purus ultricies enim ultrices efficitur. Proin vehicula tincidunt molestie. Mauris et placerat sem. + +Aliquam erat volutpat. Suspendisse velit turpis, posuere ac lacus eu, lacinia laoreet velit. Sed interdum felis neque, id blandit sem malesuada sit amet. Ut sagittis justo erat, efficitur semper orci tempor sed. Donec enim massa, posuere varius lectus egestas, pellentesque posuere mi. Cras tincidunt ut libero sed mattis. Suspendisse quis magna et tellus posuere interdum vel at purus. Pellentesque fringilla tristique neque, id aliquet tellus ultricies non. Duis ut tellus vel odio lobortis vulputate. + +Integer at magna ac erat convallis vestibulum. Sed lobortis porttitor mauris. Fusce varius lorem et volutpat pulvinar. Aenean ac vulputate lectus, vitae consequat velit. Suspendisse ex dui, varius ut risus ut, dictum scelerisque sem. Vivamus urna orci, volutpat ut convallis ac, venenatis vitae urna. In hac habitasse platea dictumst. Etiam eu purus arcu. Aenean vulputate leo urna, vel tristique dui sagittis euismod. Suspendisse non tellus efficitur ante rhoncus volutpat at et sapien. + +Sed dapibus accumsan porttitor. Phasellus facilisis lectus finibus ligula dignissim, id pulvinar lectus feugiat. Nullam egestas commodo nisi posuere aliquet. Morbi sit amet tortor sagittis, rutrum dui nec, dapibus sapien. Sed posuere tortor tortor, interdum auctor magna varius vitae. Vestibulum id sagittis augue. Curabitur fermentum arcu sem, eu condimentum quam rutrum non. Phasellus rutrum nibh quis lectus rhoncus pretium. Curabitur dictum interdum elit. Vestibulum maximus sodales imperdiet. Mauris auctor nec purus sed venenatis. In in urna purus. + +Duis placerat molestie suscipit. Morbi a elit id purus efficitur consequat. Nunc ac commodo turpis. Etiam sit amet lacus a ipsum tempus venenatis sed vel nibh. Duis elementum aliquam mi sed tristique. Morbi ligula tortor, semper ac est vel, lobortis maximus erat. Curabitur ipsum felis, laoreet vel condimentum eget, ullamcorper sit amet mauris. Nulla facilisi. Nam at purus sed mi egestas placerat vitae vel magna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse at dignissim diam. Phasellus consectetur eget neque vel viverra. Donec sollicitudin mattis dolor vel malesuada. Vivamus vehicula leo neque, vitae fermentum leo posuere et. Praesent dui est, finibus sit amet tristique quis, pharetra vel nibh. + +Duis nulla leo, accumsan eu odio eget, sagittis semper orci. Quisque ullamcorper ligula quam, commodo porttitor mauris ullamcorper eu. Cras varius sagittis felis in aliquam. Duis sodales risus ac justo vehicula, nec mattis diam lacinia. Cras eget lectus ipsum. Ut commodo, enim vitae malesuada hendrerit, ex dolor egestas lectus, sit amet hendrerit metus diam nec est. Vestibulum tortor metus, lobortis sit amet ante eget, tempor molestie lacus. In molestie et urna et semper. Mauris mollis, sem non hendrerit condimentum, sapien nisi cursus est, non suscipit quam justo non metus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam enim est, porta ac feugiat vitae, rutrum in lorem. Duis vehicula tortor ut posuere maximus. + +Nullam vestibulum non tellus sed commodo. Quisque mattis elit sit amet sapien sollicitudin, ut condimentum nisl congue. Aenean sagittis massa vel elit faucibus fermentum. Donec tincidunt nisi nec nisl sodales pellentesque. Mauris congue congue ligula ut suscipit. Vivamus velit tortor, tempor et gravida eget, fermentum sit amet ante. Nullam fringilla, lorem at ultrices cursus, urna neque ornare dolor, eu lacinia orci enim sed nibh. Ut a ullamcorper lectus, id mattis purus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean maximus sollicitudin posuere. Nunc at augue lacus. Aenean efficitur leo sit amet lacinia efficitur. + +Quisque venenatis quam mi, in pharetra odio vulputate eu. In vel nisl pulvinar, pulvinar ligula ut, sodales risus. Sed efficitur lectus at vestibulum tincidunt. Vestibulum eu ullamcorper elit. Fusce vestibulum magna enim, et tempor lacus posuere vitae. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer leo elit, luctus nec mattis sit amet, sollicitudin in turpis. + +Proin convallis venenatis leo, vitae tristique erat iaculis nec. Nulla facilisi. Duis porttitor, sapien et bibendum vulputate, sem libero sodales lacus, non malesuada felis erat ut libero. Nam non felis semper, finibus est a, mattis mauris. Praesent nec eros quam. Nulla hendrerit, augue consectetur eleifend ultricies, purus mi condimentum nulla, eget dapibus est nunc sed libero. Nullam elementum dui erat, vitae luctus libero sollicitudin et. Nulla odio magna, placerat in augue eu, dapibus imperdiet odio. Suspendisse imperdiet metus sit amet rhoncus dapibus. Cras at enim et urna vehicula cursus eu a mauris. Integer magna ante, eleifend ac placerat vitae, porta at nisi. Cras eget malesuada orci. Curabitur nunc est, vulputate id viverra et, dignissim sed odio. Curabitur non mattis sem. Sed bibendum, turpis vitae vehicula faucibus, nunc quam ultricies lectus, vitae viverra felis turpis at libero. + +Nullam ut egestas ligula. Proin hendrerit justo a lectus commodo venenatis. Nulla facilisi. Ut cursus lorem quis est bibendum condimentum. Aenean in tristique odio. Fusce tempor hendrerit ipsum. Curabitur mollis felis justo, quis dapibus erat auctor vel. Sed augue lectus, finibus ut urna quis, ullamcorper vestibulum dui. Etiam molestie aliquam tempor. Integer mattis sollicitudin erat, et tristique elit varius vel. Mauris a ex justo. + +Nam eros est, imperdiet non volutpat rutrum, pellentesque accumsan ligula. Duis sit amet turpis metus. Aenean in rhoncus metus, ac fringilla ex. Suspendisse condimentum egestas purus, ut pharetra odio vulputate vel. Duis tincidunt massa a placerat ultrices. Mauris ultricies nibh sit amet condimentum malesuada. Duis tincidunt id ipsum sed congue. + +Praesent eu ex augue. Nullam in porta ligula. In tincidunt accumsan arcu, in pellentesque magna tristique in. Mauris eleifend libero ac nisl viverra faucibus. Nam sollicitudin dolor in commodo hendrerit. Cras at orci metus. Ut quis laoreet orci. Vivamus ultrices leo pellentesque tempor aliquet. Maecenas ut eros vitae purus placerat vestibulum. Etiam vitae gravida dolor, quis rhoncus diam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. + +Suspendisse fringilla lacinia sagittis. Integer tincidunt consectetur tristique. Morbi non orci convallis, congue sapien quis, vulputate nunc. Donec a libero vel magna elementum facilisis non quis mi. Mauris posuere tellus non ipsum ultrices elementum. Vivamus massa velit, facilisis quis placerat aliquet, aliquet nec leo. Praesent a maximus sem. Sed neque elit, feugiat vel quam non, molestie sagittis nunc. Etiam luctus nunc ac mauris scelerisque, nec rhoncus lacus convallis. Nunc pharetra, nunc ac pulvinar aliquam, ex ipsum euismod augue, nec porttitor lacus turpis vitae neque. Fusce bibendum odio id tortor faucibus pellentesque. Sed ac porta nibh, eu gravida erat. + +Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam quis ullamcorper felis. Nulla mattis sagittis ante ac tincidunt. Integer ac felis efficitur, viverra libero et, facilisis ligula. Suspendisse a metus a massa rhoncus posuere. Phasellus suscipit ligula ut lacus facilisis, ac pellentesque ex tempor. Quisque consectetur massa mi, ac molestie libero dictum quis. Proin porttitor ligula quis erat tincidunt venenatis. Proin congue nunc sed elit gravida, nec consectetur lectus sodales. Etiam tincidunt convallis ipsum at vestibulum. Quisque maximus enim et mauris porttitor, et molestie magna tristique. Morbi vitae metus elit. Maecenas sed volutpat turpis. Aliquam vitae dolor vestibulum, elementum purus eget, dapibus nibh. Nullam egestas dui ac rutrum semper. + +Etiam hendrerit est metus, et condimentum metus aliquam ac. Pellentesque id neque id ipsum rhoncus vulputate. Aliquam erat nisl, posuere sit amet ligula ac, fermentum blandit felis. Vivamus fermentum mi risus, non lacinia purus viverra id. Aenean ac sapien consequat, finibus mauris nec, porta sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed quis consectetur ex, dignissim bibendum nulla. Phasellus ac libero at quam vehicula euismod non eu leo. Phasellus a sapien augue. + +Maecenas ligula dui, bibendum vitae mauris et, auctor laoreet felis. Duis non libero a mi semper mattis. Quisque consequat luctus massa, quis tristique eros auctor feugiat. Maecenas sodales euismod neque vitae facilisis. Nullam laoreet imperdiet velit at pellentesque. Etiam massa odio, facilisis a consequat vitae, placerat vel magna. Nunc sagittis eros nec urna fringilla, pulvinar vestibulum nibh scelerisque. Sed magna metus, cursus eu consequat et, pharetra a est. Suspendisse elementum neque a dui malesuada lacinia. Donec sed ipsum volutpat, cursus urna id, ullamcorper arcu. Maecenas laoreet nisl eget velit egestas sollicitudin. Etiam nisl turpis, mollis id dignissim vitae, tristique vehicula ante. Maecenas eget placerat est, at rutrum augue. Vivamus faucibus lacinia ullamcorper. Sed pulvinar urna sodales ante sodales, at gravida leo dictum. + +Morbi maximus, quam a lobortis bibendum, enim felis varius elit, ac vehicula elit nisl ut lacus. Quisque ut arcu augue. Praesent id turpis quam. Sed sed arcu eros. Maecenas at cursus lorem, ac eleifend nisi. Fusce mattis felis at commodo pharetra. Praesent ac commodo ipsum. Quisque finibus et eros vitae tincidunt. In hac habitasse platea dictumst. Praesent purus ipsum, luctus lobortis ornare quis, auctor eget justo. Nam vel enim sollicitudin, faucibus tortor eu, sagittis eros. Ut nec consectetur erat. Donec ultricies malesuada ligula, a hendrerit sapien volutpat in. Maecenas sed enim vitae sapien pulvinar faucibus. + +Proin semper nunc nibh, non consequat neque ullamcorper vel. Maecenas lobortis sagittis blandit. Aenean et arcu ultricies turpis malesuada malesuada. Ut quam ex, laoreet ut blandit cursus, feugiat vitae dolor. Etiam ex lacus, scelerisque vel erat vel, efficitur tincidunt magna. Morbi tristique lacinia dolor, in egestas magna ultrices vitae. Integer ultrices leo ac tempus venenatis. Praesent ac porta tortor. Vivamus ornare blandit tristique. Nulla rutrum finibus pellentesque. In non dui elementum, fermentum ipsum vel, varius magna. Pellentesque euismod tortor risus, ac pellentesque nisl faucibus eget. + +Vivamus eu enim purus. Cras ultrices rutrum egestas. Sed mollis erat nibh, at posuere nisl luctus nec. Nunc vulputate, sapien id auctor molestie, nisi diam tristique ante, non convallis tellus nibh at orci. Morbi a posuere purus, in ullamcorper ligula. Etiam elementum sit amet dui imperdiet iaculis. Proin vitae tincidunt ipsum, sit amet placerat lectus. Curabitur commodo sapien quam, et accumsan lectus fringilla non. Nullam eget accumsan enim, ac pharetra mauris. Sed quis tristique velit, vitae commodo nisi. Duis turpis dui, maximus ut risus at, finibus consequat nunc. Maecenas sed est accumsan, aliquet diam in, facilisis risus. Curabitur vehicula rutrum auctor. Nam iaculis risus pulvinar maximus viverra. Nulla vel augue et ex sagittis blandit. + +Ut sem nulla, porta ac ante ac, posuere laoreet eros. Donec sodales posuere justo a auctor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras mollis at orci hendrerit porta. Nullam sodales tortor tortor, non lacinia diam finibus id. Duis libero orci, suscipit ac odio et, dictum consequat ipsum. Pellentesque eu ligula sagittis, volutpat eros at, lacinia lorem. Cras euismod tellus in iaculis tempor. Quisque accumsan, magna a congue venenatis, ante ipsum aliquam lectus, at egestas enim nunc at justo. Quisque sem purus, viverra ut tristique ut, maximus id enim. Etiam quis placerat sem. In sollicitudin, lacus eu rutrum mollis, nulla eros luctus elit, vel dapibus urna purus nec urna. Phasellus egestas massa quam, ac molestie erat hendrerit a. Praesent ultrices neque ut turpis molestie auctor. Etiam molestie placerat purus, et euismod erat aliquam in. Morbi id suscipit justo. + +Proin est ante, consequat at varius a, mattis quis felis. Sed accumsan nibh sit amet ipsum elementum posuere. Vestibulum bibendum id diam sit amet gravida. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi nec dolor vel ipsum dignissim hendrerit vel non ipsum. Praesent facilisis orci quis elit auctor lobortis. Phasellus cursus risus lectus, vel lobortis libero dapibus in. Quisque tristique tempus leo a pulvinar. Pellentesque a magna tincidunt, pellentesque massa nec, laoreet orci. Morbi congue ornare dolor quis commodo. Phasellus massa nisi, tincidunt at eros dictum, hendrerit lobortis urna. Maecenas porta, magna id mattis molestie, nibh tellus lobortis sem, eget tincidunt ipsum quam eu turpis. + +Ut gravida orci risus, vel rutrum mauris vehicula id. Etiam bibendum, neque a placerat condimentum, ex orci imperdiet lectus, quis dapibus arcu lacus eget lectus. Sed consequat non mi sit amet venenatis. Fusce vestibulum erat libero, eget hendrerit risus vulputate sollicitudin. Integer sed eleifend felis. Donec commodo, sem eu mattis placerat, urna odio aliquam tellus, et laoreet justo tellus eget erat. Fusce sed suscipit tortor. Nam hendrerit nibh ac nunc auctor lacinia. Pellentesque placerat condimentum ipsum, eget semper tortor hendrerit vel. Nullam non urna eu lacus pellentesque congue ut id eros. + +Nunc finibus leo in rhoncus tristique. Sed eu ipsum nec nisl egestas faucibus eget a felis. Pellentesque vitae nisi in nulla accumsan fermentum. Sed venenatis feugiat eleifend. Fusce porttitor varius placerat. Aliquam aliquet lacus sit amet mattis mollis. Sed vel nulla quis dolor suscipit vehicula ac viverra lorem. Duis viverra ipsum eget nulla ullamcorper fermentum. Mauris tincidunt arcu quis quam fringilla ornare. Donec et iaculis tortor. Nam ultricies libero vel ipsum aliquet efficitur. Morbi eget dolor aliquam, tempus sapien eget, viverra ante. Donec varius mollis ex, sed efficitur purus euismod interdum. Quisque vel sapien non neque tincidunt semper. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. + +Suspendisse sit amet purus leo. Fusce lectus lorem, aliquam ac nulla eget, imperdiet ornare eros. Nullam sem augue, varius in nisi non, sollicitudin pellentesque ante. Etiam eu odio condimentum, tempor libero et, egestas arcu. Cras pellentesque eleifend aliquet. Pellentesque non blandit ligula. Ut congue viverra rhoncus. Phasellus mattis mi ac eros placerat, eu feugiat tellus ultrices. Aenean mollis laoreet libero eu imperdiet. Cras sed pulvinar mi, ac vehicula ligula. Vestibulum sit amet ex massa. In a egestas eros. + +Mauris pretium ipsum risus, venenatis cursus ante imperdiet id. Praesent eu turpis nec risus feugiat maximus ullamcorper ac lectus. Integer placerat at mi vel dapibus. Vestibulum fermentum turpis sit amet turpis viverra, id aliquet diam suscipit. Nam nec ex sed ante ullamcorper pharetra quis sit amet risus. Sed ac faucibus velit, id feugiat nibh. Nullam eget ipsum ex. Vivamus tincidunt non nunc non faucibus. Quisque bibendum viverra facilisis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at nisi hendrerit quam suscipit egestas. Curabitur laoreet maximus ultricies. Duis ut tellus ac augue molestie dictum. + +Suspendisse rhoncus iaculis erat, ut ullamcorper est tristique eget. Donec auctor nec risus at gravida. Vivamus volutpat vulputate tellus, vel ultricies eros suscipit eget. Ut pulvinar id mi eu tempus. Morbi malesuada augue in dui varius, nec blandit neque vehicula. Donec ornare nec nisl in mollis. Morbi enim nisi, rhoncus nec est id, dapibus tempus urna. Ut id elit a felis vestibulum consectetur. Duis lectus quam, pharetra sit amet diam sed, posuere vestibulum erat. Fusce vitae maximus massa. Nullam id metus tempus, iaculis risus eu, lobortis urna. Quisque in congue urna. Pellentesque placerat neque in augue dapibus, non varius ex malesuada. Curabitur ut eleifend libero. Fusce vitae ligula luctus, fermentum enim vitae, ultrices erat. + +Sed viverra augue turpis, scelerisque egestas sapien mattis eu. Duis laoreet magna at ex pharetra dapibus. Praesent eget odio vel quam venenatis dictum. Nulla in sollicitudin dolor. Mauris lobortis nec eros vel rhoncus. Vestibulum porta viverra venenatis. Curabitur vel scelerisque quam, a egestas velit. Praesent volutpat tincidunt magna at laoreet. + +Cras nec lorem odio. Pellentesque quis dui urna. Praesent at tellus ac lectus scelerisque placerat nec eu risus. Vestibulum sit amet mattis ligula. Vivamus sed nisi at leo elementum accumsan at sit amet arcu. Aenean mattis tellus nec leo gravida, eget hendrerit nisl faucibus. Mauris pellentesque luctus condimentum. Maecenas pretium sapien nunc, eget commodo dolor maximus id. Mauris vestibulum accumsan massa a dictum. Phasellus interdum quam ligula, ut maximus diam blandit aliquam. Nunc vitae ex eu erat condimentum consectetur. Maecenas interdum condimentum volutpat. + +Donec et enim a libero rutrum laoreet. Praesent a condimentum sem, at tincidunt quam. In vel molestie risus. Sed urna dui, molestie vitae mollis laoreet, tempor quis lectus. Praesent vitae auctor est, et aliquet nunc. Curabitur vulputate blandit nulla, at gravida metus. Maecenas gravida dui eu iaculis tristique. Pellentesque posuere turpis nec auctor eleifend. Suspendisse bibendum diam eu tellus lobortis, et laoreet quam congue. In hac habitasse platea dictumst. Morbi dictum neque velit, eget rutrum eros ultrices sit amet. + +Phasellus fermentum risus pharetra consectetur bibendum. Donec magna tortor, lacinia vitae nibh quis, aliquet pretium lorem. Donec turpis nisi, pretium eu enim volutpat, mattis malesuada augue. Nullam vel tellus iaculis, sollicitudin elit eget, tincidunt lacus. Fusce elementum elementum felis et iaculis. Suspendisse porta eros nec neque malesuada, in malesuada ante sollicitudin. Vivamus bibendum viverra molestie. + +Integer feugiat, erat nec convallis aliquam, velit felis congue erat, molestie eleifend tellus erat in tellus. Nunc et justo purus. Donec egestas fermentum dui non feugiat. Quisque in sapien sagittis, gravida quam id, iaculis lectus. Cras sagittis rhoncus bibendum. Fusce quis metus in velit scelerisque tincidunt at non ipsum. Vivamus efficitur ante eu odio vulputate, vitae ultricies risus vehicula. Proin eget odio eu sem tincidunt feugiat vel id lorem. + +Vestibulum sit amet nulla dignissim, euismod mi in, fermentum tortor. Donec ut aliquet libero, lacinia accumsan velit. Donec et nulla quam. Nullam laoreet odio nec nunc imperdiet, a congue eros venenatis. Quisque nec tellus sit amet neque interdum posuere. Duis quis mi gravida, tincidunt diam convallis, ultricies augue. Mauris consequat risus non porttitor congue. Ut in ligula consequat, viverra nunc a, eleifend enim. Duis ligula urna, imperdiet nec facilisis et, ornare eu ex. Proin lobortis lectus a lobortis porttitor. Nulla leo metus, egestas eu libero sed, pretium faucibus felis. Vestibulum non sem tortor. Nam cursus est leo. Vivamus luctus enim odio, non interdum sem dapibus a. Aenean accumsan consequat lectus in imperdiet. + +Donec vehicula laoreet ipsum in posuere. Quisque vel quam imperdiet, sollicitudin nisi quis, suscipit velit. Morbi id sodales mauris. Curabitur tellus arcu, feugiat sed dui sit amet, sodales sagittis libero. Aenean vel suscipit metus, non placerat leo. Vestibulum quis nulla elit. Proin scelerisque non ante ut commodo. Interdum et malesuada fames ac ante ipsum primis in faucibus. + +Sed non urna dolor. Suspendisse convallis mi porta pulvinar ultrices. Suspendisse quam ipsum, hendrerit non scelerisque molestie, interdum dictum nunc. Morbi condimentum condimentum turpis eu luctus. Pellentesque sagittis sollicitudin odio, sed ultricies felis ornare sit amet. Sed ultrices ex leo, a tincidunt nisl gravida sed. Nullam ornare accumsan porta. Praesent consectetur id est nec sollicitudin. + +In hac habitasse platea dictumst. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed sed ultrices nibh. Duis accumsan suscipit eros, a dictum odio tempus sit amet. Aenean imperdiet erat ac lacus finibus, scelerisque cursus massa imperdiet. Mauris molestie risus ut lacinia posuere. Nulla et sodales purus. Maecenas orci erat, placerat in tristique quis, placerat in mi. + +Donec sollicitudin pellentesque odio in feugiat. Morbi eu dolor ut mauris congue sollicitudin. Aliquam erat volutpat. Nulla id varius dui. Curabitur finibus urna ante, consectetur interdum nisi volutpat a. Quisque quis mi tristique, consequat tellus eget, rutrum sapien. Vivamus vitae tellus vulputate, rutrum ex eu, vulputate sem. Suspendisse viverra lorem tellus, vel interdum orci gravida quis. Ut laoreet arcu at mi ullamcorper finibus. Duis porta sagittis vestibulum. Sed commodo nisl vitae urna sollicitudin, nec lacinia est sodales. Curabitur imperdiet sodales dui sed iaculis. Sed ac tellus maximus, eleifend quam sit amet, feugiat elit. Aenean viverra, dui at mattis varius, est odio vestibulum sapien, sit amet mollis libero massa nec velit. Etiam quis sodales justo. + +Ut ultricies, sem eget sodales feugiat, nunc arcu congue elit, ac tempor justo massa nec purus. Maecenas enim nunc, pharetra eget dictum sit amet, tempus pellentesque velit. Suspendisse venenatis ligula in nulla mattis, et imperdiet ex tincidunt. Etiam vulputate, tellus et ultrices suscipit, enim velit laoreet massa, vitae congue odio enim ac urna. Morbi quam lorem, iaculis ac varius sagittis, euismod quis dolor. In ut dui eu purus feugiat consectetur. Vestibulum cursus velit quis lacus pellentesque iaculis. Cras in risus sed mauris porta rutrum. Nulla facilisi. Nullam eu bibendum est, non pellentesque lectus. Sed imperdiet feugiat lorem, quis convallis ante auctor in. Maecenas justo magna, scelerisque sit amet tellus eget, varius elementum risus. Duis placerat et quam sed varius. + +Duis nec nibh vitae nibh dignissim mollis quis sed felis. Curabitur vitae quam placerat, venenatis purus ut, euismod nisl. Curabitur porttitor nibh eu pulvinar ullamcorper. Suspendisse posuere nec ipsum ac dapibus. Cras convallis consectetur urna. Phasellus a nibh in dolor lacinia posuere id eget augue. In eu pharetra lorem, vitae cursus lacus. Aliquam tincidunt nibh lectus. Aenean facilisis ultricies posuere. Sed ut placerat orci. Curabitur scelerisque gravida blandit. Maecenas placerat ligula eget suscipit fringilla. Mauris a tortor justo. Aliquam hendrerit semper mollis. Phasellus et tincidunt libero. Etiam vel quam libero. + +Quisque aliquet tempor ex. Ut ante sem, vehicula at enim vel, gravida porta elit. Etiam vitae lacus a neque lobortis consectetur. Mauris sed interdum odio. Mauris elementum ex blandit tempor cursus. Integer in enim in leo viverra elementum. Fusce consectetur metus et sem rutrum, mattis euismod diam semper. Nunc sed ipsum vel urna consequat vehicula. Donec cursus pretium lorem, vestibulum pretium felis commodo sit amet. Nam blandit felis enim, eget gravida ex faucibus a. In nec neque massa. Etiam laoreet posuere ipsum. Praesent volutpat nunc dolor, ac vulputate magna facilisis non. Aenean congue turpis vel lectus sollicitudin tristique. Sed nec consequat purus, non vehicula quam. Etiam ultricies, est ac dictum tincidunt, turpis turpis pretium massa, a vulputate libero justo at nibh. + +Aliquam erat volutpat. Cras ultrices augue ac sollicitudin lobortis. Curabitur et aliquet purus. Duis feugiat semper facilisis. Phasellus lobortis cursus velit, a sollicitudin tortor. Nam feugiat sapien non dapibus condimentum. Morbi at mi bibendum, commodo quam at, laoreet enim. Integer eu ultrices enim. Sed vestibulum eu urna ut dictum. Curabitur at mattis leo, sed cursus massa. Aliquam porttitor, felis quis fermentum porttitor, justo velit feugiat nulla, eget condimentum sem dui ut sapien. + +In fringilla elit eu orci aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut eget fringilla tellus. Curabitur fermentum, mi et condimentum suscipit, elit neque bibendum dui, et hendrerit nunc metus id ipsum. Morbi placerat mi in hendrerit congue. Ut feugiat mauris eget scelerisque viverra. Vivamus sit amet erat dictum, sagittis lectus nec, pulvinar lorem. Sed non enim ac dui sollicitudin aliquet. Quisque ut lacus dolor. Fusce hendrerit malesuada euismod. Nulla faucibus vel mauris eu mollis. Mauris est diam, fringilla ac arcu feugiat, efficitur volutpat turpis. Aliquam venenatis cursus massa sed porttitor. Ut ac finibus enim, in tincidunt sapien. + +Nunc faucibus semper turpis a lacinia. Phasellus gravida, libero vel pulvinar ornare, ex sem tincidunt lectus, sit amet convallis augue risus at tortor. Quisque sit amet ipsum id nulla posuere vestibulum. Pellentesque scelerisque mauris vel leo viverra sodales. Nulla viverra aliquam ex, ut rutrum enim fermentum venenatis. Aenean eget dapibus ex, eget faucibus metus. Vestibulum volutpat leo in diam semper, eget porta magna suscipit. Sed sit amet nulla blandit, aliquam dolor ac, gravida velit. Sed vel velit viverra, maximus est id, convallis justo. + +Curabitur nulla ante, vulputate at libero vel, ullamcorper rutrum nibh. Pellentesque porttitor eu mauris id mattis. Duis vulputate augue elit, eget interdum justo pretium vel. Maecenas eu vulputate arcu, eget posuere purus. Suspendisse viverra a velit dictum eleifend. Suspendisse vitae dapibus diam. Donec vehicula justo in ante interdum, eu luctus diam placerat. Vivamus convallis ipsum eu orci suscipit, sed fermentum enim euismod. Maecenas faucibus elit vitae ex ornare tristique. Donec vestibulum nec elit sit amet porttitor. Aenean tempor lectus eget tortor hendrerit luctus. Nullam interdum vitae lectus vel feugiat. Cras in risus non magna consectetur lobortis. Sed faucibus enim quis gravida convallis. + +Phasellus eget massa sit amet libero ultrices suscipit. Vivamus at risus sapien. Nam mollis nunc eget velit dictum maximus. Sed pellentesque, nunc ac fringilla lacinia, quam enim mattis ex, sed euismod tortor metus eu neque. Ut mattis nisl ut lectus rhoncus, sodales bibendum eros porta. Nulla porttitor enim nec diam sagittis, eget porta velit efficitur. Vestibulum ultricies eros neque. Phasellus rutrum suscipit enim, in interdum ante gravida vitae. Sed in sagittis diam, non commodo velit. + +Morbi hendrerit odio orci, nec tincidunt odio rhoncus nec. Mauris neque velit, vehicula a lorem at, suscipit tristique dui. Sed finibus, nisl in mattis convallis, turpis neque sodales lacus, eu porta enim magna non diam. Nam commodo sodales risus consectetur malesuada. In eget elementum justo. Phasellus sit amet massa imperdiet, dapibus nunc sit amet, suscipit orci. Fusce condimentum laoreet feugiat. Ut ut viverra ante. Praesent bibendum interdum commodo. Nulla mollis nisi a est ornare volutpat. Sed at ligula eu nisi dapibus tempus. Proin cursus vestibulum justo, nec efficitur justo dignissim vel. Nunc quis maximus eros. + +Cras viverra, diam a tristique mattis, libero felis vulputate tellus, a ornare felis leo a dui. Nulla ante nulla, finibus ut tellus ut, blandit pharetra nibh. Proin eleifend fermentum ex, eget auctor libero vulputate in. Nullam ultricies, mauris placerat pretium placerat, leo urna lobortis leo, vel placerat arcu libero sed mauris. Aliquam mauris ligula, ornare at urna at, eleifend gravida ligula. Vestibulum consectetur ut nulla non scelerisque. Donec ornare, sem nec elementum aliquam, urna nulla bibendum metus, eu euismod dui ligula ac est. Fusce laoreet erat eu ex lobortis, quis bibendum ligula interdum. Sed vel mi erat. Vivamus id lacus ac enim mattis tempor. Nunc ultricies pellentesque enim sed euismod. Fusce tincidunt convallis elit quis aliquam. Mauris nulla ipsum, sollicitudin quis diam ac, feugiat volutpat tellus. In nibh nibh, vulputate quis tincidunt quis, pulvinar eget magna. Pellentesque quis finibus dolor. Suspendisse viverra vitae lectus non eleifend. + +Nunc ut orci et sapien maximus semper. Nulla dignissim sem urna, ac varius lectus ultricies id. Quisque aliquet pulvinar pretium. In ultricies molestie tellus vehicula porta. Nam enim lorem, aliquam eget ex et, hendrerit volutpat quam. Maecenas diam lacus, pellentesque eget tempus ac, pharetra eu elit. Donec vel eros a sem facilisis vulputate. Nullam ac nisi vulputate, laoreet nisl ac, eleifend sem. Nullam mi massa, rhoncus sed pharetra interdum, tincidunt eget nunc. Aliquam viverra mattis posuere. Mauris et dui sed nisl sollicitudin fermentum quis ut arcu. Nam placerat eget orci at tincidunt. Curabitur vel turpis metus. Phasellus nibh nulla, fermentum scelerisque sem vel, gravida tincidunt velit. Pellentesque vel quam tempor, finibus massa pellentesque, condimentum dui. + +Donec at mattis neque. Etiam velit diam, consequat auctor mauris id, hendrerit faucibus metus. Maecenas ullamcorper eros a est sodales, ac consectetur odio scelerisque. Donec leo metus, imperdiet at pellentesque vel, feugiat id erat. Suspendisse at magna enim. Vestibulum placerat sodales lorem id sollicitudin. Aenean at euismod ligula, eget mollis diam. Phasellus pulvinar, orci nec pretium condimentum, est erat facilisis purus, quis feugiat augue elit aliquam nulla. Aenean vitae tortor id risus congue tincidunt. Sed dolor enim, mattis a ullamcorper id, volutpat ac leo. + +Proin vehicula feugiat augue, id feugiat quam sodales quis. Donec et ultricies massa, a lacinia nulla. Duis aliquam augue ornare euismod viverra. Ut lectus risus, rutrum sit amet efficitur a, luctus nec nisl. Cras volutpat ullamcorper congue. Sed vitae odio metus. Phasellus aliquet euismod varius. + +Nullam sem ex, malesuada ut magna ut, pretium mollis arcu. Nam porttitor eros cursus mi lacinia faucibus. Suspendisse aliquet eleifend iaculis. Maecenas sit amet viverra tortor. Nunc a mollis risus. Etiam tempus dolor in tortor malesuada mattis. Ut tincidunt venenatis est sit amet dignissim. Vestibulum massa enim, tristique sed scelerisque eu, fringilla ac velit. Donec efficitur quis urna sit amet malesuada. Vestibulum consequat ac ligula in dapibus. Maecenas massa massa, molestie non posuere nec, elementum ut magna. In nisi erat, mollis non venenatis eu, faucibus in justo. Morbi gravida non ex non egestas. Pellentesque finibus laoreet diam, eu commodo augue congue vitae. + +Aenean sem mi, ullamcorper dapibus lobortis vitae, interdum tincidunt tortor. Vivamus eget vulputate libero. Ut bibendum posuere lectus, vel tincidunt tortor aliquet at. Phasellus malesuada orci et bibendum accumsan. Aliquam quis libero vel leo mollis porta. Sed sagittis leo ac lacus dictum, ac malesuada elit finibus. Suspendisse pharetra luctus commodo. Vivamus ultricies a odio non interdum. Vivamus scelerisque tincidunt turpis quis tempor. Pellentesque tortor ligula, varius non nunc eu, blandit sollicitudin neque. Nunc imperdiet, diam et tristique luctus, ipsum ex condimentum nunc, sit amet aliquam justo velit sed libero. Duis vel suscipit ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed tincidunt neque vel massa ultricies, id dictum leo consequat. Curabitur lobortis ultricies tellus, eget mattis nisl aliquam sit amet. + +Proin at suscipit justo. Vivamus ut vestibulum nisl. Pellentesque enim odio, pharetra non magna sed, efficitur auctor magna. Praesent tincidunt ante quis ante hendrerit viverra. Pellentesque vel ipsum id magna vulputate efficitur. Sed nec neque accumsan, pulvinar sapien quis, euismod mauris. Donec condimentum laoreet sapien quis gravida. Quisque sed mattis purus. Vestibulum placerat vel neque maximus scelerisque. + +Vestibulum mattis quam quis efficitur elementum. Duis dictum dolor ac scelerisque commodo. Fusce sollicitudin nisi sit amet dictum placerat. Suspendisse euismod pharetra eleifend. In eros nisl, porttitor sed mauris at, consectetur aliquet mauris. Donec euismod viverra neque sed fermentum. Phasellus libero magna, accumsan ut ultricies vitae, dignissim eget metus. Donec tellus turpis, interdum eget maximus nec, hendrerit eget massa. Curabitur auctor ligula in iaculis auctor. In ultrices quam suscipit cursus finibus. Aenean id mi at dolor interdum iaculis vitae ut lorem. Nullam sed nibh fringilla, lacinia odio nec, placerat erat. In dui libero, viverra ac viverra ac, pellentesque sit amet turpis. + +Nulla in enim ex. Sed feugiat est et consectetur venenatis. Cras varius facilisis dui vel convallis. Vestibulum et elit eget tellus feugiat pellentesque. In ut ante eu purus aliquet posuere. Nulla nec ornare sem, sed luctus lorem. Nam varius iaculis odio, eget faucibus nisl ullamcorper in. Sed eget cursus felis, nec efficitur nisi. + +Vivamus commodo et sem quis pulvinar. Pellentesque libero ante, venenatis vitae ligula sit amet, ornare sollicitudin nulla. Mauris eget tellus hendrerit, pulvinar metus quis, tempor nisi. Proin magna ex, laoreet sed tortor quis, varius fermentum enim. Integer eu dolor dictum, vulputate tortor et, aliquet ligula. Vestibulum vitae justo id mauris luctus sollicitudin. Suspendisse eget auctor neque, sodales egestas lorem. Vestibulum lacinia egestas metus vitae euismod. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus ex tellus, volutpat nec pulvinar sit amet, condimentum vitae dui. Curabitur vel felis sodales, lacinia nunc iaculis, ullamcorper augue. Pellentesque consequat dolor quis eros efficitur malesuada. Nulla ut malesuada lectus. + +Morbi et tristique ante. Aliquam erat volutpat. Vivamus vitae dui nec turpis pellentesque fermentum. Quisque eget velit massa. Pellentesque tristique aliquam nisl, eu sollicitudin justo venenatis sed. Duis eleifend sem eros, ut aliquam libero porttitor id. Sed non nunc consequat, rhoncus diam eu, commodo erat. Praesent fermentum in lectus id blandit. Donec quis ipsum at justo volutpat finibus. Nulla blandit justo nulla, at mollis lacus consequat eget. Aenean sollicitudin quis eros ut ullamcorper. + +Pellentesque venenatis nulla ut mi aliquet feugiat. Cras semper vel magna nec pharetra. Integer mattis felis et sapien commodo imperdiet. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis quis luctus felis. Vestibulum justo nibh, aliquam non lectus vitae, molestie placerat justo. Donec lorem nibh, gravida sit amet hendrerit ac, maximus id ipsum. Nunc ac libero sodales risus eleifend sagittis. Phasellus est massa, lobortis elementum ex sed, scelerisque consectetur neque. Nunc faucibus neque id lorem malesuada, eget convallis ex mattis. + +Sed turpis tortor, fermentum non turpis id, posuere varius nibh. Donec iaculis lorem dui. Etiam eros ante, sodales eget venenatis at, consectetur eget risus. Curabitur non aliquam ante, a pretium justo. Maecenas tempor nisl tortor, vitae dictum nisi ultrices eu. Duis eget dui ultrices, porttitor lacus sed, lobortis purus. Quisque mattis elit nec neque sagittis, sed commodo leo blandit. Mauris sodales interdum eleifend. Vestibulum condimentum consectetur augue, id luctus diam convallis et. + +Nunc suscipit risus in justo accumsan, a placerat magna tincidunt. Proin a nisl ipsum. Sed libero dui, tristique in augue quis, auctor tristique risus. Sed porttitor ex augue, eu porta augue molestie a. Duis rhoncus purus libero, eu tempus turpis condimentum at. Sed mollis nisi id lectus placerat tincidunt. Maecenas non scelerisque elit, quis rutrum orci. Donec in tellus pharetra urna ornare lobortis. Phasellus id risus at nisi varius rutrum eu ut turpis. + +Duis dictum justo quis nisl porta, eget tincidunt magna suscipit. Sed velit massa, ullamcorper eu sodales ac, pretium a massa. Duis et rutrum tortor. Nulla accumsan hendrerit sapien, cursus volutpat eros egestas eget. Donec sollicitudin at ante quis sollicitudin. Aenean blandit feugiat diam, id feugiat eros faucibus eget. Donec viverra dolor vel justo scelerisque dignissim. Nulla semper sem nunc, rhoncus semper tellus ultricies sed. Duis in ornare diam. Donec vehicula feugiat varius. Maecenas ut suscipit est. Vivamus sem sem, finibus at dolor sit amet, euismod dapibus ligula. Vestibulum fringilla odio dapibus, congue massa eget, congue sem. Donec feugiat magna eget tortor lacinia scelerisque non et ipsum. + +Suspendisse potenti. Nunc convallis sollicitudin ex eget venenatis. Sed iaculis nibh ex, vel ornare ligula congue dignissim. Quisque sollicitudin dolor ac dui vestibulum, sit amet molestie nisi aliquet. Donec at risus felis. Aenean sollicitudin metus a feugiat porta. Aenean a tortor ut dolor cursus sagittis. Vivamus consectetur porttitor nunc in facilisis. Proin sit amet mi vel lectus consectetur ultrices. + +Sed cursus lectus vitae nunc tristique, nec commodo turpis dapibus. Pellentesque luctus ex id facilisis ornare. Morbi quis placerat dolor. Donec in lectus in arcu mattis porttitor ac sit amet metus. Cras congue mauris non risus sodales, vitae feugiat ipsum bibendum. Nulla venenatis urna sed libero elementum, a cursus lorem commodo. Mauris faucibus lobortis eros nec commodo. + +Nullam suscipit ligula ullamcorper lorem commodo blandit. Nulla porta nibh quis pulvinar placerat. Vivamus eu arcu justo. Vestibulum imperdiet est ut fermentum porttitor. Pellentesque consectetur libero in sapien efficitur scelerisque. Curabitur ac erat sit amet odio aliquet dignissim. Pellentesque mi sem, rhoncus et luctus at, porttitor rutrum lectus. Vestibulum sollicitudin sollicitudin suscipit. Aenean efficitur dolor non ultrices imperdiet. Donec vel sem ex. + +Sed convallis mauris aliquam rutrum cursus. Ut tempor porttitor sodales. Etiam eu risus ac augue gravida egestas et eu dolor. Proin id magna ex. Suspendisse quis lectus quis lorem ultricies tempus. Donec porttitor velit vitae tincidunt faucibus. Aliquam vitae semper nisi. Morbi ultrices, leo non pretium dapibus, dui libero pellentesque ex, vel placerat enim ante vitae dui. Nunc varius, sem sit amet sagittis lobortis, lectus odio scelerisque mauris, ut vestibulum orci magna quis neque. Sed id congue justo. Interdum et malesuada fames ac ante ipsum primis in faucibus. Mauris congue nisi est, malesuada mollis elit tincidunt sed. Curabitur sed ex sit amet felis tristique elementum vitae vel nibh. + +Etiam mollis pretium lobortis. Mauris augue lacus, efficitur at lacus sed, mollis tincidunt lectus. Aliquam erat volutpat. Donec at euismod elit, et mattis felis. Sed id lobortis urna. Morbi imperdiet vestibulum leo, sed maximus leo blandit eu. Aliquam semper lorem neque, nec euismod turpis mattis mollis. Quisque lobortis urna ultrices odio pretium, ac venenatis orci faucibus. Suspendisse bibendum odio ligula, sed lobortis massa pharetra nec. Donec turpis justo, iaculis at dictum ac, finibus eu libero. Maecenas quis porttitor mi, sit amet aliquet neque. + +Vivamus auctor vulputate ante, at egestas lorem. Donec eu risus in nulla mollis ultricies at et urna. Duis accumsan porta egestas. Ut vel euismod augue. Fusce convallis nulla ante, nec fringilla velit aliquet at. Nam malesuada dapibus ligula, a aliquam nibh scelerisque ac. Praesent malesuada neque et pellentesque interdum. Curabitur volutpat at turpis vitae tristique. Vivamus porttitor semper congue. Quisque suscipit lacus mi, rhoncus ultrices tortor auctor quis. Maecenas neque neque, molestie ac facilisis eget, luctus ac lorem. In ut odio ut lacus suscipit pulvinar vitae sed elit. Nulla imperdiet, sem quis euismod sagittis, dui erat luctus dolor, faucibus faucibus erat sem eget nunc. Nam accumsan placerat malesuada. Maecenas convallis finibus pulvinar. + +Cras at placerat tortor. Morbi facilisis auctor felis sit amet molestie. Donec sodales sed lorem vitae suscipit. Etiam fermentum pharetra ipsum, nec luctus orci gravida eu. Pellentesque gravida, est non condimentum tempus, mauris ligula molestie est, in congue dolor nisl vel sapien. Duis congue tempor augue, id rutrum eros porta dapibus. Etiam rutrum eget est eget vestibulum. Aenean mollis arcu vel consequat varius. Praesent at condimentum felis. Duis nec interdum nisl. Donec commodo lorem sed sapien scelerisque malesuada non eu urna. In blandit non ipsum at porta. Nam lobortis leo vitae dui auctor, non feugiat quam bibendum. Donec auctor lectus sagittis laoreet maximus. Maecenas rhoncus laoreet porttitor. Vestibulum porttitor augue ut lectus hendrerit, eget posuere mi gravida. + +Sed mattis ex in erat pulvinar, eu imperdiet magna dapibus. Etiam nisi nibh, tempus non tellus sit amet, mattis tempor odio. Quisque nec lorem feugiat, lobortis odio et, commodo nunc. Maecenas semper purus nisi, nec vehicula nibh eleifend vitae. Nulla fermentum a lectus at maximus. Phasellus finibus metus non euismod ultrices. Etiam a pulvinar ante. Quisque convallis nec metus sit amet facilisis. Praesent laoreet massa et sollicitudin laoreet. Vestibulum in mauris aliquet, convallis mi ut, elementum purus. Nulla purus nulla, sodales at hendrerit quis, tempus sed lectus. + +Nam ut laoreet neque, ut maximus nibh. Maecenas quis justo pellentesque, sollicitudin elit at, venenatis velit. Aenean nunc velit, vehicula scelerisque odio at, consectetur laoreet purus. Duis dui purus, malesuada quis ipsum sit amet, tempor interdum libero. Curabitur porta scelerisque sapien, vitae cursus diam condimentum eu. Phasellus sed orci quam. Nullam vitae dui quis purus tincidunt vestibulum. Curabitur quis nulla porta, cursus arcu non, auctor enim. Etiam sollicitudin ex id sem vehicula mollis. Morbi viverra laoreet tincidunt. Praesent ut semper dui. Nam sit amet pretium neque. Mauris vitae luctus diam, in lacinia purus. Maecenas ut placerat justo, ut porta felis. Integer eu mauris ante. + +Aenean porttitor tellus diam, tempor consequat metus efficitur id. Suspendisse ut felis at erat tempor dictum at nec sapien. Sed vestibulum interdum felis, ac mattis mauris porta in. Nunc et condimentum massa. Sed cursus dictum justo et luctus. Integer convallis enim nisl, a rutrum lectus ultricies in. Donec dapibus lacus at nulla dapibus, id sollicitudin velit hendrerit. Fusce a magna at orci mollis rutrum ac a dolor. Aliquam erat volutpat. Morbi varius porta nunc, sit amet sodales ex hendrerit commodo. Donec tincidunt tortor sapien, vitae egestas sapien vehicula eget. + +Suspendisse potenti. Donec pulvinar felis nec leo malesuada interdum. Integer posuere placerat maximus. Donec nibh ipsum, tincidunt vitae luctus vitae, bibendum at leo. Sed cursus nisl ut ex faucibus aliquet sed nec eros. Curabitur molestie posuere felis. Integer faucibus velit eget consequat iaculis. Mauris sed vulputate odio. Phasellus maximus, elit a pharetra egestas, lorem magna semper tellus, vestibulum semper diam felis at sapien. Suspendisse facilisis, nisl sit amet euismod vehicula, libero nulla vehicula dolor, quis fermentum nibh elit sit amet diam. + +Morbi lorem enim, euismod eu varius ut, scelerisque quis odio. Nam tempus vitae eros id molestie. Nunc pretium in nulla eget accumsan. Quisque mattis est ut semper aliquet. Maecenas eget diam elementum, fermentum ipsum a, euismod sapien. Duis quam ligula, cursus et velit nec, ullamcorper tincidunt magna. Donec vulputate nisl est, et ullamcorper urna tempor sit amet. + +Proin lacinia dui non turpis congue pretium. Morbi posuere metus vel purus imperdiet interdum. Morbi venenatis vel eros non ultricies. Nulla vel semper elit. Ut quis purus tincidunt, auctor justo ut, faucibus turpis. Proin quis mattis erat, at faucibus ligula. Mauris in mauris enim. Donec facilisis enim at est feugiat hendrerit. Nam vel nisi lorem. Fusce ultricies convallis diam, in feugiat tortor luctus quis. Donec tempor, leo vitae volutpat aliquam, magna elit feugiat leo, quis placerat sapien felis eget arcu. Donec ornare fermentum eleifend. Integer a est orci. + +Proin rhoncus egestas leo. Nulla ultricies porta elit quis ornare. Nunc fermentum interdum vehicula. In in ligula lorem. Donec nec arcu sit amet orci lobortis iaculis. Mauris at mollis erat, sit amet mollis tortor. Mauris laoreet justo ullamcorper porttitor auctor. Aenean sit amet aliquam lectus, id fermentum eros. Praesent urna sem, vehicula ac fermentum id, dapibus ut purus. Vestibulum vitae tempus nunc. Donec at nunc ornare metus volutpat porta at eget magna. Donec varius aliquet metus, eu lobortis risus aliquam sed. Ut dapibus fermentum velit, ac tincidunt libero faucibus at. + +In in purus auctor, feugiat massa quis, facilisis nisi. Donec dolor purus, gravida eget dolor ac, porttitor imperdiet urna. Donec faucibus placerat erat, a sagittis ante finibus ac. Sed venenatis dignissim elit, in iaculis felis posuere faucibus. Praesent sed viverra dolor. Mauris sed nulla consectetur nunc laoreet molestie in ut metus. Proin ac ex sit amet magna vulputate hendrerit ac condimentum urna. Proin ligula metus, gravida et sollicitudin facilisis, iaculis ut odio. Cras tincidunt urna et augue varius, ut facilisis urna consequat. Aenean vehicula finibus quam. Ut iaculis eu diam ac mollis. Nam mi lorem, tristique eget varius at, sodales at urna. + +Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vitae dictum erat, et auctor ipsum. Nullam nunc nunc, sollicitudin quis magna a, vestibulum fermentum mauris. Praesent at erat dolor. Proin laoreet tristique nulla vel efficitur. Nam sed ultrices nibh, id rutrum nunc. Curabitur eleifend a erat sit amet sollicitudin. Nullam metus quam, laoreet vitae dapibus id, placerat sed leo. Aliquam erat volutpat. Donec turpis nisl, cursus eu ex sit amet, lacinia pellentesque nisl. Sed id ipsum massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec interdum scelerisque lorem eu mattis. + +Vivamus ac tristique massa, nec facilisis nisl. Nam ipsum neque, tincidunt vel urna in, cursus imperdiet enim. Nam pellentesque egestas tempus. Morbi facilisis imperdiet libero vitae fringilla. Nam lacinia ligula at sapien facilisis malesuada. Nullam accumsan pulvinar sem, et cursus libero porta sit amet. Curabitur vulputate erat elit, ut pulvinar erat maximus vel. + +Cras aliquet metus ut purus sagittis, vel venenatis ante consectetur. Pellentesque nulla lacus, viverra viverra mattis non, placerat vitae nibh. Donec enim turpis, accumsan sit amet tincidunt eu, imperdiet non metus. Morbi ipsum eros, tincidunt vel est ac, tristique porttitor nibh. Praesent ut ullamcorper mauris. Sed laoreet sit amet diam congue venenatis. Integer porta purus nec orci sagittis posuere. + +Donec vehicula mauris eget lacus mollis venenatis et sed nibh. Nam sodales ligula ipsum, scelerisque lacinia ligula sagittis in. Nam sit amet ipsum at erat malesuada congue. Aenean ut sollicitudin sapien. Etiam at tempor odio. Mauris vitae purus ut magna suscipit consequat. Vivamus quis sapien neque. Nulla vulputate sem sit amet massa pellentesque, eleifend tristique ligula egestas. Suspendisse tincidunt gravida mi, in pulvinar lectus egestas non. Aenean imperdiet ex sit amet nunc sollicitudin porta. Integer justo odio, ultricies at interdum in, rhoncus vitae sem. Sed porttitor arcu quis purus aliquet hendrerit. Praesent tempor tortor at dolor dictum pulvinar. Nulla aliquet nunc non ligula scelerisque accumsan. Donec nulla justo, congue vitae massa in, faucibus hendrerit magna. Donec non egestas purus. + +Vivamus iaculis, lacus efficitur faucibus porta, dui nulla facilisis ligula, ut sodales odio nunc id sapien. Cras viverra auctor ipsum, dapibus mattis neque dictum sed. Sed convallis fermentum molestie. Nulla facilisi turpis duis. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vulputate, ipsum quis interdum fermentum, lorem sem fermentum eros, vitae auctor neque lacus in nisi. Suspendisse potenti. Maecenas et scelerisque elit, in tincidunt quam. Sed eu tincidunt quam. Nullam justo ex, imperdiet a imperdiet et, fermentum sit amet eros. Aenean quis tempus sem. Pellentesque accumsan magna mi, ut mollis velit sagittis id. Etiam quis ipsum orci. Fusce purus ante, accumsan a lobortis at, venenatis eu nisl. Praesent ornare sed ante placerat accumsan. Suspendisse tempus dignissim fermentum. Nunc a leo ac lacus sodales iaculis eu vitae mi. In feugiat ante at massa finibus cursus. Suspendisse posuere fringilla ornare. Mauris elementum ac quam id convallis. Vestibulum non elit quis urna volutpat aliquam a eu lacus. + +Aliquam vestibulum imperdiet neque, suscipit aliquam elit ultrices bibendum. Suspendisse ultrices pulvinar cursus. Morbi risus nisi, cursus consequat rutrum vitae, molestie sed dui. Fusce posuere, augue quis dignissim aliquam, nisi ipsum porttitor ante, quis fringilla nisl turpis ac nisi. Nulla varius enim eget lorem vehicula gravida. Donec finibus malesuada leo nec semper. Proin ac enim eros. Vivamus non tincidunt nisi, vel tristique lorem. + +Nunc consequat ex id eros dignissim, id rutrum risus laoreet. Sed euismod non erat eu ultricies. Etiam vehicula gravida lacus ut porta. Vestibulum eu eros quis nunc aliquet luctus. Cras quis semper ligula. Nullam gravida vehicula quam sed porta. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In porta cursus vulputate. Quisque porta a nisi eget cursus. Aliquam risus leo, luctus ac magna in, efficitur cursus magna. In condimentum non mi id semper. Donec interdum ante eget commodo maximus. + +Vivamus sit amet vestibulum lectus. Fusce tincidunt mi sapien, dictum sollicitudin diam vulputate in. Integer fringilla consequat mollis. Cras aliquet consequat felis eget feugiat. Nunc tempor cursus arcu, vitae ornare nunc varius et. Vestibulum et tortor vel ante viverra porttitor. Nam at tortor ullamcorper, facilisis augue quis, tristique erat. Aenean ut euismod nibh. Quisque eu tincidunt est, nec euismod eros. + +Proin vehicula nibh non viverra egestas. Phasellus sem dolor, ultricies ac sagittis tristique, lacinia a purus. Vestibulum in ante eros. Pellentesque lacus nulla, tristique vitae interdum vel, malesuada ac diam. Aenean bibendum posuere turpis in accumsan. Ut est nulla, ullamcorper quis turpis at, viverra sagittis mauris. Sed in interdum purus. Praesent scelerisque nibh eget sem euismod, ut imperdiet mi venenatis. Vivamus pulvinar orci sed dapibus auctor. Nulla facilisi. Vestibulum tincidunt erat nec porttitor egestas. Mauris quis risus ante. Nulla facilisi. + +Aliquam ullamcorper ornare lobortis. Phasellus quis sem et ipsum mollis malesuada sed in ex. Ut aliquam ex eget metus finibus maximus. Proin suscipit mauris eu nibh lacinia, quis feugiat dui dapibus. Nam sed libero est. Aenean vulputate orci sit amet diam faucibus, eu sagittis sapien volutpat. Nam imperdiet felis turpis, at pretium odio pulvinar in. Sed vestibulum id eros nec ultricies. Sed quis aliquam tortor, vitae ullamcorper tellus. Donec egestas laoreet eros, id suscipit est rutrum nec. Sed auctor nulla eget metus aliquam, ut condimentum enim elementum. + +Aliquam suscipit non turpis sit amet bibendum. Fusce velit ligula, euismod et maximus at, luctus sed neque. Quisque pretium, nisl at ullamcorper finibus, lectus leo mattis sapien, vel euismod mauris diam ullamcorper ex. Nulla ut risus finibus, lacinia ligula at, auctor erat. Mauris consectetur sagittis ligula vel dapibus. Nullam libero libero, lobortis aliquam libero vel, venenatis ultricies leo. Duis porttitor, nibh congue fermentum posuere, erat libero pulvinar tortor, a pellentesque nunc ipsum vel sem. Nullam volutpat, eros sit amet facilisis consectetur, ipsum est vehicula massa, non vestibulum neque elit in mauris. Nunc hendrerit ipsum non enim bibendum, vitae rhoncus mi egestas. Etiam ullamcorper massa vel nisl sagittis, nec bibendum arcu malesuada. Aenean aliquet turpis justo, a consectetur arcu mollis convallis. Etiam tellus ipsum, ultricies vitae lorem et, ornare facilisis orci. Praesent fringilla justo urna, vel mollis neque pulvinar vestibulum. + +Donec non iaculis erat. Aliquam et mi sed nunc pulvinar ultricies in ut ipsum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Praesent feugiat lacus ac dignissim semper. Phasellus vitae quam nisi. Morbi vel diam ultricies risus lobortis ornare. Fusce maximus et ligula quis iaculis. Sed congue ex eget felis convallis, sit amet hendrerit elit tempor. Donec vehicula blandit ante eget commodo. Vestibulum eleifend diam at feugiat euismod. Etiam magna tellus, dignissim eget fermentum vel, vestibulum vitae mauris. Nam accumsan et erat id sagittis. Donec lacinia, odio ut ornare ultricies, dolor velit accumsan tortor, non finibus erat tellus quis ligula. Nunc quis metus in leo volutpat ornare vulputate eu nisl. + +Donec quis viverra ex. Nullam id feugiat mauris, eu fringilla nulla. Vestibulum id maximus elit. Cras elementum elit sed felis lobortis, eget sagittis nisi hendrerit. Vivamus vitae elit neque. Donec vulputate lacus ut libero ultrices accumsan. Vivamus accumsan nulla orci, in dignissim est laoreet sagittis. Proin at commodo velit. Curabitur in velit felis. Aliquam erat volutpat. Sed consequat, nulla et cursus sodales, nisi lacus mattis risus, quis eleifend erat ex nec turpis. Sed suscipit ultrices lorem in hendrerit. + +Morbi vitae lacus nec libero ornare tempus eu et diam. Suspendisse magna ipsum, fermentum vel odio quis, molestie aliquam urna. Fusce mollis turpis a eros accumsan porttitor. Pellentesque rhoncus dolor sit amet magna rutrum, et dapibus justo tempor. Sed purus nisi, maximus vitae fringilla eu, molestie nec urna. Fusce malesuada finibus pretium. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec sed aliquet eros. Pellentesque luctus diam ante, eget euismod nisl aliquet eu. Sed accumsan elit purus, tempor varius ligula tempus nec. Curabitur ornare leo suscipit suscipit fermentum. Morbi eget nulla est. Maecenas faucibus interdum tristique. + +Etiam ut elit eros. Nulla pharetra suscipit molestie. Nulla facilisis bibendum nisl non molestie. Curabitur turpis lectus, facilisis vel diam non, vulputate ultrices mauris. Aenean placerat aliquam convallis. Suspendisse sed scelerisque tellus. Vivamus lacinia neque eget risus cursus suscipit. Proin consequat dolor vel neque tempor, eu aliquam sem scelerisque. Duis non eros a purus malesuada pharetra non et nulla. Suspendisse potenti. Mauris libero eros, finibus vel nulla id, sagittis dapibus ante. Proin iaculis sed nunc et cursus. + +Quisque accumsan lorem sit amet lorem aliquet euismod. Curabitur fermentum rutrum posuere. Etiam ultricies, sem id pellentesque suscipit, urna magna lacinia eros, quis efficitur risus nisl at lacus. Nulla quis lacus tortor. Mauris placerat ex in dolor tincidunt, vel aliquet nisi pretium. Cras iaculis risus vitae pellentesque aliquet. Quisque a enim imperdiet, ullamcorper arcu vitae, rutrum risus. Nullam consectetur libero at felis fringilla, nec congue nibh dignissim. Nam et lobortis felis, eu pellentesque ligula. Aenean facilisis, ligula non imperdiet maximus, massa orci gravida sapien, at sagittis lacus nisl in lacus. Nulla quis mauris luctus, scelerisque felis consequat, tempus risus. Fusce auctor nisl non nulla luctus molestie. Maecenas sapien nisl, auctor non dolor et, iaculis scelerisque lorem. Suspendisse egestas enim aliquet, accumsan mauris nec, posuere quam. Nulla iaculis dui dui, sit amet vestibulum erat ultricies ac. + +Cras eget dolor erat. Proin at nisl ut leo consectetur ultricies vel ut arcu. Nulla in felis malesuada, ullamcorper tortor et, convallis massa. Nunc urna justo, ornare in nibh vitae, hendrerit condimentum libero. Etiam vitae libero in purus venenatis fringilla. Nullam velit nulla, consequat ut turpis non, egestas hendrerit nibh. Duis tortor turpis, interdum non ante ac, cursus accumsan lectus. Cras pharetra bibendum augue quis dictum. Sed euismod vestibulum justo. Proin porta lobortis purus. Duis venenatis diam tortor, sit amet condimentum eros rhoncus a. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc at magna nec diam lobortis efficitur sit amet ut lacus. Nulla quis orci tortor. Pellentesque tempus velit a odio finibus porta. + +Proin feugiat mauris a tellus scelerisque convallis. Maecenas libero magna, blandit nec ultrices id, congue vel mi. Aliquam lacinia, quam vel condimentum convallis, tortor turpis aliquam odio, sed blandit libero lacus et eros. In eleifend iaculis magna ac finibus. Praesent auctor facilisis tellus in congue. Sed molestie lobortis dictum. Nam quis dignissim augue, vel euismod lorem. Curabitur posuere dapibus luctus. Donec ultricies dictum lectus, quis blandit arcu commodo ac. Aenean tincidunt ligula in nunc imperdiet dignissim. Curabitur egestas sollicitudin sapien ut semper. Aenean nec dignissim lacus. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec aliquam dictum vehicula. Donec tortor est, volutpat non nisi nec, varius gravida ex. Nunc vel tristique nunc, vitae mattis nisi. Nunc nec luctus ex, vitae tincidunt lectus. In hac habitasse platea dictumst. Curabitur lobortis ex eget tincidunt tempor. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut a vehicula mi. + +Fusce eu libero finibus, interdum nulla a, placerat neque. Cras bibendum tempor libero nec feugiat. Cras ut sodales eros. Proin viverra, massa sit amet viverra egestas, neque nisl porta ex, sit amet hendrerit libero ligula vel urna. Mauris suscipit lacus id justo rhoncus suscipit. Etiam vel libero tellus. Maecenas non diam molestie, condimentum tellus a, bibendum enim. Mauris aliquet imperdiet tellus, eget sagittis dolor. Sed blandit in neque et luctus. Cras elementum sagittis nunc, vel mollis lorem euismod et. Donec posuere at lacus eget suscipit. + +Nulla nunc mi, pretium non massa vel, tempor semper magna. Nunc a leo pulvinar, tincidunt nunc at, dignissim mi. Aliquam erat volutpat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut viverra nulla a nisl finibus, at hendrerit ligula ullamcorper. Donec a lorem semper, tempor magna et, lobortis libero. Mauris id sapien leo. Donec dignissim, quam vitae porttitor dignissim, quam justo mattis dui, vel consequat odio elit quis orci. Etiam nec pretium neque, sit amet pretium orci. Duis ac tortor venenatis, feugiat purus non, feugiat nunc. Proin scelerisque nisl in turpis aliquam vulputate. + +Praesent sed est semper, fringilla lorem vitae, tincidunt nibh. Cras eros metus, auctor at mauris sit amet, sodales semper orci. Nunc a ornare ex. Curabitur bibendum arcu congue urna vulputate egestas. Vestibulum finibus id risus et accumsan. Aenean ut volutpat tellus. Aenean tincidunt malesuada urna sit amet vestibulum. Mauris vel tellus dictum, varius lacus quis, dictum arcu. + +Aenean quis metus eu erat feugiat cursus vel at ligula. Proin dapibus sodales urna, id euismod lectus tempus id. Pellentesque ex ligula, convallis et erat vel, vulputate condimentum nisl. Pellentesque pharetra nulla quis massa eleifend hendrerit. Praesent sed massa ipsum. Maecenas vehicula dolor massa, id sodales urna faucibus et. Mauris ac quam non massa tincidunt feugiat et at lacus. Fusce libero massa, vulputate vel scelerisque non, mollis in leo. Ut sit amet ultricies odio. Suspendisse in sapien viverra, facilisis purus ut, pretium libero. + +Vivamus tristique pharetra molestie. Nam a volutpat purus. Praesent consequat gravida nisi, ac blandit nisi suscipit ut. Quisque posuere, ligula a ultrices laoreet, ligula nunc vulputate libero, ut rutrum erat odio tincidunt justo. Sed vitae leo at leo fringilla bibendum. Vestibulum ut augue nec dolor auctor accumsan. Praesent laoreet id eros pulvinar commodo. Suspendisse potenti. Ut pharetra, mauris vitae blandit fringilla, odio ante tincidunt lorem, sit amet tempor metus diam ut turpis. + +Praesent quis egestas arcu. Nullam at porta arcu. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi vulputate ligula malesuada ligula luctus, vulputate tempus erat bibendum. Nunc ullamcorper non lectus at euismod. Etiam nibh felis, tincidunt a metus vel, pellentesque rhoncus neque. Etiam at diam in erat luctus interdum. Nunc vel ipsum pulvinar, sollicitudin lacus ac, tempus urna. Etiam vel lacinia sapien. Pellentesque sagittis velit vel mi efficitur iaculis. Integer euismod sit amet urna in sagittis. Cras eleifend ut nibh in facilisis. Donec et lacus vitae nunc placerat sodales. Nulla sed hendrerit ligula, at dapibus sapien. + +Praesent at iaculis ex. Curabitur est purus, cursus a faucibus quis, dictum id velit. Donec dignissim fringilla viverra. Nunc mauris felis, laoreet sit amet sagittis at, vestibulum in libero. Maecenas quis orci turpis. Quisque ut nibh vitae magna mollis consequat id at mauris. Aliquam eu odio eget nulla bibendum sodales. Quisque vel orci eleifend nisi pretium lacinia. Suspendisse eget risus eget mi volutpat molestie eget quis lacus. Duis nisi libero, tincidunt nec nulla id, faucibus cursus felis. + +Donec tempor eget risus pellentesque molestie. Phasellus porta neque vel arcu egestas, nec blandit velit fringilla. Nullam porta faucibus justo vitae laoreet. Pellentesque viverra id nunc eu varius. Nulla pulvinar lobortis iaculis. Etiam vestibulum odio nec velit tristique, a tristique nisi mattis. In sed fringilla orci, vitae efficitur odio. Quisque dui odio, ornare eget velit at, lacinia consequat libero. Quisque lectus nulla, aliquet eu leo in, porta rutrum diam. Donec nec mattis neque. Nam rutrum, odio ac eleifend bibendum, dolor arcu rutrum neque, eget porta elit tellus a lacus. Sed massa metus, sollicitudin et sapien eu, finibus tempus orci. Proin et sapien sit amet erat molestie interdum. In quis rutrum velit, faucibus ultrices tellus. + +Sed sagittis sed justo eget tincidunt. Maecenas ut leo sagittis, feugiat magna et, viverra velit. Maecenas ex arcu, feugiat at consequat vitae, auctor eu massa. Integer egestas, enim vitae maximus convallis, est lectus pretium mauris, ac posuere lectus nisl quis quam. Aliquam tempus laoreet mi, vitae dapibus dolor varius dapibus. Suspendisse potenti. Donec sit amet purus nec libero dapibus tristique. Pellentesque viverra bibendum ligula. Donec sed felis et ex lobortis laoreet. Phasellus a fringilla libero, vitae malesuada nulla. Pellentesque blandit mattis lacus, et blandit tortor laoreet consequat. Suspendisse libero nunc, viverra sed fermentum in, accumsan egestas arcu. Proin in placerat elit. Sed interdum imperdiet malesuada. Suspendisse aliquet quis mauris eget sollicitudin. + +Vivamus accumsan tellus non erat volutpat, quis dictum dolor feugiat. Praesent rutrum nunc ac est mollis cursus. Fusce semper volutpat dui ut egestas. Curabitur sit amet posuere massa. Cras tincidunt nulla et mi mollis imperdiet. Suspendisse scelerisque ex id sodales vulputate. In nunc augue, pharetra in placerat eu, mattis id tellus. Vivamus cursus efficitur vehicula. Nulla aliquet vehicula aliquet. + +Sed cursus tellus sed porta pulvinar. Sed vitae nisi neque. Nullam aliquet, lorem et efficitur scelerisque, arcu diam aliquam felis, sed pulvinar lorem odio et turpis. Praesent convallis pulvinar turpis eu iaculis. Aliquam nec gravida mi. Curabitur eu nibh tempor, blandit justo in, ultrices felis. Fusce placerat metus non mi sagittis rutrum. Morbi sed dui fringilla, sagittis mauris eget, imperdiet nunc. Phasellus hendrerit sem elit, id hendrerit libero auctor sit amet. Integer sodales elit sit amet consequat cursus. + +Nam semper est eget nunc mollis, in pellentesque lectus fringilla. In finibus vel diam id semper. Nunc mattis quis erat eu consectetur. In hac habitasse platea dictumst. Nullam et ipsum vestibulum ex pulvinar ultricies sit amet id velit. Aenean suscipit mi tortor, a lobortis magna viverra non. Nulla condimentum aliquet ante et ullamcorper. Pellentesque porttitor arcu a posuere tempus. Aenean lacus quam, imperdiet eu justo vitae, pretium efficitur ex. Duis id purus id magna rhoncus ultrices id eu risus. Nunc dignissim et libero id dictum. + +Quisque a tincidunt neque. Phasellus commodo mi sit amet tempor fringilla. Ut rhoncus, neque non porttitor elementum, libero nulla egestas augue, sed fringilla sapien felis ac velit. Phasellus viverra rhoncus mollis. Nam ullamcorper leo vel erat laoreet luctus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus semper a metus a cursus. Nulla sed orci egestas, efficitur purus ac, malesuada tellus. Aenean rutrum velit at tellus fermentum mollis. Aliquam eleifend euismod metus. + +In hac habitasse platea dictumst. Vestibulum volutpat neque vitae porttitor laoreet. Nam at tellus consequat, sodales quam in, pulvinar arcu. Maecenas varius convallis diam, ac lobortis tellus pellentesque quis. Maecenas eget augue massa. Nullam volutpat nibh ac justo rhoncus, ut iaculis tellus rutrum. Fusce efficitur efficitur libero quis condimentum. Curabitur congue neque non tincidunt tristique. Fusce eget tempor ex, at pellentesque odio. Praesent luctus dictum vestibulum. Etiam non orci nunc. Vivamus vitae laoreet purus, a lobortis velit. Curabitur tincidunt purus ac lectus elementum pellentesque. Quisque sed tincidunt est. + +Sed vel ultrices massa, vitae ultricies justo. Cras finibus mauris nec lacus tempus dignissim. Cras faucibus maximus velit, eget faucibus orci luctus vehicula. Nulla massa nunc, porta ac consequat eget, rhoncus non tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce sed maximus metus, vel imperdiet ipsum. Ut scelerisque lectus at blandit porttitor. Ut vulputate nunc pharetra, aliquet sapien ac, sollicitudin sapien. Aenean eget ante lorem. Nam accumsan venenatis tellus id dignissim. + +Curabitur fringilla, magna non maximus dapibus, nulla sapien vestibulum lectus, sit amet semper dolor neque vitae nisl. Nunc ultrices vehicula augue sed iaculis. Maecenas nec diam mollis, suscipit orci et, vestibulum ante. Pellentesque eu nisl tortor. Nunc eleifend, lacus quis volutpat volutpat, nisi mi molestie sem, quis mollis ipsum libero a tellus. Ut viverra dolor mattis convallis interdum. Sed tempus nisl at nunc scelerisque aliquet. Quisque tempor tempor lorem id feugiat. Nullam blandit lectus velit, vitae porta lacus tincidunt a. Vivamus sit amet arcu ultrices, tincidunt mi quis, viverra quam. Aenean fringilla libero elementum lorem semper, quis pulvinar eros gravida. Nullam sodales blandit mauris, sed fermentum velit fermentum sit amet. Donec malesuada mauris in augue sodales vulputate. Vestibulum gravida turpis id elit rhoncus dignissim. Integer non congue lorem, eu viverra orci. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec at dolor magna. Aliquam consectetur erat augue, id iaculis velit pharetra ac. Integer rutrum venenatis dignissim. Integer non sodales elit. Curabitur ut magna ut nibh feugiat aliquam ac ut risus. Morbi nibh quam, aliquam id placerat nec, vestibulum eget velit. Suspendisse at dignissim quam. Vivamus aliquet sem sed nisl volutpat, ut cursus orci ultrices. Aliquam ultrices lacinia enim, vitae aliquet neque. + +Quisque scelerisque finibus diam in mattis. Cras cursus auctor velit. Aliquam sem leo, fermentum et maximus et, molestie a libero. Aenean justo elit, rutrum a ornare id, egestas eget enim. Aenean auctor tristique erat. Curabitur condimentum libero lacus, nec consequat orci vestibulum sed. Fusce elit ligula, blandit vitae sapien vitae, dictum ultrices risus. Nam laoreet suscipit sapien, at interdum velit faucibus sit amet. Duis quis metus egestas lectus elementum posuere non nec libero. Aliquam a dolor bibendum, facilisis nunc a, maximus diam. Vestibulum suscipit tristique magna, non dignissim turpis sodales sed. Nunc ornare, velit ac facilisis fringilla, dolor mi consectetur lorem, vitae finibus erat justo suscipit urna. Maecenas sit amet eros erat. Nunc non arcu ornare, suscipit lorem eget, sodales mauris. Aliquam tincidunt, quam nec mollis lacinia, nisi orci fermentum libero, consequat eleifend lectus quam et sapien. Vestibulum a quam urna. + +Cras arcu leo, euismod ac ullamcorper at, faucibus sed massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus porttitor velit in enim interdum, non commodo metus ornare. Morbi vel lorem quis nisl luctus tristique quis vitae nisl. Suspendisse condimentum tortor enim, nec eleifend ipsum euismod et. Sed gravida quam ut tristique lacinia. Mauris eu interdum ipsum, ac ultrices odio. Nullam auctor tellus a risus porttitor vehicula. Nulla blandit euismod dictum. In pharetra, enim iaculis pulvinar interdum, dui nunc placerat nunc, sit amet pretium lectus nulla vitae quam. Phasellus quis enim sollicitudin, varius nulla id, ornare purus. Donec quam lacus, vestibulum quis nunc ac, mollis dictum nisi. Cras ut mollis elit. Maecenas ultrices ligula at risus faucibus scelerisque. Etiam vitae porttitor purus. Curabitur blandit lectus urna, ut hendrerit tortor feugiat ut. + +Phasellus fringilla, sapien pellentesque commodo pharetra, ante libero aliquam tellus, ut consectetur augue libero a sapien. Maecenas blandit luctus nisl eget aliquet. Maecenas vitae porta dolor, faucibus laoreet sapien. Suspendisse lobortis, ipsum sed vehicula aliquam, elit purus scelerisque dui, rutrum consectetur diam odio et lorem. In nec lacinia metus. Donec viverra libero est, vel bibendum erat condimentum quis. Donec feugiat purus leo. In laoreet vitae felis a porttitor. Mauris ullamcorper, lacus id condimentum suscipit, neque magna pellentesque arcu, eget cursus neque tellus id metus. Curabitur volutpat ac orci vel ultricies. + +Sed ut finibus erat. Sed diam purus, varius non tincidunt quis, ultrices sit amet ipsum. Donec et egestas nulla. Suspendisse placerat nisi at dui laoreet iaculis. Aliquam aliquet leo at augue faucibus molestie. Nullam lacus augue, hendrerit sed nisi eu, faucibus porta est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam ut leo aliquet sem fermentum rutrum quis ac justo. Integer placerat aliquam nisl ut sagittis. Proin erat orci, lobortis et sem eget, eleifend fringilla augue. Mauris varius laoreet arcu, sed tincidunt felis. Pellentesque venenatis lorem odio, id pulvinar velit molestie feugiat. Donec mattis lacus sed eleifend pulvinar. + +Sed condimentum ex in tincidunt hendrerit. Etiam eget risus lacinia, euismod nibh eu, pellentesque quam. Proin elit eros, convallis id mauris ac, bibendum ultrices lectus. Morbi venenatis, purus id fermentum consequat, nunc libero tincidunt ligula, non dictum ligula orci nec quam. Nulla nec ultrices lorem. Aenean maximus augue vel dictum pharetra. Etiam turpis urna, pellentesque quis malesuada eu, molestie faucibus felis. + +Vestibulum pharetra augue ut quam blandit congue in nec risus. Proin eu nibh eu dui eleifend porta vitae id lectus. Proin lacus nibh, lobortis sed ligula vitae, interdum lobortis erat. Suspendisse potenti. In sollicitudin quis sapien ut aliquet. Mauris ac nulla arcu. Fusce tristique justo quis lectus mollis, eu volutpat lectus finibus. Vivamus venenatis facilisis ex ut vestibulum. + +Etiam varius lobortis purus, in hendrerit elit tristique at. In tempus, augue vestibulum fermentum gravida, ligula tellus vulputate arcu, eu molestie ex sapien at purus. Vestibulum nec egestas metus. Duis pulvinar quam nec consequat interdum. Aenean non dapibus lacus. Aliquam sit amet aliquet nulla. Sed venenatis volutpat purus nec convallis. Phasellus aliquet semper sodales. Cras risus sapien, condimentum auctor urna a, pulvinar ornare nisl. Sed tincidunt felis elit, ut elementum est bibendum ac. Morbi interdum justo vel dui faucibus condimentum. + +Sed convallis eu sem at tincidunt. Nullam at auctor est, et ullamcorper ipsum. Pellentesque eget ante ante. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer euismod, sapien sed dapibus ornare, nibh enim maximus lacus, lacinia placerat urna quam quis felis. Morbi accumsan id nisl ut condimentum. Donec bibendum nisi est, sed volutpat lorem rhoncus in. Vestibulum ac lacinia nunc, eget volutpat magna. Integer aliquam pharetra ipsum, id placerat nunc volutpat quis. Etiam urna diam, rhoncus sit amet varius vel, euismod vel sem. Nullam vel molestie urna. Vivamus ornare erat at venenatis euismod. Suspendisse potenti. Fusce diam justo, tincidunt vel sem at, commodo faucibus nisl. Duis gravida efficitur diam, vel sagittis erat pulvinar ut. + +Quisque vel pharetra felis. Duis efficitur tortor dolor, vitae porttitor erat fermentum sed. Sed eu mi purus. Etiam dignissim tortor eu tempus molestie. Aenean pretium erat enim, in hendrerit ante hendrerit at. Sed ut risus vel nunc venenatis ultricies quis in lacus. Pellentesque vitae purus euismod, placerat risus non, ullamcorper augue. Quisque varius quam ligula, nec aliquet ex faucibus vitae. Quisque rhoncus sit amet leo tincidunt mattis. Cras id mauris eget purus pretium gravida sit amet eu augue. Aliquam dapibus odio augue, id lacinia velit pulvinar eu. + +Mauris fringilla, tellus nec pharetra iaculis, neque nisi ultrices massa, et tincidunt sem dui sed mi. Curabitur erat lorem, venenatis quis tempus lacinia, tempus sit amet nunc. Aliquam at neque ac metus commodo dictum quis vitae justo. Phasellus eget lacus tempus, blandit lorem vel, rutrum est. Aenean pharetra sem ut augue lobortis dignissim. Sed rhoncus at nulla id ultrices. Cras id condimentum felis. In suscipit luctus vulputate. Donec tincidunt lacus nec enim tincidunt sollicitudin ut quis enim. Nam at libero urna. Praesent sit amet massa vitae massa ullamcorper vehicula. + +Nullam bibendum augue ut turpis condimentum bibendum. Proin sit amet urna hendrerit, sodales tortor a, lobortis lectus. Integer sagittis velit turpis, et tincidunt nisi commodo eget. Duis tincidunt elit finibus accumsan cursus. Aenean dignissim scelerisque felis vel lacinia. Nunc lacinia maximus luctus. In hac habitasse platea dictumst. Vestibulum eget urna et enim tempor tempor. Nam feugiat, felis vel vestibulum tempus, orci justo viverra diam, id dapibus lorem justo in ligula. + +Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In ac pellentesque sem. Vestibulum lacinia magna dui, eu lacinia augue placerat et. Maecenas pulvinar congue est. Pellentesque commodo dui non pulvinar scelerisque. Etiam interdum est posuere sem bibendum, ac commodo magna dictum. Cras ipsum turpis, rhoncus nec posuere vitae, laoreet a arcu. Integer ac massa sit amet enim placerat lacinia sed ultrices arcu. Suspendisse sem nibh, luctus sit amet volutpat in, pellentesque eu metus. Ut gravida neque eget mi accumsan tempus. Nam sit amet aliquet nibh. + +Pellentesque a purus cursus nulla hendrerit congue quis et odio. Aenean hendrerit, leo ullamcorper sagittis hendrerit, erat dui molestie quam, sed condimentum lacus risus sed tellus. Morbi a dapibus lectus, ut feugiat ex. Phasellus pretium quam et sapien mollis, vel iaculis dui dignissim. Sed ullamcorper est turpis, a viverra lorem consectetur in. Aenean aliquet nibh non cursus rutrum. Suspendisse at tristique urna, id lobortis urna. In hac habitasse platea dictumst. Phasellus libero velit, rutrum sed tellus nec, dapibus tincidunt ligula. Quisque vel dui venenatis, consequat nisl ut, lacinia ipsum. Phasellus vitae magna pellentesque, lobortis est id, faucibus quam. Nam eleifend faucibus dui vel pellentesque. + +Etiam ut est non lacus tincidunt interdum. Maecenas sed massa urna. Quisque ut nibh tortor. Pellentesque felis ipsum, tempor finibus ipsum et, euismod pretium metus. Donec sit amet est ipsum. Quisque rhoncus justo non finibus elementum. Nulla nec lectus ac tortor placerat fringilla. Phasellus ac ultrices nunc, eu efficitur nisl. Nulla rhoncus nunc vitae ante dictum tincidunt. Nunc ultrices, massa sit amet malesuada dignissim, lectus lacus consequat sapien, non eleifend metus sem in eros. Phasellus mauris ante, dictum sit amet suscipit ac, rhoncus eget nisi. Phasellus at orci mollis, imperdiet neque eget, faucibus nulla. In at purus massa. Pellentesque quis rutrum lectus. + +Integer eu faucibus turpis, sit amet mollis massa. Vestibulum id nulla commodo, rutrum ipsum sed, semper ante. Phasellus condimentum orci nec nibh convallis, ac maximus orci ullamcorper. Maecenas vitae sollicitudin mi. Integer et finibus lectus, et condimentum ligula. Donec elementum tristique quam vitae dapibus. Morbi euismod ipsum in tristique ullamcorper. + +Duis fermentum non enim eu auctor. Quisque lacinia nibh vehicula nibh posuere, eu volutpat turpis facilisis. Ut ac faucibus nulla. Sed eleifend quis ex et pellentesque. Vestibulum sollicitudin in libero id fringilla. Phasellus dignissim purus consequat, condimentum dui sit amet, condimentum ante. Pellentesque ac consectetur massa, quis sagittis est. Nulla maximus tristique risus accumsan convallis. Curabitur imperdiet ac lacus a ultrices. Nulla facilisi. Sed quis quam quis lectus placerat lobortis vel sed turpis. In mollis dui id neque iaculis, ut aliquet tellus malesuada. Proin at luctus odio, vel blandit sapien. Praesent dignissim tortor vehicula libero fringilla, nec ultrices erat suscipit. Maecenas scelerisque purus in dapibus fermentum. + +Curabitur magna odio, mattis in tortor ut, porttitor congue est. Vestibulum mollis lacinia elementum. Fusce maximus erat vitae nunc rutrum lobortis. Integer ligula eros, auctor vel elit non, posuere luctus lacus. Maecenas quis auctor massa. Ut ipsum lacus, efficitur posuere euismod et, hendrerit efficitur est. Phasellus fringilla, quam id tincidunt pretium, nunc dui sollicitudin orci, eu dignissim nisi metus ut magna. Integer lobortis interdum dolor, non bibendum purus posuere et. Donec non lectus aliquet, pretium dolor eu, cursus massa. Sed ut dui sapien. In sed vestibulum massa. Pellentesque blandit, dui non sodales vehicula, orci metus mollis nunc, non pharetra ex tellus ac est. Mauris sagittis metus et fermentum pretium. Nulla facilisi. Quisque quis ante ut nulla placerat mattis ut quis nisi. + +Sed quis nulla ligula. Quisque dignissim ligula urna, sed aliquam purus semper at. Suspendisse potenti. Nunc massa lectus, pharetra vehicula arcu bibendum, imperdiet sodales ipsum. Nam ac sapien diam. Mauris iaculis fringilla mattis. Pellentesque tempus eros sit amet justo volutpat mollis. Phasellus ac turpis ipsum. Morbi vel ante elit. Aenean posuere quam consequat velit varius suscipit. Donec tempor quam ut nibh cursus efficitur. + +Morbi molestie dolor nec sem egestas suscipit. Etiam placerat pharetra lectus, et ullamcorper risus tristique in. Sed faucibus ullamcorper lectus eget fringilla. Maecenas malesuada hendrerit congue. Sed eget neque a erat placerat tincidunt. Aliquam vitae dignissim turpis. Fusce at placerat magna, a laoreet lectus. Maecenas a purus nec diam gravida fringilla. Nam malesuada euismod ante non vehicula. In faucibus bibendum leo, faucibus posuere nisl pretium quis. Fusce finibus bibendum finibus. Vestibulum eu justo maximus, hendrerit diam nec, dignissim sapien. Aenean dolor lacus, malesuada quis vestibulum ac, venenatis ac ipsum. Cras a est id nunc finibus facilisis. Cras lacinia neque et interdum vehicula. Suspendisse vulputate tellus elit, eget tempor dui finibus vel. + +Cras sed pretium odio. Proin hendrerit elementum felis in tincidunt. Nam sed turpis vel justo molestie accumsan condimentum eu nunc. Praesent lobortis euismod rhoncus. Nulla vitae euismod nibh, quis mattis mi. Fusce ultrices placerat porttitor. Duis sem ipsum, pellentesque sit amet odio a, molestie vulputate mauris. + +Duis blandit mollis ligula, sit amet mattis ligula finibus sit amet. Nunc a leo molestie, placerat diam et, vestibulum leo. Suspendisse facilisis neque purus, nec pellentesque ligula fermentum nec. Aenean malesuada mauris lorem, eu blandit arcu pulvinar quis. Duis laoreet urna lacus, non maximus arcu rutrum ultricies. Nulla augue dolor, suscipit eu mollis eu, aliquam condimentum diam. Ut semper orci luctus, pharetra turpis at, euismod mi. Nulla leo diam, finibus sit amet purus sed, maximus dictum lorem. Integer eu mi id turpis laoreet rhoncus. + +Integer a mauris tincidunt, finibus orci ut, pretium mauris. Nulla molestie nunc mi, id finibus lorem elementum sed. Proin quis laoreet ante. Integer nulla augue, commodo id molestie quis, rutrum ut turpis. Suspendisse et tortor turpis. Sed ut pharetra massa. Pellentesque elementum blandit sem, ut elementum tellus egestas a. Fusce eu purus nibh. + +Cras dignissim ligula scelerisque magna faucibus ullamcorper. Proin at condimentum risus, auctor malesuada quam. Nullam interdum interdum egestas. Nulla aliquam nisi vitae felis mollis dictum. Suspendisse dapibus consectetur tortor. Ut ut nisi non sem bibendum tincidunt. Vivamus suscipit leo quis gravida dignissim. + +Aliquam interdum, leo id vehicula mollis, eros eros rhoncus diam, non mollis ligula mi eu mauris. Sed ultrices vel velit sollicitudin tincidunt. Nunc auctor metus at ligula gravida elementum. Praesent interdum eu elit et mollis. Duis egestas quam sit amet velit dignissim consequat. Aliquam ac turpis nec nunc convallis sagittis. Fusce blandit, erat ac fringilla consectetur, dolor eros sodales leo, vel aliquet risus nisl et diam. Aliquam luctus felis vitae est eleifend euismod facilisis et lacus. Sed leo tellus, auctor eu arcu in, volutpat sagittis nisl. Pellentesque nisl ligula, placerat vel ullamcorper at, vulputate ac odio. Morbi ac faucibus orci, et tempus nulla. Proin rhoncus rutrum dolor, in venenatis mauris. Suspendisse a fermentum augue, non semper mi. Nunc eget pretium neque. Phasellus augue erat, feugiat ac aliquam congue, rutrum non sapien. Pellentesque ac diam gravida, consectetur felis at, ornare neque. + +Nullam interdum mattis sapien quis porttitor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Phasellus aliquet rutrum ipsum id euismod. Maecenas consectetur massa et mi porta viverra. Nunc quam nibh, dignissim vitae maximus et, ullamcorper nec lorem. Nunc vitae justo dapibus, luctus lacus vitae, pretium elit. Maecenas et efficitur leo. Curabitur mauris lectus, placerat quis vehicula vitae, auctor ut urna. Quisque rhoncus pharetra luctus. In hac habitasse platea dictumst. Integer sit amet metus nec eros malesuada aliquam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi hendrerit mi ac leo aliquam, sit amet ultricies libero commodo. Mauris dapibus purus metus, sit amet viverra nibh imperdiet et. Nullam porta nulla tellus, quis vehicula diam imperdiet non. Vivamus enim massa, bibendum in fermentum in, ultrices at ex. + +Suspendisse fermentum id nibh eget accumsan. Duis dapibus bibendum erat ut sollicitudin. Aliquam nec felis risus. Pellentesque rhoncus ligula id sem maximus mollis sed nec massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus ipsum ipsum, sodales sed enim id, convallis faucibus eros. Donec ultricies dictum tincidunt. Cras vitae nibh arcu. Pellentesque cursus, sapien nec consequat fermentum, ipsum ante suscipit dui, imperdiet hendrerit est nisl eu massa. Quisque vitae sem ligula. Aenean iaculis metus ut mauris interdum laoreet. Vivamus sed gravida dolor. + +Morbi nulla metus, porttitor sed eros sit amet, efficitur efficitur est. In vel nisl urna. Ut aliquet tellus at congue convallis. Phasellus imperdiet lobortis sollicitudin. Integer sodales, sem eu ultricies pharetra, erat erat porttitor odio, eget dapibus libero ipsum eget velit. Phasellus gravida nulla nisl, eu pharetra mi auctor vel. Sed blandit pharetra velit, ut egestas libero placerat non. Aliquam a interdum quam. Proin at tortor nec dui sollicitudin tempus sed vestibulum elit. Nunc non sollicitudin velit. + +Aenean consequat diam velit, sed rutrum tortor faucibus dictum. Quisque at semper augue. Duis ut est eget mi ornare bibendum id et ligula. Phasellus consequat tortor non leo pulvinar posuere. Proin vestibulum eleifend felis, in hendrerit tortor sollicitudin eu. Phasellus hendrerit, lacus vel laoreet interdum, dui tortor consequat justo, commodo ultricies arcu felis vitae enim. Vivamus eu sapien at leo suscipit rutrum eu at justo. Aenean et dolor a libero ullamcorper posuere. Integer laoreet placerat nisi in vulputate. Mauris laoreet eget risus sed cursus. Donec scelerisque neque a libero eleifend hendrerit. Nulla varius condimentum nunc sit amet fermentum. Aliquam lorem ex, varius nec mollis ut, ultrices in neque. Morbi sit amet porta leo. Integer iaculis fermentum lacus in vestibulum. + +Ut gravida, tellus ut maximus ultrices, erat est venenatis nisl, vitae pretium massa ex ac magna. Sed non purus eget ligula aliquet volutpat non quis arcu. Nam aliquam tincidunt risus, sit amet fringilla sapien vulputate ut. Mauris luctus suscipit pellentesque. Nunc porttitor dapibus ex quis tempus. Ut ullamcorper metus a eros vulputate, vitae viverra lectus convallis. Mauris semper imperdiet augue quis tincidunt. Integer porta pretium magna, sed cursus sem scelerisque sollicitudin. Nam efficitur, nibh pretium eleifend vestibulum, purus diam posuere sem, in egestas mauris augue sit amet urna. + +Vestibulum tincidunt euismod massa in congue. Duis interdum metus non laoreet fringilla. Donec at ligula congue, tincidunt nunc non, scelerisque nunc. Donec bibendum magna non est scelerisque feugiat at nec neque. Ut orci tortor, tempus eget massa non, dignissim faucibus dolor. Nam odio risus, accumsan pretium neque eget, accumsan dignissim dui. In ut neque auctor, scelerisque tellus sed, ullamcorper nisi. Suspendisse varius cursus quam at hendrerit. Vivamus elit libero, sagittis vitae sem ac, vulputate iaculis ligula. + +Sed lobortis laoreet purus sit amet rutrum. Pellentesque feugiat non leo vel lacinia. Quisque feugiat nisl a orci bibendum vestibulum. In et sollicitudin urna. Morbi a arcu ac metus faucibus tempus. Nam eu imperdiet sapien, suscipit mattis tortor. Aenean blandit ipsum nisi, a eleifend ligula euismod at. Integer tincidunt pharetra felis, mollis placerat mauris hendrerit at. Curabitur convallis, est sit amet luctus volutpat, massa lacus cursus augue, sed eleifend magna quam et risus. Aliquam lobortis tincidunt metus vitae porttitor. Suspendisse potenti. Aenean ullamcorper, neque id commodo luctus, nulla nunc lobortis quam, id dapibus neque dui nec mauris. Etiam quis lorem quis elit commodo ornare. Ut pharetra purus ultricies enim ultrices efficitur. Proin vehicula tincidunt molestie. Mauris et placerat sem. + +Aliquam erat volutpat. Suspendisse velit turpis, posuere ac lacus eu, lacinia laoreet velit. Sed interdum felis neque, id blandit sem malesuada sit amet. Ut sagittis justo erat, efficitur semper orci tempor sed. Donec enim massa, posuere varius lectus egestas, pellentesque posuere mi. Cras tincidunt ut libero sed mattis. Suspendisse quis magna et tellus posuere interdum vel at purus. Pellentesque fringilla tristique neque, id aliquet tellus ultricies non. Duis ut tellus vel odio lobortis vulputate. + +Integer at magna ac erat convallis vestibulum. Sed lobortis porttitor mauris. Fusce varius lorem et volutpat pulvinar. Aenean ac vulputate lectus, vitae consequat velit. Suspendisse ex dui, varius ut risus ut, dictum scelerisque sem. Vivamus urna orci, volutpat ut convallis ac, venenatis vitae urna. In hac habitasse platea dictumst. Etiam eu purus arcu. Aenean vulputate leo urna, vel tristique dui sagittis euismod. Suspendisse non tellus efficitur ante rhoncus volutpat at et sapien. + +Sed dapibus accumsan porttitor. Phasellus facilisis lectus finibus ligula dignissim, id pulvinar lectus feugiat. Nullam egestas commodo nisi posuere aliquet. Morbi sit amet tortor sagittis, rutrum dui nec, dapibus sapien. Sed posuere tortor tortor, interdum auctor magna varius vitae. Vestibulum id sagittis augue. Curabitur fermentum arcu sem, eu condimentum quam rutrum non. Phasellus rutrum nibh quis lectus rhoncus pretium. Curabitur dictum interdum elit. Vestibulum maximus sodales imperdiet. Mauris auctor nec purus sed venenatis. In in urna purus. + +Duis placerat molestie suscipit. Morbi a elit id purus efficitur consequat. Nunc ac commodo turpis. Etiam sit amet lacus a ipsum tempus venenatis sed vel nibh. Duis elementum aliquam mi sed tristique. Morbi ligula tortor, semper ac est vel, lobortis maximus erat. Curabitur ipsum felis, laoreet vel condimentum eget, ullamcorper sit amet mauris. Nulla facilisi. Nam at purus sed mi egestas placerat vitae vel magna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse at dignissim diam. Phasellus consectetur eget neque vel viverra. Donec sollicitudin mattis dolor vel malesuada. Vivamus vehicula leo neque, vitae fermentum leo posuere et. Praesent dui est, finibus sit amet tristique quis, pharetra vel nibh. + +Duis nulla leo, accumsan eu odio eget, sagittis semper orci. Quisque ullamcorper ligula quam, commodo porttitor mauris ullamcorper eu. Cras varius sagittis felis in aliquam. Duis sodales risus ac justo vehicula, nec mattis diam lacinia. Cras eget lectus ipsum. Ut commodo, enim vitae malesuada hendrerit, ex dolor egestas lectus, sit amet hendrerit metus diam nec est. Vestibulum tortor metus, lobortis sit amet ante eget, tempor molestie lacus. In molestie et urna et semper. Mauris mollis, sem non hendrerit condimentum, sapien nisi cursus est, non suscipit quam justo non metus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam enim est, porta ac feugiat vitae, rutrum in lorem. Duis vehicula tortor ut posuere maximus. + +Nullam vestibulum non tellus sed commodo. Quisque mattis elit sit amet sapien sollicitudin, ut condimentum nisl congue. Aenean sagittis massa vel elit faucibus fermentum. Donec tincidunt nisi nec nisl sodales pellentesque. Mauris congue congue ligula ut suscipit. Vivamus velit tortor, tempor et gravida eget, fermentum sit amet ante. Nullam fringilla, lorem at ultrices cursus, urna neque ornare dolor, eu lacinia orci enim sed nibh. Ut a ullamcorper lectus, id mattis purus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean maximus sollicitudin posuere. Nunc at augue lacus. Aenean efficitur leo sit amet lacinia efficitur. + +Quisque venenatis quam mi, in pharetra odio vulputate eu. In vel nisl pulvinar, pulvinar ligula ut, sodales risus. Sed efficitur lectus at vestibulum tincidunt. Vestibulum eu ullamcorper elit. Fusce vestibulum magna enim, et tempor lacus posuere vitae. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer leo elit, luctus nec mattis sit amet, sollicitudin in turpis. + +Proin convallis venenatis leo, vitae tristique erat iaculis nec. Nulla facilisi. Duis porttitor, sapien et bibendum vulputate, sem libero sodales lacus, non malesuada felis erat ut libero. Nam non felis semper, finibus est a, mattis mauris. Praesent nec eros quam. Nulla hendrerit, augue consectetur eleifend ultricies, purus mi condimentum nulla, eget dapibus est nunc sed libero. Nullam elementum dui erat, vitae luctus libero sollicitudin et. Nulla odio magna, placerat in augue eu, dapibus imperdiet odio. Suspendisse imperdiet metus sit amet rhoncus dapibus. Cras at enim et urna vehicula cursus eu a mauris. Integer magna ante, eleifend ac placerat vitae, porta at nisi. Cras eget malesuada orci. Curabitur nunc est, vulputate id viverra et, dignissim sed odio. Curabitur non mattis sem. Sed bibendum, turpis vitae vehicula faucibus, nunc quam ultricies lectus, vitae viverra felis turpis at libero. + +Nullam ut egestas ligula. Proin hendrerit justo a lectus commodo venenatis. Nulla facilisi. Ut cursus lorem quis est bibendum condimentum. Aenean in tristique odio. Fusce tempor hendrerit ipsum. Curabitur mollis felis justo, quis dapibus erat auctor vel. Sed augue lectus, finibus ut urna quis, ullamcorper vestibulum dui. Etiam molestie aliquam tempor. Integer mattis sollicitudin erat, et tristique elit varius vel. Mauris a ex justo. + +Nam eros est, imperdiet non volutpat rutrum, pellentesque accumsan ligula. Duis sit amet turpis metus. Aenean in rhoncus metus, ac fringilla ex. Suspendisse condimentum egestas purus, ut pharetra odio vulputate vel. Duis tincidunt massa a placerat ultrices. Mauris ultricies nibh sit amet condimentum malesuada. Duis tincidunt id ipsum sed congue. + +Praesent eu ex augue. Nullam in porta ligula. In tincidunt accumsan arcu, in pellentesque magna tristique in. Mauris eleifend libero ac nisl viverra faucibus. Nam sollicitudin dolor in commodo hendrerit. Cras at orci metus. Ut quis laoreet orci. Vivamus ultrices leo pellentesque tempor aliquet. Maecenas ut eros vitae purus placerat vestibulum. Etiam vitae gravida dolor, quis rhoncus diam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. + +Suspendisse fringilla lacinia sagittis. Integer tincidunt consectetur tristique. Morbi non orci convallis, congue sapien quis, vulputate nunc. Donec a libero vel magna elementum facilisis non quis mi. Mauris posuere tellus non ipsum ultrices elementum. Vivamus massa velit, facilisis quis placerat aliquet, aliquet nec leo. Praesent a maximus sem. Sed neque elit, feugiat vel quam non, molestie sagittis nunc. Etiam luctus nunc ac mauris scelerisque, nec rhoncus lacus convallis. Nunc pharetra, nunc ac pulvinar aliquam, ex ipsum euismod augue, nec porttitor lacus turpis vitae neque. Fusce bibendum odio id tortor faucibus pellentesque. Sed ac porta nibh, eu gravida erat. + +Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam quis ullamcorper felis. Nulla mattis sagittis ante ac tincidunt. Integer ac felis efficitur, viverra libero et, facilisis ligula. Suspendisse a metus a massa rhoncus posuere. Phasellus suscipit ligula ut lacus facilisis, ac pellentesque ex tempor. Quisque consectetur massa mi, ac molestie libero dictum quis. Proin porttitor ligula quis erat tincidunt venenatis. Proin congue nunc sed elit gravida, nec consectetur lectus sodales. Etiam tincidunt convallis ipsum at vestibulum. Quisque maximus enim et mauris porttitor, et molestie magna tristique. Morbi vitae metus elit. Maecenas sed volutpat turpis. Aliquam vitae dolor vestibulum, elementum purus eget, dapibus nibh. Nullam egestas dui ac rutrum semper. + +Etiam hendrerit est metus, et condimentum metus aliquam ac. Pellentesque id neque id ipsum rhoncus vulputate. Aliquam erat nisl, posuere sit amet ligula ac, fermentum blandit felis. Vivamus fermentum mi risus, non lacinia purus viverra id. Aenean ac sapien consequat, finibus mauris nec, porta sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed quis consectetur ex, dignissim bibendum nulla. Phasellus ac libero at quam vehicula euismod non eu leo. Phasellus a sapien augue. + +Maecenas ligula dui, bibendum vitae mauris et, auctor laoreet felis. Duis non libero a mi semper mattis. Quisque consequat luctus massa, quis tristique eros auctor feugiat. Maecenas sodales euismod neque vitae facilisis. Nullam laoreet imperdiet velit at pellentesque. Etiam massa odio, facilisis a consequat vitae, placerat vel magna. Nunc sagittis eros nec urna fringilla, pulvinar vestibulum nibh scelerisque. Sed magna metus, cursus eu consequat et, pharetra a est. Suspendisse elementum neque a dui malesuada lacinia. Donec sed ipsum volutpat, cursus urna id, ullamcorper arcu. Maecenas laoreet nisl eget velit egestas sollicitudin. Etiam nisl turpis, mollis id dignissim vitae, tristique vehicula ante. Maecenas eget placerat est, at rutrum augue. Vivamus faucibus lacinia ullamcorper. Sed pulvinar urna sodales ante sodales, at gravida leo dictum. + +Morbi maximus, quam a lobortis bibendum, enim felis varius elit, ac vehicula elit nisl ut lacus. Quisque ut arcu augue. Praesent id turpis quam. Sed sed arcu eros. Maecenas at cursus lorem, ac eleifend nisi. Fusce mattis felis at commodo pharetra. Praesent ac commodo ipsum. Quisque finibus et eros vitae tincidunt. In hac habitasse platea dictumst. Praesent purus ipsum, luctus lobortis ornare quis, auctor eget justo. Nam vel enim sollicitudin, faucibus tortor eu, sagittis eros. Ut nec consectetur erat. Donec ultricies malesuada ligula, a hendrerit sapien volutpat in. Maecenas sed enim vitae sapien pulvinar faucibus. + +Proin semper nunc nibh, non consequat neque ullamcorper vel. Maecenas lobortis sagittis blandit. Aenean et arcu ultricies turpis malesuada malesuada. Ut quam ex, laoreet ut blandit cursus, feugiat vitae dolor. Etiam ex lacus, scelerisque vel erat vel, efficitur tincidunt magna. Morbi tristique lacinia dolor, in egestas magna ultrices vitae. Integer ultrices leo ac tempus venenatis. Praesent ac porta tortor. Vivamus ornare blandit tristique. Nulla rutrum finibus pellentesque. In non dui elementum, fermentum ipsum vel, varius magna. Pellentesque euismod tortor risus, ac pellentesque nisl faucibus eget. + +Vivamus eu enim purus. Cras ultrices rutrum egestas. Sed mollis erat nibh, at posuere nisl luctus nec. Nunc vulputate, sapien id auctor molestie, nisi diam tristique ante, non convallis tellus nibh at orci. Morbi a posuere purus, in ullamcorper ligula. Etiam elementum sit amet dui imperdiet iaculis. Proin vitae tincidunt ipsum, sit amet placerat lectus. Curabitur commodo sapien quam, et accumsan lectus fringilla non. Nullam eget accumsan enim, ac pharetra mauris. Sed quis tristique velit, vitae commodo nisi. Duis turpis dui, maximus ut risus at, finibus consequat nunc. Maecenas sed est accumsan, aliquet diam in, facilisis risus. Curabitur vehicula rutrum auctor. Nam iaculis risus pulvinar maximus viverra. Nulla vel augue et ex sagittis blandit. + +Ut sem nulla, porta ac ante ac, posuere laoreet eros. Donec sodales posuere justo a auctor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras mollis at orci hendrerit porta. Nullam sodales tortor tortor, non lacinia diam finibus id. Duis libero orci, suscipit ac odio et, dictum consequat ipsum. Pellentesque eu ligula sagittis, volutpat eros at, lacinia lorem. Cras euismod tellus in iaculis tempor. Quisque accumsan, magna a congue venenatis, ante ipsum aliquam lectus, at egestas enim nunc at justo. Quisque sem purus, viverra ut tristique ut, maximus id enim. Etiam quis placerat sem. In sollicitudin, lacus eu rutrum mollis, nulla eros luctus elit, vel dapibus urna purus nec urna. Phasellus egestas massa quam, ac molestie erat hendrerit a. Praesent ultrices neque ut turpis molestie auctor. Etiam molestie placerat purus, et euismod erat aliquam in. Morbi id suscipit justo. + +Proin est ante, consequat at varius a, mattis quis felis. Sed accumsan nibh sit amet ipsum elementum posuere. Vestibulum bibendum id diam sit amet gravida. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi nec dolor vel ipsum dignissim hendrerit vel non ipsum. Praesent facilisis orci quis elit auctor lobortis. Phasellus cursus risus lectus, vel lobortis libero dapibus in. Quisque tristique tempus leo a pulvinar. Pellentesque a magna tincidunt, pellentesque massa nec, laoreet orci. Morbi congue ornare dolor quis commodo. Phasellus massa nisi, tincidunt at eros dictum, hendrerit lobortis urna. Maecenas porta, magna id mattis molestie, nibh tellus lobortis sem, eget tincidunt ipsum quam eu turpis. + +Ut gravida orci risus, vel rutrum mauris vehicula id. Etiam bibendum, neque a placerat condimentum, ex orci imperdiet lectus, quis dapibus arcu lacus eget lectus. Sed consequat non mi sit amet venenatis. Fusce vestibulum erat libero, eget hendrerit risus vulputate sollicitudin. Integer sed eleifend felis. Donec commodo, sem eu mattis placerat, urna odio aliquam tellus, et laoreet justo tellus eget erat. Fusce sed suscipit tortor. Nam hendrerit nibh ac nunc auctor lacinia. Pellentesque placerat condimentum ipsum, eget semper tortor hendrerit vel. Nullam non urna eu lacus pellentesque congue ut id eros. + +Nunc finibus leo in rhoncus tristique. Sed eu ipsum nec nisl egestas faucibus eget a felis. Pellentesque vitae nisi in nulla accumsan fermentum. Sed venenatis feugiat eleifend. Fusce porttitor varius placerat. Aliquam aliquet lacus sit amet mattis mollis. Sed vel nulla quis dolor suscipit vehicula ac viverra lorem. Duis viverra ipsum eget nulla ullamcorper fermentum. Mauris tincidunt arcu quis quam fringilla ornare. Donec et iaculis tortor. Nam ultricies libero vel ipsum aliquet efficitur. Morbi eget dolor aliquam, tempus sapien eget, viverra ante. Donec varius mollis ex, sed efficitur purus euismod interdum. Quisque vel sapien non neque tincidunt semper. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. + +Suspendisse sit amet purus leo. Fusce lectus lorem, aliquam ac nulla eget, imperdiet ornare eros. Nullam sem augue, varius in nisi non, sollicitudin pellentesque ante. Etiam eu odio condimentum, tempor libero et, egestas arcu. Cras pellentesque eleifend aliquet. Pellentesque non blandit ligula. Ut congue viverra rhoncus. Phasellus mattis mi ac eros placerat, eu feugiat tellus ultrices. Aenean mollis laoreet libero eu imperdiet. Cras sed pulvinar mi, ac vehicula ligula. Vestibulum sit amet ex massa. In a egestas eros. + +Mauris pretium ipsum risus, venenatis cursus ante imperdiet id. Praesent eu turpis nec risus feugiat maximus ullamcorper ac lectus. Integer placerat at mi vel dapibus. Vestibulum fermentum turpis sit amet turpis viverra, id aliquet diam suscipit. Nam nec ex sed ante ullamcorper pharetra quis sit amet risus. Sed ac faucibus velit, id feugiat nibh. Nullam eget ipsum ex. Vivamus tincidunt non nunc non faucibus. Quisque bibendum viverra facilisis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at nisi hendrerit quam suscipit egestas. Curabitur laoreet maximus ultricies. Duis ut tellus ac augue molestie dictum. + +Suspendisse rhoncus iaculis erat, ut ullamcorper est tristique eget. Donec auctor nec risus at gravida. Vivamus volutpat vulputate tellus, vel ultricies eros suscipit eget. Ut pulvinar id mi eu tempus. Morbi malesuada augue in dui varius, nec blandit neque vehicula. Donec ornare nec nisl in mollis. Morbi enim nisi, rhoncus nec est id, dapibus tempus urna. Ut id elit a felis vestibulum consectetur. Duis lectus quam, pharetra sit amet diam sed, posuere vestibulum erat. Fusce vitae maximus massa. Nullam id metus tempus, iaculis risus eu, lobortis urna. Quisque in congue urna. Pellentesque placerat neque in augue dapibus, non varius ex malesuada. Curabitur ut eleifend libero. Fusce vitae ligula luctus, fermentum enim vitae, ultrices erat. + +Sed viverra augue turpis, scelerisque egestas sapien mattis eu. Duis laoreet magna at ex pharetra dapibus. Praesent eget odio vel quam venenatis dictum. Nulla in sollicitudin dolor. Mauris lobortis nec eros vel rhoncus. Vestibulum porta viverra venenatis. Curabitur vel scelerisque quam, a egestas velit. Praesent volutpat tincidunt magna at laoreet. + +Cras nec lorem odio. Pellentesque quis dui urna. Praesent at tellus ac lectus scelerisque placerat nec eu risus. Vestibulum sit amet mattis ligula. Vivamus sed nisi at leo elementum accumsan at sit amet arcu. Aenean mattis tellus nec leo gravida, eget hendrerit nisl faucibus. Mauris pellentesque luctus condimentum. Maecenas pretium sapien nunc, eget commodo dolor maximus id. Mauris vestibulum accumsan massa a dictum. Phasellus interdum quam ligula, ut maximus diam blandit aliquam. Nunc vitae ex eu erat condimentum consectetur. Maecenas interdum condimentum volutpat. + +Donec et enim a libero rutrum laoreet. Praesent a condimentum sem, at tincidunt quam. In vel molestie risus. Sed urna dui, molestie vitae mollis laoreet, tempor quis lectus. Praesent vitae auctor est, et aliquet nunc. Curabitur vulputate blandit nulla, at gravida metus. Maecenas gravida dui eu iaculis tristique. Pellentesque posuere turpis nec auctor eleifend. Suspendisse bibendum diam eu tellus lobortis, et laoreet quam congue. In hac habitasse platea dictumst. Morbi dictum neque velit, eget rutrum eros ultrices sit amet. + +Phasellus fermentum risus pharetra consectetur bibendum. Donec magna tortor, lacinia vitae nibh quis, aliquet pretium lorem. Donec turpis nisi, pretium eu enim volutpat, mattis malesuada augue. Nullam vel tellus iaculis, sollicitudin elit eget, tincidunt lacus. Fusce elementum elementum felis et iaculis. Suspendisse porta eros nec neque malesuada, in malesuada ante sollicitudin. Vivamus bibendum viverra molestie. + +Integer feugiat, erat nec convallis aliquam, velit felis congue erat, molestie eleifend tellus erat in tellus. Nunc et justo purus. Donec egestas fermentum dui non feugiat. Quisque in sapien sagittis, gravida quam id, iaculis lectus. Cras sagittis rhoncus bibendum. Fusce quis metus in velit scelerisque tincidunt at non ipsum. Vivamus efficitur ante eu odio vulputate, vitae ultricies risus vehicula. Proin eget odio eu sem tincidunt feugiat vel id lorem. + +Vestibulum sit amet nulla dignissim, euismod mi in, fermentum tortor. Donec ut aliquet libero, lacinia accumsan velit. Donec et nulla quam. Nullam laoreet odio nec nunc imperdiet, a congue eros venenatis. Quisque nec tellus sit amet neque interdum posuere. Duis quis mi gravida, tincidunt diam convallis, ultricies augue. Mauris consequat risus non porttitor congue. Ut in ligula consequat, viverra nunc a, eleifend enim. Duis ligula urna, imperdiet nec facilisis et, ornare eu ex. Proin lobortis lectus a lobortis porttitor. Nulla leo metus, egestas eu libero sed, pretium faucibus felis. Vestibulum non sem tortor. Nam cursus est leo. Vivamus luctus enim odio, non interdum sem dapibus a. Aenean accumsan consequat lectus in imperdiet. + +Donec vehicula laoreet ipsum in posuere. Quisque vel quam imperdiet, sollicitudin nisi quis, suscipit velit. Morbi id sodales mauris. Curabitur tellus arcu, feugiat sed dui sit amet, sodales sagittis libero. Aenean vel suscipit metus, non placerat leo. Vestibulum quis nulla elit. Proin scelerisque non ante ut commodo. Interdum et malesuada fames ac ante ipsum primis in faucibus. + +Sed non urna dolor. Suspendisse convallis mi porta pulvinar ultrices. Suspendisse quam ipsum, hendrerit non scelerisque molestie, interdum dictum nunc. Morbi condimentum condimentum turpis eu luctus. Pellentesque sagittis sollicitudin odio, sed ultricies felis ornare sit amet. Sed ultrices ex leo, a tincidunt nisl gravida sed. Nullam ornare accumsan porta. Praesent consectetur id est nec sollicitudin. + +In hac habitasse platea dictumst. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed sed ultrices nibh. Duis accumsan suscipit eros, a dictum odio tempus sit amet. Aenean imperdiet erat ac lacus finibus, scelerisque cursus massa imperdiet. Mauris molestie risus ut lacinia posuere. Nulla et sodales purus. Maecenas orci erat, placerat in tristique quis, placerat in mi. + +Donec sollicitudin pellentesque odio in feugiat. Morbi eu dolor ut mauris congue sollicitudin. Aliquam erat volutpat. Nulla id varius dui. Curabitur finibus urna ante, consectetur interdum nisi volutpat a. Quisque quis mi tristique, consequat tellus eget, rutrum sapien. Vivamus vitae tellus vulputate, rutrum ex eu, vulputate sem. Suspendisse viverra lorem tellus, vel interdum orci gravida quis. Ut laoreet arcu at mi ullamcorper finibus. Duis porta sagittis vestibulum. Sed commodo nisl vitae urna sollicitudin, nec lacinia est sodales. Curabitur imperdiet sodales dui sed iaculis. Sed ac tellus maximus, eleifend quam sit amet, feugiat elit. Aenean viverra, dui at mattis varius, est odio vestibulum sapien, sit amet mollis libero massa nec velit. Etiam quis sodales justo. + +Ut ultricies, sem eget sodales feugiat, nunc arcu congue elit, ac tempor justo massa nec purus. Maecenas enim nunc, pharetra eget dictum sit amet, tempus pellentesque velit. Suspendisse venenatis ligula in nulla mattis, et imperdiet ex tincidunt. Etiam vulputate, tellus et ultrices suscipit, enim velit laoreet massa, vitae congue odio enim ac urna. Morbi quam lorem, iaculis ac varius sagittis, euismod quis dolor. In ut dui eu purus feugiat consectetur. Vestibulum cursus velit quis lacus pellentesque iaculis. Cras in risus sed mauris porta rutrum. Nulla facilisi. Nullam eu bibendum est, non pellentesque lectus. Sed imperdiet feugiat lorem, quis convallis ante auctor in. Maecenas justo magna, scelerisque sit amet tellus eget, varius elementum risus. Duis placerat et quam sed varius. + +Duis nec nibh vitae nibh dignissim mollis quis sed felis. Curabitur vitae quam placerat, venenatis purus ut, euismod nisl. Curabitur porttitor nibh eu pulvinar ullamcorper. Suspendisse posuere nec ipsum ac dapibus. Cras convallis consectetur urna. Phasellus a nibh in dolor lacinia posuere id eget augue. In eu pharetra lorem, vitae cursus lacus. Aliquam tincidunt nibh lectus. Aenean facilisis ultricies posuere. Sed ut placerat orci. Curabitur scelerisque gravida blandit. Maecenas placerat ligula eget suscipit fringilla. Mauris a tortor justo. Aliquam hendrerit semper mollis. Phasellus et tincidunt libero. Etiam vel quam libero. + +Quisque aliquet tempor ex. Ut ante sem, vehicula at enim vel, gravida porta elit. Etiam vitae lacus a neque lobortis consectetur. Mauris sed interdum odio. Mauris elementum ex blandit tempor cursus. Integer in enim in leo viverra elementum. Fusce consectetur metus et sem rutrum, mattis euismod diam semper. Nunc sed ipsum vel urna consequat vehicula. Donec cursus pretium lorem, vestibulum pretium felis commodo sit amet. Nam blandit felis enim, eget gravida ex faucibus a. In nec neque massa. Etiam laoreet posuere ipsum. Praesent volutpat nunc dolor, ac vulputate magna facilisis non. Aenean congue turpis vel lectus sollicitudin tristique. Sed nec consequat purus, non vehicula quam. Etiam ultricies, est ac dictum tincidunt, turpis turpis pretium massa, a vulputate libero justo at nibh. + +Aliquam erat volutpat. Cras ultrices augue ac sollicitudin lobortis. Curabitur et aliquet purus. Duis feugiat semper facilisis. Phasellus lobortis cursus velit, a sollicitudin tortor. Nam feugiat sapien non dapibus condimentum. Morbi at mi bibendum, commodo quam at, laoreet enim. Integer eu ultrices enim. Sed vestibulum eu urna ut dictum. Curabitur at mattis leo, sed cursus massa. Aliquam porttitor, felis quis fermentum porttitor, justo velit feugiat nulla, eget condimentum sem dui ut sapien. + +In fringilla elit eu orci aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut eget fringilla tellus. Curabitur fermentum, mi et condimentum suscipit, elit neque bibendum dui, et hendrerit nunc metus id ipsum. Morbi placerat mi in hendrerit congue. Ut feugiat mauris eget scelerisque viverra. Vivamus sit amet erat dictum, sagittis lectus nec, pulvinar lorem. Sed non enim ac dui sollicitudin aliquet. Quisque ut lacus dolor. Fusce hendrerit malesuada euismod. Nulla faucibus vel mauris eu mollis. Mauris est diam, fringilla ac arcu feugiat, efficitur volutpat turpis. Aliquam venenatis cursus massa sed porttitor. Ut ac finibus enim, in tincidunt sapien. + +Nunc faucibus semper turpis a lacinia. Phasellus gravida, libero vel pulvinar ornare, ex sem tincidunt lectus, sit amet convallis augue risus at tortor. Quisque sit amet ipsum id nulla posuere vestibulum. Pellentesque scelerisque mauris vel leo viverra sodales. Nulla viverra aliquam ex, ut rutrum enim fermentum venenatis. Aenean eget dapibus ex, eget faucibus metus. Vestibulum volutpat leo in diam semper, eget porta magna suscipit. Sed sit amet nulla blandit, aliquam dolor ac, gravida velit. Sed vel velit viverra, maximus est id, convallis justo. + +Curabitur nulla ante, vulputate at libero vel, ullamcorper rutrum nibh. Pellentesque porttitor eu mauris id mattis. Duis vulputate augue elit, eget interdum justo pretium vel. Maecenas eu vulputate arcu, eget posuere purus. Suspendisse viverra a velit dictum eleifend. Suspendisse vitae dapibus diam. Donec vehicula justo in ante interdum, eu luctus diam placerat. Vivamus convallis ipsum eu orci suscipit, sed fermentum enim euismod. Maecenas faucibus elit vitae ex ornare tristique. Donec vestibulum nec elit sit amet porttitor. Aenean tempor lectus eget tortor hendrerit luctus. Nullam interdum vitae lectus vel feugiat. Cras in risus non magna consectetur lobortis. Sed faucibus enim quis gravida convallis. + +Phasellus eget massa sit amet libero ultrices suscipit. Vivamus at risus sapien. Nam mollis nunc eget velit dictum maximus. Sed pellentesque, nunc ac fringilla lacinia, quam enim mattis ex, sed euismod tortor metus eu neque. Ut mattis nisl ut lectus rhoncus, sodales bibendum eros porta. Nulla porttitor enim nec diam sagittis, eget porta velit efficitur. Vestibulum ultricies eros neque. Phasellus rutrum suscipit enim, in interdum ante gravida vitae. Sed in sagittis diam, non commodo velit. + +Morbi hendrerit odio orci, nec tincidunt odio rhoncus nec. Mauris neque velit, vehicula a lorem at, suscipit tristique dui. Sed finibus, nisl in mattis convallis, turpis neque sodales lacus, eu porta enim magna non diam. Nam commodo sodales risus consectetur malesuada. In eget elementum justo. Phasellus sit amet massa imperdiet, dapibus nunc sit amet, suscipit orci. Fusce condimentum laoreet feugiat. Ut ut viverra ante. Praesent bibendum interdum commodo. Nulla mollis nisi a est ornare volutpat. Sed at ligula eu nisi dapibus tempus. Proin cursus vestibulum justo, nec efficitur justo dignissim vel. Nunc quis maximus eros. + +Cras viverra, diam a tristique mattis, libero felis vulputate tellus, a ornare felis leo a dui. Nulla ante nulla, finibus ut tellus ut, blandit pharetra nibh. Proin eleifend fermentum ex, eget auctor libero vulputate in. Nullam ultricies, mauris placerat pretium placerat, leo urna lobortis leo, vel placerat arcu libero sed mauris. Aliquam mauris ligula, ornare at urna at, eleifend gravida ligula. Vestibulum consectetur ut nulla non scelerisque. Donec ornare, sem nec elementum aliquam, urna nulla bibendum metus, eu euismod dui ligula ac est. Fusce laoreet erat eu ex lobortis, quis bibendum ligula interdum. Sed vel mi erat. Vivamus id lacus ac enim mattis tempor. Nunc ultricies pellentesque enim sed euismod. Fusce tincidunt convallis elit quis aliquam. Mauris nulla ipsum, sollicitudin quis diam ac, feugiat volutpat tellus. In nibh nibh, vulputate quis tincidunt quis, pulvinar eget magna. Pellentesque quis finibus dolor. Suspendisse viverra vitae lectus non eleifend. + +Nunc ut orci et sapien maximus semper. Nulla dignissim sem urna, ac varius lectus ultricies id. Quisque aliquet pulvinar pretium. In ultricies molestie tellus vehicula porta. Nam enim lorem, aliquam eget ex et, hendrerit volutpat quam. Maecenas diam lacus, pellentesque eget tempus ac, pharetra eu elit. Donec vel eros a sem facilisis vulputate. Nullam ac nisi vulputate, laoreet nisl ac, eleifend sem. Nullam mi massa, rhoncus sed pharetra interdum, tincidunt eget nunc. Aliquam viverra mattis posuere. Mauris et dui sed nisl sollicitudin fermentum quis ut arcu. Nam placerat eget orci at tincidunt. Curabitur vel turpis metus. Phasellus nibh nulla, fermentum scelerisque sem vel, gravida tincidunt velit. Pellentesque vel quam tempor, finibus massa pellentesque, condimentum dui. + +Donec at mattis neque. Etiam velit diam, consequat auctor mauris id, hendrerit faucibus metus. Maecenas ullamcorper eros a est sodales, ac consectetur odio scelerisque. Donec leo metus, imperdiet at pellentesque vel, feugiat id erat. Suspendisse at magna enim. Vestibulum placerat sodales lorem id sollicitudin. Aenean at euismod ligula, eget mollis diam. Phasellus pulvinar, orci nec pretium condimentum, est erat facilisis purus, quis feugiat augue elit aliquam nulla. Aenean vitae tortor id risus congue tincidunt. Sed dolor enim, mattis a ullamcorper id, volutpat ac leo. + +Proin vehicula feugiat augue, id feugiat quam sodales quis. Donec et ultricies massa, a lacinia nulla. Duis aliquam augue ornare euismod viverra. Ut lectus risus, rutrum sit amet efficitur a, luctus nec nisl. Cras volutpat ullamcorper congue. Sed vitae odio metus. Phasellus aliquet euismod varius. + +Nullam sem ex, malesuada ut magna ut, pretium mollis arcu. Nam porttitor eros cursus mi lacinia faucibus. Suspendisse aliquet eleifend iaculis. Maecenas sit amet viverra tortor. Nunc a mollis risus. Etiam tempus dolor in tortor malesuada mattis. Ut tincidunt venenatis est sit amet dignissim. Vestibulum massa enim, tristique sed scelerisque eu, fringilla ac velit. Donec efficitur quis urna sit amet malesuada. Vestibulum consequat ac ligula in dapibus. Maecenas massa massa, molestie non posuere nec, elementum ut magna. In nisi erat, mollis non venenatis eu, faucibus in justo. Morbi gravida non ex non egestas. Pellentesque finibus laoreet diam, eu commodo augue congue vitae. + +Aenean sem mi, ullamcorper dapibus lobortis vitae, interdum tincidunt tortor. Vivamus eget vulputate libero. Ut bibendum posuere lectus, vel tincidunt tortor aliquet at. Phasellus malesuada orci et bibendum accumsan. Aliquam quis libero vel leo mollis porta. Sed sagittis leo ac lacus dictum, ac malesuada elit finibus. Suspendisse pharetra luctus commodo. Vivamus ultricies a odio non interdum. Vivamus scelerisque tincidunt turpis quis tempor. Pellentesque tortor ligula, varius non nunc eu, blandit sollicitudin neque. Nunc imperdiet, diam et tristique luctus, ipsum ex condimentum nunc, sit amet aliquam justo velit sed libero. Duis vel suscipit ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed tincidunt neque vel massa ultricies, id dictum leo consequat. Curabitur lobortis ultricies tellus, eget mattis nisl aliquam sit amet. + +Proin at suscipit justo. Vivamus ut vestibulum nisl. Pellentesque enim odio, pharetra non magna sed, efficitur auctor magna. Praesent tincidunt ante quis ante hendrerit viverra. Pellentesque vel ipsum id magna vulputate efficitur. Sed nec neque accumsan, pulvinar sapien quis, euismod mauris. Donec condimentum laoreet sapien quis gravida. Quisque sed mattis purus. Vestibulum placerat vel neque maximus scelerisque. + +Vestibulum mattis quam quis efficitur elementum. Duis dictum dolor ac scelerisque commodo. Fusce sollicitudin nisi sit amet dictum placerat. Suspendisse euismod pharetra eleifend. In eros nisl, porttitor sed mauris at, consectetur aliquet mauris. Donec euismod viverra neque sed fermentum. Phasellus libero magna, accumsan ut ultricies vitae, dignissim eget metus. Donec tellus turpis, interdum eget maximus nec, hendrerit eget massa. Curabitur auctor ligula in iaculis auctor. In ultrices quam suscipit cursus finibus. Aenean id mi at dolor interdum iaculis vitae ut lorem. Nullam sed nibh fringilla, lacinia odio nec, placerat erat. In dui libero, viverra ac viverra ac, pellentesque sit amet turpis. + +Nulla in enim ex. Sed feugiat est et consectetur venenatis. Cras varius facilisis dui vel convallis. Vestibulum et elit eget tellus feugiat pellentesque. In ut ante eu purus aliquet posuere. Nulla nec ornare sem, sed luctus lorem. Nam varius iaculis odio, eget faucibus nisl ullamcorper in. Sed eget cursus felis, nec efficitur nisi. + +Vivamus commodo et sem quis pulvinar. Pellentesque libero ante, venenatis vitae ligula sit amet, ornare sollicitudin nulla. Mauris eget tellus hendrerit, pulvinar metus quis, tempor nisi. Proin magna ex, laoreet sed tortor quis, varius fermentum enim. Integer eu dolor dictum, vulputate tortor et, aliquet ligula. Vestibulum vitae justo id mauris luctus sollicitudin. Suspendisse eget auctor neque, sodales egestas lorem. Vestibulum lacinia egestas metus vitae euismod. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus ex tellus, volutpat nec pulvinar sit amet, condimentum vitae dui. Curabitur vel felis sodales, lacinia nunc iaculis, ullamcorper augue. Pellentesque consequat dolor quis eros efficitur malesuada. Nulla ut malesuada lectus. + +Morbi et tristique ante. Aliquam erat volutpat. Vivamus vitae dui nec turpis pellentesque fermentum. Quisque eget velit massa. Pellentesque tristique aliquam nisl, eu sollicitudin justo venenatis sed. Duis eleifend sem eros, ut aliquam libero porttitor id. Sed non nunc consequat, rhoncus diam eu, commodo erat. Praesent fermentum in lectus id blandit. Donec quis ipsum at justo volutpat finibus. Nulla blandit justo nulla, at mollis lacus consequat eget. Aenean sollicitudin quis eros ut ullamcorper. + +Pellentesque venenatis nulla ut mi aliquet feugiat. Cras semper vel magna nec pharetra. Integer mattis felis et sapien commodo imperdiet. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis quis luctus felis. Vestibulum justo nibh, aliquam non lectus vitae, molestie placerat justo. Donec lorem nibh, gravida sit amet hendrerit ac, maximus id ipsum. Nunc ac libero sodales risus eleifend sagittis. Phasellus est massa, lobortis elementum ex sed, scelerisque consectetur neque. Nunc faucibus neque id lorem malesuada, eget convallis ex mattis. + +Sed turpis tortor, fermentum non turpis id, posuere varius nibh. Donec iaculis lorem dui. Etiam eros ante, sodales eget venenatis at, consectetur eget risus. Curabitur non aliquam ante, a pretium justo. Maecenas tempor nisl tortor, vitae dictum nisi ultrices eu. Duis eget dui ultrices, porttitor lacus sed, lobortis purus. Quisque mattis elit nec neque sagittis, sed commodo leo blandit. Mauris sodales interdum eleifend. Vestibulum condimentum consectetur augue, id luctus diam convallis et. + +Nunc suscipit risus in justo accumsan, a placerat magna tincidunt. Proin a nisl ipsum. Sed libero dui, tristique in augue quis, auctor tristique risus. Sed porttitor ex augue, eu porta augue molestie a. Duis rhoncus purus libero, eu tempus turpis condimentum at. Sed mollis nisi id lectus placerat tincidunt. Maecenas non scelerisque elit, quis rutrum orci. Donec in tellus pharetra urna ornare lobortis. Phasellus id risus at nisi varius rutrum eu ut turpis. + +Duis dictum justo quis nisl porta, eget tincidunt magna suscipit. Sed velit massa, ullamcorper eu sodales ac, pretium a massa. Duis et rutrum tortor. Nulla accumsan hendrerit sapien, cursus volutpat eros egestas eget. Donec sollicitudin at ante quis sollicitudin. Aenean blandit feugiat diam, id feugiat eros faucibus eget. Donec viverra dolor vel justo scelerisque dignissim. Nulla semper sem nunc, rhoncus semper tellus ultricies sed. Duis in ornare diam. Donec vehicula feugiat varius. Maecenas ut suscipit est. Vivamus sem sem, finibus at dolor sit amet, euismod dapibus ligula. Vestibulum fringilla odio dapibus, congue massa eget, congue sem. Donec feugiat magna eget tortor lacinia scelerisque non et ipsum. + +Suspendisse potenti. Nunc convallis sollicitudin ex eget venenatis. Sed iaculis nibh ex, vel ornare ligula congue dignissim. Quisque sollicitudin dolor ac dui vestibulum, sit amet molestie nisi aliquet. Donec at risus felis. Aenean sollicitudin metus a feugiat porta. Aenean a tortor ut dolor cursus sagittis. Vivamus consectetur porttitor nunc in facilisis. Proin sit amet mi vel lectus consectetur ultrices. + +Sed cursus lectus vitae nunc tristique, nec commodo turpis dapibus. Pellentesque luctus ex id facilisis ornare. Morbi quis placerat dolor. Donec in lectus in arcu mattis porttitor ac sit amet metus. Cras congue mauris non risus sodales, vitae feugiat ipsum bibendum. Nulla venenatis urna sed libero elementum, a cursus lorem commodo. Mauris faucibus lobortis eros nec commodo. + +Nullam suscipit ligula ullamcorper lorem commodo blandit. Nulla porta nibh quis pulvinar placerat. Vivamus eu arcu justo. Vestibulum imperdiet est ut fermentum porttitor. Pellentesque consectetur libero in sapien efficitur scelerisque. Curabitur ac erat sit amet odio aliquet dignissim. Pellentesque mi sem, rhoncus et luctus at, porttitor rutrum lectus. Vestibulum sollicitudin sollicitudin suscipit. Aenean efficitur dolor non ultrices imperdiet. Donec vel sem ex. + +Sed convallis mauris aliquam rutrum cursus. Ut tempor porttitor sodales. Etiam eu risus ac augue gravida egestas et eu dolor. Proin id magna ex. Suspendisse quis lectus quis lorem ultricies tempus. Donec porttitor velit vitae tincidunt faucibus. Aliquam vitae semper nisi. Morbi ultrices, leo non pretium dapibus, dui libero pellentesque ex, vel placerat enim ante vitae dui. Nunc varius, sem sit amet sagittis lobortis, lectus odio scelerisque mauris, ut vestibulum orci magna quis neque. Sed id congue justo. Interdum et malesuada fames ac ante ipsum primis in faucibus. Mauris congue nisi est, malesuada mollis elit tincidunt sed. Curabitur sed ex sit amet felis tristique elementum vitae vel nibh. + +Etiam mollis pretium lobortis. Mauris augue lacus, efficitur at lacus sed, mollis tincidunt lectus. Aliquam erat volutpat. Donec at euismod elit, et mattis felis. Sed id lobortis urna. Morbi imperdiet vestibulum leo, sed maximus leo blandit eu. Aliquam semper lorem neque, nec euismod turpis mattis mollis. Quisque lobortis urna ultrices odio pretium, ac venenatis orci faucibus. Suspendisse bibendum odio ligula, sed lobortis massa pharetra nec. Donec turpis justo, iaculis at dictum ac, finibus eu libero. Maecenas quis porttitor mi, sit amet aliquet neque. + +Vivamus auctor vulputate ante, at egestas lorem. Donec eu risus in nulla mollis ultricies at et urna. Duis accumsan porta egestas. Ut vel euismod augue. Fusce convallis nulla ante, nec fringilla velit aliquet at. Nam malesuada dapibus ligula, a aliquam nibh scelerisque ac. Praesent malesuada neque et pellentesque interdum. Curabitur volutpat at turpis vitae tristique. Vivamus porttitor semper congue. Quisque suscipit lacus mi, rhoncus ultrices tortor auctor quis. Maecenas neque neque, molestie ac facilisis eget, luctus ac lorem. In ut odio ut lacus suscipit pulvinar vitae sed elit. Nulla imperdiet, sem quis euismod sagittis, dui erat luctus dolor, faucibus faucibus erat sem eget nunc. Nam accumsan placerat malesuada. Maecenas convallis finibus pulvinar. + +Cras at placerat tortor. Morbi facilisis auctor felis sit amet molestie. Donec sodales sed lorem vitae suscipit. Etiam fermentum pharetra ipsum, nec luctus orci gravida eu. Pellentesque gravida, est non condimentum tempus, mauris ligula molestie est, in congue dolor nisl vel sapien. Duis congue tempor augue, id rutrum eros porta dapibus. Etiam rutrum eget est eget vestibulum. Aenean mollis arcu vel consequat varius. Praesent at condimentum felis. Duis nec interdum nisl. Donec commodo lorem sed sapien scelerisque malesuada non eu urna. In blandit non ipsum at porta. Nam lobortis leo vitae dui auctor, non feugiat quam bibendum. Donec auctor lectus sagittis laoreet maximus. Maecenas rhoncus laoreet porttitor. Vestibulum porttitor augue ut lectus hendrerit, eget posuere mi gravida. + +Sed mattis ex in erat pulvinar, eu imperdiet magna dapibus. Etiam nisi nibh, tempus non tellus sit amet, mattis tempor odio. Quisque nec lorem feugiat, lobortis odio et, commodo nunc. Maecenas semper purus nisi, nec vehicula nibh eleifend vitae. Nulla fermentum a lectus at maximus. Phasellus finibus metus non euismod ultrices. Etiam a pulvinar ante. Quisque convallis nec metus sit amet facilisis. Praesent laoreet massa et sollicitudin laoreet. Vestibulum in mauris aliquet, convallis mi ut, elementum purus. Nulla purus nulla, sodales at hendrerit quis, tempus sed lectus. + +Nam ut laoreet neque, ut maximus nibh. Maecenas quis justo pellentesque, sollicitudin elit at, venenatis velit. Aenean nunc velit, vehicula scelerisque odio at, consectetur laoreet purus. Duis dui purus, malesuada quis ipsum sit amet, tempor interdum libero. Curabitur porta scelerisque sapien, vitae cursus diam condimentum eu. Phasellus sed orci quam. Nullam vitae dui quis purus tincidunt vestibulum. Curabitur quis nulla porta, cursus arcu non, auctor enim. Etiam sollicitudin ex id sem vehicula mollis. Morbi viverra laoreet tincidunt. Praesent ut semper dui. Nam sit amet pretium neque. Mauris vitae luctus diam, in lacinia purus. Maecenas ut placerat justo, ut porta felis. Integer eu mauris ante. + +Aenean porttitor tellus diam, tempor consequat metus efficitur id. Suspendisse ut felis at erat tempor dictum at nec sapien. Sed vestibulum interdum felis, ac mattis mauris porta in. Nunc et condimentum massa. Sed cursus dictum justo et luctus. Integer convallis enim nisl, a rutrum lectus ultricies in. Donec dapibus lacus at nulla dapibus, id sollicitudin velit hendrerit. Fusce a magna at orci mollis rutrum ac a dolor. Aliquam erat volutpat. Morbi varius porta nunc, sit amet sodales ex hendrerit commodo. Donec tincidunt tortor sapien, vitae egestas sapien vehicula eget. + +Suspendisse potenti. Donec pulvinar felis nec leo malesuada interdum. Integer posuere placerat maximus. Donec nibh ipsum, tincidunt vitae luctus vitae, bibendum at leo. Sed cursus nisl ut ex faucibus aliquet sed nec eros. Curabitur molestie posuere felis. Integer faucibus velit eget consequat iaculis. Mauris sed vulputate odio. Phasellus maximus, elit a pharetra egestas, lorem magna semper tellus, vestibulum semper diam felis at sapien. Suspendisse facilisis, nisl sit amet euismod vehicula, libero nulla vehicula dolor, quis fermentum nibh elit sit amet diam. + +Morbi lorem enim, euismod eu varius ut, scelerisque quis odio. Nam tempus vitae eros id molestie. Nunc pretium in nulla eget accumsan. Quisque mattis est ut semper aliquet. Maecenas eget diam elementum, fermentum ipsum a, euismod sapien. Duis quam ligula, cursus et velit nec, ullamcorper tincidunt magna. Donec vulputate nisl est, et ullamcorper urna tempor sit amet. + +Proin lacinia dui non turpis congue pretium. Morbi posuere metus vel purus imperdiet interdum. Morbi venenatis vel eros non ultricies. Nulla vel semper elit. Ut quis purus tincidunt, auctor justo ut, faucibus turpis. Proin quis mattis erat, at faucibus ligula. Mauris in mauris enim. Donec facilisis enim at est feugiat hendrerit. Nam vel nisi lorem. Fusce ultricies convallis diam, in feugiat tortor luctus quis. Donec tempor, leo vitae volutpat aliquam, magna elit feugiat leo, quis placerat sapien felis eget arcu. Donec ornare fermentum eleifend. Integer a est orci. + +Proin rhoncus egestas leo. Nulla ultricies porta elit quis ornare. Nunc fermentum interdum vehicula. In in ligula lorem. Donec nec arcu sit amet orci lobortis iaculis. Mauris at mollis erat, sit amet mollis tortor. Mauris laoreet justo ullamcorper porttitor auctor. Aenean sit amet aliquam lectus, id fermentum eros. Praesent urna sem, vehicula ac fermentum id, dapibus ut purus. Vestibulum vitae tempus nunc. Donec at nunc ornare metus volutpat porta at eget magna. Donec varius aliquet metus, eu lobortis risus aliquam sed. Ut dapibus fermentum velit, ac tincidunt libero faucibus at. + +In in purus auctor, feugiat massa quis, facilisis nisi. Donec dolor purus, gravida eget dolor ac, porttitor imperdiet urna. Donec faucibus placerat erat, a sagittis ante finibus ac. Sed venenatis dignissim elit, in iaculis felis posuere faucibus. Praesent sed viverra dolor. Mauris sed nulla consectetur nunc laoreet molestie in ut metus. Proin ac ex sit amet magna vulputate hendrerit ac condimentum urna. Proin ligula metus, gravida et sollicitudin facilisis, iaculis ut odio. Cras tincidunt urna et augue varius, ut facilisis urna consequat. Aenean vehicula finibus quam. Ut iaculis eu diam ac mollis. Nam mi lorem, tristique eget varius at, sodales at urna. + +Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vitae dictum erat, et auctor ipsum. Nullam nunc nunc, sollicitudin quis magna a, vestibulum fermentum mauris. Praesent at erat dolor. Proin laoreet tristique nulla vel efficitur. Nam sed ultrices nibh, id rutrum nunc. Curabitur eleifend a erat sit amet sollicitudin. Nullam metus quam, laoreet vitae dapibus id, placerat sed leo. Aliquam erat volutpat. Donec turpis nisl, cursus eu ex sit amet, lacinia pellentesque nisl. Sed id ipsum massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec interdum scelerisque lorem eu mattis. + +Vivamus ac tristique massa, nec facilisis nisl. Nam ipsum neque, tincidunt vel urna in, cursus imperdiet enim. Nam pellentesque egestas tempus. Morbi facilisis imperdiet libero vitae fringilla. Nam lacinia ligula at sapien facilisis malesuada. Nullam accumsan pulvinar sem, et cursus libero porta sit amet. Curabitur vulputate erat elit, ut pulvinar erat maximus vel. + +Cras aliquet metus ut purus sagittis, vel venenatis ante consectetur. Pellentesque nulla lacus, viverra viverra mattis non, placerat vitae nibh. Donec enim turpis, accumsan sit amet tincidunt eu, imperdiet non metus. Morbi ipsum eros, tincidunt vel est ac, tristique porttitor nibh. Praesent ut ullamcorper mauris. Sed laoreet sit amet diam congue venenatis. Integer porta purus nec orci sagittis posuere. + +Donec vehicula mauris eget lacus mollis venenatis et sed nibh. Nam sodales ligula ipsum, scelerisque lacinia ligula sagittis in. Nam sit amet ipsum at erat malesuada congue. Aenean ut sollicitudin sapien. Etiam at tempor odio. Mauris vitae purus ut magna suscipit consequat. Vivamus quis sapien neque. Nulla vulputate sem sit amet massa pellentesque, eleifend tristique ligula egestas. Suspendisse tincidunt gravida mi, in pulvinar lectus egestas non. Aenean imperdiet ex sit amet nunc sollicitudin porta. Integer justo odio, ultricies at interdum in, rhoncus vitae sem. Sed porttitor arcu quis purus aliquet hendrerit. Praesent tempor tortor at dolor dictum pulvinar. Nulla aliquet nunc non ligula scelerisque accumsan. Donec nulla justo, congue vitae massa in, faucibus hendrerit magna. Donec non egestas purus. + +Vivamus iaculis, lacus efficitur faucibus porta, dui nulla facilisis ligula, ut sodales odio nunc id sapien. Cras viverra auctor ipsum, dapibus mattis neque dictum sed. Sed convallis fermentum molestie. Nulla facilisi turpis duis. diff --git a/src/vs/platform/instantiation/common/extensions.ts b/src/vs/platform/instantiation/common/extensions.ts index 257054074d..2c5bbe9646 100644 --- a/src/vs/platform/instantiation/common/extensions.ts +++ b/src/vs/platform/instantiation/common/extensions.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { SyncDescriptor } from './descriptors'; -import { ServiceIdentifier, IConstructorSignature0 } from './instantiation'; +import { ServiceIdentifier, BrandedService } from './instantiation'; const _registry: [ServiceIdentifier, SyncDescriptor][] = []; -export function registerSingleton(id: ServiceIdentifier, ctor: IConstructorSignature0, supportsDelayedInstantiation?: boolean): void { +export function registerSingleton(id: ServiceIdentifier, ctor: { new(...services: Services): T }, supportsDelayedInstantiation?: boolean): void { _registry.push([id, new SyncDescriptor(ctor, [], supportsDelayedInstantiation)]); } diff --git a/src/vs/platform/instantiation/common/instantiation.ts b/src/vs/platform/instantiation/common/instantiation.ts index 06b635015b..047e5c4092 100644 --- a/src/vs/platform/instantiation/common/instantiation.ts +++ b/src/vs/platform/instantiation/common/instantiation.ts @@ -22,7 +22,7 @@ export namespace _util { // --- interfaces ------ -type BrandedService = { _serviceBrand: undefined }; +export type BrandedService = { _serviceBrand: undefined }; export interface IConstructorSignature0 { new(...services: BrandedService[]): T; @@ -67,6 +67,22 @@ export interface ServicesAccessor { export const IInstantiationService = createDecorator('instantiationService'); +/** + * Given a list of arguments as a tuple, attempt to extract the leading, non-service arguments + * to their own tuple. + */ +type GetLeadingNonServiceArgs = + Args extends [...BrandedService[]] ? [] + : Args extends [infer A1, ...BrandedService[]] ? [A1] + : Args extends [infer A1, infer A2, ...BrandedService[]] ? [A1, A2] + : Args extends [infer A1, infer A2, infer A3, ...BrandedService[]] ? [A1, A2, A3] + : Args extends [infer A1, infer A2, infer A3, infer A4, ...BrandedService[]] ? [A1, A2, A3, A4] + : Args extends [infer A1, infer A2, infer A3, infer A4, infer A5, ...BrandedService[]] ? [A1, A2, A3, A4, A5] + : Args extends [infer A1, infer A2, infer A3, infer A4, infer A5, infer A6, ...BrandedService[]] ? [A1, A2, A3, A4, A5, A6] + : Args extends [infer A1, infer A2, infer A3, infer A4, infer A5, infer A6, infer A7, ...BrandedService[]] ? [A1, A2, A3, A4, A5, A6, A7] + : Args extends [infer A1, infer A2, infer A3, infer A4, infer A5, infer A6, infer A7, infer A8, ...BrandedService[]] ? [A1, A2, A3, A4, A5, A6, A7, A8] + : never; + export interface IInstantiationService { _serviceBrand: undefined; @@ -85,15 +101,8 @@ export interface IInstantiationService { createInstance(descriptor: descriptors.SyncDescriptor7, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7): T; createInstance(descriptor: descriptors.SyncDescriptor8, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8): T; - createInstance(ctor: IConstructorSignature0): T; - createInstance(ctor: IConstructorSignature1, first: A1): T; - createInstance(ctor: IConstructorSignature2, first: A1, second: A2): T; - createInstance(ctor: IConstructorSignature3, first: A1, second: A2, third: A3): T; - createInstance(ctor: IConstructorSignature4, first: A1, second: A2, third: A3, fourth: A4): T; - createInstance(ctor: IConstructorSignature5, first: A1, second: A2, third: A3, fourth: A4, fifth: A5): T; - createInstance(ctor: IConstructorSignature6, first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6): T; - createInstance(ctor: IConstructorSignature7, first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6, seventh: A7): T; - createInstance(ctor: IConstructorSignature8, first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6, seventh: A7, eigth: A8): T; + createInstance any, R extends InstanceType>(t: Ctor, ...args: GetLeadingNonServiceArgs>): R; + createInstance any, R extends InstanceType>(t: Ctor): R; /** * diff --git a/src/vs/platform/instantiation/common/instantiationService.ts b/src/vs/platform/instantiation/common/instantiationService.ts index 1b259194d8..e5cae20e43 100644 --- a/src/vs/platform/instantiation/common/instantiationService.ts +++ b/src/vs/platform/instantiation/common/instantiationService.ts @@ -157,7 +157,7 @@ export class InstantiationService implements IInstantiationService { graph.lookupOrInsertNode(item); // a weak but working heuristic for cycle checks - if (cycleCount++ > 150) { // {{SQL CARBON EDIT}} we hit ~102 with our services + if (cycleCount++ > 150) { throw new CyclicDependencyError(graph); } diff --git a/src/vs/platform/keybinding/common/abstractKeybindingService.ts b/src/vs/platform/keybinding/common/abstractKeybindingService.ts index 5c0288b3a6..05ca3828c4 100644 --- a/src/vs/platform/keybinding/common/abstractKeybindingService.ts +++ b/src/vs/platform/keybinding/common/abstractKeybindingService.ts @@ -11,7 +11,7 @@ import { KeyCode, Keybinding, ResolvedKeybinding } from 'vs/base/common/keyCodes import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IContextKeyService, IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey'; -import { IKeybindingEvent, IKeybindingService, IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; +import { IKeybindingEvent, IKeybindingService, IKeyboardEvent, KeybindingsSchemaContribution } from 'vs/platform/keybinding/common/keybinding'; import { IResolveResult, KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -57,6 +57,7 @@ export abstract class AbstractKeybindingService extends Disposable implements IK public abstract resolveKeybinding(keybinding: Keybinding): ResolvedKeybinding[]; public abstract resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): ResolvedKeybinding; public abstract resolveUserBinding(userBinding: string): ResolvedKeybinding[]; + public abstract registerSchemaContribution(contribution: KeybindingsSchemaContribution): void; public abstract _dumpDebugInfo(): string; public abstract _dumpDebugInfoJSON(): string; @@ -64,11 +65,11 @@ export abstract class AbstractKeybindingService extends Disposable implements IK return ''; } - public getDefaultKeybindings(): ResolvedKeybindingItem[] { + public getDefaultKeybindings(): readonly ResolvedKeybindingItem[] { return this._getResolver().getDefaultKeybindings(); } - public getKeybindings(): ResolvedKeybindingItem[] { + public getKeybindings(): readonly ResolvedKeybindingItem[] { return this._getResolver().getKeybindings(); } diff --git a/src/vs/platform/keybinding/common/keybinding.ts b/src/vs/platform/keybinding/common/keybinding.ts index 5682946aba..7fa513352f 100644 --- a/src/vs/platform/keybinding/common/keybinding.ts +++ b/src/vs/platform/keybinding/common/keybinding.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; -import { KeyCode, Keybinding, ResolvedKeybinding } from 'vs/base/common/keyCodes'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { Keybinding, KeyCode, ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IResolveResult } from 'vs/platform/keybinding/common/keybindingResolver'; @@ -38,6 +39,12 @@ export interface IKeyboardEvent { readonly code: string; } +export interface KeybindingsSchemaContribution { + readonly onDidChange?: Event; + + getSchemaAdditions(): IJSONSchema[]; +} + export const IKeybindingService = createDecorator('keybindingService'); export interface IKeybindingService { @@ -80,9 +87,9 @@ export interface IKeybindingService { getDefaultKeybindingsContent(): string; - getDefaultKeybindings(): ResolvedKeybindingItem[]; + getDefaultKeybindings(): readonly ResolvedKeybindingItem[]; - getKeybindings(): ResolvedKeybindingItem[]; + getKeybindings(): readonly ResolvedKeybindingItem[]; customKeybindingsCount(): number; @@ -92,6 +99,8 @@ export interface IKeybindingService { */ mightProducePrintableCharacter(event: IKeyboardEvent): boolean; + registerSchemaContribution(contribution: KeybindingsSchemaContribution): void; + _dumpDebugInfo(): string; _dumpDebugInfoJSON(): string; } diff --git a/src/vs/platform/keybinding/common/keybindingResolver.ts b/src/vs/platform/keybinding/common/keybindingResolver.ts index 5172a4052d..a1569b601f 100644 --- a/src/vs/platform/keybinding/common/keybindingResolver.ts +++ b/src/vs/platform/keybinding/common/keybindingResolver.ts @@ -215,11 +215,11 @@ export class KeybindingResolver { return this._defaultBoundCommands; } - public getDefaultKeybindings(): ResolvedKeybindingItem[] { + public getDefaultKeybindings(): readonly ResolvedKeybindingItem[] { return this._defaultKeybindings; } - public getKeybindings(): ResolvedKeybindingItem[] { + public getKeybindings(): readonly ResolvedKeybindingItem[] { return this._keybindings; } diff --git a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts index 13f87aefa3..c1fba7c67b 100644 --- a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts +++ b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts @@ -87,6 +87,10 @@ suite('AbstractKeybindingService', () => { public _dumpDebugInfoJSON(): string { return ''; } + + public registerSchemaContribution() { + // noop + } } let createTestKeybindingService: (items: ResolvedKeybindingItem[], contextValue?: any) => TestKeybindingService = null!; diff --git a/src/vs/platform/keybinding/test/common/mockKeybindingService.ts b/src/vs/platform/keybinding/test/common/mockKeybindingService.ts index 0c1e2111b8..39111c9ee0 100644 --- a/src/vs/platform/keybinding/test/common/mockKeybindingService.ts +++ b/src/vs/platform/keybinding/test/common/mockKeybindingService.ts @@ -141,4 +141,8 @@ export class MockKeybindingService implements IKeybindingService { public _dumpDebugInfoJSON(): string { return ''; } + + public registerSchemaContribution() { + // noop + } } diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 3ab9ba36f8..b9c0fbb2f6 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -22,12 +22,12 @@ import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Registry } from 'vs/platform/registry/common/platform'; -import { attachListStyler, computeStyles, defaultListStyles } from 'vs/platform/theme/common/styler'; +import { attachListStyler, computeStyles, defaultListStyles, IColorMapping, attachStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; import { ObjectTree, IObjectTreeOptions, ICompressibleTreeRenderer, CompressibleObjectTree, ICompressibleObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree'; import { ITreeEvent, ITreeRenderer, IAsyncDataSource, IDataSource, ITreeMouseEvent } from 'vs/base/browser/ui/tree/tree'; -import { AsyncDataTree, IAsyncDataTreeOptions, CompressibleAsyncDataTree, ITreeCompressionDelegate } from 'vs/base/browser/ui/tree/asyncDataTree'; +import { AsyncDataTree, IAsyncDataTreeOptions, CompressibleAsyncDataTree, ITreeCompressionDelegate, ICompressibleAsyncDataTreeOptions } from 'vs/base/browser/ui/tree/asyncDataTree'; import { DataTree, IDataTreeOptions } from 'vs/base/browser/ui/tree/dataTree'; import { IKeyboardNavigationEventFilter, IAbstractTreeOptions, RenderIndentGuides } from 'vs/base/browser/ui/tree/abstractTree'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; @@ -55,6 +55,7 @@ export class ListService implements IListService { _serviceBrand: undefined; + private disposables = new DisposableStore(); private lists: IRegisteredList[] = []; private _lastFocusedWidget: ListWidget | undefined = undefined; @@ -62,7 +63,11 @@ export class ListService implements IListService { return this._lastFocusedWidget; } - constructor(@IContextKeyService contextKeyService: IContextKeyService) { } + constructor(@IThemeService themeService: IThemeService) { + // create a shared default tree style sheet for performance reasons + const styleController = new DefaultStyleController(createStyleSheet(), ''); + this.disposables.add(attachListStyler(styleController, themeService)); + } register(widget: ListWidget, extraContextKeys?: (IContextKey)[]): IDisposable { if (this.lists.some(l => l.widget === widget)) { @@ -89,6 +94,10 @@ export class ListService implements IListService { }) ); } + + dispose(): void { + this.disposables.dispose(); + } } const RawWorkbenchListFocusContextKey = new RawContextKey('listFocus', true); @@ -221,13 +230,8 @@ function toWorkbenchListOptions(options: IListOptions, configurationServic return [result, disposables]; } -let sharedListStyleSheet: HTMLStyleElement; -function getSharedListStyleSheet(): HTMLStyleElement { - if (!sharedListStyleSheet) { - sharedListStyleSheet = createStyleSheet(); - } - - return sharedListStyleSheet; +export interface IWorkbenchListOptions extends IListOptions { + readonly overrideStyles?: IColorMapping; } export class WorkbenchList extends List { @@ -246,7 +250,7 @@ export class WorkbenchList extends List { container: HTMLElement, delegate: IListVirtualDelegate, renderers: IListRenderer[], - options: IListOptions, + options: IWorkbenchListOptions, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, @@ -259,7 +263,6 @@ export class WorkbenchList extends List { super(user, container, delegate, renderers, { keyboardSupport: false, - styleController: new DefaultStyleController(getSharedListStyleSheet()), ...computeStyles(themeService.getTheme(), defaultListStyles), ...workbenchListOptions, horizontalScrolling @@ -282,7 +285,11 @@ export class WorkbenchList extends List { this.disposables.add(this.contextKeyService); this.disposables.add((listService as ListService).register(this)); - this.disposables.add(attachListStyler(this, themeService)); + + if (options.overrideStyles) { + this.disposables.add(attachStyler(themeService, options.overrideStyles, this)); + } + this.disposables.add(this.onSelectionChange(() => { const selection = this.getSelection(); const focus = this.getFocus(); @@ -328,7 +335,7 @@ export class WorkbenchPagedList extends PagedList { container: HTMLElement, delegate: IListVirtualDelegate, renderers: IPagedRenderer[], - options: IListOptions, + options: IWorkbenchListOptions, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, @@ -340,7 +347,6 @@ export class WorkbenchPagedList extends PagedList { super(user, container, delegate, renderers, { keyboardSupport: false, - styleController: new DefaultStyleController(getSharedListStyleSheet()), ...computeStyles(themeService.getTheme(), defaultListStyles), ...workbenchListOptions, horizontalScrolling @@ -360,7 +366,10 @@ export class WorkbenchPagedList extends PagedList { this.disposables.add(this.contextKeyService); this.disposables.add((listService as ListService).register(this)); - this.disposables.add(attachListStyler(this, themeService)); + + if (options.overrideStyles) { + this.disposables.add(attachStyler(themeService, options.overrideStyles, this)); + } this.registerListeners(); } @@ -777,6 +786,10 @@ function createKeyboardNavigationEventFilter(container: HTMLElement, keybindingS }; } +export interface IWorkbenchObjectTreeOptions extends IObjectTreeOptions { + readonly overrideStyles?: IColorMapping; +} + export class WorkbenchObjectTree, TFilterData = void> extends ObjectTree { private internals: WorkbenchTreeInternals; @@ -788,7 +801,7 @@ export class WorkbenchObjectTree, TFilterData = void> container: HTMLElement, delegate: IListVirtualDelegate, renderers: ITreeRenderer[], - options: IObjectTreeOptions, + options: IWorkbenchObjectTreeOptions, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, @@ -796,14 +809,18 @@ export class WorkbenchObjectTree, TFilterData = void> @IKeybindingService keybindingService: IKeybindingService, @IAccessibilityService accessibilityService: IAccessibilityService ) { - const { options: treeOptions, getAutomaticKeyboardNavigation, disposable } = workbenchTreeDataPreamble(container, options, contextKeyService, themeService, configurationService, keybindingService, accessibilityService); + const { options: treeOptions, getAutomaticKeyboardNavigation, disposable } = workbenchTreeDataPreamble(container, options, contextKeyService, configurationService, keybindingService, accessibilityService); super(user, container, delegate, renderers, treeOptions); this.disposables.add(disposable); - this.internals = new WorkbenchTreeInternals(this, treeOptions, getAutomaticKeyboardNavigation, contextKeyService, listService, themeService, configurationService, accessibilityService); + this.internals = new WorkbenchTreeInternals(this, treeOptions, getAutomaticKeyboardNavigation, options.overrideStyles, contextKeyService, listService, themeService, configurationService, accessibilityService); this.disposables.add(this.internals); } } +export interface IWorkbenchCompressibleObjectTreeOptions extends ICompressibleObjectTreeOptions { + readonly overrideStyles?: IColorMapping; +} + export class WorkbenchCompressibleObjectTree, TFilterData = void> extends CompressibleObjectTree { private internals: WorkbenchTreeInternals; @@ -815,7 +832,7 @@ export class WorkbenchCompressibleObjectTree, TFilter container: HTMLElement, delegate: IListVirtualDelegate, renderers: ICompressibleTreeRenderer[], - options: ICompressibleObjectTreeOptions, + options: IWorkbenchCompressibleObjectTreeOptions, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, @@ -823,14 +840,18 @@ export class WorkbenchCompressibleObjectTree, TFilter @IKeybindingService keybindingService: IKeybindingService, @IAccessibilityService accessibilityService: IAccessibilityService ) { - const { options: treeOptions, getAutomaticKeyboardNavigation, disposable } = workbenchTreeDataPreamble(container, options, contextKeyService, themeService, configurationService, keybindingService, accessibilityService); + const { options: treeOptions, getAutomaticKeyboardNavigation, disposable } = workbenchTreeDataPreamble(container, options, contextKeyService, configurationService, keybindingService, accessibilityService); super(user, container, delegate, renderers, treeOptions); this.disposables.add(disposable); - this.internals = new WorkbenchTreeInternals(this, treeOptions, getAutomaticKeyboardNavigation, contextKeyService, listService, themeService, configurationService, accessibilityService); + this.internals = new WorkbenchTreeInternals(this, treeOptions, getAutomaticKeyboardNavigation, options.overrideStyles, contextKeyService, listService, themeService, configurationService, accessibilityService); this.disposables.add(this.internals); } } +export interface IWorkbenchDataTreeOptions extends IDataTreeOptions { + readonly overrideStyles?: IColorMapping; +} + export class WorkbenchDataTree extends DataTree { private internals: WorkbenchTreeInternals; @@ -843,7 +864,7 @@ export class WorkbenchDataTree extends DataTree, renderers: ITreeRenderer[], dataSource: IDataSource, - options: IDataTreeOptions, + options: IWorkbenchDataTreeOptions, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, @@ -851,14 +872,18 @@ export class WorkbenchDataTree extends DataTree extends IAsyncDataTreeOptions { + readonly overrideStyles?: IColorMapping; +} + export class WorkbenchAsyncDataTree extends AsyncDataTree { private internals: WorkbenchTreeInternals; @@ -871,7 +896,7 @@ export class WorkbenchAsyncDataTree extends Async delegate: IListVirtualDelegate, renderers: ITreeRenderer[], dataSource: IAsyncDataSource, - options: IAsyncDataTreeOptions, + options: IWorkbenchAsyncDataTreeOptions, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, @@ -879,14 +904,18 @@ export class WorkbenchAsyncDataTree extends Async @IKeybindingService keybindingService: IKeybindingService, @IAccessibilityService accessibilityService: IAccessibilityService ) { - const { options: treeOptions, getAutomaticKeyboardNavigation, disposable } = workbenchTreeDataPreamble(container, options, contextKeyService, themeService, configurationService, keybindingService, accessibilityService); + const { options: treeOptions, getAutomaticKeyboardNavigation, disposable } = workbenchTreeDataPreamble(container, options, contextKeyService, configurationService, keybindingService, accessibilityService); super(user, container, delegate, renderers, dataSource, treeOptions); this.disposables.add(disposable); - this.internals = new WorkbenchTreeInternals(this, treeOptions, getAutomaticKeyboardNavigation, contextKeyService, listService, themeService, configurationService, accessibilityService); + this.internals = new WorkbenchTreeInternals(this, treeOptions, getAutomaticKeyboardNavigation, options.overrideStyles, contextKeyService, listService, themeService, configurationService, accessibilityService); this.disposables.add(this.internals); } } +export interface IWorkbenchCompressibleAsyncDataTreeOptions extends ICompressibleAsyncDataTreeOptions { + readonly overrideStyles?: IColorMapping; +} + export class WorkbenchCompressibleAsyncDataTree extends CompressibleAsyncDataTree { private internals: WorkbenchTreeInternals; @@ -900,7 +929,7 @@ export class WorkbenchCompressibleAsyncDataTree e compressionDelegate: ITreeCompressionDelegate, renderers: ICompressibleTreeRenderer[], dataSource: IAsyncDataSource, - options: IAsyncDataTreeOptions, + options: IWorkbenchCompressibleAsyncDataTreeOptions, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, @@ -908,10 +937,10 @@ export class WorkbenchCompressibleAsyncDataTree e @IKeybindingService keybindingService: IKeybindingService, @IAccessibilityService accessibilityService: IAccessibilityService ) { - const { options: treeOptions, getAutomaticKeyboardNavigation, disposable } = workbenchTreeDataPreamble(container, options, contextKeyService, themeService, configurationService, keybindingService, accessibilityService); + const { options: treeOptions, getAutomaticKeyboardNavigation, disposable } = workbenchTreeDataPreamble(container, options, contextKeyService, configurationService, keybindingService, accessibilityService); super(user, container, virtualDelegate, compressionDelegate, renderers, dataSource, treeOptions); this.disposables.add(disposable); - this.internals = new WorkbenchTreeInternals(this, treeOptions, getAutomaticKeyboardNavigation, contextKeyService, listService, themeService, configurationService, accessibilityService); + this.internals = new WorkbenchTreeInternals(this, treeOptions, getAutomaticKeyboardNavigation, options.overrideStyles, contextKeyService, listService, themeService, configurationService, accessibilityService); this.disposables.add(this.internals); } } @@ -920,7 +949,6 @@ function workbenchTreeDataPreamble(treeIndentKey), renderIndentGuides: configurationService.getValue(treeRenderIndentGuidesKey), @@ -966,7 +993,8 @@ function workbenchTreeDataPreamble { tree: WorkbenchObjectTree | CompressibleObjectTree | WorkbenchDataTree | WorkbenchAsyncDataTree | WorkbenchCompressibleAsyncDataTree, options: IAbstractTreeOptions | IAsyncDataTreeOptions, getAutomaticKeyboardNavigation: () => boolean | undefined, + overrideStyles: IColorMapping | undefined, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, @@ -1015,7 +1044,7 @@ class WorkbenchTreeInternals { this.disposables.push( this.contextKeyService, (listService as ListService).register(tree), - attachListStyler(tree, themeService), + overrideStyles ? attachStyler(themeService, overrideStyles, tree) : Disposable.None, tree.onDidChangeSelection(() => { const selection = tree.getSelection(); const focus = tree.getFocus(); diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index 25c0c9cdf0..160041e1f0 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -560,8 +560,8 @@ export class Menubar { label: this.mnemonicLabel(nls.localize('miCheckForUpdates', "Check for &&Updates...")), click: () => setTimeout(() => { this.reportMenuActionTelemetry('CheckForUpdate'); - const focusedWindow = BrowserWindow.getFocusedWindow(); - const context = focusedWindow ? { windowId: focusedWindow.id } : null; + const window = this.windowsMainService.getLastActiveWindow(); + const context = window && `window:${window.id}`; // sessionId this.updateService.checkForUpdates(context); }, 0) })]; diff --git a/src/vs/platform/opener/common/opener.ts b/src/vs/platform/opener/common/opener.ts index 7e8abbc118..8f2f72cd93 100644 --- a/src/vs/platform/opener/common/opener.ts +++ b/src/vs/platform/opener/common/opener.ts @@ -6,6 +6,7 @@ import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { equalsIgnoreCase, startsWithIgnoreCase } from 'vs/base/common/strings'; export const IOpenerService = createDecorator('openerService'); @@ -28,13 +29,22 @@ type OpenExternalOptions = { readonly openExternal?: boolean; readonly allowTunn export type OpenOptions = OpenInternalOptions & OpenExternalOptions; +export type ResolveExternalUriOptions = { readonly allowTunneling?: boolean }; + +export interface IResolvedExternalUri extends IDisposable { + resolved: URI; +} + export interface IOpener { - open(resource: URI, options?: OpenInternalOptions): Promise; - open(resource: URI, options?: OpenExternalOptions): Promise; + open(resource: URI | string, options?: OpenInternalOptions | OpenExternalOptions): Promise; +} + +export interface IExternalOpener { + openExternal(href: string): Promise; } export interface IValidator { - shouldOpen(resource: URI): Promise; + shouldOpen(resource: URI | string): Promise; } export interface IExternalUriResolver { @@ -61,16 +71,24 @@ export interface IOpenerService { */ registerExternalUriResolver(resolver: IExternalUriResolver): IDisposable; + /** + * Sets the handler for opening externally. If not provided, + * a default handler will be used. + */ + setExternalOpener(opener: IExternalOpener): void; + /** * Opens a resource, like a webaddress, a document uri, or executes command. * * @param resource A resource * @return A promise that resolves when the opening is done. */ - open(resource: URI, options?: OpenInternalOptions): Promise; - open(resource: URI, options?: OpenExternalOptions): Promise; + open(resource: URI | string, options?: OpenInternalOptions | OpenExternalOptions): Promise; - resolveExternalUri(resource: URI, options?: { readonly allowTunneling?: boolean }): Promise<{ resolved: URI, dispose(): void }>; + /** + * Resolve a resource to its external form. + */ + resolveExternalUri(resource: URI, options?: ResolveExternalUriOptions): Promise; } export const NullOpenerService: IOpenerService = Object.freeze({ @@ -78,6 +96,15 @@ export const NullOpenerService: IOpenerService = Object.freeze({ registerOpener() { return Disposable.None; }, registerValidator() { return Disposable.None; }, registerExternalUriResolver() { return Disposable.None; }, - open() { return Promise.resolve(false); }, + setExternalOpener() { }, + async open() { return false; }, async resolveExternalUri(uri: URI) { return { resolved: uri, dispose() { } }; }, }); + +export function matchesScheme(target: URI | string, scheme: string) { + if (URI.isUri(target)) { + return equalsIgnoreCase(target.scheme, scheme); + } else { + return startsWithIgnoreCase(target, scheme + ':'); + } +} diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index 57cd734c2a..c6b3b6cc81 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -21,10 +21,11 @@ if (isWeb) { // Running out of sources if (Object.keys(product).length === 0) { assign(product, { - version: '1.39.0-dev', + version: '1.41.0-dev', vscodeVersion: '1.39.0-dev', nameLong: 'Visual Studio Code Web Dev', - nameShort: 'VSCode Web Dev' + nameShort: 'VSCode Web Dev', + urlProtocol: 'code-oss' }); } } diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts index 812a687ebc..fb6098b8a6 100644 --- a/src/vs/platform/product/common/productService.ts +++ b/src/vs/platform/product/common/productService.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ExtensionKind } from 'vs/platform/extensions/common/extensions'; export const IProductService = createDecorator('productService'); @@ -101,13 +102,18 @@ export interface IProductConfiguration { readonly portable?: string; - readonly uiExtensions?: readonly string[]; + readonly extensionKind?: { readonly [extensionId: string]: ExtensionKind | ExtensionKind[]; }; readonly extensionAllowedProposedApi?: readonly string[]; readonly msftInternalDomains?: string[]; readonly linkProtectionTrustedDomains?: readonly string[]; - readonly settingsSyncStoreUrl?: string; + readonly auth?: { + loginUrl: string; + tokenUrl: string; + redirectUrl: string; + clientId: string; + }; } export interface IExeBasedExtensionTip { diff --git a/src/vs/platform/quickinput/common/quickInput.ts b/src/vs/platform/quickinput/common/quickInput.ts index 9b5b230596..73adf3269b 100644 --- a/src/vs/platform/quickinput/common/quickInput.ts +++ b/src/vs/platform/quickinput/common/quickInput.ts @@ -168,9 +168,9 @@ export interface IQuickPick extends IQuickInput { customButton: boolean; - customLabel: string; + customLabel: string | undefined; - customHover: string; + customHover: string | undefined; buttons: ReadonlyArray; @@ -188,6 +188,8 @@ export interface IQuickPick extends IQuickInput { matchOnLabel: boolean; + sortByLabel: boolean; + autoFocusOnList: boolean; quickNavigate: IQuickNavigateConfiguration | undefined; diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts index b910f899bb..b75f9e0d10 100644 --- a/src/vs/platform/remote/common/remoteAgentConnection.ts +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts @@ -89,8 +89,8 @@ async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptio options.host, options.port, `reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`, - (err: any, socket: ISocket) => { - if (err) { + (err: any, socket: ISocket | undefined) => { + if (err || !socket) { options.logService.error(`${logPrefix} socketFactory.connect() failed. Error:`); options.logService.error(err); e(err); diff --git a/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts b/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts index 92880756ab..6604584f85 100644 --- a/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts +++ b/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts @@ -8,10 +8,13 @@ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle' import { URI, UriComponents } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { FileChangeType, FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileType, IFileChange, IFileSystemProvider, IStat, IWatchOptions, FileOpenOptions } from 'vs/platform/files/common/files'; +import { FileChangeType, FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileType, IFileChange, IStat, IWatchOptions, FileOpenOptions, IFileSystemProviderWithFileReadWriteCapability, FileWriteOptions, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileFolderCopyCapability, FileReadStreamOptions, IFileSystemProviderWithOpenReadWriteCloseCapability } from 'vs/platform/files/common/files'; import { VSBuffer } from 'vs/base/common/buffer'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { OperatingSystem } from 'vs/base/common/platform'; +import { newWriteableStream, ReadableStreamEvents, ReadableStreamEventPayload } from 'vs/base/common/stream'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { canceled } from 'vs/base/common/errors'; export const REMOTE_FILE_SYSTEM_CHANNEL_NAME = 'remotefilesystem'; @@ -20,7 +23,11 @@ export interface IFileChangeDto { type: FileChangeType; } -export class RemoteExtensionsFileSystemProvider extends Disposable implements IFileSystemProvider { +export class RemoteFileSystemProvider extends Disposable implements + IFileSystemProviderWithFileReadWriteCapability, + IFileSystemProviderWithOpenReadWriteCloseCapability, + IFileSystemProviderWithFileReadStreamCapability, + IFileSystemProviderWithFileFolderCopyCapability { private readonly session: string = generateUuid(); @@ -46,7 +53,7 @@ export class RemoteExtensionsFileSystemProvider extends Disposable implements IF } private registerListeners(): void { - this._register(this.channel.listen('filechange', [this.session])((eventsOrError) => { + this._register(this.channel.listen('filechange', [this.session])(eventsOrError => { if (Array.isArray(eventsOrError)) { const events = eventsOrError; this._onDidChange.fire(events.map(event => ({ resource: URI.revive(event.resource), type: event.type }))); @@ -59,7 +66,9 @@ export class RemoteExtensionsFileSystemProvider extends Disposable implements IF setCaseSensitive(isCaseSensitive: boolean) { let capabilities = ( - FileSystemProviderCapabilities.FileOpenReadWriteClose + FileSystemProviderCapabilities.FileReadWrite + | FileSystemProviderCapabilities.FileOpenReadWriteClose + | FileSystemProviderCapabilities.FileReadStream | FileSystemProviderCapabilities.FileFolderCopy ); @@ -97,10 +106,57 @@ export class RemoteExtensionsFileSystemProvider extends Disposable implements IF return bytesRead; } + async readFile(resource: URI): Promise { + const buff = await this.channel.call('readFile', [resource]); + + return buff.buffer; + } + + readFileStream(resource: URI, opts: FileReadStreamOptions, token?: CancellationToken): ReadableStreamEvents { + const stream = newWriteableStream(data => VSBuffer.concat(data.map(data => VSBuffer.wrap(data))).buffer); + + // Reading as file stream goes through an event to the remote side + const listener = this.channel.listen>('readFileStream', [resource, opts])(dataOrErrorOrEnd => { + if (dataOrErrorOrEnd instanceof VSBuffer) { + + // data: forward into the stream + stream.write(dataOrErrorOrEnd.buffer); + } else { + + // error / end: always end the stream on errors too + stream.end(dataOrErrorOrEnd === 'end' ? undefined : dataOrErrorOrEnd); + + // Signal to the remote side that we no longer listen + listener.dispose(); + } + }); + + // Support cancellation + if (token) { + Event.once(token.onCancellationRequested)(() => { + + // Ensure to end the stream properly with an error + // to indicate the cancellation. + stream.end(canceled()); + + // Ensure to dispose the listener upon cancellation. This will + // bubble through the remote side as event and allows to stop + // reading the file. + listener.dispose(); + }); + } + + return stream; + } + write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { return this.channel.call('write', [fd, pos, VSBuffer.wrap(data), offset, length]); } + writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise { + return this.channel.call('writeFile', [resource, VSBuffer.wrap(content), opts]); + } + delete(resource: URI, opts: FileDeleteOptions): Promise { return this.channel.call('delete', [resource, opts]); } diff --git a/src/vs/platform/remote/common/tunnel.ts b/src/vs/platform/remote/common/tunnel.ts index 919b2c46a1..5689ad5ff0 100644 --- a/src/vs/platform/remote/common/tunnel.ts +++ b/src/vs/platform/remote/common/tunnel.ts @@ -5,13 +5,14 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; +import { Event } from 'vs/base/common/event'; export const ITunnelService = createDecorator('tunnelService'); export interface RemoteTunnel { readonly tunnelRemotePort: number; readonly tunnelLocalPort: number; - + readonly localAddress?: string; dispose(): void; } @@ -19,8 +20,11 @@ export interface ITunnelService { _serviceBrand: undefined; readonly tunnels: Promise; + readonly onTunnelOpened: Event; + readonly onTunnelClosed: Event; - openTunnel(remotePort: number): Promise | undefined; + openTunnel(remotePort: number, localPort?: number): Promise | undefined; + closeTunnel(remotePort: number): Promise; } export function extractLocalHostUriMetaDataForPortMapping(uri: URI): { address: string, port: number } | undefined { diff --git a/src/vs/platform/remote/common/tunnelService.ts b/src/vs/platform/remote/common/tunnelService.ts index 2292d97fdc..a3719a715e 100644 --- a/src/vs/platform/remote/common/tunnelService.ts +++ b/src/vs/platform/remote/common/tunnelService.ts @@ -4,13 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; +import { Event, Emitter } from 'vs/base/common/event'; export class NoOpTunnelService implements ITunnelService { _serviceBrand: undefined; public readonly tunnels: Promise = Promise.resolve([]); - + private _onTunnelOpened: Emitter = new Emitter(); + public onTunnelOpened: Event = this._onTunnelOpened.event; + private _onTunnelClosed: Emitter = new Emitter(); + public onTunnelClosed: Event = this._onTunnelClosed.event; openTunnel(_remotePort: number): Promise | undefined { return undefined; } + async closeTunnel(_remotePort: number): Promise { + } } diff --git a/src/vs/platform/request/common/request.ts b/src/vs/platform/request/common/request.ts index 346dff23d1..e9ae973f08 100644 --- a/src/vs/platform/request/common/request.ts +++ b/src/vs/platform/request/common/request.ts @@ -69,7 +69,7 @@ Registry.as(Extensions.Configuration) properties: { 'http.proxy': { type: 'string', - pattern: '^https?://([^:]*(:[^@]*)?@)?([^:]+)(:\\d+)?/?$|^$', + pattern: '^https?://([^:]*(:[^@]*)?@)?([^:]+|\\[[:0-9a-fA-F]+\\])(:\\d+)?/?$|^$', markdownDescription: localize('proxy', "The proxy setting to use. If not set, will be inherited from the `http_proxy` and `https_proxy` environment variables.") }, 'http.proxyStrictSSL': { diff --git a/src/vs/platform/request/node/proxy.ts b/src/vs/platform/request/node/proxy.ts index 8657157d86..324829df99 100644 --- a/src/vs/platform/request/node/proxy.ts +++ b/src/vs/platform/request/node/proxy.ts @@ -39,12 +39,12 @@ export async function getProxyAgent(rawRequestURL: string, options: IOptions = { const opts = { host: proxyEndpoint.hostname || '', - port: Number(proxyEndpoint.port), + port: proxyEndpoint.port || (proxyEndpoint.protocol === 'https' ? '443' : '80'), auth: proxyEndpoint.auth, - rejectUnauthorized: isBoolean(options.strictSSL) ? options.strictSSL : true + rejectUnauthorized: isBoolean(options.strictSSL) ? options.strictSSL : true, }; return requestURL.protocol === 'http:' - ? new (await import('http-proxy-agent'))(opts) + ? new (await import('http-proxy-agent'))(opts as any as Url) : new (await import('https-proxy-agent'))(opts); } diff --git a/src/vs/platform/request/node/requestService.ts b/src/vs/platform/request/node/requestService.ts index 8fe3c942d1..986622b53c 100644 --- a/src/vs/platform/request/node/requestService.ts +++ b/src/vs/platform/request/node/requestService.ts @@ -5,7 +5,7 @@ import * as https from 'https'; import * as http from 'http'; -import { Stream } from 'stream'; +import * as streams from 'vs/base/common/stream'; import { createGunzip } from 'zlib'; import { parse as parseUrl } from 'url'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -18,7 +18,7 @@ import { IRequestOptions, IRequestContext } from 'vs/base/parts/request/common/r import { getProxyAgent, Agent } from 'vs/platform/request/node/proxy'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; -import { toVSBufferReadableStream } from 'vs/base/common/buffer'; +import { streamToBufferReadableStream } from 'vs/base/common/buffer'; export interface IRawRequestFunction { (options: http.RequestOptions, callback?: (res: http.IncomingMessage) => void): http.ClientRequest; @@ -112,13 +112,13 @@ export class RequestService extends Disposable implements IRequestService { followRedirects: followRedirects - 1 }), token).then(c, e); } else { - let stream: Stream = res; + let stream: streams.ReadableStream = res; if (res.headers['content-encoding'] === 'gzip') { - stream = stream.pipe(createGunzip()); + stream = res.pipe(createGunzip()); } - c({ res, stream: toVSBufferReadableStream(stream) } as IRequestContext); + c({ res, stream: streamToBufferReadableStream(stream) } as IRequestContext); } }); diff --git a/src/vs/platform/severityIcon/common/severityIcon.ts b/src/vs/platform/severityIcon/common/severityIcon.ts index 7cc3a81a96..c3374a4232 100644 --- a/src/vs/platform/severityIcon/common/severityIcon.ts +++ b/src/vs/platform/severityIcon/common/severityIcon.ts @@ -31,7 +31,8 @@ registerThemingParticipant((theme, collector) => { collector.addRule(` .monaco-workbench .zone-widget .codicon-error, .monaco-workbench .markers-panel .marker-icon.codicon-error, - .monaco-workbench .extensions-viewlet > .extensions .codicon-error { + .monaco-workbench .extensions-viewlet > .extensions .codicon-error, + .monaco-workbench .dialog-box .dialog-message-row .codicon-error { color: ${errorIconForeground}; } `); @@ -42,7 +43,9 @@ registerThemingParticipant((theme, collector) => { collector.addRule(` .monaco-workbench .zone-widget .codicon-warning, .monaco-workbench .markers-panel .marker-icon.codicon-warning, - .monaco-workbench .extensions-viewlet > .extensions .codicon-warning { + .monaco-workbench .extensions-viewlet > .extensions .codicon-warning, + .monaco-workbench .extension-editor .codicon-warning, + .monaco-workbench .dialog-box .dialog-message-row .codicon-warning { color: ${warningIconForeground}; } `); @@ -52,7 +55,10 @@ registerThemingParticipant((theme, collector) => { if (errorIconForeground) { collector.addRule(` .monaco-workbench .zone-widget .codicon-info, - .monaco-workbench .markers-panel .marker-icon.codicon-info { + .monaco-workbench .markers-panel .marker-icon.codicon-info, + .monaco-workbench .extensions-viewlet > .extensions .codicon-info, + .monaco-workbench .extension-editor .codicon-info, + .monaco-workbench .dialog-box .dialog-message-row .codicon-info { color: ${infoIconForeground}; } `); diff --git a/src/vs/platform/storage/node/storageIpc.ts b/src/vs/platform/storage/node/storageIpc.ts index 5569dcbd49..a29e152059 100644 --- a/src/vs/platform/storage/node/storageIpc.ts +++ b/src/vs/platform/storage/node/storageIpc.ts @@ -83,7 +83,7 @@ export class GlobalStorageDatabaseChannel extends Disposable implements IServerC // Listen for changes in global storage to send to listeners // that are listening. Use a debouncer to reduce IPC traffic. - this._register(Event.debounce(this.storageMainService.onDidChangeStorage, (prev: IStorageChangeEvent[], cur: IStorageChangeEvent) => { + this._register(Event.debounce(this.storageMainService.onDidChangeStorage, (prev: IStorageChangeEvent[] | undefined, cur: IStorageChangeEvent) => { if (!prev) { prev = [cur]; } else { @@ -161,7 +161,7 @@ export class GlobalStorageDatabaseChannelClient extends Disposable implements IS } private registerListeners(): void { - this.onDidChangeItemsOnMainListener = this.channel.listen('onDidChangeItems')((e: ISerializableItemsChangeEvent) => this.onDidChangeItemsOnMain(e)); + this.onDidChangeItemsOnMainListener = this.channel.listen('onDidChangeItems')((e: ISerializableItemsChangeEvent) => this.onDidChangeItemsOnMain(e)); } private onDidChangeItemsOnMain(e: ISerializableItemsChangeEvent): void { diff --git a/src/vs/platform/telemetry/browser/workbenchCommonProperties.ts b/src/vs/platform/telemetry/browser/workbenchCommonProperties.ts index 08af83cba1..d2f574382c 100644 --- a/src/vs/platform/telemetry/browser/workbenchCommonProperties.ts +++ b/src/vs/platform/telemetry/browser/workbenchCommonProperties.ts @@ -9,6 +9,7 @@ export const instanceStorageKey = 'telemetry.instanceId'; export const currentSessionDateStorageKey = 'telemetry.currentSessionDate'; export const firstSessionDateStorageKey = 'telemetry.firstSessionDate'; export const lastSessionDateStorageKey = 'telemetry.lastSessionDate'; +export const machineIdKey = 'telemetry.machineId'; import * as Platform from 'vs/base/common/platform'; import * as uuid from 'vs/base/common/uuid'; @@ -19,7 +20,6 @@ export async function resolveWorkbenchCommonProperties( storageService: IStorageService, commit: string | undefined, version: string | undefined, - machineId: string, remoteAuthority?: string, resolveAdditionalProperties?: () => { [key: string]: any } ): Promise<{ [name: string]: string | undefined }> { @@ -27,6 +27,12 @@ export async function resolveWorkbenchCommonProperties( const firstSessionDate = storageService.get(firstSessionDateStorageKey, StorageScope.GLOBAL)!; const lastSessionDate = storageService.get(lastSessionDateStorageKey, StorageScope.GLOBAL)!; + let machineId = storageService.get(machineIdKey, StorageScope.GLOBAL); + if (!machineId) { + machineId = uuid.generateUuid(); + storageService.store(machineIdKey, machineId, StorageScope.GLOBAL); + } + /** * Note: In the web, session date information is fetched from browser storage, so these dates are tied to a specific * browser and not the machine overall. diff --git a/src/vs/platform/telemetry/node/appInsightsAppender.ts b/src/vs/platform/telemetry/node/appInsightsAppender.ts index 59415e88d5..ea4d8ad32d 100644 --- a/src/vs/platform/telemetry/node/appInsightsAppender.ts +++ b/src/vs/platform/telemetry/node/appInsightsAppender.ts @@ -42,7 +42,7 @@ export class AppInsightsAppender implements ITelemetryAppender { constructor( private _eventPrefix: string, private _defaultData: { [key: string]: any } | null, - aiKeyOrClientFactory: string | (() => appInsights.ITelemetryClient), // allow factory function for testing + aiKeyOrClientFactory: string | (() => appInsights.TelemetryClient), // allow factory function for testing @ILogService private _logService?: ILogService ) { if (!this._defaultData) { diff --git a/src/vs/platform/telemetry/test/browser/commonProperties.test.ts b/src/vs/platform/telemetry/test/browser/commonProperties.test.ts index cd571e4f15..f26a988def 100644 --- a/src/vs/platform/telemetry/test/browser/commonProperties.test.ts +++ b/src/vs/platform/telemetry/test/browser/commonProperties.test.ts @@ -23,7 +23,7 @@ suite('Browser Telemetry - common properties', function () { }; }; - const props = await resolveWorkbenchCommonProperties(testStorageService, commit, version, 'someMachineId', undefined, resolveCommonTelemetryProperties); + const props = await resolveWorkbenchCommonProperties(testStorageService, commit, version, undefined, resolveCommonTelemetryProperties); assert.ok('commitHash' in props); assert.ok('sessionID' in props); @@ -53,10 +53,10 @@ suite('Browser Telemetry - common properties', function () { }); }; - const props = await resolveWorkbenchCommonProperties(testStorageService, commit, version, 'someMachineId', undefined, resolveCommonTelemetryProperties); + const props = await resolveWorkbenchCommonProperties(testStorageService, commit, version, undefined, resolveCommonTelemetryProperties); assert.equal(props['userId'], '1'); - const props2 = await resolveWorkbenchCommonProperties(testStorageService, commit, version, 'someMachineId', undefined, resolveCommonTelemetryProperties); + const props2 = await resolveWorkbenchCommonProperties(testStorageService, commit, version, undefined, resolveCommonTelemetryProperties); assert.equal(props2['userId'], '2'); }); }); diff --git a/src/vs/platform/telemetry/test/electron-browser/appInsightsAppender.test.ts b/src/vs/platform/telemetry/test/electron-browser/appInsightsAppender.test.ts index 3b0ccdf0f3..c8518a04f1 100644 --- a/src/vs/platform/telemetry/test/electron-browser/appInsightsAppender.test.ts +++ b/src/vs/platform/telemetry/test/electron-browser/appInsightsAppender.test.ts @@ -5,15 +5,19 @@ import * as assert from 'assert'; import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender'; import { ILogService, AbstractLogService, LogLevel, DEFAULT_LOG_LEVEL } from 'vs/platform/log/common/log'; -import { ITelemetryClient, EventTelemetry } from 'applicationinsights'; +import { TelemetryClient, Contracts } from 'applicationinsights'; -class AppInsightsMock implements ITelemetryClient { +class AppInsightsMock extends TelemetryClient { public config: any; public channel: any; - public events: EventTelemetry[] = []; + public events: Contracts.EventTelemetry[] = []; public IsTrackingPageView: boolean = false; public exceptions: any[] = []; + constructor() { + super('testKey'); + } + public trackEvent(event: any) { this.events.push(event); } diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index a87a5b53b2..d97e6ea1c0 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -103,7 +103,7 @@ class ColorRegistry implements IColorRegistry { public registerColor(id: string, defaults: ColorDefaults | null, description: string, needsTransparency = false, deprecationMessage?: string): ColorIdentifier { let colorContribution: ColorContribution = { id, description, defaults, needsTransparency, deprecationMessage }; this.colorsById[id] = colorContribution; - let propertySchema: IJSONSchema = { type: 'string', description, format: 'color-hex', defaultSnippets: [{ body: '#ff0000' }] }; + let propertySchema: IJSONSchema = { type: 'string', description, format: 'color-hex', defaultSnippets: [{ body: '${1:#ff0000}' }] }; if (deprecationMessage) { propertySchema.deprecationMessage = deprecationMessage; } @@ -301,14 +301,22 @@ export const editorFindMatchBorder = registerColor('editor.findMatchBorder', { l export const editorFindMatchHighlightBorder = registerColor('editor.findMatchHighlightBorder', { light: null, dark: null, hc: activeContrastBorder }, nls.localize('findMatchHighlightBorder', "Border color of the other search matches.")); export const editorFindRangeHighlightBorder = registerColor('editor.findRangeHighlightBorder', { dark: null, light: null, hc: transparent(activeContrastBorder, 0.4) }, nls.localize('findRangeHighlightBorder', "Border color of the range limiting the search. The color must not be opaque so as not to hide underlying decorations."), true); +/** + * Search Editor query match colors. + * + * Distinct from normal editor find match to allow for better differentiation + */ +export const searchEditorFindMatch = registerColor('searchEditor.findMatchBackground', { light: transparent(editorFindMatchHighlight, 0.5), dark: transparent(editorFindMatchHighlight, 0.5), hc: editorFindMatchHighlight }, nls.localize('searchEditor.queryMatch', "Color of the Search Editor query matches.")); +export const searchEditorFindMatchBorder = registerColor('searchEditor.findMatchBorder', { light: transparent(editorFindMatchHighlightBorder, 0.5), dark: transparent(editorFindMatchHighlightBorder, 0.5), hc: editorFindMatchHighlightBorder }, nls.localize('searchEditor.editorFindMatchBorder', "Border color of the Search Editor query matches.")); + /** * Editor hover */ export const editorHoverHighlight = registerColor('editor.hoverHighlightBackground', { light: '#ADD6FF26', dark: '#264f7840', hc: '#ADD6FF26' }, nls.localize('hoverHighlight', 'Highlight below the word for which a hover is shown. The color must not be opaque so as not to hide underlying decorations.'), true); export const editorHoverBackground = registerColor('editorHoverWidget.background', { light: editorWidgetBackground, dark: editorWidgetBackground, hc: editorWidgetBackground }, nls.localize('hoverBackground', 'Background color of the editor hover.')); +export const editorHoverForeground = registerColor('editorHoverWidget.foreground', { light: editorWidgetForeground, dark: editorWidgetForeground, hc: editorWidgetForeground }, nls.localize('hoverForeground', 'Foreground color of the editor hover.')); export const editorHoverBorder = registerColor('editorHoverWidget.border', { light: editorWidgetBorder, dark: editorWidgetBorder, hc: editorWidgetBorder }, nls.localize('hoverBorder', 'Border color of the editor hover.')); export const editorHoverStatusBarBackground = registerColor('editorHoverWidget.statusBarBackground', { dark: lighten(editorHoverBackground, 0.2), light: darken(editorHoverBackground, 0.05), hc: editorWidgetBackground }, nls.localize('statusBarBackground', "Background color of the editor hover status bar.")); - /** * Editor link colors */ @@ -416,6 +424,8 @@ export const overviewRulerSelectionHighlightForeground = registerColor('editorOv export const minimapFindMatch = registerColor('minimap.findMatchHighlight', { light: '#d18616', dark: '#d18616', hc: '#AB5A00' }, nls.localize('minimapFindMatchHighlight', 'Minimap marker color for find matches.'), true); export const minimapSelection = registerColor('minimap.selectionHighlight', { light: '#ADD6FF', dark: '#264F78', hc: '#ffffff' }, nls.localize('minimapSelectionHighlight', 'Minimap marker color for the editor selection.'), true); +export const minimapError = registerColor('minimap.errorHighlight', { dark: new Color(new RGBA(255, 18, 18, 0.7)), light: new Color(new RGBA(255, 18, 18, 0.7)), hc: new Color(new RGBA(255, 50, 50, 1)) }, nls.localize('minimapError', 'Minimap marker color for errors.')); +export const minimapWarning = registerColor('minimap.warningHighlight', { dark: editorWarningForeground, light: editorWarningForeground, hc: editorWarningBorder }, nls.localize('overviewRuleWarning', 'Minimap marker color for warnings.')); export const problemsErrorIconForeground = registerColor('problemsErrorIcon.foreground', { dark: editorErrorForeground, light: editorErrorForeground, hc: editorErrorForeground }, nls.localize('problemsErrorIconForeground', "The color used for the problems error icon.")); export const problemsWarningIconForeground = registerColor('problemsWarningIcon.foreground', { dark: editorWarningForeground, light: editorWarningForeground, hc: editorWarningForeground }, nls.localize('problemsWarningIconForeground', "The color used for the problems warning icon.")); diff --git a/src/vs/platform/theme/common/styler.ts b/src/vs/platform/theme/common/styler.ts index acb757b9a4..b030c31162 100644 --- a/src/vs/platform/theme/common/styler.ts +++ b/src/vs/platform/theme/common/styler.ts @@ -7,18 +7,12 @@ import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { focusBorder, inputBackground, inputForeground, ColorIdentifier, selectForeground, selectBackground, selectListBackground, selectBorder, inputBorder, foreground, editorBackground, contrastBorder, inputActiveOptionBorder, inputActiveOptionBackground, listFocusBackground, listFocusForeground, listActiveSelectionBackground, listActiveSelectionForeground, listInactiveSelectionForeground, listInactiveSelectionBackground, listInactiveFocusBackground, listHoverBackground, listHoverForeground, listDropBackground, pickerGroupBorder, pickerGroupForeground, widgetShadow, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationErrorBorder, inputValidationErrorBackground, activeContrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, ColorFunction, badgeBackground, badgeForeground, progressBarBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, editorWidgetBorder, inputValidationInfoForeground, inputValidationWarningForeground, inputValidationErrorForeground, menuForeground, menuBackground, menuSelectionForeground, menuSelectionBackground, menuSelectionBorder, menuBorder, menuSeparatorBackground, darken, listFilterWidgetOutline, listFilterWidgetNoMatchesOutline, listFilterWidgetBackground, editorWidgetBackground, treeIndentGuidesStroke, editorWidgetForeground, simpleCheckboxBackground, simpleCheckboxBorder, simpleCheckboxForeground, ColorValue, resolveColorValue } from 'vs/platform/theme/common/colorRegistry'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Color } from 'vs/base/common/color'; -import { mixin } from 'vs/base/common/objects'; - -export type styleFn = (colors: { [name: string]: Color | undefined }) => void; +import { IThemable, styleFn } from 'vs/base/common/styler'; export interface IStyleOverrides { [color: string]: ColorIdentifier | undefined; } -export interface IThemable { - style: styleFn; -} - export interface IColorMapping { [optionsKey: string]: ColorValue | undefined; } @@ -209,6 +203,7 @@ export function attachQuickOpenStyler(widget: IThemable, themeService: IThemeSer } export interface IListStyleOverrides extends IStyleOverrides { + listBackground?: ColorIdentifier; listFocusBackground?: ColorIdentifier; listFocusForeground?: ColorIdentifier; listActiveSelectionBackground?: ColorIdentifier; @@ -232,8 +227,8 @@ export interface IListStyleOverrides extends IStyleOverrides { treeIndentGuidesStroke?: ColorIdentifier; } -export function attachListStyler(widget: IThemable, themeService: IThemeService, overrides?: IListStyleOverrides): IDisposable { - return attachStyler(themeService, mixin(overrides || Object.create(null), defaultListStyles, false) as IListStyleOverrides, widget); +export function attachListStyler(widget: IThemable, themeService: IThemeService, overrides?: IColorMapping): IDisposable { + return attachStyler(themeService, { ...defaultListStyles, ...(overrides || {}) }, widget); } export const defaultListStyles: IColorMapping = { diff --git a/src/vs/platform/theme/common/themeService.ts b/src/vs/platform/theme/common/themeService.ts index b2e8489377..8fe5fd85ed 100644 --- a/src/vs/platform/theme/common/themeService.ts +++ b/src/vs/platform/theme/common/themeService.ts @@ -26,6 +26,42 @@ export interface ThemeIcon { readonly id: string; } +export namespace ThemeIcon { + export function isThemeIcon(obj: any): obj is ThemeIcon { + return obj && typeof obj === 'object' && typeof (obj).id === 'string'; + } + + const _regexFromString = /^\$\(([a-z.]+\/)?([a-z-~]+)\)$/i; + + export function fromString(str: string): ThemeIcon | undefined { + const match = _regexFromString.exec(str); + if (!match) { + return undefined; + } + let [, owner, name] = match; + if (!owner) { + owner = `codicon/`; + } + return { id: owner + name }; + } + + const _regexAsClassName = /^(codicon\/)?([a-z-]+)(~[a-z]+)?$/i; + + export function asClassName(icon: ThemeIcon): string | undefined { + // todo@martin,joh -> this should go into the ThemeService + const match = _regexAsClassName.exec(icon.id); + if (!match) { + return undefined; + } + let [, , name, modifier] = match; + let className = `codicon codicon-${name}`; + if (modifier) { + className += ` ${modifier.substr(1)}`; + } + return className; + } +} + export const FileThemeIcon = { id: 'file' }; export const FolderThemeIcon = { id: 'folder' }; @@ -59,6 +95,16 @@ export interface ITheme { * default color will be used. */ defines(color: ColorIdentifier): boolean; + + /** + * Returns the token style for a given classification. The result uses the MetadataConsts format + */ + getTokenStyleMetadata(type: string, modifiers: string[]): number | undefined; + + /** + * List of all colors used with tokens. getTokenStyleMetadata references the colors by index into this list. + */ + readonly tokenColorMap: string[]; } export interface IIconTheme { diff --git a/src/vs/platform/theme/common/tokenClassificationRegistry.ts b/src/vs/platform/theme/common/tokenClassificationRegistry.ts new file mode 100644 index 0000000000..7beb2a2b54 --- /dev/null +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -0,0 +1,395 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as platform from 'vs/platform/registry/common/platform'; +import { Color } from 'vs/base/common/color'; +import { ITheme } from 'vs/platform/theme/common/themeService'; +import * as nls from 'vs/nls'; +import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; +import { RunOnceScheduler } from 'vs/base/common/async'; +import { Event, Emitter } from 'vs/base/common/event'; +import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; + +// ------ API types + +export const TOKEN_TYPE_WILDCARD = '*'; +export const TOKEN_TYPE_WILDCARD_NUM = -1; + +// qualified string [type|*](.modifier)* +export type TokenClassificationString = string; + +export interface TokenClassification { + type: number; + modifiers: number; +} + +export interface TokenTypeOrModifierContribution { + readonly num: number; + readonly id: string; + readonly description: string; + readonly deprecationMessage: string | undefined; +} + + +export interface TokenStyleData { + foreground?: Color; + bold?: boolean; + underline?: boolean; + italic?: boolean; +} + +export class TokenStyle implements Readonly { + constructor( + public readonly foreground?: Color, + public readonly bold?: boolean, + public readonly underline?: boolean, + public readonly italic?: boolean, + ) { + } +} + +export namespace TokenStyle { + export function fromData(data: { foreground?: Color, bold?: boolean, underline?: boolean, italic?: boolean }) { + return new TokenStyle(data.foreground, data.bold, data.underline, data.italic); + } +} + +export type ProbeScope = string[]; + +export interface TokenStyleFunction { + (theme: ITheme): TokenStyle | undefined; +} + +export interface TokenStyleDefaults { + scopesToProbe: ProbeScope[]; + light: TokenStyleValue | null; + dark: TokenStyleValue | null; + hc: TokenStyleValue | null; +} + +export interface TokenStylingDefaultRule { + classification: TokenClassification; + matchScore: number; + defaults: TokenStyleDefaults; +} + +export interface TokenStylingRule { + classification: TokenClassification; + matchScore: number; + value: TokenStyle; +} + +/** + * A TokenStyle Value is either a token style literal, or a TokenClassificationString + */ +export type TokenStyleValue = TokenStyle | TokenClassificationString; + +// TokenStyle registry +export const Extensions = { + TokenClassificationContribution: 'base.contributions.tokenClassification' +}; + +export interface ITokenClassificationRegistry { + + readonly onDidChangeSchema: Event; + + /** + * Register a token type to the registry. + * @param id The TokenType id as used in theme description files + * @description the description + */ + registerTokenType(id: string, description: string): void; + + /** + * Register a token modifier to the registry. + * @param id The TokenModifier id as used in theme description files + * @description the description + */ + registerTokenModifier(id: string, description: string): void; + + getTokenClassification(type: string, modifiers: string[]): TokenClassification | undefined; + + getTokenStylingRule(classification: TokenClassification, value: TokenStyle): TokenStylingRule; + + /** + * Register a TokenStyle default to the registry. + * @param selector The rule selector + * @param defaults The default values + */ + registerTokenStyleDefault(selector: TokenClassification, defaults: TokenStyleDefaults): void; + + /** + * Deregister a TokenType from the registry. + */ + deregisterTokenType(id: string): void; + + /** + * Deregister a TokenModifier from the registry. + */ + deregisterTokenModifier(id: string): void; + + /** + * Get all TokenType contributions + */ + getTokenTypes(): TokenTypeOrModifierContribution[]; + + /** + * Get all TokenModifier contributions + */ + getTokenModifiers(): TokenTypeOrModifierContribution[]; + + /** + * The styling rules to used when a schema does not define any styling rules. + */ + getTokenStylingDefaultRules(): TokenStylingDefaultRule[]; + + /** + * JSON schema for an object to assign styling to token classifications + */ + getTokenStylingSchema(): IJSONSchema; +} + +class TokenClassificationRegistry implements ITokenClassificationRegistry { + + private readonly _onDidChangeSchema = new Emitter(); + readonly onDidChangeSchema: Event = this._onDidChangeSchema.event; + + private currentTypeNumber = 0; + private currentModifierBit = 1; + + private tokenTypeById: { [key: string]: TokenTypeOrModifierContribution }; + private tokenModifierById: { [key: string]: TokenTypeOrModifierContribution }; + + private tokenStylingDefaultRules: TokenStylingDefaultRule[] = []; + + private tokenStylingSchema: IJSONSchema & { properties: IJSONSchemaMap } = { + type: 'object', + properties: {}, + additionalProperties: getStylingSchemeEntry(), + definitions: { + style: { + type: 'object', + description: nls.localize('schema.token.settings', 'Colors and styles for the token.'), + properties: { + foreground: { + type: 'string', + description: nls.localize('schema.token.foreground', 'Foreground color for the token.'), + format: 'color-hex', + default: '#ff0000' + }, + background: { + type: 'string', + deprecationMessage: nls.localize('schema.token.background.warning', 'Token background colors are currently not supported.') + }, + fontStyle: { + type: 'string', + description: nls.localize('schema.token.fontStyle', 'Font style of the rule: \'italic\', \'bold\' or \'underline\', \'-italic\', \'-bold\' or \'-underline\'or a combination. The empty string unsets inherited settings.'), + pattern: '^(\\s*(-?italic|-?bold|-?underline))*\\s*$', + patternErrorMessage: nls.localize('schema.fontStyle.error', 'Font style must be \'italic\', \'bold\' or \'underline\' to set a style or \'-italic\', \'-bold\' or \'-underline\' to unset or a combination. The empty string unsets all styles.'), + defaultSnippets: [{ label: nls.localize('schema.token.fontStyle.none', 'None (clear inherited style)'), bodyText: '""' }, { body: 'italic' }, { body: 'bold' }, { body: 'underline' }, { body: '-italic' }, { body: '-bold' }, { body: '-underline' }, { body: 'italic bold' }, { body: 'italic underline' }, { body: 'bold underline' }, { body: 'italic bold underline' }] + } + }, + additionalProperties: false, + defaultSnippets: [{ body: { foreground: '${1:#FF0000}', fontStyle: '${2:bold}' } }] + } + } + }; + + constructor() { + this.tokenTypeById = {}; + this.tokenModifierById = {}; + + this.tokenTypeById[TOKEN_TYPE_WILDCARD] = { num: TOKEN_TYPE_WILDCARD_NUM, id: TOKEN_TYPE_WILDCARD, description: '', deprecationMessage: undefined }; + } + + public registerTokenType(id: string, description: string, deprecationMessage?: string): void { + const num = this.currentTypeNumber++; + let tokenStyleContribution: TokenTypeOrModifierContribution = { num, id, description, deprecationMessage }; + this.tokenTypeById[id] = tokenStyleContribution; + + this.tokenStylingSchema.properties[id] = getStylingSchemeEntry(description, deprecationMessage); + } + + public registerTokenModifier(id: string, description: string, deprecationMessage?: string): void { + const num = this.currentModifierBit; + this.currentModifierBit = this.currentModifierBit * 2; + let tokenStyleContribution: TokenTypeOrModifierContribution = { num, id, description, deprecationMessage }; + this.tokenModifierById[id] = tokenStyleContribution; + + this.tokenStylingSchema.properties[`*.${id}`] = getStylingSchemeEntry(description, deprecationMessage); + } + + public getTokenClassification(type: string, modifiers: string[]): TokenClassification | undefined { + const tokenTypeDesc = this.tokenTypeById[type]; + if (!tokenTypeDesc) { + return undefined; + } + let allModifierBits = 0; + for (const modifier of modifiers) { + const tokenModifierDesc = this.tokenModifierById[modifier]; + if (tokenModifierDesc) { + allModifierBits |= tokenModifierDesc.num; + } + } + return { type: tokenTypeDesc.num, modifiers: allModifierBits }; + } + + public getTokenStylingRule(classification: TokenClassification, value: TokenStyle): TokenStylingRule { + return { classification, matchScore: getTokenStylingScore(classification), value }; + } + + public registerTokenStyleDefault(classification: TokenClassification, defaults: TokenStyleDefaults): void { + this.tokenStylingDefaultRules.push({ classification, matchScore: getTokenStylingScore(classification), defaults }); + } + + public deregisterTokenType(id: string): void { + delete this.tokenTypeById[id]; + delete this.tokenStylingSchema.properties[id]; + } + + public deregisterTokenModifier(id: string): void { + delete this.tokenModifierById[id]; + delete this.tokenStylingSchema.properties[`*.${id}`]; + } + + public getTokenTypes(): TokenTypeOrModifierContribution[] { + return Object.keys(this.tokenTypeById).map(id => this.tokenTypeById[id]); + } + + public getTokenModifiers(): TokenTypeOrModifierContribution[] { + return Object.keys(this.tokenModifierById).map(id => this.tokenModifierById[id]); + } + + public getTokenStylingSchema(): IJSONSchema { + return this.tokenStylingSchema; + } + + public getTokenStylingDefaultRules(): TokenStylingDefaultRule[] { + return this.tokenStylingDefaultRules; + } + + public toString() { + let sorter = (a: string, b: string) => { + let cat1 = a.indexOf('.') === -1 ? 0 : 1; + let cat2 = b.indexOf('.') === -1 ? 0 : 1; + if (cat1 !== cat2) { + return cat1 - cat2; + } + return a.localeCompare(b); + }; + + return Object.keys(this.tokenTypeById).sort(sorter).map(k => `- \`${k}\`: ${this.tokenTypeById[k].description}`).join('\n'); + } + +} + +export function matchTokenStylingRule(themeSelector: TokenStylingRule | TokenStylingDefaultRule, classification: TokenClassification): number { + const selectorType = themeSelector.classification.type; + if (selectorType !== TOKEN_TYPE_WILDCARD_NUM && selectorType !== classification.type) { + return -1; + } + const selectorModifier = themeSelector.classification.modifiers; + if ((classification.modifiers & selectorModifier) !== selectorModifier) { + return -1; + } + return themeSelector.matchScore; +} + + +const tokenClassificationRegistry = new TokenClassificationRegistry(); +platform.Registry.add(Extensions.TokenClassificationContribution, tokenClassificationRegistry); + +export function registerTokenType(id: string, description: string, scopesToProbe: ProbeScope[] = [], extendsTC: string | null = null, deprecationMessage?: string): string { + tokenClassificationRegistry.registerTokenType(id, description, deprecationMessage); + + if (scopesToProbe || extendsTC) { + const classification = tokenClassificationRegistry.getTokenClassification(id, []); + tokenClassificationRegistry.registerTokenStyleDefault(classification!, { scopesToProbe, light: extendsTC, dark: extendsTC, hc: extendsTC }); + } + return id; +} + +export function registerTokenModifier(id: string, description: string, deprecationMessage?: string): string { + tokenClassificationRegistry.registerTokenModifier(id, description, deprecationMessage); + return id; +} + +export function getTokenClassificationRegistry(): ITokenClassificationRegistry { + return tokenClassificationRegistry; +} + +export const comments = registerTokenType('comments', nls.localize('comments', "Style for comments."), [['comment']]); +export const strings = registerTokenType('strings', nls.localize('strings', "Style for strings."), [['string']]); +export const keywords = registerTokenType('keywords', nls.localize('keywords', "Style for keywords."), [['keyword.control']]); +export const numbers = registerTokenType('numbers', nls.localize('numbers', "Style for numbers."), [['constant.numeric']]); +export const regexp = registerTokenType('regexp', nls.localize('regexp', "Style for expressions."), [['constant.regexp']]); +export const operators = registerTokenType('operators', nls.localize('operator', "Style for operators."), [['keyword.operator']]); + +export const namespaces = registerTokenType('namespaces', nls.localize('namespace', "Style for namespaces."), [['entity.name.namespace']]); + +export const types = registerTokenType('types', nls.localize('types', "Style for types."), [['entity.name.type'], ['entity.name.class'], ['support.type'], ['support.class']]); +export const structs = registerTokenType('structs', nls.localize('struct', "Style for structs."), [['storage.type.struct']], types); +export const classes = registerTokenType('classes', nls.localize('class', "Style for classes."), [['entity.name.class']], types); +export const interfaces = registerTokenType('interfaces', nls.localize('interface', "Style for interfaces."), undefined, types); +export const enums = registerTokenType('enums', nls.localize('enum', "Style for enums."), undefined, types); +export const parameterTypes = registerTokenType('parameterTypes', nls.localize('parameterType', "Style for parameter types."), undefined, types); + +export const functions = registerTokenType('functions', nls.localize('functions', "Style for functions"), [['entity.name.function'], ['support.function']]); +export const macros = registerTokenType('macros', nls.localize('macro', "Style for macros."), undefined, functions); + +export const variables = registerTokenType('variables', nls.localize('variables', "Style for variables."), [['variable'], ['entity.name.variable']]); +export const constants = registerTokenType('constants', nls.localize('constants', "Style for constants."), undefined, variables); +export const parameters = registerTokenType('parameters', nls.localize('parameters', "Style for parameters."), undefined, variables); +export const property = registerTokenType('properties', nls.localize('properties', "Style for properties."), undefined, variables); + +export const labels = registerTokenType('labels', nls.localize('labels', "Style for labels. "), undefined); + +export const m_declaration = registerTokenModifier('declaration', nls.localize('declaration', "Style for all symbol declarations."), undefined); +export const m_documentation = registerTokenModifier('documentation', nls.localize('documentation', "Style to use for references in documentation."), undefined); +export const m_member = registerTokenModifier('member', nls.localize('member', "Style to use for member functions, variables (fields) and types."), undefined); +export const m_static = registerTokenModifier('static', nls.localize('static', "Style to use for symbols that are static."), undefined); +export const m_abstract = registerTokenModifier('abstract', nls.localize('abstract', "Style to use for symbols that are abstract."), undefined); +export const m_deprecated = registerTokenModifier('deprecated', nls.localize('deprecated', "Style to use for symbols that are deprecated."), undefined); +export const m_modification = registerTokenModifier('modification', nls.localize('modification', "Style to use for write accesses."), undefined); +export const m_async = registerTokenModifier('async', nls.localize('async', "Style to use for symbols that are async."), undefined); + +function bitCount(u: number) { + // https://blogs.msdn.microsoft.com/jeuge/2005/06/08/bit-fiddling-3/ + const uCount = u - ((u >> 1) & 0o33333333333) - ((u >> 2) & 0o11111111111); + return ((uCount + (uCount >> 3)) & 0o30707070707) % 63; +} + +function getTokenStylingScore(classification: TokenClassification) { + return bitCount(classification.modifiers) + ((classification.type !== TOKEN_TYPE_WILDCARD_NUM) ? 1 : 0); +} + +function getStylingSchemeEntry(description?: string, deprecationMessage?: string): IJSONSchema { + return { + description, + deprecationMessage, + defaultSnippets: [{ body: '${1:#ff0000}' }], + anyOf: [ + { + type: 'string', + format: 'color-hex' + }, + { + $ref: '#definitions/style' + } + ] + }; +} + +export const tokenStylingSchemaId = 'vscode://schemas/token-styling'; + +let schemaRegistry = platform.Registry.as(JSONExtensions.JSONContribution); +schemaRegistry.registerSchema(tokenStylingSchemaId, tokenClassificationRegistry.getTokenStylingSchema()); + +const delayer = new RunOnceScheduler(() => schemaRegistry.notifySchemaChanged(tokenStylingSchemaId), 200); +tokenClassificationRegistry.onDidChangeSchema(() => { + if (!delayer.isScheduled()) { + delayer.schedule(); + } +}); diff --git a/src/vs/platform/theme/test/common/testThemeService.ts b/src/vs/platform/theme/test/common/testThemeService.ts index 45c28936d8..444db92178 100644 --- a/src/vs/platform/theme/test/common/testThemeService.ts +++ b/src/vs/platform/theme/test/common/testThemeService.ts @@ -23,6 +23,14 @@ export class TestTheme implements ITheme { defines(color: string): boolean { throw new Error('Method not implemented.'); } + + getTokenStyleMetadata(type: string, modifiers: string[]): number | undefined { + return undefined; + } + + get tokenColorMap(): string[] { + return []; + } } export class TestIconTheme implements IIconTheme { diff --git a/src/vs/platform/update/electron-main/updateService.linux.ts b/src/vs/platform/update/electron-main/updateService.linux.ts index 944006ed95..05e7cac5a5 100644 --- a/src/vs/platform/update/electron-main/updateService.linux.ts +++ b/src/vs/platform/update/electron-main/updateService.linux.ts @@ -41,7 +41,7 @@ export class LinuxUpdateService extends AbstractUpdateService { this.setState(State.CheckingForUpdates(context)); this.requestService.request({ url: this.url }, CancellationToken.None) - .then(asJson) + .then(asJson) .then(update => { if (!update || !update.url || !update.version || !update.productVersion) { this.telemetryService.publicLog2<{ explicit: boolean }, UpdateNotAvailableClassification>('update:notAvailable', { explicit: !!context }); diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts index 41b8a7e1e3..ab1e95ebe3 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -110,7 +110,7 @@ export class Win32UpdateService extends AbstractUpdateService { this.setState(State.CheckingForUpdates(context)); this.requestService.request({ url: this.url }, CancellationToken.None) - .then(asJson) + .then(asJson) .then(update => { const updateType = getUpdateType(); diff --git a/src/vs/platform/url/electron-main/electronUrlListener.ts b/src/vs/platform/url/electron-main/electronUrlListener.ts index 3f0316c496..19db80ca22 100644 --- a/src/vs/platform/url/electron-main/electronUrlListener.ts +++ b/src/vs/platform/url/electron-main/electronUrlListener.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IURLService } from 'vs/platform/url/common/url'; import product from 'vs/platform/product/common/product'; import { app, Event as ElectronEvent } from 'electron'; @@ -32,7 +33,8 @@ export class ElectronURLListener { constructor( initial: string | string[], @IURLService private readonly urlService: IURLService, - @IWindowsMainService windowsMainService: IWindowsMainService + @IWindowsMainService windowsMainService: IWindowsMainService, + @IEnvironmentService environmentService: IEnvironmentService ) { const globalBuffer = ((global).getOpenUrls() || []) as string[]; const rawBuffer = [ @@ -43,7 +45,9 @@ export class ElectronURLListener { this.uris = coalesce(rawBuffer.map(uriFromRawUrl)); if (isWindows) { - app.setAsDefaultProtocolClient(product.urlProtocol, process.execPath, ['--open-url', '--']); + const windowsParameters = environmentService.isBuilt ? [] : [`"${environmentService.appRoot}"`]; + windowsParameters.push('--open-url', '--'); + app.setAsDefaultProtocolClient(product.urlProtocol, process.execPath, windowsParameters); } const onOpenElectronUrl = Event.map( diff --git a/src/vs/platform/userDataSync/common/content.ts b/src/vs/platform/userDataSync/common/content.ts new file mode 100644 index 0000000000..4af6aa8237 --- /dev/null +++ b/src/vs/platform/userDataSync/common/content.ts @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { JSONPath } from 'vs/base/common/json'; +import { setProperty } from 'vs/base/common/jsonEdit'; +import { FormattingOptions } from 'vs/base/common/jsonFormatter'; + + +export function edit(content: string, originalPath: JSONPath, value: any, formattingOptions: FormattingOptions): string { + const edit = setProperty(content, originalPath, value, formattingOptions)[0]; + if (edit) { + content = content.substring(0, edit.offset) + edit.content + content.substring(edit.offset + edit.length); + } + return content; +} + +export function getLineStartOffset(content: string, eol: string, atOffset: number): number { + let lineStartingOffset = atOffset; + while (lineStartingOffset >= 0) { + if (content.charAt(lineStartingOffset) === eol.charAt(eol.length - 1)) { + if (eol.length === 1) { + return lineStartingOffset + 1; + } + } + lineStartingOffset--; + if (eol.length === 2) { + if (lineStartingOffset >= 0 && content.charAt(lineStartingOffset) === eol.charAt(0)) { + return lineStartingOffset + 2; + } + } + } + return 0; +} + +export function getLineEndOffset(content: string, eol: string, atOffset: number): number { + let lineEndOffset = atOffset; + while (lineEndOffset >= 0) { + if (content.charAt(lineEndOffset) === eol.charAt(eol.length - 1)) { + if (eol.length === 1) { + return lineEndOffset; + } + } + lineEndOffset++; + if (eol.length === 2) { + if (lineEndOffset >= 0 && content.charAt(lineEndOffset) === eol.charAt(1)) { + return lineEndOffset; + } + } + } + return content.length - 1; +} diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 7acedf8262..f495c95359 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -69,11 +69,14 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser } async sync(): Promise { - if (!this.configurationService.getValue('configurationSync.enableExtensions')) { + if (!this.configurationService.getValue('sync.enableExtensions')) { this.logService.trace('Extensions: Skipping synchronizing extensions as it is disabled.'); return false; } - + if (!this.extensionGalleryService.isEnabled()) { + this.logService.trace('Extensions: Skipping synchronizing extensions as gallery is disabled.'); + return false; + } if (this.status !== SyncStatus.Idle) { this.logService.trace('Extensions: Skipping synchronizing extensions as it is running already.'); return false; @@ -105,7 +108,7 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser return this.replaceQueue.queue(async () => { const remoteData = await this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, null); const remoteExtensions: ISyncExtension[] = remoteData.content ? JSON.parse(remoteData.content) : []; - const ignoredExtensions = this.configurationService.getValue('configurationSync.extensionsToIgnore') || []; + const ignoredExtensions = this.configurationService.getValue('sync.ignoredExtensions') || []; const removedExtensions = remoteExtensions.filter(e => !ignoredExtensions.some(id => areSameExtensions({ id }, e.identifier)) && areSameExtensions(e.identifier, identifier)); if (removedExtensions.length) { for (const removedExtension of removedExtensions) { @@ -159,11 +162,11 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser * - Update remote with those local extension which are newly added or updated or removed and untouched in remote. */ private merge(localExtensions: ISyncExtension[], remoteExtensions: ISyncExtension[] | null, lastSyncExtensions: ISyncExtension[] | null): { added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[], remote: ISyncExtension[] | null } { - const ignoredExtensions = this.configurationService.getValue('configurationSync.extensionsToIgnore') || []; + const ignoredExtensions = this.configurationService.getValue('sync.ignoredExtensions') || []; // First time sync if (!remoteExtensions) { this.logService.info('Extensions: Remote extensions does not exist. Synchronizing extensions for the first time.'); - return { added: [], removed: [], updated: [], remote: localExtensions.filter(({ identifier }) => ignoredExtensions.some(id => id.toLowerCase() === identifier.id.toLowerCase())) }; + return { added: [], removed: [], updated: [], remote: localExtensions.filter(({ identifier }) => ignoredExtensions.every(id => id.toLowerCase() !== identifier.id.toLowerCase())) }; } const uuids: Map = new Map(); diff --git a/src/vs/platform/userDataSync/common/keybindingsMerge.ts b/src/vs/platform/userDataSync/common/keybindingsMerge.ts new file mode 100644 index 0000000000..77e9771989 --- /dev/null +++ b/src/vs/platform/userDataSync/common/keybindingsMerge.ts @@ -0,0 +1,367 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as objects from 'vs/base/common/objects'; +import { parse } from 'vs/base/common/json'; +import { values, keys } from 'vs/base/common/map'; +import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; +import { firstIndex as findFirstIndex, equals } from 'vs/base/common/arrays'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import * as contentUtil from 'vs/platform/userDataSync/common/content'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { FormattingOptions } from 'vs/base/common/jsonFormatter'; +import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; + +interface ICompareResult { + added: Set; + removed: Set; + updated: Set; +} + +interface IMergeResult { + hasLocalForwarded: boolean; + hasRemoteForwarded: boolean; + added: Set; + removed: Set; + updated: Set; + conflicts: Set; +} + +export async function merge(localContent: string, remoteContent: string, baseContent: string | null, formattingOptions: FormattingOptions, userDataSyncUtilService: IUserDataSyncUtilService): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> { + const local = parse(localContent); + const remote = parse(remoteContent); + const base = baseContent ? parse(baseContent) : null; + + const userbindings: string[] = [...local, ...remote, ...(base || [])].map(keybinding => keybinding.key); + const normalizedKeys = await userDataSyncUtilService.resolveUserBindings(userbindings); + let keybindingsMergeResult = computeMergeResultByKeybinding(local, remote, base, normalizedKeys); + + if (!keybindingsMergeResult.hasLocalForwarded && !keybindingsMergeResult.hasRemoteForwarded) { + // No changes found between local and remote. + return { mergeContent: localContent, hasChanges: false, hasConflicts: false }; + } + + if (!keybindingsMergeResult.hasLocalForwarded && keybindingsMergeResult.hasRemoteForwarded) { + return { mergeContent: remoteContent, hasChanges: true, hasConflicts: false }; + } + + if (keybindingsMergeResult.hasLocalForwarded && !keybindingsMergeResult.hasRemoteForwarded) { + // Local has moved forward and remote has not. Return local. + return { mergeContent: localContent, hasChanges: true, hasConflicts: false }; + } + + // Both local and remote has moved forward. + const localByCommand = byCommand(local); + const remoteByCommand = byCommand(remote); + const baseByCommand = base ? byCommand(base) : null; + const localToRemoteByCommand = compareByCommand(localByCommand, remoteByCommand, normalizedKeys); + const baseToLocalByCommand = baseByCommand ? compareByCommand(baseByCommand, localByCommand, normalizedKeys) : { added: keys(localByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + const baseToRemoteByCommand = baseByCommand ? compareByCommand(baseByCommand, remoteByCommand, normalizedKeys) : { added: keys(remoteByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + + const commandsMergeResult = computeMergeResult(localToRemoteByCommand, baseToLocalByCommand, baseToRemoteByCommand); + let mergeContent = localContent; + + // Removed commands in Remote + for (const command of values(commandsMergeResult.removed)) { + if (commandsMergeResult.conflicts.has(command)) { + continue; + } + mergeContent = removeKeybindings(mergeContent, command, formattingOptions); + } + + // Added commands in remote + for (const command of values(commandsMergeResult.added)) { + if (commandsMergeResult.conflicts.has(command)) { + continue; + } + const keybindings = remoteByCommand.get(command)!; + // Ignore negated commands + if (keybindings.some(keybinding => keybinding.command !== `-${command}` && keybindingsMergeResult.conflicts.has(normalizedKeys[keybinding.key]))) { + commandsMergeResult.conflicts.add(command); + continue; + } + mergeContent = addKeybindings(mergeContent, keybindings, formattingOptions); + } + + // Updated commands in Remote + for (const command of values(commandsMergeResult.updated)) { + if (commandsMergeResult.conflicts.has(command)) { + continue; + } + const keybindings = remoteByCommand.get(command)!; + // Ignore negated commands + if (keybindings.some(keybinding => keybinding.command !== `-${command}` && keybindingsMergeResult.conflicts.has(normalizedKeys[keybinding.key]))) { + commandsMergeResult.conflicts.add(command); + continue; + } + mergeContent = updateKeybindings(mergeContent, command, keybindings, formattingOptions); + } + + const hasConflicts = commandsMergeResult.conflicts.size > 0; + if (hasConflicts) { + mergeContent = `<<<<<<< local${formattingOptions.eol}` + + mergeContent + + `${formattingOptions.eol}=======${formattingOptions.eol}` + + remoteContent + + `${formattingOptions.eol}>>>>>>> remote`; + } + + return { mergeContent, hasChanges: true, hasConflicts }; +} + +function computeMergeResult(localToRemote: ICompareResult, baseToLocal: ICompareResult, baseToRemote: ICompareResult): { added: Set, removed: Set, updated: Set, conflicts: Set } { + const added: Set = new Set(); + const removed: Set = new Set(); + const updated: Set = new Set(); + const conflicts: Set = new Set(); + + // Removed keys in Local + for (const key of values(baseToLocal.removed)) { + // Got updated in remote + if (baseToRemote.updated.has(key)) { + conflicts.add(key); + } + } + + // Removed keys in Remote + for (const key of values(baseToRemote.removed)) { + if (conflicts.has(key)) { + continue; + } + // Got updated in local + if (baseToLocal.updated.has(key)) { + conflicts.add(key); + } else { + // remove the key + removed.add(key); + } + } + + // Added keys in Local + for (const key of values(baseToLocal.added)) { + if (conflicts.has(key)) { + continue; + } + // Got added in remote + if (baseToRemote.added.has(key)) { + // Has different value + if (localToRemote.updated.has(key)) { + conflicts.add(key); + } + } + } + + // Added keys in remote + for (const key of values(baseToRemote.added)) { + if (conflicts.has(key)) { + continue; + } + // Got added in local + if (baseToLocal.added.has(key)) { + // Has different value + if (localToRemote.updated.has(key)) { + conflicts.add(key); + } + } else { + added.add(key); + } + } + + // Updated keys in Local + for (const key of values(baseToLocal.updated)) { + if (conflicts.has(key)) { + continue; + } + // Got updated in remote + if (baseToRemote.updated.has(key)) { + // Has different value + if (localToRemote.updated.has(key)) { + conflicts.add(key); + } + } + } + + // Updated keys in Remote + for (const key of values(baseToRemote.updated)) { + if (conflicts.has(key)) { + continue; + } + // Got updated in local + if (baseToLocal.updated.has(key)) { + // Has different value + if (localToRemote.updated.has(key)) { + conflicts.add(key); + } + } else { + // updated key + updated.add(key); + } + } + return { added, removed, updated, conflicts }; +} + +function computeMergeResultByKeybinding(local: IUserFriendlyKeybinding[], remote: IUserFriendlyKeybinding[], base: IUserFriendlyKeybinding[] | null, normalizedKeys: IStringDictionary): IMergeResult { + const empty = new Set(); + const localByKeybinding = byKeybinding(local, normalizedKeys); + const remoteByKeybinding = byKeybinding(remote, normalizedKeys); + const baseByKeybinding = base ? byKeybinding(base, normalizedKeys) : null; + + const localToRemoteByKeybinding = compareByKeybinding(localByKeybinding, remoteByKeybinding); + if (localToRemoteByKeybinding.added.size === 0 && localToRemoteByKeybinding.removed.size === 0 && localToRemoteByKeybinding.updated.size === 0) { + return { hasLocalForwarded: false, hasRemoteForwarded: false, added: empty, removed: empty, updated: empty, conflicts: empty }; + } + + const baseToLocalByKeybinding = baseByKeybinding ? compareByKeybinding(baseByKeybinding, localByKeybinding) : { added: keys(localByKeybinding).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + if (baseToLocalByKeybinding.added.size === 0 && baseToLocalByKeybinding.removed.size === 0 && baseToLocalByKeybinding.updated.size === 0) { + // Remote has moved forward and local has not. + return { hasLocalForwarded: false, hasRemoteForwarded: true, added: empty, removed: empty, updated: empty, conflicts: empty }; + } + + const baseToRemoteByKeybinding = baseByKeybinding ? compareByKeybinding(baseByKeybinding, remoteByKeybinding) : { added: keys(remoteByKeybinding).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + if (baseToRemoteByKeybinding.added.size === 0 && baseToRemoteByKeybinding.removed.size === 0 && baseToRemoteByKeybinding.updated.size === 0) { + return { hasLocalForwarded: true, hasRemoteForwarded: false, added: empty, removed: empty, updated: empty, conflicts: empty }; + } + + const { added, removed, updated, conflicts } = computeMergeResult(localToRemoteByKeybinding, baseToLocalByKeybinding, baseToRemoteByKeybinding); + return { hasLocalForwarded: true, hasRemoteForwarded: true, added, removed, updated, conflicts }; +} + +function byKeybinding(keybindings: IUserFriendlyKeybinding[], keys: IStringDictionary) { + const map: Map = new Map(); + for (const keybinding of keybindings) { + const key = keys[keybinding.key]; + let value = map.get(key); + if (!value) { + value = []; + map.set(key, value); + } + value.push(keybinding); + + } + return map; +} + +function byCommand(keybindings: IUserFriendlyKeybinding[]): Map { + const map: Map = new Map(); + for (const keybinding of keybindings) { + const command = keybinding.command[0] === '-' ? keybinding.command.substring(1) : keybinding.command; + let value = map.get(command); + if (!value) { + value = []; + map.set(command, value); + } + value.push(keybinding); + } + return map; +} + + +function compareByKeybinding(from: Map, to: Map): ICompareResult { + const fromKeys = keys(from); + const toKeys = keys(to); + const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); + const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); + const updated: Set = new Set(); + + for (const key of fromKeys) { + if (removed.has(key)) { + continue; + } + const value1: IUserFriendlyKeybinding[] = from.get(key)!.map(keybinding => ({ ...keybinding, ...{ key } })); + const value2: IUserFriendlyKeybinding[] = to.get(key)!.map(keybinding => ({ ...keybinding, ...{ key } })); + if (!equals(value1, value2, (a, b) => isSameKeybinding(a, b))) { + updated.add(key); + } + } + + return { added, removed, updated }; +} + +function compareByCommand(from: Map, to: Map, normalizedKeys: IStringDictionary): ICompareResult { + const fromKeys = keys(from); + const toKeys = keys(to); + const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); + const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); + const updated: Set = new Set(); + + for (const key of fromKeys) { + if (removed.has(key)) { + continue; + } + const value1: IUserFriendlyKeybinding[] = from.get(key)!.map(keybinding => ({ ...keybinding, ...{ key: normalizedKeys[keybinding.key] } })); + const value2: IUserFriendlyKeybinding[] = to.get(key)!.map(keybinding => ({ ...keybinding, ...{ key: normalizedKeys[keybinding.key] } })); + if (!areSameKeybindingsWithSameCommand(value1, value2)) { + updated.add(key); + } + } + + return { added, removed, updated }; +} + +function areSameKeybindingsWithSameCommand(value1: IUserFriendlyKeybinding[], value2: IUserFriendlyKeybinding[]): boolean { + // Compare entries adding keybindings + if (!equals(value1.filter(({ command }) => command[0] !== '-'), value2.filter(({ command }) => command[0] !== '-'), (a, b) => isSameKeybinding(a, b))) { + return false; + } + // Compare entries removing keybindings + if (!equals(value1.filter(({ command }) => command[0] === '-'), value2.filter(({ command }) => command[0] === '-'), (a, b) => isSameKeybinding(a, b))) { + return false; + } + return true; +} + +function isSameKeybinding(a: IUserFriendlyKeybinding, b: IUserFriendlyKeybinding): boolean { + if (a.command !== b.command) { + return false; + } + if (a.key !== b.key) { + return false; + } + const whenA = ContextKeyExpr.deserialize(a.when); + const whenB = ContextKeyExpr.deserialize(b.when); + if ((whenA && !whenB) || (!whenA && whenB)) { + return false; + } + if (whenA && whenB && !whenA.equals(whenB)) { + return false; + } + if (!objects.equals(a.args, b.args)) { + return false; + } + return true; +} + +function addKeybindings(content: string, keybindings: IUserFriendlyKeybinding[], formattingOptions: FormattingOptions): string { + for (const keybinding of keybindings) { + content = contentUtil.edit(content, [-1], keybinding, formattingOptions); + } + return content; +} + +function removeKeybindings(content: string, command: string, formattingOptions: FormattingOptions): string { + const keybindings = parse(content); + for (let index = keybindings.length - 1; index >= 0; index--) { + if (keybindings[index].command === command || keybindings[index].command === `-${command}`) { + content = contentUtil.edit(content, [index], undefined, formattingOptions); + } + } + return content; +} + +function updateKeybindings(content: string, command: string, keybindings: IUserFriendlyKeybinding[], formattingOptions: FormattingOptions): string { + const allKeybindings = parse(content); + const location = findFirstIndex(allKeybindings, keybinding => keybinding.command === command || keybinding.command === `-${command}`); + // Remove all entries with this command + for (let index = allKeybindings.length - 1; index >= 0; index--) { + if (allKeybindings[index].command === command || allKeybindings[index].command === `-${command}`) { + content = contentUtil.edit(content, [index], undefined, formattingOptions); + } + } + // add all entries at the same location where the entry with this command was located. + for (let index = keybindings.length - 1; index >= 0; index--) { + content = contentUtil.edit(content, [location], keybindings[index], formattingOptions); + } + return content; +} diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts new file mode 100644 index 0000000000..933f9e39cb --- /dev/null +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -0,0 +1,348 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent } from 'vs/platform/files/common/files'; +import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; +import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { parse, ParseError } from 'vs/base/common/json'; +import { localize } from 'vs/nls'; +import { Emitter, Event } from 'vs/base/common/event'; +import { CancelablePromise, createCancelablePromise, ThrottledDelayer } from 'vs/base/common/async'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { URI } from 'vs/base/common/uri'; +import { joinPath } from 'vs/base/common/resources'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { OS, OperatingSystem } from 'vs/base/common/platform'; +import { isUndefined } from 'vs/base/common/types'; + +interface ISyncContent { + mac?: string; + linux?: string; + windows?: string; + all?: string; +} + +interface ISyncPreviewResult { + readonly fileContent: IFileContent | null; + readonly remoteUserData: IUserData; + readonly hasLocalChanged: boolean; + readonly hasRemoteChanged: boolean; + readonly hasConflicts: boolean; +} + +export class KeybindingsSynchroniser extends Disposable implements ISynchroniser { + + private static EXTERNAL_USER_DATA_KEYBINDINGS_KEY: string = 'keybindings'; + + private syncPreviewResultPromise: CancelablePromise | null = null; + + private _status: SyncStatus = SyncStatus.Idle; + get status(): SyncStatus { return this._status; } + private _onDidChangStatus: Emitter = this._register(new Emitter()); + readonly onDidChangeStatus: Event = this._onDidChangStatus.event; + + private readonly throttledDelayer: ThrottledDelayer; + private _onDidChangeLocal: Emitter = this._register(new Emitter()); + readonly onDidChangeLocal: Event = this._onDidChangeLocal.event; + + private readonly lastSyncKeybindingsResource: URI; + + constructor( + @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IFileService private readonly fileService: IFileService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IUserDataSyncUtilService private readonly userDataSyncUtilService: IUserDataSyncUtilService, + ) { + super(); + this.lastSyncKeybindingsResource = joinPath(this.environmentService.userRoamingDataHome, '.lastSyncKeybindings.json'); + this.throttledDelayer = this._register(new ThrottledDelayer(500)); + this._register(this.fileService.watch(this.environmentService.keybindingsResource)); + this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.keybindingsResource))(() => this.throttledDelayer.trigger(() => this.onDidChangeKeybindings()))); + } + + private async onDidChangeKeybindings(): Promise { + const localFileContent = await this.getLocalContent(); + const lastSyncData = await this.getLastSyncUserData(); + if (localFileContent && lastSyncData) { + if (localFileContent.value.toString() !== lastSyncData.content) { + this._onDidChangeLocal.fire(); + return; + } + } + if (!localFileContent || !lastSyncData) { + this._onDidChangeLocal.fire(); + return; + } + } + + private setStatus(status: SyncStatus): void { + if (this._status !== status) { + this._status = status; + this._onDidChangStatus.fire(status); + } + } + + async sync(_continue?: boolean): Promise { + if (!this.configurationService.getValue('sync.enableKeybindings')) { + this.logService.trace('Keybindings: Skipping synchronizing keybindings as it is disabled.'); + return false; + } + + if (_continue) { + this.logService.info('Keybindings: Resumed synchronizing keybindings'); + return this.continueSync(); + } + + if (this.status !== SyncStatus.Idle) { + this.logService.trace('Keybindings: Skipping synchronizing keybindings as it is running already.'); + return false; + } + + this.logService.trace('Keybindings: Started synchronizing keybindings...'); + this.setStatus(SyncStatus.Syncing); + + try { + const result = await this.getPreview(); + if (result.hasConflicts) { + this.logService.info('Keybindings: Detected conflicts while synchronizing keybindings.'); + this.setStatus(SyncStatus.HasConflicts); + return false; + } + await this.apply(); + return true; + } catch (e) { + this.syncPreviewResultPromise = null; + this.setStatus(SyncStatus.Idle); + if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) { + // Rejected as there is a new remote version. Syncing again, + this.logService.info('Keybindings: Failed to synchronise keybindings as there is a new remote version available. Synchronizing again...'); + return this.sync(); + } + if (e instanceof FileSystemProviderError && e.code === FileSystemProviderErrorCode.FileExists) { + // Rejected as there is a new local version. Syncing again. + this.logService.info('Keybindings: Failed to synchronise keybindings as there is a new local version available. Synchronizing again...'); + return this.sync(); + } + throw e; + } + } + + stop(): void { + if (this.syncPreviewResultPromise) { + this.syncPreviewResultPromise.cancel(); + this.syncPreviewResultPromise = null; + this.logService.info('Keybindings: Stopped synchronizing keybindings.'); + } + this.fileService.del(this.environmentService.keybindingsSyncPreviewResource); + this.setStatus(SyncStatus.Idle); + } + + private async continueSync(): Promise { + if (this.status !== SyncStatus.HasConflicts) { + return false; + } + await this.apply(); + return true; + } + + private async apply(): Promise { + if (!this.syncPreviewResultPromise) { + return; + } + + if (await this.fileService.exists(this.environmentService.keybindingsSyncPreviewResource)) { + const keybindingsPreivew = await this.fileService.readFile(this.environmentService.keybindingsSyncPreviewResource); + const content = keybindingsPreivew.value.toString(); + if (this.hasErrors(content)) { + const error = new Error(localize('errorInvalidKeybindings', "Unable to sync keybindings. Please resolve conflicts without any errors/warnings and try again.")); + this.logService.error(error); + throw error; + } + + let { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged } = await this.syncPreviewResultPromise; + if (!hasLocalChanged && !hasRemoteChanged) { + this.logService.trace('Keybindings: No changes found during synchronizing keybindings.'); + } + if (hasLocalChanged) { + this.logService.info('Keybindings: Updating local keybindings'); + await this.updateLocalContent(content, fileContent); + } + if (hasRemoteChanged) { + this.logService.info('Keybindings: Updating remote keybindings'); + const remoteContents = this.updateSyncContent(content, remoteUserData.content); + const ref = await this.updateRemoteUserData(remoteContents, remoteUserData.ref); + remoteUserData = { ref, content: remoteContents }; + } + if (remoteUserData.content) { + this.logService.info('Keybindings: Updating last synchronised keybindings'); + const lastSyncContent = this.updateSyncContent(content, null); + await this.updateLastSyncUserData({ ref: remoteUserData.ref, content: lastSyncContent }); + } + + // Delete the preview + await this.fileService.del(this.environmentService.keybindingsSyncPreviewResource); + } else { + this.logService.trace('Keybindings: No changes found during synchronizing keybindings.'); + } + + this.logService.trace('Keybindings: Finised synchronizing keybindings.'); + this.syncPreviewResultPromise = null; + this.setStatus(SyncStatus.Idle); + } + + private hasErrors(content: string): boolean { + const parseErrors: ParseError[] = []; + parse(content, parseErrors, { allowEmptyContent: true, allowTrailingComma: true }); + return parseErrors.length > 0; + } + + private getPreview(): Promise { + if (!this.syncPreviewResultPromise) { + this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(token)); + } + return this.syncPreviewResultPromise; + } + + private async generatePreview(token: CancellationToken): Promise { + const lastSyncData = await this.getLastSyncUserData(); + const lastSyncContent = lastSyncData && lastSyncData.content ? this.getKeybindingsContentFromSyncContent(lastSyncData.content) : null; + const remoteUserData = await this.getRemoteUserData(lastSyncData); + const remoteContent = remoteUserData.content ? this.getKeybindingsContentFromSyncContent(remoteUserData.content) : null; + // Get file content last to get the latest + const fileContent = await this.getLocalContent(); + let hasLocalChanged: boolean = false; + let hasRemoteChanged: boolean = false; + let hasConflicts: boolean = false; + let previewContent = null; + + if (remoteContent) { + const localContent: string = fileContent ? fileContent.value.toString() : '[]'; + if (this.hasErrors(localContent)) { + this.logService.error('Keybindings: Unable to sync keybindings as there are errors/warning in keybindings file.'); + return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts }; + } + + if (!lastSyncContent // First time sync + || lastSyncContent !== localContent // Local has forwarded + || lastSyncContent !== remoteContent // Remote has forwarded + ) { + this.logService.trace('Keybindings: Merging remote keybindings with local keybindings...'); + const formattingOptions = await this.userDataSyncUtilService.resolveFormattingOptions(this.environmentService.keybindingsResource); + const result = await merge(localContent, remoteContent, lastSyncContent, formattingOptions, this.userDataSyncUtilService); + // Sync only if there are changes + if (result.hasChanges) { + hasLocalChanged = result.mergeContent !== localContent; + hasRemoteChanged = result.mergeContent !== remoteContent; + hasConflicts = result.hasConflicts; + previewContent = result.mergeContent; + } + } + } + + // First time syncing to remote + else if (fileContent) { + this.logService.info('Keybindings: Remote keybindings does not exist. Synchronizing keybindings for the first time.'); + hasRemoteChanged = true; + previewContent = fileContent.value.toString(); + } + + if (previewContent && !token.isCancellationRequested) { + await this.fileService.writeFile(this.environmentService.keybindingsSyncPreviewResource, VSBuffer.fromString(previewContent)); + } + + return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts }; + } + + private async getLocalContent(): Promise { + try { + return await this.fileService.readFile(this.environmentService.keybindingsResource); + } catch (error) { + return null; + } + } + + private async updateLocalContent(newContent: string, oldContent: IFileContent | null): Promise { + if (oldContent) { + // file exists already + await this.fileService.writeFile(this.environmentService.keybindingsResource, VSBuffer.fromString(newContent), oldContent); + } else { + // file does not exist + await this.fileService.createFile(this.environmentService.keybindingsResource, VSBuffer.fromString(newContent), { overwrite: false }); + } + } + + private async getLastSyncUserData(): Promise { + try { + const content = await this.fileService.readFile(this.lastSyncKeybindingsResource); + return JSON.parse(content.value.toString()); + } catch (error) { + return null; + } + } + + private async updateLastSyncUserData(remoteUserData: IUserData): Promise { + await this.fileService.writeFile(this.lastSyncKeybindingsResource, VSBuffer.fromString(JSON.stringify(remoteUserData))); + } + + private async getRemoteUserData(lastSyncData: IUserData | null): Promise { + return this.userDataSyncStoreService.read(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, lastSyncData); + } + + private async updateRemoteUserData(content: string, ref: string | null): Promise { + return this.userDataSyncStoreService.write(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, content, ref); + } + + private getKeybindingsContentFromSyncContent(syncContent: string): string | null { + try { + const parsed = JSON.parse(syncContent); + if (!this.configurationService.getValue('sync.keybindingsPerPlatform')) { + return isUndefined(parsed.all) ? null : parsed.all; + } + switch (OS) { + case OperatingSystem.Macintosh: + return isUndefined(parsed.mac) ? null : parsed.mac; + case OperatingSystem.Linux: + return isUndefined(parsed.linux) ? null : parsed.linux; + case OperatingSystem.Windows: + return isUndefined(parsed.windows) ? null : parsed.windows; + } + } catch (e) { + this.logService.error(e); + return null; + } + } + + private updateSyncContent(keybindingsContent: string, syncContent: string | null): string { + let parsed: ISyncContent = {}; + try { + parsed = JSON.parse(syncContent || '{}'); + } catch (e) { + this.logService.error(e); + } + if (!this.configurationService.getValue('sync.keybindingsPerPlatform')) { + parsed.all = keybindingsContent; + } else { + delete parsed.all; + } + switch (OS) { + case OperatingSystem.Macintosh: + parsed.mac = keybindingsContent; + break; + case OperatingSystem.Linux: + parsed.linux = keybindingsContent; + break; + case OperatingSystem.Windows: + parsed.windows = keybindingsContent; + break; + } + return JSON.stringify(parsed); + } + +} diff --git a/src/vs/platform/userDataSync/common/keybindingsSyncIpc.ts b/src/vs/platform/userDataSync/common/keybindingsSyncIpc.ts new file mode 100644 index 0000000000..02b96500f3 --- /dev/null +++ b/src/vs/platform/userDataSync/common/keybindingsSyncIpc.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; +import { Event } from 'vs/base/common/event'; +import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { URI } from 'vs/base/common/uri'; +import { FormattingOptions } from 'vs/base/common/jsonFormatter'; + +export class UserDataSycnUtilServiceChannel implements IServerChannel { + + constructor(private readonly service: IUserDataSyncUtilService) { } + + listen(_: unknown, event: string): Event { + throw new Error(`Event not found: ${event}`); + } + + call(context: any, command: string, args?: any): Promise { + switch (command) { + case 'resolveUserKeybindings': return this.service.resolveUserBindings(args[0]); + case 'resolveFormattingOptions': return this.service.resolveFormattingOptions(URI.revive(args[0])); + } + throw new Error('Invalid call'); + } +} + +export class UserDataSyncUtilServiceClient implements IUserDataSyncUtilService { + + _serviceBrand: undefined; + + constructor(private readonly channel: IChannel) { + } + + async resolveUserBindings(userbindings: string[]): Promise> { + return this.channel.call('resolveUserKeybindings', [userbindings]); + } + + async resolveFormattingOptions(file: URI): Promise { + return this.channel.call('resolveFormattingOptions', [file]); + } + +} diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index caaf88f550..ed78d72517 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -80,7 +80,7 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { } async sync(_continue?: boolean): Promise { - if (!this.configurationService.getValue('configurationSync.enableSettings')) { + if (!this.configurationService.getValue('sync.enableSettings')) { this.logService.trace('Settings: Skipping synchronizing settings as it is disabled.'); return false; } @@ -135,10 +135,9 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { } private async continueSync(): Promise { - if (this.status !== SyncStatus.HasConflicts) { - return false; + if (this.status === SyncStatus.HasConflicts) { + await this.apply(); } - await this.apply(); return true; } @@ -153,7 +152,7 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { if (this.hasErrors(content)) { const error = new Error(localize('errorInvalidSettings', "Unable to sync settings. Please resolve conflicts without any errors/warnings and try again.")); this.logService.error(error); - return Promise.reject(error); + throw error; } let { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged } = await this.syncPreviewResultPromise; @@ -188,7 +187,7 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { private hasErrors(content: string): boolean { const parseErrors: ParseError[] = []; - parse(content, parseErrors); + parse(content, parseErrors, { allowEmptyContent: true, allowTrailingComma: true }); return parseErrors.length > 0; } @@ -218,8 +217,8 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { } if (!lastSyncData // First time sync - || lastSyncData.content !== localContent // Local has moved forwarded - || lastSyncData.content !== remoteContent // Remote has moved forwarded + || lastSyncData.content !== localContent // Local has forwarded + || lastSyncData.content !== remoteContent // Remote has forwarded ) { this.logService.trace('Settings: Merging remote settings with local settings...'); const result = await this.settingsMergeService.merge(localContent, remoteContent, lastSyncData ? lastSyncData.content : null, this.getIgnoredSettings()); @@ -248,13 +247,23 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { } private getIgnoredSettings(settingsContent?: string): string[] { - const value: string[] = (settingsContent ? parse(settingsContent)['configurationSync.settingsToIgnore'] : this.configurationService.getValue('configurationSync.settingsToIgnore')) || []; + let value: string[] = []; + if (settingsContent) { + const setting = parse(settingsContent); + if (setting) { + value = setting['sync.ignoredSettings']; + } + } else { + value = this.configurationService.getValue('sync.ignoredSettings'); + } const added: string[] = [], removed: string[] = []; - for (const key of value) { - if (startsWith(key, '-')) { - removed.push(key.substring(1)); - } else { - added.push(key); + if (Array.isArray(value)) { + for (const key of value) { + if (startsWith(key, '-')) { + removed.push(key.substring(1)); + } else { + added.push(key); + } } } return [...DEFAULT_IGNORED_SETTINGS, ...added].filter(setting => removed.indexOf(setting) === -1); diff --git a/src/vs/platform/userDataSync/common/userDataAutoSync.ts b/src/vs/platform/userDataSync/common/userDataAutoSync.ts new file mode 100644 index 0000000000..849f9afe82 --- /dev/null +++ b/src/vs/platform/userDataSync/common/userDataAutoSync.ts @@ -0,0 +1,69 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IUserDataSyncService, SyncStatus, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { Event } from 'vs/base/common/event'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { timeout } from 'vs/base/common/async'; +import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth'; + +export class UserDataAutoSync extends Disposable { + + private enabled: boolean = false; + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, + @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, + @IAuthTokenService private readonly authTokenService: IAuthTokenService, + ) { + super(); + this.updateEnablement(false); + this._register(Event.any(authTokenService.onDidChangeStatus, userDataSyncService.onDidChangeStatus)(() => this.updateEnablement(true))); + this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('sync.enable'))(() => this.updateEnablement(true))); + } + + private updateEnablement(stopIfDisabled: boolean): void { + const enabled = this.isSyncEnabled(); + if (this.enabled === enabled) { + return; + } + + this.enabled = enabled; + if (this.enabled) { + this.logService.info('Syncing configuration started'); + this.sync(true); + return; + } else { + if (stopIfDisabled) { + this.userDataSyncService.stop(); + this.logService.info('Syncing configuration stopped.'); + } + } + + } + + protected async sync(loop: boolean): Promise { + if (this.enabled) { + try { + await this.userDataSyncService.sync(); + } catch (e) { + this.logService.error(e); + } + if (loop) { + await timeout(1000 * 60 * 5); // Loop sync for every 5 min. + this.sync(loop); + } + } + } + + private isSyncEnabled(): boolean { + return this.configurationService.getValue('sync.enable') + && this.userDataSyncService.status !== SyncStatus.Uninitialized + && this.authTokenService.status === AuthTokenStatus.SignedIn; + } + +} diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index dc2b337d3a..683627ecca 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -14,61 +14,74 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { ILogService } from 'vs/platform/log/common/log'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { FormattingOptions } from 'vs/base/common/jsonFormatter'; +import { URI } from 'vs/base/common/uri'; + +const CONFIGURATION_SYNC_STORE_KEY = 'configurationSync.store'; export const DEFAULT_IGNORED_SETTINGS = [ - 'configurationSync.enable', - 'configurationSync.enableSettings', - 'configurationSync.enableExtensions', + CONFIGURATION_SYNC_STORE_KEY, + 'sync.enable', + 'sync.enableSettings', + 'sync.enableExtensions', ]; export function registerConfiguration(): IDisposable { const ignoredSettingsSchemaId = 'vscode://schemas/ignoredSettings'; const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); configurationRegistry.registerConfiguration({ - id: 'configurationSync', + id: 'sync', order: 30, - title: localize('configurationSync', "Configuration Sync"), + title: localize('sync', "Sync"), type: 'object', properties: { - 'configurationSync.enable': { + 'sync.enable': { type: 'boolean', - description: localize('configurationSync.enable', "When enabled, synchronises configuration that includes Settings and Extensions."), - default: true, + description: localize('sync.enable', "Enable synchronization."), + default: false, scope: ConfigurationScope.APPLICATION }, - 'configurationSync.enableSettings': { + 'sync.enableSettings': { type: 'boolean', - description: localize('configurationSync.enableSettings', "When enabled settings are synchronised while synchronizing configuration."), + description: localize('sync.enableSettings', "Enable synchronizing settings."), default: true, scope: ConfigurationScope.APPLICATION, }, - 'configurationSync.enableExtensions': { + 'sync.enableExtensions': { type: 'boolean', - description: localize('configurationSync.enableExtensions', "When enabled extensions are synchronised while synchronizing configuration."), + description: localize('sync.enableExtensions', "Enable synchronizing extensions."), default: true, scope: ConfigurationScope.APPLICATION, }, - 'configurationSync.extensionsToIgnore': { + 'sync.enableKeybindings': { + type: 'boolean', + description: localize('sync.enableKeybindings', "Enable synchronizing keybindings."), + default: true, + scope: ConfigurationScope.APPLICATION, + }, + 'sync.keybindingsPerPlatform': { + type: 'boolean', + description: localize('sync.keybindingsPerPlatform', "Synchronize keybindings per platform."), + default: true, + scope: ConfigurationScope.APPLICATION, + }, + 'sync.ignoredExtensions': { 'type': 'array', - description: localize('configurationSync.extensionsToIgnore', "Configure extensions to be ignored while syncing."), + description: localize('sync.ignoredExtensions', "Configure extensions to be ignored while synchronizing."), 'default': [], 'scope': ConfigurationScope.APPLICATION, uniqueItems: true }, - 'configurationSync.settingsToIgnore': { + 'sync.ignoredSettings': { 'type': 'array', - description: localize('configurationSync.settingsToIgnore', "Configure settings to be ignored while syncing. \nDefault Ignored Settings:\n\n{0}", DEFAULT_IGNORED_SETTINGS.sort().map(setting => `- ${setting}`).join('\n')), + description: localize('sync.ignoredSettings', "Configure settings to be ignored while synchronizing. \nDefault Ignored Settings:\n\n{0}", DEFAULT_IGNORED_SETTINGS.sort().map(setting => `- ${setting}`).join('\n')), 'default': [], 'scope': ConfigurationScope.APPLICATION, $ref: ignoredSettingsSchemaId, additionalProperties: true, uniqueItems: true - }, - 'configurationSync.enableAuth': { - 'type': 'boolean', - description: localize('configurationSync.enableAuth', "Enables authentication and requires VS Code restart when changed"), - 'default': false, - 'scope': ConfigurationScope.APPLICATION } } }); @@ -104,12 +117,23 @@ export class UserDataSyncStoreError extends Error { } +export interface IUserDataSyncStore { + url: string; + name: string; + account: string; +} + +export function getUserDataSyncStore(configurationService: IConfigurationService): IUserDataSyncStore | undefined { + const value = configurationService.getValue(CONFIGURATION_SYNC_STORE_KEY); + return value && value.url && value.name && value.account ? value : undefined; +} + export const IUserDataSyncStoreService = createDecorator('IUserDataSyncStoreService'); export interface IUserDataSyncStoreService { _serviceBrand: undefined; - readonly enabled: boolean; + readonly userDataSyncStore: IUserDataSyncStore | undefined; read(key: string, oldValue: IUserData | null): Promise; write(key: string, content: string, ref: string | null): Promise; @@ -123,6 +147,7 @@ export interface ISyncExtension { export const enum SyncSource { Settings = 1, + Keybindings, Extensions } @@ -164,6 +189,18 @@ export interface ISettingsMergeService { } +export const IUserDataSyncUtilService = createDecorator('IUserDataSyncUtilService'); + +export interface IUserDataSyncUtilService { + + _serviceBrand: undefined; + + resolveUserBindings(userbindings: string[]): Promise>; + + resolveFormattingOptions(resource: URI): Promise; + +} + export const IUserDataSyncLogService = createDecorator('IUserDataSyncLogService'); export interface IUserDataSyncLogService extends ILogService { diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index eda99c7245..3b9245026d 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -3,16 +3,15 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncService, SyncStatus, ISynchroniser, IUserDataSyncStoreService, SyncSource, IUserDataSyncLogService, UserDataSyncStoreError, UserDataSyncStoreErrorCode } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, SyncStatus, ISynchroniser, IUserDataSyncStoreService, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; import { Emitter, Event } from 'vs/base/common/event'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { timeout } from 'vs/base/common/async'; import { ExtensionsSynchroniser } from 'vs/platform/userDataSync/common/extensionsSync'; import { IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth'; +import { KeybindingsSynchroniser } from 'vs/platform/userDataSync/common/keybindingsSync'; export class UserDataSyncService extends Disposable implements IUserDataSyncService { @@ -31,6 +30,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ get conflictsSource(): SyncSource | null { return this._conflictsSource; } private readonly settingsSynchroniser: SettingsSynchroniser; + private readonly keybindingsSynchroniser: KeybindingsSynchroniser; private readonly extensionsSynchroniser: ExtensionsSynchroniser; constructor( @@ -40,20 +40,25 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ ) { super(); this.settingsSynchroniser = this._register(this.instantiationService.createInstance(SettingsSynchroniser)); + this.keybindingsSynchroniser = this._register(this.instantiationService.createInstance(KeybindingsSynchroniser)); this.extensionsSynchroniser = this._register(this.instantiationService.createInstance(ExtensionsSynchroniser)); - this.synchronisers = [this.settingsSynchroniser, this.extensionsSynchroniser]; + this.synchronisers = [this.settingsSynchroniser, this.keybindingsSynchroniser, this.extensionsSynchroniser]; this.updateStatus(); - this._register(Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeStatus, () => undefined)))(() => this.updateStatus())); + + if (this.userDataSyncStoreService.userDataSyncStore) { + this._register(Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeStatus, () => undefined)))(() => this.updateStatus())); + this._register(authTokenService.onDidChangeStatus(() => this.onDidChangeAuthTokenStatus())); + } + this.onDidChangeLocal = Event.any(...this.synchronisers.map(s => s.onDidChangeLocal)); - this._register(authTokenService.onDidChangeStatus(() => this.onDidChangeAuthTokenStatus())); } async sync(_continue?: boolean): Promise { - if (!this.userDataSyncStoreService.enabled) { + if (!this.userDataSyncStoreService.userDataSyncStore) { throw new Error('Not enabled'); } - if (this.authTokenService.status === AuthTokenStatus.Inactive) { - return Promise.reject('Not Authenticated. Please sign in to start sync.'); + if (this.authTokenService.status === AuthTokenStatus.SignedOut) { + throw new Error('Not Authenticated. Please sign in to start sync.'); } for (const synchroniser of this.synchronisers) { if (!await synchroniser.sync(_continue)) { @@ -64,7 +69,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } stop(): void { - if (!this.userDataSyncStoreService.enabled) { + if (!this.userDataSyncStoreService.userDataSyncStore) { throw new Error('Not enabled'); } for (const synchroniser of this.synchronisers) { @@ -89,7 +94,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } private computeStatus(): SyncStatus { - if (!this.userDataSyncStoreService.enabled) { + if (!this.userDataSyncStoreService.userDataSyncStore) { return SyncStatus.Uninitialized; } if (this.synchronisers.some(s => s.status === SyncStatus.HasConflicts)) { @@ -107,83 +112,17 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ if (source instanceof SettingsSynchroniser) { return SyncSource.Settings; } + if (source instanceof KeybindingsSynchroniser) { + return SyncSource.Keybindings; + } } return null; } private onDidChangeAuthTokenStatus(): void { - if (this.authTokenService.status === AuthTokenStatus.Inactive) { + if (this.authTokenService.status === AuthTokenStatus.SignedOut) { this.stop(); } } } - -export class UserDataAutoSync extends Disposable { - - private enabled: boolean = false; - - constructor( - @IConfigurationService private readonly configurationService: IConfigurationService, - @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, - @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, - @IAuthTokenService private readonly authTokenService: IAuthTokenService, - ) { - super(); - this.updateEnablement(false); - this._register(Event.any(authTokenService.onDidChangeStatus, userDataSyncService.onDidChangeStatus)(() => this.updateEnablement(true))); - this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('configurationSync.enable'))(() => this.updateEnablement(true))); - - // Sync immediately if there is a local change. - this._register(Event.debounce(this.userDataSyncService.onDidChangeLocal, () => undefined, 500)(() => this.sync(false))); - } - - private updateEnablement(stopIfDisabled: boolean): void { - const enabled = this.isSyncEnabled(); - if (this.enabled === enabled) { - return; - } - - this.enabled = enabled; - if (this.enabled) { - this.logService.info('Syncing configuration started'); - this.sync(true); - return; - } else { - if (stopIfDisabled) { - this.userDataSyncService.stop(); - this.logService.info('Syncing configuration stopped.'); - } - } - - } - - private async sync(loop: boolean): Promise { - if (this.enabled) { - try { - await this.userDataSyncService.sync(); - } catch (e) { - if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Unauthroized) { - if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Unauthroized && this.authTokenService.status === AuthTokenStatus.Disabled) { - this.logService.error('Sync failed because the server requires authorization. Please enable authorization.'); - } else { - this.logService.error(e); - } - } - this.logService.error(e); - } - if (loop) { - await timeout(1000 * 5); // Loop sync for every 5s. - this.sync(loop); - } - } - } - - private isSyncEnabled(): boolean { - return this.configurationService.getValue('configurationSync.enable') - && this.userDataSyncService.status !== SyncStatus.Uninitialized - && this.authTokenService.status !== AuthTokenStatus.Inactive; - } - -} diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index 62121a6acc..06dd7512c5 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -4,35 +4,36 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, } from 'vs/base/common/lifecycle'; -import { IUserData, IUserDataSyncStoreService, UserDataSyncStoreErrorCode, UserDataSyncStoreError } from 'vs/platform/userDataSync/common/userDataSync'; -import { IProductService } from 'vs/platform/product/common/productService'; +import { IUserData, IUserDataSyncStoreService, UserDataSyncStoreErrorCode, UserDataSyncStoreError, IUserDataSyncStore, getUserDataSyncStore } from 'vs/platform/userDataSync/common/userDataSync'; import { IRequestService, asText, isSuccess } from 'vs/platform/request/common/request'; import { URI } from 'vs/base/common/uri'; import { joinPath } from 'vs/base/common/resources'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IHeaders, IRequestOptions, IRequestContext } from 'vs/base/parts/request/common/request'; -import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth'; +import { IAuthTokenService } from 'vs/platform/auth/common/auth'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class UserDataSyncStoreService extends Disposable implements IUserDataSyncStoreService { _serviceBrand: any; - get enabled(): boolean { return !!this.productService.settingsSyncStoreUrl; } + readonly userDataSyncStore: IUserDataSyncStore | undefined; constructor( - @IProductService private readonly productService: IProductService, + @IConfigurationService configurationService: IConfigurationService, @IRequestService private readonly requestService: IRequestService, @IAuthTokenService private readonly authTokenService: IAuthTokenService, ) { super(); + this.userDataSyncStore = getUserDataSyncStore(configurationService); } async read(key: string, oldValue: IUserData | null): Promise { - if (!this.enabled) { - return Promise.reject(new Error('No settings sync store url configured.')); + if (!this.userDataSyncStore) { + throw new Error('No settings sync store url configured.'); } - const url = joinPath(URI.parse(this.productService.settingsSyncStoreUrl!), 'resource', key, 'latest').toString(); + const url = joinPath(URI.parse(this.userDataSyncStore.url), 'resource', key, 'latest').toString(); const headers: IHeaders = {}; if (oldValue) { headers['If-None-Match'] = oldValue.ref; @@ -58,11 +59,11 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn } async write(key: string, data: string, ref: string | null): Promise { - if (!this.enabled) { - return Promise.reject(new Error('No settings sync store url configured.')); + if (!this.userDataSyncStore) { + throw new Error('No settings sync store url configured.'); } - const url = joinPath(URI.parse(this.productService.settingsSyncStoreUrl!), 'resource', key).toString(); + const url = joinPath(URI.parse(this.userDataSyncStore.url), 'resource', key).toString(); const headers: IHeaders = { 'Content-Type': 'text/plain' }; if (ref) { headers['If-Match'] = ref; @@ -87,21 +88,17 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn } private async request(options: IRequestOptions, token: CancellationToken): Promise { - if (this.authTokenService.status !== AuthTokenStatus.Disabled) { - const authToken = await this.authTokenService.getToken(); - if (!authToken) { - return Promise.reject(new Error('No Auth Token Available.')); - } - options.headers = options.headers || {}; - options.headers['authorization'] = `Bearer ${authToken}`; + const authToken = await this.authTokenService.getToken(); + if (!authToken) { + throw new Error('No Auth Token Available.'); } + options.headers = options.headers || {}; + options.headers['authorization'] = `Bearer ${authToken}`; const context = await this.requestService.request(options, token); if (context.res.statusCode === 401) { - if (this.authTokenService.status !== AuthTokenStatus.Disabled) { - this.authTokenService.refreshToken(); - } + this.authTokenService.refreshToken(); // Throw Unauthorized Error throw new UserDataSyncStoreError('Unauthorized', UserDataSyncStoreErrorCode.Unauthroized); } diff --git a/src/vs/platform/userDataSync/electron-browser/userDataAutoSync.ts b/src/vs/platform/userDataSync/electron-browser/userDataAutoSync.ts new file mode 100644 index 0000000000..e9b360f579 --- /dev/null +++ b/src/vs/platform/userDataSync/electron-browser/userDataAutoSync.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IUserDataSyncService, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; +import { Event } from 'vs/base/common/event'; +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { UserDataAutoSync as BaseUserDataAutoSync } from 'vs/platform/userDataSync/common/userDataAutoSync'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IAuthTokenService } from 'vs/platform/auth/common/auth'; + +export class UserDataAutoSync extends BaseUserDataAutoSync { + + constructor( + @IUserDataSyncService userDataSyncService: IUserDataSyncService, + @IElectronService electronService: IElectronService, + @IConfigurationService configurationService: IConfigurationService, + @IUserDataSyncLogService logService: IUserDataSyncLogService, + @IAuthTokenService authTokenService: IAuthTokenService, + ) { + super(configurationService, userDataSyncService, logService, authTokenService); + + // Sync immediately if there is a local change. + this._register(Event.debounce(Event.any( + electronService.onWindowFocus, + electronService.onWindowOpen, + userDataSyncService.onDidChangeLocal + ), () => undefined, 500)(() => this.sync(false))); + } + +} diff --git a/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts new file mode 100644 index 0000000000..59c706f40f --- /dev/null +++ b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts @@ -0,0 +1,735 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { OperatingSystem, OS } from 'vs/base/common/platform'; +import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; +import { FormattingOptions } from 'vs/base/common/jsonFormatter'; +import { URI } from 'vs/base/common/uri'; + +suite('KeybindingsMerge - No Conflicts', () => { + + test('merge when local and remote are same with one entry', async () => { + const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(!actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when local and remote are same with similar when contexts', async () => { + const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: '!editorReadonly && editorTextFocus' }]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(!actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when local and remote has entries in different order', async () => { + const localContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+a', command: 'a', when: 'editorTextFocus' } + ]); + const remoteContent = stringify([ + { key: 'alt+a', command: 'a', when: 'editorTextFocus' }, + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' } + ]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(!actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when local and remote are same with multiple entries', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } } + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } } + ]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(!actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when local and remote are same with different base content', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } } + ]); + const baseContent = stringify([ + { key: 'ctrl+c', command: 'e' }, + { key: 'shift+d', command: 'd', args: { text: '`' } } + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } } + ]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(!actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when local and remote are same with multiple entries in different order', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } } + ]); + const remoteContent = stringify([ + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(!actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when local and remote are same when remove entry is in different order', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } } + ]); + const remoteContent = stringify([ + { key: 'alt+d', command: '-a' }, + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(!actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when a new entry is added to remote', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, remoteContent); + }); + + test('merge when multiple new entries are added to remote', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + { key: 'cmd+d', command: 'c' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, remoteContent); + }); + + test('merge when multiple new entries are added to remote from base and local has not changed', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + { key: 'cmd+d', command: 'c' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, localContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, remoteContent); + }); + + test('merge when an entry is removed from remote from base and local has not changed', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, localContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, remoteContent); + }); + + test('merge when an entry (same command) is removed from remote from base and local has not changed', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, localContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, remoteContent); + }); + + test('merge when an entry is updated in remote from base and local has not changed', async () => { + const localContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, localContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, remoteContent); + }); + + test('merge when a command with multiple entries is updated from remote from base and local has not changed', async () => { + const localContent = stringify([ + { key: 'shift+c', command: 'c' }, + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: 'b' }, + { key: 'cmd+c', command: 'a' }, + ]); + const remoteContent = stringify([ + { key: 'shift+c', command: 'c' }, + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: 'b' }, + { key: 'cmd+d', command: 'a' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, localContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, remoteContent); + }); + + test('merge when remote has moved forwareded with multiple changes and local stays with base', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+e', command: 'd' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + ]); + const remoteContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'cmd+e', command: 'd' }, + { key: 'alt+d', command: '-a' }, + { key: 'alt+f', command: 'f' }, + { key: 'alt+d', command: '-f' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'cmd+c', command: '-c' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, localContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, remoteContent); + }); + + test('merge when a new entry is added to local', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when multiple new entries are added to local', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + { key: 'cmd+d', command: 'c' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when multiple new entries are added to local from base and remote is not changed', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + { key: 'cmd+d', command: 'c' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, remoteContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when an entry is removed from local from base and remote has not changed', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, remoteContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when an entry (with same command) is removed from local from base and remote has not changed', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, remoteContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when an entry is updated in local from base and remote has not changed', async () => { + const localContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, remoteContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when a command with multiple entries is updated from local from base and remote has not changed', async () => { + const localContent = stringify([ + { key: 'shift+c', command: 'c' }, + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: 'b' }, + { key: 'cmd+c', command: 'a' }, + ]); + const remoteContent = stringify([ + { key: 'shift+c', command: 'c' }, + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: 'b' }, + { key: 'cmd+d', command: 'a' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, remoteContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when local has moved forwareded with multiple changes and remote stays with base', async () => { + const localContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'cmd+e', command: 'd' }, + { key: 'alt+d', command: '-a' }, + { key: 'alt+f', command: 'f' }, + { key: 'alt+d', command: '-f' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'cmd+c', command: '-c' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+e', command: 'd' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + ]); + const expected = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'cmd+e', command: 'd' }, + { key: 'alt+d', command: '-a' }, + { key: 'alt+f', command: 'f' }, + { key: 'alt+d', command: '-f' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'cmd+c', command: '-c' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, remoteContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, expected); + }); + + test('merge when local and remote has moved forwareded with conflicts', async () => { + const baseContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'ctrl+c', command: '-a' }, + { key: 'cmd+e', command: 'd' }, + { key: 'alt+a', command: 'f' }, + { key: 'alt+d', command: '-f' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'cmd+c', command: '-c' }, + ]); + const localContent = stringify([ + { key: 'alt+d', command: '-f' }, + { key: 'cmd+e', command: 'd' }, + { key: 'cmd+c', command: '-c' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'alt+a', command: 'f' }, + { key: 'alt+e', command: 'e' }, + ]); + const remoteContent = stringify([ + { key: 'alt+a', command: 'f' }, + { key: 'cmd+c', command: '-c' }, + { key: 'cmd+d', command: 'd' }, + { key: 'alt+d', command: '-f' }, + { key: 'alt+c', command: 'c', when: 'context1' }, + { key: 'alt+g', command: 'g', when: 'context2' }, + ]); + const expected = stringify([ + { key: 'alt+d', command: '-f' }, + { key: 'cmd+d', command: 'd' }, + { key: 'cmd+c', command: '-c' }, + { key: 'alt+c', command: 'c', when: 'context1' }, + { key: 'alt+a', command: 'f' }, + { key: 'alt+e', command: 'e' }, + { key: 'alt+g', command: 'g', when: 'context2' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, expected); + }); + +}); + + +suite.skip('KeybindingsMerge - Conflicts', () => { + + test('merge when local and remote with one entry but different value', async () => { + const localContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local +[ + { + "key": "alt+d", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + } +] +======= +[ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + } +] +>>>>>>> remote`); + }); + + test('merge when local and remote with different keybinding', async () => { + const localContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } + ]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local +[ + { + "key": "alt+d", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + }, + { + "key": "alt+a", + "command": "-a", + "when": "editorTextFocus && !editorReadonly" + } +] +======= +[ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + }, + { + "key": "alt+a", + "command": "-a", + "when": "editorTextFocus && !editorReadonly" + } +] +>>>>>>> remote`); + }); + + test('merge when the entry is removed in local but updated in remote', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local +[] +======= +[ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + } +] +>>>>>>> remote`); + }); + + test('merge when the entry is removed in local but updated in remote and a new entry is added in local', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([{ key: 'alt+b', command: 'b' }]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local +[ + { + "key": "alt+b", + "command": "b" + } +] +======= +[ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + } +] +>>>>>>> remote`); + }); + + test('merge when the entry is removed in remote but updated in local', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local +[ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + } +] +======= +[] +>>>>>>> remote`); + }); + + test('merge when the entry is removed in remote but updated in local and a new entry is added in remote', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([{ key: 'alt+b', command: 'b' }]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local +[ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + }, + { + "key": "alt+b", + "command": "b" + } +] +======= +[ + { + "key": "alt+b", + "command": "b" + } +] +>>>>>>> remote`); + }); + + test('merge when local and remote has moved forwareded with conflicts', async () => { + const baseContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+c', command: '-a' }, + { key: 'cmd+e', command: 'd' }, + { key: 'alt+a', command: 'f' }, + { key: 'alt+d', command: '-f' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'cmd+c', command: '-c' }, + ]); + const localContent = stringify([ + { key: 'alt+d', command: '-f' }, + { key: 'cmd+e', command: 'd' }, + { key: 'cmd+c', command: '-c' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'alt+a', command: 'f' }, + { key: 'alt+e', command: 'e' }, + ]); + const remoteContent = stringify([ + { key: 'alt+a', command: 'f' }, + { key: 'cmd+c', command: '-c' }, + { key: 'cmd+d', command: 'd' }, + { key: 'alt+d', command: '-f' }, + { key: 'alt+c', command: 'c', when: 'context1' }, + { key: 'alt+g', command: 'g', when: 'context2' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local +[ + { + "key": "alt+d", + "command": "-f" + }, + { + "key": "cmd+d", + "command": "d" + }, + { + "key": "cmd+c", + "command": "-c" + }, + { + "key": "cmd+d", + "command": "c", + "when": "context1" + }, + { + "key": "alt+a", + "command": "f" + }, + { + "key": "alt+e", + "command": "e" + }, + { + "key": "alt+g", + "command": "g", + "when": "context2" + } +] +======= +[ + { + "key": "alt+a", + "command": "f" + }, + { + "key": "cmd+c", + "command": "-c" + }, + { + "key": "cmd+d", + "command": "d" + }, + { + "key": "alt+d", + "command": "-f" + }, + { + "key": "alt+c", + "command": "c", + "when": "context1" + }, + { + "key": "alt+g", + "command": "g", + "when": "context2" + } +] +>>>>>>> remote`); + }); + +}); + + +async function mergeKeybindings(localContent: string, remoteContent: string, baseContent: string | null) { + const userDataSyncUtilService = new MockUserDataSyncUtilService(); + const formattingOptions = await userDataSyncUtilService.resolveFormattingOptions(); + return merge(localContent, remoteContent, baseContent, formattingOptions, userDataSyncUtilService); +} + +function stringify(value: any): string { + return JSON.stringify(value, null, '\t'); +} + +class MockUserDataSyncUtilService implements IUserDataSyncUtilService { + + _serviceBrand: any; + + async resolveUserBindings(userbindings: string[]): Promise> { + const keys: IStringDictionary = {}; + for (const keybinding of userbindings) { + keys[keybinding] = keybinding; + } + return keys; + } + + async resolveFormattingOptions(file?: URI): Promise { + return { eol: OS === OperatingSystem.Windows ? '\r\n' : '\n', insertSpaces: false, tabSize: 4 }; + } +} diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 2324a228e2..c5a370841e 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -25,6 +25,7 @@ export interface IBaseOpenWindowsOptions { export interface IOpenWindowOptions extends IBaseOpenWindowsOptions { forceNewWindow?: boolean; + preferNewWindow?: boolean; noRecentEntry?: boolean; } @@ -219,8 +220,9 @@ export interface IAddFoldersRequest { } export interface IWindowConfiguration extends ParsedArgs { - machineId: string; - windowId: number; + machineId?: string; // NOTE: This is undefined in the web, the telemetry service directly resolves this. + windowId: number; // TODO: should we deprecate this in favor of sessionId? + sessionId: string; logLevel: LogLevel; mainPid: number; @@ -245,19 +247,14 @@ export interface IWindowConfiguration extends ParsedArgs { fullscreen?: boolean; maximized?: boolean; highContrast?: boolean; - frameless?: boolean; accessibilitySupport?: boolean; partsSplashPath?: string; - perfStartTime?: number; - perfAppReady?: number; - perfWindowLoadTime?: number; perfEntries: ExportData; filesToOpenOrCreate?: IPath[]; filesToDiff?: IPath[]; filesToWait?: IPathsToWaitFor; - termProgram?: string; } export interface IRunActionInWindowRequest { diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index ddd61b0988..f0612bd763 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as fs from 'fs'; -import { basename, normalize, join, } from 'vs/base/common/path'; +import { basename, normalize, join, posix } from 'vs/base/common/path'; import { localize } from 'vs/nls'; import * as arrays from 'vs/base/common/arrays'; import { assign, mixin } from 'vs/base/common/objects'; @@ -496,7 +496,8 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // Remember in recent document list (unless this opens for extension development) // Also do not add paths when files are opened for diffing, only if opened individually - if (!usedWindows.some(window => window.isExtensionDevelopmentHost) && !openConfig.diffMode && !openConfig.noRecentEntry) { + const isDiff = fileInputs && fileInputs.filesToDiff.length > 0; + if (!usedWindows.some(window => window.isExtensionDevelopmentHost) && !isDiff && !openConfig.noRecentEntry) { const recents: IRecent[] = []; for (let pathToOpen of pathsToOpen) { if (pathToOpen.workspace) { @@ -1107,8 +1108,6 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic const remoteAuthority = options.remoteAuthority; if (remoteAuthority) { - // assume it's a folder or workspace file - const first = anyPath.charCodeAt(0); // make absolute if (first !== CharCode.Slash) { @@ -1120,11 +1119,15 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic const uri = URI.from({ scheme: Schemas.vscodeRemote, authority: remoteAuthority, path: anyPath }); - if (hasWorkspaceFileExtension(anyPath)) { - if (forceOpenWorkspaceAsFile) { + // guess the file type: If it ends with a slash it's a folder. If it has a file extension, it's a file or a workspace. By defaults it's a folder. + if (anyPath.charCodeAt(anyPath.length - 1) !== CharCode.Slash) { + if (hasWorkspaceFileExtension(anyPath)) { + if (forceOpenWorkspaceAsFile) { + return { fileUri: uri, remoteAuthority }; + } + } else if (posix.extname(anyPath).length > 0) { return { fileUri: uri, remoteAuthority }; } - return { workspace: getWorkspaceIdentifier(uri), remoteAuthority }; } return { folderUri: uri, remoteAuthority }; } diff --git a/src/vs/platform/workspace/common/workspace.ts b/src/vs/platform/workspace/common/workspace.ts index d1a376d5fb..49271d25b2 100644 --- a/src/vs/platform/workspace/common/workspace.ts +++ b/src/vs/platform/workspace/common/workspace.ts @@ -9,10 +9,11 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { TernarySearchTree } from 'vs/base/common/map'; import { Event } from 'vs/base/common/event'; import { IWorkspaceIdentifier, IStoredWorkspaceFolder, isRawFileWorkspaceFolder, isRawUriWorkspaceFolder, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceFolderProvider } from 'vs/base/common/labels'; export const IWorkspaceContextService = createDecorator('contextService'); -export interface IWorkspaceContextService { +export interface IWorkspaceContextService extends IWorkspaceFolderProvider { _serviceBrand: undefined; /** diff --git a/src/vs/platform/workspaces/common/workspaces.ts b/src/vs/platform/workspaces/common/workspaces.ts index 9a665730a8..3955da58ca 100644 --- a/src/vs/platform/workspaces/common/workspaces.ts +++ b/src/vs/platform/workspaces/common/workspaces.ts @@ -19,6 +19,7 @@ import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; import { ILogService } from 'vs/platform/log/common/log'; import { Event as CommonEvent } from 'vs/base/common/event'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; export const WORKSPACE_EXTENSION = 'code-workspace'; export const WORKSPACE_FILTER = [{ name: localize('codeWorkspace', "Code Workspace"), extensions: [WORKSPACE_EXTENSION] }]; @@ -172,6 +173,10 @@ export function toWorkspaceIdentifier(workspace: IWorkspace): IWorkspaceIdentifi return undefined; } +export function isUntitledWorkspace(path: URI, environmentService: IEnvironmentService): boolean { + return isEqualOrParent(path, environmentService.untitledWorkspacesHome); +} + export type IMultiFolderWorkspaceInitializationPayload = IWorkspaceIdentifier; export interface ISingleFolderWorkspaceInitializationPayload { id: string; folder: ISingleFolderWorkspaceIdentifier; } export interface IEmptyWorkspaceInitializationPayload { id: string; } @@ -280,12 +285,9 @@ function doParseStoredWorkspace(path: URI, contents: string): IStoredWorkspace { let storedWorkspace: IStoredWorkspace = json.parse(contents); // use fault tolerant parser // Filter out folders which do not have a path or uri set - if (Array.isArray(storedWorkspace.folders)) { + if (storedWorkspace && Array.isArray(storedWorkspace.folders)) { storedWorkspace.folders = storedWorkspace.folders.filter(folder => isStoredWorkspaceFolder(folder)); - } - - // Validate - if (!Array.isArray(storedWorkspace.folders)) { + } else { throw new Error(`${path} looks like an invalid workspace file.`); } diff --git a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts index bc4dc061b6..b9f2840c7b 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkspaceIdentifier, hasWorkspaceFileExtension, UNTITLED_WORKSPACE_NAME, IResolvedWorkspace, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IUntitledWorkspaceInfo, getStoredWorkspaceFolder, IEnterWorkspaceResult } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceIdentifier, hasWorkspaceFileExtension, UNTITLED_WORKSPACE_NAME, IResolvedWorkspace, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IUntitledWorkspaceInfo, getStoredWorkspaceFolder, IEnterWorkspaceResult, isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { join, dirname } from 'vs/base/common/path'; import { mkdirp, writeFile, rimrafSync, readdirSync, writeFileSync } from 'vs/base/node/pfs'; @@ -17,7 +17,7 @@ import { toWorkspaceFolders } from 'vs/platform/workspace/common/workspace'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { Disposable } from 'vs/base/common/lifecycle'; -import { originalFSPath, isEqualOrParent, joinPath, isEqual, basename } from 'vs/base/common/resources'; +import { originalFSPath, joinPath, isEqual, basename } from 'vs/base/common/resources'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ICodeWindow } from 'vs/platform/windows/electron-main/windows'; import { localize } from 'vs/nls'; @@ -104,7 +104,7 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain } private isWorkspacePath(uri: URI): boolean { - return this.isInsideWorkspacesHome(uri) || hasWorkspaceFileExtension(uri); + return isUntitledWorkspace(uri, this.environmentService) || hasWorkspaceFileExtension(uri); } private doResolveWorkspace(path: URI, contents: string): IResolvedWorkspace | null { @@ -130,22 +130,15 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain let storedWorkspace: IStoredWorkspace = json.parse(contents); // use fault tolerant parser // Filter out folders which do not have a path or uri set - if (Array.isArray(storedWorkspace.folders)) { + if (storedWorkspace && Array.isArray(storedWorkspace.folders)) { storedWorkspace.folders = storedWorkspace.folders.filter(folder => isStoredWorkspaceFolder(folder)); - } - - // Validate - if (!Array.isArray(storedWorkspace.folders)) { + } else { throw new Error(`${path.toString()} looks like an invalid workspace file.`); } return storedWorkspace; } - private isInsideWorkspacesHome(path: URI): boolean { - return isEqualOrParent(path, this.environmentService.untitledWorkspacesHome); - } - async createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise { const { workspace, storedWorkspace } = this.newUntitledWorkspace(folders, remoteAuthority); const configPath = workspace.configPath.fsPath; @@ -196,7 +189,7 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain } isUntitledWorkspace(workspace: IWorkspaceIdentifier): boolean { - return this.isInsideWorkspacesHome(workspace.configPath); + return isUntitledWorkspace(workspace.configPath, this.environmentService); } deleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void { diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 0a4b3c53c9..6f181b922a 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -128,7 +128,7 @@ declare module 'vscode' { readonly isDirty: boolean; /** - * `true` if the document have been closed. A closed document isn't synchronized anymore + * `true` if the document has been closed. A closed document isn't synchronized anymore * and won't be re-used when the same resource is opened again. */ readonly isClosed: boolean; @@ -2009,8 +2009,9 @@ declare module 'vscode' { /** * Base kind for source actions: `source` * - * Source code actions apply to the entire file and can be run on save - * using `editor.codeActionsOnSave`. They also are shown in `source` context menu. + * Source code actions apply to the entire file. They must be explicitly requested and will not show in the + * normal [light bulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action) menu. Source actions + * can be run on save using `editor.codeActionsOnSave` and are also shown in the `source` context menu. */ static readonly Source: CodeActionKind; @@ -2967,6 +2968,17 @@ declare module 'vscode' { */ appendPlaceholder(value: string | ((snippet: SnippetString) => any), number?: number): SnippetString; + /** + * Builder-function that appends a choice (`${1|a,b,c}`) to + * the [`value`](#SnippetString.value) of this snippet string. + * + * @param values The values for choices - the array of strings + * @param number The number of this tabstop, defaults to an auto-increment + * value starting at 1. + * @return This snippet string. + */ + appendChoice(values: string[], number?: number): SnippetString; + /** * Builder-function that appends a variable (`${VAR}`) to * the [`value`](#SnippetString.value) of this snippet string. @@ -3003,6 +3015,9 @@ declare module 'vscode' { * be a range or a range and a placeholder text. The placeholder text should be the identifier of the symbol * which is being renamed - when omitted the text in the returned range is used. * + * *Note: * This function should throw an error or return a rejected thenable when the provided location + * doesn't allow for a rename. + * * @param document The document in which rename will be invoked. * @param position The position at which rename will be invoked. * @param token A cancellation token. @@ -3877,6 +3892,149 @@ declare module 'vscode' { provideSelectionRanges(document: TextDocument, positions: Position[], token: CancellationToken): ProviderResult; } + /** + * Represents programming constructs like functions or constructors in the context + * of call hierarchy. + */ + export class CallHierarchyItem { + /** + * The name of this item. + */ + name: string; + + /** + * The kind of this item. + */ + kind: SymbolKind; + + /** + * Tags for this item. + */ + tags?: ReadonlyArray; + + /** + * More detail for this item, e.g. the signature of a function. + */ + detail?: string; + + /** + * The resource identifier of this item. + */ + uri: Uri; + + /** + * The range enclosing this symbol not including leading/trailing whitespace but everything else, e.g. comments and code. + */ + range: Range; + + /** + * The range that should be selected and revealed when this symbol is being picked, e.g. the name of a function. + * Must be contained by the [`range`](#CallHierarchyItem.range). + */ + selectionRange: Range; + + /** + * Creates a new call hierarchy item. + */ + constructor(kind: SymbolKind, name: string, detail: string, uri: Uri, range: Range, selectionRange: Range); + } + + /** + * Represents an incoming call, e.g. a caller of a method or constructor. + */ + export class CallHierarchyIncomingCall { + + /** + * The item that makes the call. + */ + from: CallHierarchyItem; + + /** + * The range at which at which the calls appears. This is relative to the caller + * denoted by [`this.from`](#CallHierarchyIncomingCall.from). + */ + fromRanges: Range[]; + + /** + * Create a new call object. + * + * @param item The item making the call. + * @param fromRanges The ranges at which the calls appear. + */ + constructor(item: CallHierarchyItem, fromRanges: Range[]); + } + + /** + * Represents an outgoing call, e.g. calling a getter from a method or a method from a constructor etc. + */ + export class CallHierarchyOutgoingCall { + + /** + * The item that is called. + */ + to: CallHierarchyItem; + + /** + * The range at which this item is called. This is the range relative to the caller, e.g the item + * passed to [`provideCallHierarchyOutgoingCalls`](#CallHierarchyItemProvider.provideCallHierarchyOutgoingCalls) + * and not [`this.to`](#CallHierarchyOutgoingCall.to). + */ + fromRanges: Range[]; + + /** + * Create a new call object. + * + * @param item The item being called + * @param fromRanges The ranges at which the calls appear. + */ + constructor(item: CallHierarchyItem, fromRanges: Range[]); + } + + /** + * The call hierarchy provider interface describes the constract between extensions + * and the call hierarchy feature which allows to browse calls and caller of function, + * methods, constructor etc. + */ + export interface CallHierarchyProvider { + + /** + * Bootstraps call hierarchy by returning the item that is denoted by the given document + * and position. This item will be used as entry into the call graph. Providers should + * return `undefined` or `null` when there is no item at the given location. + * + * @param document The document in which the command was invoked. + * @param position The position at which the command was invoked. + * @param token A cancellation token. + * @returns A call hierarchy item or a thenable that resolves to such. The lack of a result can be + * signaled by returning `undefined` or `null`. + */ + prepareCallHierarchy(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; + + /** + * Provide all incoming calls for an item, e.g all callers for a method. In graph terms this describes directed + * and annotated edges inside the call graph, e.g the given item is the starting node and the result is the nodes + * that can be reached. + * + * @param item The hierarchy item for which incoming calls should be computed. + * @param token A cancellation token. + * @returns A set of incoming calls or a thenable that resolves to such. The lack of a result can be + * signaled by returning `undefined` or `null`. + */ + provideCallHierarchyIncomingCalls(item: CallHierarchyItem, token: CancellationToken): ProviderResult; + + /** + * Provide all outgoing calls for an item, e.g call calls to functions, methods, or constructors from the given item. In + * graph terms this describes directed and annotated edges inside the call graph, e.g the given item is the starting + * node and the result is the nodes that can be reached. + * + * @param item The hierarchy item for which outgoing calls should be computed. + * @param token A cancellation token. + * @returns A set of outgoing calls or a thenable that resolves to such. The lack of a result can be + * signaled by returning `undefined` or `null`. + */ + provideCallHierarchyOutgoingCalls(item: CallHierarchyItem, token: CancellationToken): ProviderResult; + } + /** * A tuple of two characters, like a pair of * opening and closing brackets. @@ -4080,11 +4238,11 @@ declare module 'vscode' { * - Workspace configuration (if available) * - Workspace folder configuration of the requested resource (if available) * - * *Global configuration* comes from User Settings and shadows Defaults. + * *Global configuration* comes from User Settings and overrides Defaults. * - * *Workspace configuration* comes from Workspace Settings and shadows Global configuration. + * *Workspace configuration* comes from Workspace Settings and overrides Global configuration. * - * *Workspace Folder configuration* comes from `.vscode` folder under one of the [workspace folders](#workspace.workspaceFolders). + * *Workspace Folder configuration* comes from `.vscode` folder under one of the [workspace folders](#workspace.workspaceFolders) and overrides Workspace configuration. * * *Note:* Workspace and Workspace Folder configurations contains `launch` and `tasks` settings. Their basename will be * part of the section identifier. The following snippets shows how to retrieve all configurations @@ -4133,9 +4291,9 @@ declare module 'vscode' { * a workspace-specific value and a folder-specific value. * * The *effective* value (returned by [`get`](#WorkspaceConfiguration.get)) - * is computed like this: `defaultValue` overwritten by `globalValue`, - * `globalValue` overwritten by `workspaceValue`. `workspaceValue` overwritten by `workspaceFolderValue`. - * Refer to [Settings Inheritance](https://code.visualstudio.com/docs/getstarted/settings) + * is computed like this: `defaultValue` overridden by `globalValue`, + * `globalValue` overridden by `workspaceValue`. `workspaceValue` overwridden by `workspaceFolderValue`. + * Refer to [Settings](https://code.visualstudio.com/docs/getstarted/settings) * for more information. * * *Note:* The configuration name must denote a leaf in the configuration tree @@ -4624,7 +4782,7 @@ declare module 'vscode' { * * `My text $(icon-name) contains icons like $(icon-name) this one.` * - * Where the icon-name is taken from the [octicon](https://octicons.github.com) icon set, e.g. + * Where the icon-name is taken from the [codicon](https://microsoft.github.io/vscode-codicons/dist/codicon.html) icon set, e.g. * `light-bulb`, `thumbsup`, `zap` etc. */ text: string; @@ -4688,7 +4846,7 @@ declare module 'vscode' { /** * The process ID of the shell process. */ - readonly processId: Thenable; + readonly processId: Thenable; /** * Send text to the terminal. The text is written to the stdin of the underlying pty process @@ -4766,8 +4924,8 @@ declare module 'vscode' { /** * The extension kind describes if an extension runs where the UI runs * or if an extension runs where the remote extension host runs. The extension kind - * if defined in the `package.json` file of extensions but can also be refined - * via the the `remote.extensionKind`-setting. When no remote extension host exists, + * is defined in the `package.json`-file of extensions but can also be refined + * via the `remote.extensionKind`-setting. When no remote extension host exists, * the value is [`ExtensionKind.UI`](#ExtensionKind.UI). */ extensionKind: ExtensionKind; @@ -5589,10 +5747,18 @@ declare module 'vscode' { ctime: number; /** * The modification timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC. + * + * *Note:* If the file changed, it is important to provide an updated `mtime` that advanced + * from the previous value. Otherwise there may be optimizations in place that will not show + * the updated file contents in an editor for example. */ mtime: number; /** * The size in bytes. + * + * *Note:* If the file changed, it is important to provide an updated `size`. Otherwise there + * may be optimizations in place that will not show the updated file contents in an editor for + * example. */ size: number; } @@ -5706,6 +5872,11 @@ declare module 'vscode' { * An event to signal that a resource has been created, changed, or deleted. This * event should fire for resources that are being [watched](#FileSystemProvider.watch) * by clients of this provider. + * + * *Note:* It is important that the metadata of the file that changed provides an + * updated `mtime` that advanced from the previous value in the [stat](#FileStat) and a + * correct `size` value. Otherwise there may be optimizations in place that will not show + * the change in an editor for example. */ readonly onDidChangeFile: Event; @@ -5843,6 +6014,9 @@ declare module 'vscode' { /** * Create a new directory (Note, that new files are created via `write`-calls). * + * *Note* that missing directories are created automatically, e.g this call has + * `mkdirp` semantics. + * * @param uri The uri of the new folder. */ createDirectory(uri: Uri): Thenable; @@ -6290,22 +6464,51 @@ declare module 'vscode' { export function openExternal(target: Uri): Thenable; /** + * Resolves a uri to form that is accessible externally. Currently only supports `https:`, `http:` and + * `vscode.env.uriScheme` uris. + * + * #### `http:` or `https:` scheme + * * Resolves an *external* uri, such as a `http:` or `https:` link, from where the extension is running to a * uri to the same resource on the client machine. * - * This is a no-op if the extension is running on the client machine. Currently only supports - * `https:` and `http:` uris. + * This is a no-op if the extension is running on the client machine. * * If the extension is running remotely, this function automatically establishes a port forwarding tunnel * from the local machine to `target` on the remote and returns a local uri to the tunnel. The lifetime of * the port fowarding tunnel is managed by VS Code and the tunnel can be closed by the user. * - * Extensions should not cache the result of `asExternalUri` as the resolved uri may become invalid due to - * a system or user action — for example, in remote cases, a user may close a port forwardng tunnel - * that was opened by `asExternalUri`. + * *Note* that uris passed through `openExternal` are automatically resolved and you should not call `asExternalUri` on them. * - * *Note* that uris passed through `openExternal` are automatically resolved and you should not call `asExternalUri` - * on them. + * #### `vscode.env.uriScheme` + * + * Creates a uri that - if opened in a browser (e.g. via `openExternal`) - will result in a registered [UriHandler](#UriHandler) + * to trigger. + * + * Extensions should not make any assumptions about the resulting uri and should not alter it in anyway. + * Rather, extensions can e.g. use this uri in an authentication flow, by adding the uri as callback query + * argument to the server to authenticate to. + * + * *Note* that if the server decides to add additional query parameters to the uri (e.g. a token or secret), it + * will appear in the uri that is passed to the [UriHandler](#UriHandler). + * + * **Example** of an authentication flow: + * ```typescript + * vscode.window.registerUriHandler({ + * handleUri(uri: vscode.Uri): vscode.ProviderResult { + * if (uri.path === '/did-authenticate') { + * console.log(uri.toString()); + * } + * } + * }); + * + * const callableUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://my.extension/did-authenticate`)); + * await vscode.env.openExternal(callableUri); + * ``` + * + * *Note* that extensions should not cache the result of `asExternalUri` as the resolved uri may become invalid due to + * a system or user action — for example, in remote cases, a user may close a port forwarding tunnel that was opened by + * `asExternalUri`. * * @return A uri that can be used on the client machine. */ @@ -7084,6 +7287,12 @@ declare module 'vscode' { */ message?: string; + /** + * The tree view title is initially taken from the extension package.json + * Changes to the title property will be properly reflected in the UI in the title of the view. + */ + title?: string; + /** * Reveals the given element in the tree view. * If the tree view is not visible then the tree view is shown and element is revealed. @@ -7312,6 +7521,9 @@ declare module 'vscode' { * [Terminal.sendText](#Terminal.sendText) which sends text to the underlying _process_ * (the pty "slave"), this will write the text to the terminal itself (the pty "master"). * + * Note writing `\n` will just move the cursor down 1 row, you need to write `\r` as well + * to move the cursor to the left-most cell. + * * **Example:** Write red text to the terminal * ```typescript * const writeEmitter = new vscode.EventEmitter(); @@ -8103,8 +8315,8 @@ declare module 'vscode' { * * All changes of a workspace edit are applied in the same order in which they have been added. If * multiple textual inserts are made at the same position, these strings appear in the resulting text - * in the order the 'inserts' were made. Invalid sequences like 'delete file a' -> 'insert text in file a' - * cause failure of the operation. + * in the order the 'inserts' were made, unless that are interleaved with resource edits. Invalid sequences + * like 'delete file a' -> 'insert text in file a' cause failure of the operation. * * When applying a workspace edit that consists only of text edits an 'all-or-nothing'-strategy is used. * A workspace edit with resource creations or deletions aborts the operation, e.g. consecutive edits will @@ -8693,6 +8905,15 @@ declare module 'vscode' { */ export function registerSelectionRangeProvider(selector: DocumentSelector, provider: SelectionRangeProvider): Disposable; + /** + * Register a call hierarchy provider. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider A call hierarchy provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ + export function registerCallHierarchyProvider(selector: DocumentSelector, provider: CallHierarchyProvider): Disposable; + /** * Set a [language configuration](#LanguageConfiguration) for a language. * @@ -9282,6 +9503,48 @@ declare module 'vscode' { constructor(functionName: string, enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string); } + /** + * Debug console mode used by debug session, see [options](#DebugSessionOptions). + */ + export enum DebugConsoleMode { + /** + * Debug session should have a separate debug console. + */ + Separate = 0, + + /** + * Debug session should share debug console with its parent session. + * This value has no effect for sessions which do not have a parent session. + */ + MergeWithParent = 1 + } + + /** + * Options for [starting a debug session](#debug.startDebugging). + */ + export interface DebugSessionOptions { + + /** + * When specified the newly created debug session is registered as a "child" session of this + * "parent" debug session. + */ + parentSession?: DebugSession; + + /** + * Controls whether this session should have a separate debug console or share it + * with the parent session. Has no effect for sessions which do not have a parent session. + * Defaults to Separate. + */ + consoleMode?: DebugConsoleMode; + } + + /** + * A DebugProtocolSource is an opaque stand-in type for the [Source](https://microsoft.github.io/debug-adapter-protocol/specification#Types_Source) type defined in the Debug Adapter Protocol. + */ + export interface DebugProtocolSource { + // Properties: see details [here](https://microsoft.github.io/debug-adapter-protocol/specification#Types_Source). + } + /** * Namespace for debug functionality. */ @@ -9388,6 +9651,19 @@ declare module 'vscode' { * @param breakpoints The breakpoints to remove. */ export function removeBreakpoints(breakpoints: Breakpoint[]): void; + + /** + * Converts a "Source" descriptor object received via the Debug Adapter Protocol into a Uri that can be used to load its contents. + * If the source descriptor is based on a path, a file Uri is returned. + * If the source descriptor uses a reference number, a specific debug Uri (scheme 'debug') is constructed that requires a corresponding VS Code ContentProvider and a running debug session + * + * If the "Source" descriptor has insufficient information for creating the Uri, an error is thrown. + * + * @param source An object conforming to the [Source](https://microsoft.github.io/debug-adapter-protocol/specification#Types_Source) type defined in the Debug Adapter Protocol. + * @param session An optional debug session that will be used when the source descriptor uses a reference number to load the contents from an active debug session. + * @return A uri that can be used to load the contents of the source. + */ + export function asDebugSourceUri(source: DebugProtocolSource, session?: DebugSession): Uri; } /** diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 578f98b272..81c5ad7fae 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -16,89 +16,6 @@ declare module 'vscode' { - //#region Joh - call hierarchy - - export class CallHierarchyItem { - /** - * The name of this item. - */ - name: string; - - /** - * The kind of this item. - */ - kind: SymbolKind; - - /** - * Tags for this item. - */ - tags?: ReadonlyArray; - - /** - * More detail for this item, e.g. the signature of a function. - */ - detail?: string; - - /** - * The resource identifier of this item. - */ - uri: Uri; - - /** - * The range enclosing this symbol not including leading/trailing whitespace but everything else, e.g. comments and code. - */ - range: Range; - - /** - * The range that should be selected and reveal when this symbol is being picked, e.g. the name of a function. - * Must be contained by the [`range`](#CallHierarchyItem.range). - */ - selectionRange: Range; - - constructor(kind: SymbolKind, name: string, detail: string, uri: Uri, range: Range, selectionRange: Range); - } - - export class CallHierarchyIncomingCall { - from: CallHierarchyItem; - fromRanges: Range[]; - constructor(item: CallHierarchyItem, fromRanges: Range[]); - } - - export class CallHierarchyOutgoingCall { - fromRanges: Range[]; - to: CallHierarchyItem; - constructor(item: CallHierarchyItem, fromRanges: Range[]); - } - - export interface CallHierarchyItemProvider { - - /** - * Provide a list of callers for the provided item, e.g. all function calling a function. - */ - provideCallHierarchyIncomingCalls(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; - - /** - * Provide a list of calls for the provided item, e.g. all functions call from a function. - */ - provideCallHierarchyOutgoingCalls(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; - - // todo@joh this could return as 'prepareCallHierarchy' (similar to the RenameProvider#prepareRename) - // - // /** - // * - // * Given a document and position compute a call hierarchy item. This is justed as - // * anchor for call hierarchy and then `resolveCallHierarchyItem` is being called. - // */ - // resolveCallHierarchyItem(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; - } - - export namespace languages { - export function registerCallHierarchyProvider(selector: DocumentSelector, provider: CallHierarchyItemProvider): Disposable; - } - - //#endregion - - //#region Alex - resolvers export interface RemoteAuthorityResolverContext { @@ -151,8 +68,82 @@ declare module 'vscode' { //#endregion + //#region Alex - semantic tokens - // #region Joh - code insets + export class SemanticTokensLegend { + public readonly tokenTypes: string[]; + public readonly tokenModifiers: string[]; + + constructor(tokenTypes: string[], tokenModifiers: string[]); + } + + export class SemanticTokensBuilder { + constructor(); + push(line: number, char: number, length: number, tokenType: number, tokenModifiers: number): void; + build(): Uint32Array; + } + + /** + * A certain token (at index `i` is encoded using 5 uint32 integers): + * - at index `5*i` - `deltaLine`: token line number, relative to `SemanticColoringArea.line` + * - at index `5*i+1` - `deltaStart`: token start character offset inside the line (relative to 0 or the previous token if they are on the same line) + * - at index `5*i+2` - `length`: the length of the token + * - at index `5*i+3` - `tokenType`: will be looked up in `SemanticColoringLegend.tokenTypes` + * - at index `5*i+4` - `tokenModifiers`: each set bit will be looked up in `SemanticColoringLegend.tokenModifiers` + */ + export class SemanticTokens { + readonly resultId?: string; + readonly data: Uint32Array; + + constructor(data: Uint32Array, resultId?: string); + } + + export class SemanticTokensEdits { + readonly resultId?: string; + readonly edits: SemanticTokensEdit[]; + + constructor(edits: SemanticTokensEdit[], resultId?: string); + } + + export class SemanticTokensEdit { + readonly start: number; + readonly deleteCount: number; + readonly data?: Uint32Array; + + constructor(start: number, deleteCount: number, data?: Uint32Array); + } + + export interface SemanticTokensRequestOptions { + readonly ranges?: readonly Range[]; + readonly previousResultId?: string; + } + + /** + * The semantic tokens provider interface defines the contract between extensions and + * semantic tokens. + */ + export interface SemanticTokensProvider { + provideSemanticTokens(document: TextDocument, options: SemanticTokensRequestOptions, token: CancellationToken): ProviderResult; + } + + export namespace languages { + /** + * Register a semantic tokens provider. + * + * Multiple providers can be registered for a language. In that case providers are sorted + * by their [score](#languages.match) and the best-matching provider is used. Failure + * of the selected provider will cause a failure of the whole operation. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider A semantic tokens provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ + export function registerSemanticTokensProvider(selector: DocumentSelector, provider: SemanticTokensProvider, legend: SemanticTokensLegend): Disposable; + } + + //#endregion + + //#region editor insets: https://github.com/microsoft/vscode/issues/85682 export interface WebviewEditorInset { readonly editor: TextEditor; @@ -169,7 +160,7 @@ declare module 'vscode' { //#endregion - //#region Joh - read/write in chunks + //#region read/write in chunks: https://github.com/microsoft/vscode/issues/84515 export interface FileSystemProvider { open?(resource: Uri, options: { create: boolean }): number | Thenable; @@ -550,7 +541,7 @@ declare module 'vscode' { //#endregion - //#region Joao: diff command + //#region diff command: https://github.com/microsoft/vscode/issues/84899 /** * The contiguous set of modified lines in a diff. @@ -583,7 +574,7 @@ declare module 'vscode' { //#endregion - //#region Joh: decorations + //#region file-decorations: https://github.com/microsoft/vscode/issues/54938 export class Decoration { letter?: string; @@ -604,7 +595,45 @@ declare module 'vscode' { //#endregion - //#region André: debug + //#region André: debug API for inline debug adapters https://github.com/microsoft/vscode/issues/85544 + + /** + * A DebugProtocolMessage is an opaque stand-in type for the [ProtocolMessage](https://microsoft.github.io/debug-adapter-protocol/specification#Base_Protocol_ProtocolMessage) type defined in the Debug Adapter Protocol. + */ + export interface DebugProtocolMessage { + // Properties: see details [here](https://microsoft.github.io/debug-adapter-protocol/specification#Base_Protocol_ProtocolMessage). + } + + /** + * A debug adapter that implements the Debug Adapter Protocol can be registered with VS Code if it implements the DebugAdapter interface. + */ + export interface DebugAdapter extends Disposable { + + /** + * An event which fires when the debug adapter sends a Debug Adapter Protocol message to VS Code. + * Messages can be requests, responses, or events. + */ + readonly onSendMessage: Event; + + /** + * Handle a Debug Adapter Protocol message. + * Messages can be requests, responses, or events. + * Results or errors are returned via onSendMessage events. + * @param message A Debug Adapter Protocol message + */ + handleMessage(message: DebugProtocolMessage): void; + } + + /** + * A debug adapter descriptor for an inline implementation. + */ + export class DebugAdapterInlineImplementation { + + /** + * Create a descriptor for an inline implementation of a debug adapter. + */ + constructor(implementation: DebugAdapter); + } // deprecated @@ -616,41 +645,6 @@ declare module 'vscode' { debugAdapterExecutable?(folder: WorkspaceFolder | undefined, token?: CancellationToken): ProviderResult; } - /** - * Debug console mode used by debug session, see [options](#DebugSessionOptions). - */ - export enum DebugConsoleMode { - /** - * Debug session should have a separate debug console. - */ - Separate = 0, - - /** - * Debug session should share debug console with its parent session. - * This value has no effect for sessions which do not have a parent session. - */ - MergeWithParent = 1 - } - - /** - * Options for [starting a debug session](#debug.startDebugging). - */ - export interface DebugSessionOptions { - - /** - * When specified the newly created debug session is registered as a "child" session of this - * "parent" debug session. - */ - parentSession?: DebugSession; - - /** - * Controls whether this session should have a separate debug console or share it - * with the parent session. Has no effect for sessions which do not have a parent session. - * Defaults to Separate. - */ - consoleMode?: DebugConsoleMode; - } - //#endregion //#region Rob, Matt: logging @@ -764,8 +758,76 @@ declare module 'vscode' { //#endregion + //#region Terminal data write event https://github.com/microsoft/vscode/issues/78502 - //#region Terminal + export interface TerminalDataWriteEvent { + /** + * The [terminal](#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 pty slave pseudo-device is written to. 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 Terminal exit status https://github.com/microsoft/vscode/issues/62103 + + export interface TerminalExitStatus { + /** + * The exit code that a terminal exited with, it can have the following values: + * - Zero: the terminal process or custom execution succeeded. + * - Non-zero: the terminal process or custom execution failed. + * - `undefined`: the user forcefully closed the terminal or a custom execution exited + * without providing an exit code. + */ + readonly code: number | undefined; + } + + export interface Terminal { + /** + * The exit status of the terminal, this will be undefined while the terminal is active. + * + * **Example:** Show a notification with the exit code when the terminal exits with a + * non-zero exit code. + * ```typescript + * window.onDidCloseTerminal(t => { + * if (t.exitStatus && t.exitStatus.code) { + * vscode.window.showInformationMessage(`Exit code: ${t.exitStatus.code}`); + * } + * }); + * ``` + */ + readonly exitStatus: TerminalExitStatus | undefined; + } + + //#endregion + + //#region Terminal creation options https://github.com/microsoft/vscode/issues/63052 + + export interface Terminal { + /** + * The object used to initialize the terminal, this is useful for things like detecting the + * shell type of shells not launched by the extension or detecting what folder the shell was + * launched in. + */ + readonly creationOptions: Readonly; + } + + //#endregion + + //#region Terminal dimensions property and change event https://github.com/microsoft/vscode/issues/55718 /** * An [event](#Event) which fires when a [Terminal](#Terminal)'s dimensions change. @@ -781,29 +843,11 @@ declare module 'vscode' { readonly dimensions: TerminalDimensions; } - export interface TerminalDataWriteEvent { - /** - * The [terminal](#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 [dimensions](#Terminal.dimensions) of the terminal change. */ export const onDidChangeTerminalDimensions: Event; - - /** - * An event which fires when the terminal's pty slave pseudo-device is written to. 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; } export interface Terminal { @@ -826,20 +870,243 @@ declare module 'vscode' { //#endregion //#region mjbvz,joh: https://github.com/Microsoft/vscode/issues/43768 - export interface FileRenameEvent { - readonly oldUri: Uri; - readonly newUri: Uri; + + /** + * An event that is fired when files are going to be created. + * + * To make modifications to the workspace before the files are created, + * call the [`waitUntil](#FileWillCreateEvent.waitUntil)-function with a + * thenable that resolves to a [workspace edit](#WorkspaceEdit). + */ + export interface FileWillCreateEvent { + + /** + * The files that are going to be created. + */ + readonly files: ReadonlyArray; + + /** + * Allows to pause the event and to apply a [workspace edit](#WorkspaceEdit). + * + * *Note:* This function can only be called during event dispatch and not + * in an asynchronous manner: + * + * ```ts + * workspace.onWillCreateFiles(event => { + * // async, will *throw* an error + * setTimeout(() => event.waitUntil(promise)); + * + * // sync, OK + * event.waitUntil(promise); + * }) + * ``` + * + * @param thenable A thenable that delays saving. + */ + waitUntil(thenable: Thenable): void; + + /** + * Allows to pause the event until the provided thenable resolves. + * + * *Note:* This function can only be called during event dispatch. + * + * @param thenable A thenable that delays saving. + */ + waitUntil(thenable: Thenable): void; } - export interface FileWillRenameEvent { - readonly oldUri: Uri; - readonly newUri: Uri; + /** + * An event that is fired after files are created. + */ + export interface FileCreateEvent { + + /** + * The files that got created. + */ + readonly files: ReadonlyArray; + } + + /** + * An event that is fired when files are going to be deleted. + * + * To make modifications to the workspace before the files are deleted, + * call the [`waitUntil](#FileWillCreateEvent.waitUntil)-function with a + * thenable that resolves to a [workspace edit](#WorkspaceEdit). + */ + export interface FileWillDeleteEvent { + + /** + * The files that are going to be deleted. + */ + readonly files: ReadonlyArray; + + /** + * Allows to pause the event and to apply a [workspace edit](#WorkspaceEdit). + * + * *Note:* This function can only be called during event dispatch and not + * in an asynchronous manner: + * + * ```ts + * workspace.onWillCreateFiles(event => { + * // async, will *throw* an error + * setTimeout(() => event.waitUntil(promise)); + * + * // sync, OK + * event.waitUntil(promise); + * }) + * ``` + * + * @param thenable A thenable that delays saving. + */ waitUntil(thenable: Thenable): void; + + /** + * Allows to pause the event until the provided thenable resolves. + * + * *Note:* This function can only be called during event dispatch. + * + * @param thenable A thenable that delays saving. + */ + waitUntil(thenable: Thenable): void; + } + + /** + * An event that is fired after files are deleted. + */ + export interface FileDeleteEvent { + + /** + * The files that got deleted. + */ + readonly files: ReadonlyArray; + } + + /** + * An event that is fired when files are going to be renamed. + * + * To make modifications to the workspace before the files are renamed, + * call the [`waitUntil](#FileWillCreateEvent.waitUntil)-function with a + * thenable that resolves to a [workspace edit](#WorkspaceEdit). + */ + export interface FileWillRenameEvent { + + /** + * The files that are going to be renamed. + */ + readonly files: ReadonlyArray<{ oldUri: Uri, newUri: Uri }>; + + /** + * Allows to pause the event and to apply a [workspace edit](#WorkspaceEdit). + * + * *Note:* This function can only be called during event dispatch and not + * in an asynchronous manner: + * + * ```ts + * workspace.onWillCreateFiles(event => { + * // async, will *throw* an error + * setTimeout(() => event.waitUntil(promise)); + * + * // sync, OK + * event.waitUntil(promise); + * }) + * ``` + * + * @param thenable A thenable that delays saving. + */ + waitUntil(thenable: Thenable): void; + + /** + * Allows to pause the event until the provided thenable resolves. + * + * *Note:* This function can only be called during event dispatch. + * + * @param thenable A thenable that delays saving. + */ + waitUntil(thenable: Thenable): void; + } + + /** + * An event that is fired after files are renamed. + */ + export interface FileRenameEvent { + + /** + * The files that got renamed. + */ + readonly files: ReadonlyArray<{ oldUri: Uri, newUri: Uri }>; } export namespace workspace { - export const onWillRenameFile: Event; - export const onDidRenameFile: Event; + + /** + * An event that is emitted when files are being created. + * + * *Note 1:* This event is triggered by user gestures, like creating a file from the + * explorer, or from the [`workspace.applyEdit`](#workspace.applyEdit)-api. This event is *not* fired when + * files change on disk, e.g triggered by another application, or when using the + * [`workspace.fs`](#FileSystem)-api. + * + * *Note 2:* When this event is fired, edits to files thare are being created cannot be applied. + */ + export const onWillCreateFiles: Event; + + /** + * An event that is emitted when files have been created. + * + * *Note:* This event is triggered by user gestures, like creating a file from the + * explorer, or from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when + * files change on disk, e.g triggered by another application, or when using the + * [`workspace.fs`](#FileSystem)-api. + */ + export const onDidCreateFiles: Event; + + /** + * An event that is emitted when files are being deleted. + * + * *Note 1:* This event is triggered by user gestures, like deleting a file from the + * explorer, or from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when + * files change on disk, e.g triggered by another application, or when using the + * [`workspace.fs`](#FileSystem)-api. + * + * *Note 2:* When deleting a folder with children only one event is fired. + */ + export const onWillDeleteFiles: Event; + + /** + * An event that is emitted when files have been deleted. + * + * *Note 1:* This event is triggered by user gestures, like deleting a file from the + * explorer, or from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when + * files change on disk, e.g triggered by another application, or when using the + * [`workspace.fs`](#FileSystem)-api. + * + * *Note 2:* When deleting a folder with children only one event is fired. + */ + export const onDidDeleteFiles: Event; + + /** + * An event that is emitted when files are being renamed. + * + * *Note 1:* This event is triggered by user gestures, like renaming a file from the + * explorer, and from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when + * files change on disk, e.g triggered by another application, or when using the + * [`workspace.fs`](#FileSystem)-api. + * + * *Note 2:* When renaming a folder with children only one event is fired. + */ + export const onWillRenameFiles: Event; + + /** + * An event that is emitted when files have been renamed. + * + * *Note 1:* This event is triggered by user gestures, like renaming a file from the + * explorer, and from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when + * files change on disk, e.g triggered by another application, or when using the + * [`workspace.fs`](#FileSystem)-api. + * + * *Note 2:* When renaming a folder with children only one event is fired. + */ + export const onDidRenameFiles: Event; } //#endregion @@ -852,16 +1119,7 @@ declare module 'vscode' { } //#endregion - //#region Tree View - - export interface TreeView { - /** - * The tree view title is initially taken from the extension package.json - * Changes to the title property will be properly reflected in the UI in the title of the view. - */ - title?: string; - } - + //#region Tree View: https://github.com/microsoft/vscode/issues/61313 /** * Label describing the [Tree item](#TreeItem) */ @@ -894,9 +1152,7 @@ declare module 'vscode' { } //#endregion - //#region CustomExecution - - + //#region CustomExecution: https://github.com/microsoft/vscode/issues/81007 /** * A task to execute */ @@ -904,6 +1160,20 @@ declare module 'vscode' { detail?: string; } + export class CustomExecution2 extends CustomExecution { + /** + * Constructs a CustomExecution task object. The callback will be executed the task is run, at which point the + * extension should return the Pseudoterminal it will "run in". The task should wait to do further execution until + * [Pseudoterminal.open](#Pseudoterminal.open) is called. Task cancellation should be handled using + * [Pseudoterminal.close](#Pseudoterminal.close). When the task is complete fire + * [Pseudoterminal.onDidClose](#Pseudoterminal.onDidClose). + * @param callback The callback that will be called when the task is started by a user. + */ + constructor(callback: (resolvedDefinition?: TaskDefinition) => Thenable); + } + //#endregion + + //#region Task presentation group: https://github.com/microsoft/vscode/issues/47265 export interface TaskPresentationOptions { /** * Controls whether the task is executed in a specific terminal group using split panes. @@ -912,7 +1182,7 @@ declare module 'vscode' { } //#endregion - // #region Ben - status bar item with ID and Name + //#region Ben - status bar item with ID and Name export namespace window { @@ -960,7 +1230,7 @@ declare module 'vscode' { //#endregion - // #region Ben - extension auth flow (desktop+web) + //#region Ben - extension auth flow (desktop+web) export interface AppUriOptions { payload?: { @@ -973,63 +1243,173 @@ declare module 'vscode' { export namespace env { /** - * Creates a Uri that - if opened in a browser - will result in a - * registered [UriHandler](#UriHandler) to fire. The handler's - * Uri will be configured with the path, query and fragment of - * [AppUriOptions](#AppUriOptions) if provided, otherwise it will be empty. - * - * Extensions should not make any assumptions about the resulting - * Uri and should not alter it in anyway. Rather, extensions can e.g. - * use this Uri in an authentication flow, by adding the Uri as - * callback query argument to the server to authenticate to. - * - * Note: If the server decides to add additional query parameters to the Uri - * (e.g. a token or secret), it will appear in the Uri that is passed - * to the [UriHandler](#UriHandler). - * - * **Example** of an authentication flow: - * ```typescript - * vscode.window.registerUriHandler({ - * handleUri(uri: vscode.Uri): vscode.ProviderResult { - * if (uri.path === '/did-authenticate') { - * console.log(uri.toString()); - * } - * } - * }); - * - * const callableUri = await vscode.env.createAppUri({ payload: { path: '/did-authenticate' } }); - * await vscode.env.openExternal(callableUri); - * ``` + * @deprecated use `vscode.env.asExternalUri` instead. */ export function createAppUri(options?: AppUriOptions): Thenable; } //#endregion - //#region Custom editors, mjbvz + //#region Custom editors: https://github.com/microsoft/vscode/issues/77131 - export interface WebviewEditor extends WebviewPanel { + /** + * Defines how a webview editor interacts with VS Code. + */ + interface WebviewEditorCapabilities { + /** + * Invoked when the resource has been renamed in VS Code. + * + * This is called when the resource's new name also matches the custom editor selector. + * + * If this is not implemented—or if the new resource name does not match the existing selector—then VS Code + * will close and reopen the editor on rename. + * + * @param newResource Full path to the resource. + * + * @return Thenable that signals the save is complete. + */ + // rename?(newResource: Uri): Thenable; + + /** + * Controls the editing functionality of a webview editor. This allows the webview editor to hook into standard + * editor events such as `undo` or `save`. + * + * WebviewEditors that do not have `editingCapability` are considered to be readonly. Users can still interact + * with readonly editors, but these editors will not integrate with VS Code's standard editor functionality. + */ + readonly editingCapability?: WebviewEditorEditingCapability; + } + + /** + * Defines the editing functionality of a webview editor. This allows the webview editor to hook into standard + * editor events such as `undo` or `save`. + */ + interface WebviewEditorEditingCapability { + /** + * Persist the resource. + * + * Extensions should persist the resource + * + * @return Thenable signaling that the save has completed. + */ + save(): Thenable; + + /** + * + * @param resource Resource being saved. + * @param targetResource Location to save to. + */ + saveAs(resource: Uri, targetResource: Uri): Thenable; + + /** + * Event triggered by extensions to signal to VS Code that an edit has occurred. + * + * The edit must be a json serializable object. + */ + readonly onEdit: Event; + + /** + * Apply a set of edits. + * + * This is triggered on redo and when restoring a custom editor after restart. Note that is not invoked + * when `onEdit` is called as `onEdit` implies also updating the view to reflect the edit. + * + * @param edit Array of edits. Sorted from oldest to most recent. + */ + applyEdits(edits: readonly any[]): Thenable; + + /** + * Undo a set of edits. + * + * This is triggered when a user undoes an edit or when revert is called on a file. + * + * @param edit Array of edits. Sorted from most recent to oldest. + */ + undoEdits(edits: readonly any[]): Thenable; } export interface WebviewEditorProvider { /** - * Fills out a `WebviewEditor` for a given resource. - * - * The provider should take ownership of passed in `editor`. - */ + * Resolve a webview editor for a given resource. + * + * To resolve a webview editor, a provider must fill in its initial html content and hook up all + * the event listeners it is interested it. The provider should also take ownership of the passed in `WebviewPanel`. + * + * @param input Information about the resource being resolved. + * @param webview Webview being resolved. The provider should take ownership of this webview. + * + * @return Thenable to a `WebviewEditorCapabilities` indicating that the webview editor has been resolved. + * The `WebviewEditorCapabilities` defines how the custom editor interacts with VS Code. + */ resolveWebviewEditor( - resource: Uri, - editor: WebviewEditor - ): Thenable; + input: { + readonly resource: Uri + }, + webview: WebviewPanel, + ): Thenable; } namespace window { + /** + * Register a new provider for webview editors of a given type. + * + * @param viewType Type of the webview editor provider. + * @param provider Resolves webview editors. + * @param options Content settings for a webview panels the provider is given. + * + * @return Disposable that unregisters the `WebviewEditorProvider`. + */ export function registerWebviewEditorProvider( viewType: string, provider: WebviewEditorProvider, - options?: WebviewPanelOptions + options?: WebviewPanelOptions, ): Disposable; } //#endregion + + //#region insert/replace completions: https://github.com/microsoft/vscode/issues/10266 + + export interface CompletionItem { + + /** + * A range or a insert and replace range selecting the text that should be replaced by this completion item. + * + * When omitted, the range of the [current word](#TextDocument.getWordRangeAtPosition) is used as replace-range + * and as insert-range the start of the [current word](#TextDocument.getWordRangeAtPosition) to the + * current position is used. + * + * *Note 1:* A range must be a [single line](#Range.isSingleLine) and it must + * [contain](#Range.contains) the position at which completion has been [requested](#CompletionItemProvider.provideCompletionItems). + * *Note 2:* A insert range must be a prefix of a replace range, that means it must be contained and starting at the same position. + */ + range2?: Range | { inserting: Range; replacing: Range; }; + } + + //#endregion + + //#region allow QuickPicks to skip sorting: 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 Surfacing reasons why a code action cannot be applied to users: https://github.com/microsoft/vscode/issues/85160 + + export interface CodeAction { + /** + * Marks that the code action cannot currently be applied. + * + * This should be a human readable description of why the code action is currently disabled. Disabled code actions + * will be surfaced in the refactor UI but cannot be applied. + */ + disabled?: string; + } + + //#endregion } diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index 95ee945efc..55f37a6c3f 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -18,7 +18,7 @@ import { Extensions as PanelExtensions, PanelDescriptor, PanelRegistry } from 'v import { ICommentInfo, ICommentService } from 'vs/workbench/contrib/comments/browser/commentService'; import { CommentsPanel } from 'vs/workbench/contrib/comments/browser/commentsPanel'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { CommentProviderFeatures, ExtHostCommentsShape, ExtHostContext, IExtHostContext, MainContext, MainThreadCommentsShape } from '../common/extHost.protocol'; +import { CommentProviderFeatures, ExtHostCommentsShape, ExtHostContext, IExtHostContext, MainContext, MainThreadCommentsShape, CommentThreadChanges } from '../common/extHost.protocol'; import { COMMENTS_PANEL_ID, COMMENTS_PANEL_TITLE } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; @@ -116,17 +116,15 @@ export class MainThreadCommentThread implements modes.CommentThread { this._isDisposed = false; } - batchUpdate( - range: IRange, - label: string, - contextValue: string | undefined, - comments: modes.Comment[], - collapsibleState: modes.CommentThreadCollapsibleState) { - this._range = range; - this._label = label; - this._contextValue = contextValue; - this._comments = comments; - this._collapsibleState = collapsibleState; + batchUpdate(changes: CommentThreadChanges) { + const modified = (value: keyof CommentThreadChanges): boolean => + Object.prototype.hasOwnProperty.call(changes, value); + + if (modified('range')) { this._range = changes.range!; } + if (modified('label')) { this._label = changes.label; } + if (modified('contextValue')) { this._contextValue = changes.contextValue; } + if (modified('comments')) { this._comments = changes.comments; } + if (modified('collapseState')) { this._collapsibleState = changes.collapseState; } } dispose() { @@ -228,13 +226,9 @@ export class MainThreadCommentController { updateCommentThread(commentThreadHandle: number, threadId: string, resource: UriComponents, - range: IRange, - label: string, - contextValue: string | undefined, - comments: modes.Comment[], - collapsibleState: modes.CommentThreadCollapsibleState): void { + changes: CommentThreadChanges): void { let thread = this.getKnownThread(commentThreadHandle); - thread.batchUpdate(range, label, contextValue, comments, collapsibleState); + thread.batchUpdate(changes); this._commentService.updateComments(this._uniqueId, { added: [], @@ -430,18 +424,14 @@ export class MainThreadComments extends Disposable implements MainThreadComments commentThreadHandle: number, threadId: string, resource: UriComponents, - range: IRange, - label: string, - contextValue: string | undefined, - comments: modes.Comment[], - collapsibleState: modes.CommentThreadCollapsibleState): void { + changes: CommentThreadChanges): void { let provider = this._commentControllers.get(handle); if (!provider) { return undefined; } - return provider.updateCommentThread(commentThreadHandle, threadId, resource, range, label, contextValue, comments, collapsibleState); + return provider.updateCommentThread(commentThreadHandle, threadId, resource, changes); } $deleteCommentThread(handle: number, commentThreadHandle: number) { @@ -456,7 +446,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments private registerPanel(commentsPanelAlreadyConstructed: boolean) { if (!commentsPanelAlreadyConstructed) { - Registry.as(PanelExtensions.Panels).registerPanel(new PanelDescriptor( + Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( CommentsPanel, COMMENTS_PANEL_ID, COMMENTS_PANEL_TITLE, diff --git a/src/vs/workbench/api/browser/mainThreadDialogs.ts b/src/vs/workbench/api/browser/mainThreadDialogs.ts index 576a7edf80..27e06cc209 100644 --- a/src/vs/workbench/api/browser/mainThreadDialogs.ts +++ b/src/vs/workbench/api/browser/mainThreadDialogs.ts @@ -33,11 +33,11 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape { private static _convertOpenOptions(options: MainThreadDialogOpenOptions): IOpenDialogOptions { const result: IOpenDialogOptions = { - openLabel: options.openLabel, + openLabel: options.openLabel || undefined, canSelectFiles: options.canSelectFiles || (!options.canSelectFiles && !options.canSelectFolders), canSelectFolders: options.canSelectFolders, canSelectMany: options.canSelectMany, - defaultUri: URI.revive(options.defaultUri) + defaultUri: options.defaultUri ? URI.revive(options.defaultUri) : undefined }; if (options.filters) { result.filters = []; @@ -48,8 +48,8 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape { private static _convertSaveOptions(options: MainThreadDialogSaveOptions): ISaveDialogOptions { const result: ISaveDialogOptions = { - defaultUri: URI.revive(options.defaultUri), - saveLabel: options.saveLabel + defaultUri: options.defaultUri ? URI.revive(options.defaultUri) : undefined, + saveLabel: options.saveLabel || undefined }; if (options.filters) { result.filters = []; diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index dc94f9ab43..5fcdc4c612 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -16,7 +16,7 @@ import { MainThreadDocumentsAndEditors } from 'vs/workbench/api/browser/mainThre import { ExtHostContext, ExtHostDocumentsShape, IExtHostContext, MainThreadDocumentsShape } from 'vs/workbench/api/common/extHost.protocol'; import { ITextEditorModel } from 'vs/workbench/common/editor'; import { ITextFileService, TextFileModelChangeEvent } from 'vs/workbench/services/textfile/common/textfiles'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { toLocalResource } from 'vs/base/common/resources'; @@ -70,7 +70,7 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { private readonly _textModelResolverService: ITextModelService; private readonly _textFileService: ITextFileService; private readonly _fileService: IFileService; - private readonly _untitledEditorService: IUntitledEditorService; + private readonly _untitledTextEditorService: IUntitledTextEditorService; private readonly _environmentService: IWorkbenchEnvironmentService; private readonly _toDispose = new DisposableStore(); @@ -87,14 +87,14 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { @ITextFileService textFileService: ITextFileService, @IFileService fileService: IFileService, @ITextModelService textModelResolverService: ITextModelService, - @IUntitledEditorService untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService untitledTextEditorService: IUntitledTextEditorService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService ) { this._modelService = modelService; this._textModelResolverService = textModelResolverService; this._textFileService = textFileService; this._fileService = fileService; - this._untitledEditorService = untitledEditorService; + this._untitledTextEditorService = untitledTextEditorService; this._environmentService = environmentService; this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocuments); @@ -227,13 +227,13 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { } private _doCreateUntitled(resource?: URI, mode?: string, initialValue?: string): Promise { - return this._untitledEditorService.loadOrCreate({ + return this._untitledTextEditorService.loadOrCreate({ resource, mode, initialValue, useResourcePath: Boolean(resource && resource.path) }).then(model => { - const resource = model.getResource(); + const resource = model.resource; if (!this._modelIsSynced.has(resource.toString())) { throw new Error(`expected URI ${resource.toString()} to have come to LIFE`); diff --git a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts index 11bb71394b..ffdbdec8e2 100644 --- a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts @@ -28,7 +28,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; namespace delta { @@ -327,7 +327,7 @@ export class MainThreadDocumentsAndEditors { @IModeService modeService: IModeService, @IFileService fileService: IFileService, @ITextModelService textModelResolverService: ITextModelService, - @IUntitledEditorService untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService untitledTextEditorService: IUntitledTextEditorService, @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, @IBulkEditService bulkEditService: IBulkEditService, @IPanelService panelService: IPanelService, @@ -335,7 +335,7 @@ export class MainThreadDocumentsAndEditors { ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocumentsAndEditors); - const mainThreadDocuments = this._toDispose.add(new MainThreadDocuments(this, extHostContext, this._modelService, modeService, this._textFileService, fileService, textModelResolverService, untitledEditorService, environmentService)); + const mainThreadDocuments = this._toDispose.add(new MainThreadDocuments(this, extHostContext, this._modelService, modeService, this._textFileService, fileService, textModelResolverService, untitledTextEditorService, environmentService)); extHostContext.set(MainContext.MainThreadDocuments, mainThreadDocuments); const mainThreadTextEditors = this._toDispose.add(new MainThreadTextEditors(this, extHostContext, codeEditorService, bulkEditService, this._editorService, this._editorGroupService)); diff --git a/src/vs/workbench/api/browser/mainThreadFileSystem.ts b/src/vs/workbench/api/browser/mainThreadFileSystem.ts index 017ce63493..58ff9f79a3 100644 --- a/src/vs/workbench/api/browser/mainThreadFileSystem.ts +++ b/src/vs/workbench/api/browser/mainThreadFileSystem.ts @@ -6,7 +6,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { FileWriteOptions, FileSystemProviderCapabilities, IFileChange, IFileService, IFileSystemProvider, IStat, IWatchOptions, FileType, FileOverwriteOptions, FileDeleteOptions, FileOpenOptions, IFileStat, FileOperationError, FileOperationResult, FileSystemProviderErrorCode } from 'vs/platform/files/common/files'; +import { FileWriteOptions, FileSystemProviderCapabilities, IFileChange, IFileService, IStat, IWatchOptions, FileType, FileOverwriteOptions, FileDeleteOptions, FileOpenOptions, IFileStat, FileOperationError, FileOperationResult, FileSystemProviderErrorCode, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithFileFolderCopyCapability } from 'vs/platform/files/common/files'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { ExtHostContext, ExtHostFileSystemShape, IExtHostContext, IFileChangeDto, MainContext, MainThreadFileSystemShape } from '../common/extHost.protocol'; import { VSBuffer } from 'vs/base/common/buffer'; @@ -52,10 +52,10 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { $stat(uri: UriComponents): Promise { return this._fileService.resolve(URI.revive(uri), { resolveMetadata: true }).then(stat => { return { - ctime: 0, + ctime: stat.ctime, mtime: stat.mtime, size: stat.size, - type: MainThreadFileSystem._getFileType(stat) + type: MainThreadFileSystem._asFileType(stat) }; }).catch(MainThreadFileSystem._handleError); } @@ -67,12 +67,22 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { err.name = FileSystemProviderErrorCode.FileNotADirectory; throw err; } - return !stat.children ? [] : stat.children.map(child => [child.name, MainThreadFileSystem._getFileType(child)]); + return !stat.children ? [] : stat.children.map(child => [child.name, MainThreadFileSystem._asFileType(child)] as [string, FileType]); }).catch(MainThreadFileSystem._handleError); } - private static _getFileType(stat: IFileStat): FileType { - return (stat.isDirectory ? FileType.Directory : FileType.File) + (stat.isSymbolicLink ? FileType.SymbolicLink : 0); + private static _asFileType(stat: IFileStat): FileType { + let res = 0; + if (stat.isFile) { + res += FileType.File; + + } else if (stat.isDirectory) { + res += FileType.Directory; + } + if (stat.isSymbolicLink) { + res += FileType.SymbolicLink; + } + return res; } $readFile(uri: UriComponents): Promise { @@ -80,19 +90,23 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { } $writeFile(uri: UriComponents, content: VSBuffer): Promise { - return this._fileService.writeFile(URI.revive(uri), content).catch(MainThreadFileSystem._handleError); + return this._fileService.writeFile(URI.revive(uri), content) + .then(() => undefined).catch(MainThreadFileSystem._handleError); } $rename(source: UriComponents, target: UriComponents, opts: FileOverwriteOptions): Promise { - return this._fileService.move(URI.revive(source), URI.revive(target), opts.overwrite).catch(MainThreadFileSystem._handleError); + return this._fileService.move(URI.revive(source), URI.revive(target), opts.overwrite) + .then(() => undefined).catch(MainThreadFileSystem._handleError); } $copy(source: UriComponents, target: UriComponents, opts: FileOverwriteOptions): Promise { - return this._fileService.copy(URI.revive(source), URI.revive(target), opts.overwrite).catch(MainThreadFileSystem._handleError); + return this._fileService.copy(URI.revive(source), URI.revive(target), opts.overwrite) + .then(() => undefined).catch(MainThreadFileSystem._handleError); } $mkdir(uri: UriComponents): Promise { - return this._fileService.createFolder(URI.revive(uri)).catch(MainThreadFileSystem._handleError); + return this._fileService.createFolder(URI.revive(uri)) + .then(() => undefined).catch(MainThreadFileSystem._handleError); } $delete(uri: UriComponents, opts: FileDeleteOptions): Promise { @@ -121,7 +135,7 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { } } -class RemoteFileSystemProvider implements IFileSystemProvider { +class RemoteFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileFolderCopyCapability { private readonly _onDidChange = new Emitter(); private readonly _registration: IDisposable; diff --git a/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts b/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts index f6e2a42865..44da9c39ee 100644 --- a/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts +++ b/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts @@ -3,21 +3,31 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { FileChangeType, IFileService, FileOperation } from 'vs/platform/files/common/files'; import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { ExtHostContext, FileSystemEvents, IExtHostContext } from '../common/extHost.protocol'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { localize } from 'vs/nls'; +import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ILogService } from 'vs/platform/log/common/log'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; @extHostCustomer export class MainThreadFileSystemEventService { - private readonly _listener = new Array(); + private readonly _listener = new DisposableStore(); constructor( extHostContext: IExtHostContext, @IFileService fileService: IFileService, - @ITextFileService textfileService: ITextFileService, + @ITextFileService textFileService: ITextFileService, + @IProgressService progressService: IProgressService, + @IConfigurationService configService: IConfigurationService, + @ILogService logService: ILogService, ) { const proxy = extHostContext.getProxy(ExtHostContext.ExtHostFileSystemEventService); @@ -28,7 +38,7 @@ export class MainThreadFileSystemEventService { changed: [], deleted: [] }; - fileService.onFileChanges(event => { + this._listener.add(fileService.onFileChanges(event => { for (let change of event.changes) { switch (change.type) { case FileChangeType.ADDED: @@ -47,22 +57,64 @@ export class MainThreadFileSystemEventService { events.created.length = 0; events.changed.length = 0; events.deleted.length = 0; - }, undefined, this._listener); + })); - // file operation events - (changes the editor makes) - fileService.onAfterOperation(e => { - if (e.isOperation(FileOperation.MOVE)) { - proxy.$onFileRename(e.resource, e.target.resource); + + // BEFORE file operation + const messages = new Map(); + messages.set(FileOperation.CREATE, localize('msg-create', "Running 'File Create' participants...")); + messages.set(FileOperation.DELETE, localize('msg-delete', "Running 'File Delete' participants...")); + messages.set(FileOperation.MOVE, localize('msg-rename', "Running 'File Rename' participants...")); + + + this._listener.add(textFileService.onWillRunOperation(e => { + + const timeout = configService.getValue('files.participants.timeout'); + if (timeout <= 0) { + return; // disabled } - }, undefined, this._listener); - textfileService.onWillMove(e => { - const promise = proxy.$onWillRename(e.oldResource, e.newResource); - e.waitUntil(promise); - }, undefined, this._listener); + const p = progressService.withProgress({ location: ProgressLocation.Window }, progress => { + + progress.report({ message: messages.get(e.operation) }); + + return new Promise((resolve, reject) => { + + const cts = new CancellationTokenSource(); + + const timeoutHandle = setTimeout(() => { + logService.trace('CANCELLED file participants because of timeout', timeout, e.target, e.operation); + cts.cancel(); + reject(new Error('timeout')); + }, timeout); + + proxy.$onWillRunFileOperation(e.operation, e.target, e.source, timeout, cts.token) + .then(resolve, reject) + .finally(() => clearTimeout(timeoutHandle)); + }); + + }); + + e.waitUntil(p); + })); + + // AFTER file operation + this._listener.add(textFileService.onDidRunOperation(e => proxy.$onDidRunFileOperation(e.operation, e.target, e.source))); } dispose(): void { - dispose(this._listener); + this._listener.dispose(); } } + + +Registry.as(Extensions.Configuration).registerConfiguration({ + id: 'files', + properties: { + 'files.participants.timeout': { + type: 'number', + default: 5000, + markdownDescription: localize('files.participants.timeout', "Timeout in milliseconds after which file participants for create, rename, and delete are cancelled. Use `0` to disable participants."), + } + } +}); diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index c1ca1167d4..9c1c865988 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -21,6 +21,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import * as callh from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy'; import { mixin } from 'vs/base/common/objects'; +import { decodeSemanticTokensDto } from 'vs/workbench/api/common/shared/semanticTokens'; @extHostNamedCustomer(MainContext.MainThreadLanguageFeatures) export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesShape { @@ -324,9 +325,16 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha })); } + // --- semantic tokens + + $registerSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticTokensLegend): void { + this._registrations.set(handle, modes.SemanticTokensProviderRegistry.register(selector, new MainThreadSemanticTokensProvider(this._proxy, handle, legend))); + } + // --- suggest - private static _inflateSuggestDto(defaultRange: IRange, data: ISuggestDataDto): modes.CompletionItem { + private static _inflateSuggestDto(defaultRange: IRange | { insert: IRange, replace: IRange }, data: ISuggestDataDto): modes.CompletionItem { + return { label: data[ISuggestDataDtoField.label], kind: data[ISuggestDataDtoField.kind], @@ -337,8 +345,8 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha filterText: data[ISuggestDataDtoField.filterText], preselect: data[ISuggestDataDtoField.preselect], insertText: typeof data.h === 'undefined' ? data[ISuggestDataDtoField.label] : data.h, - insertTextRules: data[ISuggestDataDtoField.insertTextRules], range: data[ISuggestDataDtoField.range] || defaultRange, + insertTextRules: data[ISuggestDataDtoField.insertTextRules], commitCharacters: data[ISuggestDataDtoField.commitCharacters], additionalTextEdits: data[ISuggestDataDtoField.additionalTextEdits], command: data[ISuggestDataDtoField.command], @@ -371,6 +379,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha if (!result) { return suggestion; } + let newSuggestion = MainThreadLanguageFeatures._inflateSuggestDto(suggestion.range, result); return mixin(suggestion, newSuggestion, true); }); @@ -495,29 +504,37 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha $registerCallHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void { this._registrations.set(handle, callh.CallHierarchyProviderRegistry.register(selector, { - provideOutgoingCalls: async (model, position, token) => { - const outgoing = await this._proxy.$provideCallHierarchyOutgoingCalls(handle, model.uri, position, token); + + prepareCallHierarchy: async (document, position, token) => { + const item = await this._proxy.$prepareCallHierarchy(handle, document.uri, position, token); + if (!item) { + return undefined; + } + return { + dispose: () => this._proxy.$releaseCallHierarchy(handle, item._sessionId), + root: MainThreadLanguageFeatures._reviveCallHierarchyItemDto(item) + }; + }, + + provideOutgoingCalls: async (item, token) => { + const outgoing = await this._proxy.$provideCallHierarchyOutgoingCalls(handle, item._sessionId, item._itemId, token); if (!outgoing) { return undefined; // {{SQL CARBON EDIT}} strict-null-check } - return outgoing.map(([item, fromRanges]): callh.OutgoingCall => { - return { - to: MainThreadLanguageFeatures._reviveCallHierarchyItemDto(item), - fromRanges - }; + outgoing.forEach(value => { + value.to = MainThreadLanguageFeatures._reviveCallHierarchyItemDto(value.to); }); + return outgoing; }, - provideIncomingCalls: async (model, position, token) => { - const incoming = await this._proxy.$provideCallHierarchyIncomingCalls(handle, model.uri, position, token); + provideIncomingCalls: async (item, token) => { + const incoming = await this._proxy.$provideCallHierarchyIncomingCalls(handle, item._sessionId, item._itemId, token); if (!incoming) { return undefined; // {{SQL CARBON EDIT}} strict-null-check } - return incoming.map(([item, fromRanges]): callh.IncomingCall => { - return { - from: MainThreadLanguageFeatures._reviveCallHierarchyItemDto(item), - fromRanges - }; + incoming.forEach(value => { + value.from = MainThreadLanguageFeatures._reviveCallHierarchyItemDto(value.from); }); + return incoming; } })); } @@ -585,3 +602,45 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha } } + +export class MainThreadSemanticTokensProvider implements modes.SemanticTokensProvider { + + constructor( + private readonly _proxy: ExtHostLanguageFeaturesShape, + private readonly _handle: number, + private readonly _legend: modes.SemanticTokensLegend, + ) { + } + + public releaseSemanticTokens(resultId: string | undefined): void { + if (resultId) { + this._proxy.$releaseSemanticTokens(this._handle, parseInt(resultId, 10)); + } + } + + public getLegend(): modes.SemanticTokensLegend { + return this._legend; + } + + async provideSemanticTokens(model: ITextModel, lastResultId: string | null, ranges: EditorRange[] | null, token: CancellationToken): Promise { + const nLastResultId = lastResultId ? parseInt(lastResultId, 10) : 0; + const encodedDto = await this._proxy.$provideSemanticTokens(this._handle, model.uri, ranges, nLastResultId, token); + if (!encodedDto) { + return null; + } + if (token.isCancellationRequested) { + return null; + } + const dto = decodeSemanticTokensDto(encodedDto); + if (dto.type === 'full') { + return { + resultId: String(dto.id), + data: dto.data + }; + } + return { + resultId: String(dto.id), + edits: dto.deltas + }; + } +} diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index f69951857c..9e289f63fc 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -288,7 +288,7 @@ export class MainThreadSCM implements MainThreadSCMShape { } $registerSourceControl(handle: number, id: string, label: string, rootUri: UriComponents | undefined): void { - const provider = new MainThreadSCMProvider(this._proxy, handle, id, label, rootUri && URI.revive(rootUri), this.scmService); + const provider = new MainThreadSCMProvider(this._proxy, handle, id, label, rootUri ? URI.revive(rootUri) : undefined, this.scmService); const repository = this.scmService.registerSCMProvider(provider); this._repositories.set(handle, repository); diff --git a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts index 142c801299..87ab712719 100644 --- a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts @@ -19,7 +19,7 @@ import { CodeAction } from 'vs/editor/common/modes'; import { shouldSynchronizeModel } from 'vs/editor/common/services/modelService'; import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; import { applyCodeAction } from 'vs/editor/contrib/codeAction/codeActionCommands'; -import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; import { formatDocumentWithSelectedProvider, FormattingMode } from 'vs/editor/contrib/format/format'; import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; import { localize } from 'vs/nls'; @@ -30,7 +30,8 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { ISaveParticipant, SaveReason, IResolvedTextFileEditorModel, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; // {{SQL CARBON EDIT}} +import { ISaveParticipant, IResolvedTextFileEditorModel, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; +import { SaveReason } from 'vs/workbench/common/editor'; import { ExtHostContext, ExtHostDocumentSaveParticipantShape, IExtHostContext } from '../common/extHost.protocol'; import { INotebookService } from 'sql/workbench/services/notebook/browser/notebookService'; // {{SQL CARBON EDIT}} @@ -53,7 +54,7 @@ class NotebookUpdateParticipant implements ISaveParticipantParticipant { // {{SQ } public participate(model: ITextFileEditorModel, env: { reason: SaveReason }): Promise { - let uri = model.getResource(); + let uri = model.resource; let notebookEditor = this.notebookService.findNotebookEditor(uri); if (notebookEditor) { notebookEditor.notebookParams.input.updateModel(); @@ -76,7 +77,7 @@ class TrimWhitespaceParticipant implements ISaveParticipantParticipant { } async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { - if (this.configurationService.getValue('files.trimTrailingWhitespace', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() })) { + if (this.configurationService.getValue('files.trimTrailingWhitespace', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.resource })) { this.doTrimTrailingWhitespace(model.textEditorModel, env.reason === SaveReason.AUTO); } } @@ -138,7 +139,7 @@ export class FinalNewLineParticipant implements ISaveParticipantParticipant { } async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { - if (this.configurationService.getValue('files.insertFinalNewline', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() })) { + if (this.configurationService.getValue('files.insertFinalNewline', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.resource })) { this.doInsertFinalNewLine(model.textEditorModel); } } @@ -172,7 +173,7 @@ export class TrimFinalNewLinesParticipant implements ISaveParticipantParticipant } async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { - if (this.configurationService.getValue('files.trimFinalNewlines', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() })) { + if (this.configurationService.getValue('files.trimFinalNewlines', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.resource })) { this.doTrimFinalNewLines(model.textEditorModel, env.reason === SaveReason.AUTO); } } @@ -282,7 +283,7 @@ class CodeActionOnSaveParticipant implements ISaveParticipant { const model = editorModel.textEditorModel; - const settingsOverrides = { overrideIdentifier: model.getLanguageIdentifier().language, resource: editorModel.getResource() }; + const settingsOverrides = { overrideIdentifier: model.getLanguageIdentifier().language, resource: editorModel.resource }; const setting = this._configurationService.getValue('editor.codeActionsOnSave', settingsOverrides); if (!setting) { return undefined; @@ -307,6 +308,10 @@ class CodeActionOnSaveParticipant implements ISaveParticipant { return undefined; } + const excludedActions = Object.keys(setting) + .filter(x => setting[x] === false) + .map(x => new CodeActionKind(x)); + const tokenSource = new CancellationTokenSource(); const timeout = this._configurationService.getValue('editor.codeActionsOnSaveTimeout', settingsOverrides); @@ -317,17 +322,17 @@ class CodeActionOnSaveParticipant implements ISaveParticipant { tokenSource.cancel(); reject(localize('codeActionsOnSave.didTimeout', "Aborted codeActionsOnSave after {0}ms", timeout)); }, timeout)), - this.applyOnSaveActions(model, codeActionsOnSave, tokenSource.token) + this.applyOnSaveActions(model, codeActionsOnSave, excludedActions, tokenSource.token) ]).finally(() => { tokenSource.cancel(); }); } - private async applyOnSaveActions(model: ITextModel, codeActionsOnSave: CodeActionKind[], token: CancellationToken): Promise { + private async applyOnSaveActions(model: ITextModel, codeActionsOnSave: readonly CodeActionKind[], excludes: readonly CodeActionKind[], token: CancellationToken): Promise { for (const codeActionKind of codeActionsOnSave) { - const actionsToRun = await this.getActionsToRun(model, codeActionKind, token); + const actionsToRun = await this.getActionsToRun(model, codeActionKind, excludes, token); try { - await this.applyCodeActions(actionsToRun.actions); + await this.applyCodeActions(actionsToRun.validActions); } catch { // Failure to apply a code action should not block other on save actions } finally { @@ -342,10 +347,10 @@ class CodeActionOnSaveParticipant implements ISaveParticipant { } } - private getActionsToRun(model: ITextModel, codeActionKind: CodeActionKind, token: CancellationToken) { + private getActionsToRun(model: ITextModel, codeActionKind: CodeActionKind, excludes: readonly CodeActionKind[], token: CancellationToken) { return getCodeActions(model, model.getFullModelRange(), { type: 'auto', - filter: { kind: codeActionKind, includeSourceActions: true }, + filter: { include: codeActionKind, excludes: excludes, includeSourceActions: true }, }, token); } } @@ -368,7 +373,7 @@ class ExtHostSaveParticipant implements ISaveParticipantParticipant { return new Promise((resolve, reject) => { setTimeout(() => reject(localize('timeout.onWillSave', "Aborted onWillSaveTextDocument-event after 1750ms")), 1750); - this._proxy.$participateInSave(editorModel.getResource(), env.reason).then(values => { + this._proxy.$participateInSave(editorModel.resource, env.reason).then(values => { if (!values.every(success => success)) { return Promise.reject(new Error('listener failed')); } diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index 1cdfc730bc..1357e5f219 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -12,6 +12,7 @@ import { StopWatch } from 'vs/base/common/stopwatch'; import { ITerminalInstanceService, ITerminalService, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/terminalDataBuffering'; @extHostNamedCustomer(MainContext.MainThreadTerminalService) export class MainThreadTerminalService implements MainThreadTerminalServiceShape { @@ -162,15 +163,22 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } private _onTerminalDisposed(terminalInstance: ITerminalInstance): void { - this._proxy.$acceptTerminalClosed(terminalInstance.id); + this._proxy.$acceptTerminalClosed(terminalInstance.id, terminalInstance.exitCode); } private _onTerminalOpened(terminalInstance: ITerminalInstance): void { + const shellLaunchConfigDto: IShellLaunchConfigDto = { + name: terminalInstance.shellLaunchConfig.name, + executable: terminalInstance.shellLaunchConfig.executable, + args: terminalInstance.shellLaunchConfig.args, + cwd: terminalInstance.shellLaunchConfig.cwd, + env: terminalInstance.shellLaunchConfig.env + }; if (terminalInstance.title) { - this._proxy.$acceptTerminalOpened(terminalInstance.id, terminalInstance.title); + this._proxy.$acceptTerminalOpened(terminalInstance.id, terminalInstance.title, shellLaunchConfigDto); } else { terminalInstance.waitForTitle().then(title => { - this._proxy.$acceptTerminalOpened(terminalInstance.id, title); + this._proxy.$acceptTerminalOpened(terminalInstance.id, title, shellLaunchConfigDto); }); } } @@ -256,7 +264,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape this._getTerminalProcess(terminalId).then(e => e.emitReady(pid, cwd)); } - public $sendProcessExit(terminalId: number, exitCode: number): void { + public $sendProcessExit(terminalId: number, exitCode: number | undefined): void { this._getTerminalProcess(terminalId).then(e => e.emitExit(exitCode)); this._terminalProcesses.delete(terminalId); } @@ -327,16 +335,23 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape * listeners are removed. */ class TerminalDataEventTracker extends Disposable { + private readonly _bufferer: TerminalDataBufferer; + constructor( private readonly _callback: (id: number, data: string) => void, @ITerminalService private readonly _terminalService: ITerminalService ) { super(); + + this._register(this._bufferer = new TerminalDataBufferer()); + this._terminalService.terminalInstances.forEach(instance => this._registerInstance(instance)); this._register(this._terminalService.onInstanceCreated(instance => this._registerInstance(instance))); + this._register(this._terminalService.onInstanceDisposed(instance => this._bufferer.stopBuffering(instance.id))); } private _registerInstance(instance: ITerminalInstance): void { - this._register(instance.onData(e => this._callback(instance.id, e))); + // Buffer data events to reduce the amount of messages going to the extension host + this._register(this._bufferer.startBuffering(instance.id, instance.onData, this._callback)); } } diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index 73a9e0fbac..36dc52299f 100644 --- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts @@ -12,6 +12,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { isUndefinedOrNull, isNumber } from 'vs/base/common/types'; import { Registry } from 'vs/platform/registry/common/platform'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; @extHostNamedCustomer(MainContext.MainThreadTreeViews) export class MainThreadTreeViews extends Disposable implements MainThreadTreeViewsShape { @@ -23,13 +24,16 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie extHostContext: IExtHostContext, @IViewsService private readonly viewsService: IViewsService, @INotificationService private readonly notificationService: INotificationService, - @IExtensionService private readonly extensionService: IExtensionService + @IExtensionService private readonly extensionService: IExtensionService, + @ILogService private readonly logService: ILogService ) { super(); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTreeViews); } $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean }): void { + 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); @@ -49,6 +53,8 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie } $reveal(treeViewId: string, item: ITreeItem, parentChain: ITreeItem[], options: IRevealOptions): Promise { + this.logService.trace('MainThreadTreeViews#$reveal', treeViewId, item, parentChain, options); + return this.viewsService.openView(treeViewId, options.focus) .then(() => { const viewer = this.getTreeView(treeViewId); @@ -60,6 +66,8 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie } $refresh(treeViewId: string, itemsToRefreshByHandle: { [treeItemHandle: string]: ITreeItem }): Promise { + this.logService.trace('MainThreadTreeViews#$refresh', treeViewId, itemsToRefreshByHandle); + const viewer = this.getTreeView(treeViewId); const dataProvider = this._dataProviders.get(treeViewId); if (viewer && dataProvider) { @@ -70,6 +78,8 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie } $setMessage(treeViewId: string, message: string): void { + this.logService.trace('MainThreadTreeViews#$setMessage', treeViewId, message); + const viewer = this.getTreeView(treeViewId); if (viewer) { viewer.message = message; @@ -77,6 +87,8 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie } $setTitle(treeViewId: string, title: string): void { + this.logService.trace('MainThreadTreeViews#$setTitle', treeViewId, title); + const viewer = this.getTreeView(treeViewId); if (viewer) { viewer.title = title; diff --git a/src/vs/workbench/api/browser/mainThreadUrls.ts b/src/vs/workbench/api/browser/mainThreadUrls.ts index 110b03fb5d..6f266d61bb 100644 --- a/src/vs/workbench/api/browser/mainThreadUrls.ts +++ b/src/vs/workbench/api/browser/mainThreadUrls.ts @@ -68,7 +68,11 @@ export class MainThreadUrls implements MainThreadUrlsShape { return Promise.resolve(undefined); } - async $createAppUri(extensionId: ExtensionIdentifier, options?: { payload?: Partial }): Promise { + async $createAppUri(uri: UriComponents): Promise { + return this.urlService.create(uri); + } + + async $proposedCreateAppUri(extensionId: ExtensionIdentifier, options?: { payload?: Partial }): Promise { const payload: Partial = options && options.payload ? options.payload : Object.create(null); // we define the authority to be the extension ID to ensure diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index b0cf28435e..a4f784ca55 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { onUnexpectedError } from 'vs/base/common/errors'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { isWeb } from 'vs/base/common/platform'; import { startsWith } from 'vs/base/common/strings'; @@ -20,6 +20,7 @@ import { editorGroupToViewColumn, EditorViewColumn, viewColumnToEditorGroup } fr import { IEditorInput } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { CustomFileEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; +import { ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; import { ICreateWebViewShowOptions, IWebviewWorkbenchService, WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; @@ -98,6 +99,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma constructor( context: extHostProtocol.IExtHostContext, @IExtensionService extensionService: IExtensionService, + @ICustomEditorService private readonly _customEditorService: ICustomEditorService, @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, @IEditorService private readonly _editorService: IEditorService, @IOpenerService private readonly _openerService: IOpenerService, @@ -168,13 +170,6 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma webview.setName(value); } - public $setState(handle: extHostProtocol.WebviewPanelHandle, state: modes.WebviewContentState): void { - const webview = this.getWebviewInput(handle); - if (webview instanceof CustomFileEditorInput) { - webview.setState(state); - } - } - public $setIconPath(handle: extHostProtocol.WebviewPanelHandle, value: { light: UriComponents, dark: UriComponents; } | undefined): void { const webview = this.getWebviewInput(handle); webview.iconPath = reviveWebviewIcon(value); @@ -268,7 +263,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma canResolve: (webviewInput) => { return webviewInput instanceof CustomFileEditorInput && webviewInput.viewType === viewType; }, - resolveWebview: async (webviewInput) => { + resolveWebview: async (webviewInput: CustomFileEditorInput) => { const handle = webviewInput.id; this._webviewInputs.add(handle, webviewInput); this.hookupWebviewEventDelegate(handle, webviewInput); @@ -276,15 +271,25 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma webviewInput.webview.options = options; webviewInput.webview.extension = extension; - if (webviewInput instanceof CustomFileEditorInput) { - webviewInput.onWillSave(e => { - e.waitUntil(this._proxy.$save(handle)); - }); - } + const model = await this._customEditorService.models.loadOrCreate(webviewInput.getResource(), webviewInput.viewType); + + model.onUndo(edits => { this._proxy.$undoEdits(handle, edits.map(x => x.data)); }); + model.onApplyEdit(edits => { + const editsToApply = edits.filter(x => x.source !== webviewInput).map(x => x.data); + if (editsToApply.length) { + this._proxy.$applyEdits(handle, editsToApply); + } + }); + model.onWillSave(e => { e.waitUntil(this._proxy.$onSave(handle)); }); + model.onWillSaveAs(e => { e.waitUntil(this._proxy.$onSaveAs(handle, e.resource.toJSON(), e.targetResource.toJSON())); }); + + webviewInput.onDisposeWebview(() => { + this._customEditorService.models.disposeModel(model); + }); try { await this._proxy.$resolveWebviewEditor( - webviewInput.getResource(), + { resource: webviewInput.getResource(), edits: model.currentEdits }, handle, viewType, webviewInput.getTitle(), @@ -294,6 +299,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma } catch (error) { onUnexpectedError(error); webviewInput.webview.html = MainThreadWebviews.getDeserializationFailedContents(viewType); + return; } } })); @@ -309,15 +315,35 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma this._editorProviders.delete(viewType); } + public $onEdit(handle: extHostProtocol.WebviewPanelHandle, editData: any): void { + const webview = this.getWebviewInput(handle); + if (!(webview instanceof CustomFileEditorInput)) { + throw new Error('Webview is not a webview editor'); + } + + const model = this._customEditorService.models.get(webview.getResource(), webview.viewType); + if (!model) { + throw new Error('Could not find model for webview editor'); + } + + model.makeEdit({ source: webview, data: editData }); + } + private hookupWebviewEventDelegate(handle: extHostProtocol.WebviewPanelHandle, input: WebviewInput) { - input.webview.onDidClickLink((uri: URI) => this.onDidClickLink(handle, uri)); - input.webview.onMessage((message: any) => this._proxy.$onMessage(handle, message)); + const disposables = new DisposableStore(); + + disposables.add(input.webview.onDidClickLink((uri) => this.onDidClickLink(handle, uri))); + disposables.add(input.webview.onMessage((message: any) => { this._proxy.$onMessage(handle, message); })); + disposables.add(input.webview.onMissingCsp((extension: ExtensionIdentifier) => this._proxy.$onMissingCsp(handle, extension.value))); + input.onDispose(() => { + disposables.dispose(); + }); + input.onDisposeWebview(() => { this._proxy.$onDidDisposeWebviewPanel(handle).finally(() => { this._webviewInputs.delete(handle); }); }); - input.webview.onMissingCsp((extension: ExtensionIdentifier) => this._proxy.$onMissingCsp(handle, extension.value)); } private updateWebviewViewStates() { @@ -361,9 +387,9 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma } } - private onDidClickLink(handle: extHostProtocol.WebviewPanelHandle, link: URI): void { + private onDidClickLink(handle: extHostProtocol.WebviewPanelHandle, link: string): void { const webview = this.getWebviewInput(handle); - if (this.isSupportedLink(webview, link)) { + if (this.isSupportedLink(webview, URI.parse(link))) { this._openerService.open(link, { fromUserGesture: true }); } } diff --git a/src/vs/workbench/api/browser/mainThreadWindow.ts b/src/vs/workbench/api/browser/mainThreadWindow.ts index ca8b2d2880..3a3f769bfb 100644 --- a/src/vs/workbench/api/browser/mainThreadWindow.ts +++ b/src/vs/workbench/api/browser/mainThreadWindow.ts @@ -42,9 +42,17 @@ export class MainThreadWindow implements MainThreadWindowShape { return Promise.resolve(this.hostService.hasFocus); } - async $openUri(uriComponents: UriComponents, options: IOpenUriOptions): Promise { + async $openUri(uriComponents: UriComponents, uriString: string | undefined, options: IOpenUriOptions): Promise { const uri = URI.from(uriComponents); - return this.openerService.open(uri, { openExternal: true, allowTunneling: options.allowTunneling }); + let target: URI | string; + if (uriString && URI.parse(uriString).toString() === uri.toString()) { + // called with string and no transformation happened -> keep string + target = uriString; + } else { + // called with URI or transformed -> use uri + target = uri; + } + return this.openerService.open(target, { openExternal: true, allowTunneling: options.allowTunneling }); } async $asExternalUri(uriComponents: UriComponents, options: IOpenUriOptions): Promise { diff --git a/src/vs/workbench/api/browser/mainThreadWorkspace.ts b/src/vs/workbench/api/browser/mainThreadWorkspace.ts index 2d4e5d31aa..b09c9c1f43 100644 --- a/src/vs/workbench/api/browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/browser/mainThreadWorkspace.ts @@ -15,11 +15,11 @@ import { IFileMatch, IPatternInfo, ISearchProgressItem, ISearchService } from 'v import { IWorkspaceContextService, WorkbenchState, IWorkspace } from 'vs/platform/workspace/common/workspace'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; import { ExtHostContext, ExtHostWorkspaceShape, IExtHostContext, MainContext, MainThreadWorkspaceShape, IWorkspaceData, ITextSearchComplete } from '../common/extHost.protocol'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { isEqualOrParent } from 'vs/base/common/resources'; +import { isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { withNullAsUndefined } from 'vs/base/common/types'; import { IFileService } from 'vs/platform/files/common/files'; @@ -37,7 +37,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { extHostContext: IExtHostContext, @ISearchService private readonly _searchService: ISearchService, @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, - @ITextFileService private readonly _textFileService: ITextFileService, + @IEditorService private readonly _editorService: IEditorService, @IWorkspaceEditingService private readonly _workspaceEditingService: IWorkspaceEditingService, @INotificationService private readonly _notificationService: INotificationService, @IRequestService private readonly _requestService: IRequestService, @@ -121,7 +121,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { } return { configuration: workspace.configuration || undefined, - isUntitled: workspace.configuration ? isEqualOrParent(workspace.configuration, this._environmentService.untitledWorkspacesHome) : false, + isUntitled: workspace.configuration ? isUntitledWorkspace(workspace.configuration, this._environmentService) : false, folders: workspace.folders, id: workspace.id, name: this._labelService.getWorkspaceLabel(workspace) @@ -155,13 +155,14 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { if (!isPromiseCanceledError(err)) { return Promise.reject(err); } - return undefined; + return null; }); } - $startTextSearch(pattern: IPatternInfo, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise { + $startTextSearch(pattern: IPatternInfo, _folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise { + const folder = URI.revive(_folder); const workspace = this._contextService.getWorkspace(); - const folders = workspace.folders.map(folder => folder.uri); + const folders = folder ? [folder] : workspace.folders.map(folder => folder.uri); const query = this._queryBuilder.text(pattern, folders, options); query._reason = 'startTextSearch'; @@ -181,7 +182,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { return Promise.reject(err); } - return undefined; + return null; }); return search; @@ -198,23 +199,21 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { return this._searchService.fileSearch(query, token).then( result => { - return result.limitHit; + return !!result.limitHit; }, err => { if (!isPromiseCanceledError(err)) { return Promise.reject(err); } - return undefined; + return false; }); } // --- save & edit resources --- $saveAll(includeUntitled?: boolean): Promise { - return this._textFileService.saveAll(includeUntitled).then(result => { - return result.results.every(each => each.success === true); - }); + return this._editorService.saveAll({ includeUntitled }); } $resolveProxy(url: string): Promise { diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 62a991bede..2eeba105ba 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -9,7 +9,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; import * as resources from 'vs/base/common/resources'; import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor } from 'vs/workbench/common/views'; -import { CustomTreeViewPanel, CustomTreeView } from 'vs/workbench/browser/parts/views/customView'; +import { CustomTreeViewPane, CustomTreeView } from 'vs/workbench/browser/parts/views/customView'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { coalesce, } from 'vs/base/common/arrays'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -37,7 +37,6 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { createCSSRule, asCSSUrl } from 'vs/base/browser/dom'; export interface IUserFriendlyViewsContainerDescriptor { id: string; @@ -254,10 +253,9 @@ class ViewsExtensionHandler implements IWorkbenchContribution { private registerTestViewContainer(): void { const title = localize('test', "Test"); - const cssClass = `extensionViewlet-test`; const icon = URI.parse(require.toUrl('./media/test.svg')); - this.registerCustomViewContainer(TEST_VIEW_CONTAINER_ID, title, icon, TEST_VIEW_CONTAINER_ORDER, cssClass, undefined); + this.registerCustomViewContainer(TEST_VIEW_CONTAINER_ID, title, icon, TEST_VIEW_CONTAINER_ORDER, undefined); } private isValidViewsContainer(viewsContainersDescriptors: IUserFriendlyViewsContainerDescriptor[], collector: ExtensionMessageCollector): boolean { @@ -290,10 +288,9 @@ class ViewsExtensionHandler implements IWorkbenchContribution { private registerCustomViewContainers(containers: IUserFriendlyViewsContainerDescriptor[], extension: IExtensionDescription, order: number, existingViewContainers: ViewContainer[]): number { containers.forEach(descriptor => { - const cssClass = `extensionViewlet-${descriptor.id}`; const icon = resources.joinPath(extension.extensionLocation, descriptor.icon); const id = `workbench.view.extension.${descriptor.id}`; - const viewContainer = this.registerCustomViewContainer(id, descriptor.title, icon, order++, cssClass, extension.identifier); + const viewContainer = this.registerCustomViewContainer(id, descriptor.title, icon, order++, extension.identifier); // Move those views that belongs to this container if (existingViewContainers.length) { @@ -311,7 +308,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { return order; } - private registerCustomViewContainer(id: string, title: string, icon: URI, order: number, cssClass: string, extensionId: ExtensionIdentifier | undefined): ViewContainer { + private registerCustomViewContainer(id: string, title: string, icon: URI, order: number, extensionId: ExtensionIdentifier | undefined): ViewContainer { let viewContainer = this.viewContainersRegistry.get(id); if (!viewContainer) { @@ -335,11 +332,11 @@ class ViewsExtensionHandler implements IWorkbenchContribution { super(id, `${id}.state`, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); } } - const viewletDescriptor = new ViewletDescriptor( + const viewletDescriptor = ViewletDescriptor.create( CustomViewlet, id, title, - cssClass, + undefined, order, icon ); @@ -359,14 +356,10 @@ class ViewsExtensionHandler implements IWorkbenchContribution { } const registry = Registry.as(ActionExtensions.WorkbenchActions); registry.registerWorkbenchAction( - new SyncActionDescriptor(OpenCustomViewletAction, id, localize('showViewlet', "Show {0}", title)), + SyncActionDescriptor.create(OpenCustomViewletAction, id, localize('showViewlet', "Show {0}", title)), `View: Show ${title}`, localize('view', "View") ); - - // Generate CSS to show the icon in the activity bar - const iconClass = `.monaco-workbench .activitybar .monaco-action-bar .action-label.${cssClass}`; - createCSSRule(iconClass, `-webkit-mask: ${asCSSUrl(icon)} no-repeat 50% 50%; -webkit-mask-size: 24px;`); } return viewContainer; @@ -429,7 +422,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { const viewDescriptor = { id: item.id, name: item.name, - ctorDescriptor: { ctor: CustomTreeViewPanel }, + ctorDescriptor: { ctor: CustomTreeViewPane }, when: ContextKeyExpr.deserialize(item.when), canToggleVisibility: true, collapsed: this.showCollapsed(container), diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index e67016a30f..09b0214b05 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -309,7 +309,9 @@ jsonRegistry.registerSchema('vscode://schemas/workspaceConfig', { $ref: 'vscode://schemas/extensions' }, 'remoteAuthority': { - type: 'string' + type: 'string', + doNotSuggest: true, + description: nls.localize('workspaceConfig.remoteAuthority', "The remote server where the workspace is located. Only used by unsaved remote workspaces."), } }, additionalProperties: false, diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 5168fdecd9..8cc5597c08 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -106,7 +106,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostOutputService = rpcProtocol.set(ExtHostContext.ExtHostOutputService, accessor.get(IExtHostOutputService)); // manually create and register addressable instances - const extHostWebviews = rpcProtocol.set(ExtHostContext.ExtHostWebviews, new ExtHostWebviews(rpcProtocol, initData.environment, extHostWorkspace)); + const extHostWebviews = rpcProtocol.set(ExtHostContext.ExtHostWebviews, new ExtHostWebviews(rpcProtocol, initData.environment, extHostWorkspace, extHostLogService)); const extHostUrls = rpcProtocol.set(ExtHostContext.ExtHostUrls, new ExtHostUrls(rpcProtocol)); const extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors)); const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(rpcProtocol, extHostDocumentsAndEditors, extHostLogService)); @@ -114,10 +114,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostEditors = rpcProtocol.set(ExtHostContext.ExtHostEditors, new ExtHostEditors(rpcProtocol, extHostDocumentsAndEditors)); const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands, extHostLogService)); const extHostEditorInsets = rpcProtocol.set(ExtHostContext.ExtHostEditorInsets, new ExtHostEditorInsets(rpcProtocol.getProxy(MainContext.MainThreadEditorInsets), extHostEditors, initData.environment)); - const extHostDiagnostics = rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(rpcProtocol)); + const extHostDiagnostics = rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(rpcProtocol, extHostLogService)); const extHostLanguageFeatures = rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(rpcProtocol, uriTransformer, extHostDocuments, extHostCommands, extHostDiagnostics, extHostLogService)); const extHostFileSystem = rpcProtocol.set(ExtHostContext.ExtHostFileSystem, new ExtHostFileSystem(rpcProtocol, extHostLanguageFeatures)); - const extHostFileSystemEvent = rpcProtocol.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService(rpcProtocol, extHostDocumentsAndEditors)); + const extHostFileSystemEvent = rpcProtocol.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService(rpcProtocol, extHostLogService, extHostDocumentsAndEditors)); const extHostQuickOpen = rpcProtocol.set(ExtHostContext.ExtHostQuickOpen, new ExtHostQuickOpen(rpcProtocol, extHostWorkspace, extHostCommands)); const extHostSCM = rpcProtocol.set(ExtHostContext.ExtHostSCM, new ExtHostSCM(rpcProtocol, extHostCommands, extHostLogService)); const extHostComment = rpcProtocol.set(ExtHostContext.ExtHostComments, new ExtHostComments(rpcProtocol, extHostCommands, extHostDocuments)); @@ -133,7 +133,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // Other instances const extHostClipboard = new ExtHostClipboard(rpcProtocol); - const extHostMessageService = new ExtHostMessageService(rpcProtocol); + const extHostMessageService = new ExtHostMessageService(rpcProtocol, extHostLogService); const extHostDialogs = new ExtHostDialogs(rpcProtocol); const extHostStatusBar = new ExtHostStatusBar(rpcProtocol); const extHostLanguages = new ExtHostLanguages(rpcProtocol, extHostDocuments); @@ -152,7 +152,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I let done = (!extension.isUnderDevelopment); function informOnce(selector: vscode.DocumentSelector) { if (!done) { - console.info(`Extension '${extension.identifier.value}' uses a document selector without scheme. Learn more about this: https://go.microsoft.com/fwlink/?linkid=872305`); + extHostLogService.info(`Extension '${extension.identifier.value}' uses a document selector without scheme. Learn more about this: https://go.microsoft.com/fwlink/?linkid=872305`); done = true; } } @@ -183,20 +183,19 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostCommands.registerCommand(true, id, (...args: any[]): any => { const activeTextEditor = extHostEditors.getActiveTextEditor(); if (!activeTextEditor) { - console.warn('Cannot execute ' + id + ' because there is no active text editor.'); + extHostLogService.warn('Cannot execute ' + id + ' because there is no active text editor.'); return undefined; } return activeTextEditor.edit((edit: vscode.TextEditorEdit) => { - args.unshift(activeTextEditor, edit); - callback.apply(thisArg, args); + callback.apply(thisArg, [activeTextEditor, edit, ...args]); }).then((result) => { if (!result) { - console.warn('Edits from command ' + id + ' were not applied.'); + extHostLogService.warn('Edits from command ' + id + ' were not applied.'); } }, (err) => { - console.warn('An error occurred while running command ' + id, err); + extHostLogService.warn('An error occurred while running command ' + id, err); }); }); }, @@ -205,7 +204,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostCommands.registerCommand(true, id, async (...args: any[]): Promise => { const activeTextEditor = extHostEditors.getActiveTextEditor(); if (!activeTextEditor) { - console.warn('Cannot execute ' + id + ' because there is no active text editor.'); + extHostLogService.warn('Cannot execute ' + id + ' because there is no active text editor.'); return undefined; } @@ -231,7 +230,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I get uriScheme() { return initData.environment.appUriScheme; }, createAppUri(options?) { checkProposedApiEnabled(extension); - return extHostUrls.createAppUri(extension.identifier, options); + return extHostUrls.proposedCreateAppUri(extension.identifier, options); }, get logLevel() { checkProposedApiEnabled(extension); @@ -251,6 +250,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostWindow.openUri(uri, { allowTunneling: !!initData.remote.isRemote }); }, asExternalUri(uri: URI) { + if (uri.scheme === initData.environment.appUriScheme) { + return extHostUrls.createAppUri(uri); + } + return extHostWindow.asExternalUri(uri, { allowTunneling: !!initData.remote.isRemote }); }, get remoteName() { @@ -351,6 +354,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerOnTypeFormattingEditProvider(selector: vscode.DocumentSelector, provider: vscode.OnTypeFormattingEditProvider, firstTriggerCharacter: string, ...moreTriggerCharacters: string[]): vscode.Disposable { return extHostLanguageFeatures.registerOnTypeFormattingEditProvider(extension, checkSelector(selector), provider, [firstTriggerCharacter].concat(moreTriggerCharacters)); }, + registerSemanticTokensProvider(selector: vscode.DocumentSelector, provider: vscode.SemanticTokensProvider, legend: vscode.SemanticTokensLegend): vscode.Disposable { + checkProposedApiEnabled(extension); + return extHostLanguageFeatures.registerSemanticTokensProvider(extension, checkSelector(selector), provider, legend); + }, registerSignatureHelpProvider(selector: vscode.DocumentSelector, provider: vscode.SignatureHelpProvider, firstItem?: string | vscode.SignatureHelpProviderMetadata, ...remaining: string[]): vscode.Disposable { if (typeof firstItem === 'object') { return extHostLanguageFeatures.registerSignatureHelpProvider(extension, checkSelector(selector), provider, firstItem); @@ -372,8 +379,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerSelectionRangeProvider(selector: vscode.DocumentSelector, provider: vscode.SelectionRangeProvider): vscode.Disposable { return extHostLanguageFeatures.registerSelectionRangeProvider(extension, selector, provider); }, - registerCallHierarchyProvider(selector: vscode.DocumentSelector, provider: vscode.CallHierarchyItemProvider): vscode.Disposable { - checkProposedApiEnabled(extension); + registerCallHierarchyProvider(selector: vscode.DocumentSelector, provider: vscode.CallHierarchyProvider): vscode.Disposable { return extHostLanguageFeatures.registerCallHierarchyProvider(extension, selector, provider); }, setLanguageConfiguration: (language: string, configuration: vscode.LanguageConfiguration): vscode.Disposable => { @@ -698,11 +704,27 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension); return extHostLabelService.$registerResourceLabelFormatter(formatter); }, - onDidRenameFile: (listener: (e: vscode.FileRenameEvent) => any, thisArg?: any, disposables?: vscode.Disposable[]) => { + onDidCreateFiles: (listener, thisArg, disposables) => { + checkProposedApiEnabled(extension); + return extHostFileSystemEvent.onDidCreateFile(listener, thisArg, disposables); + }, + onDidDeleteFiles: (listener, thisArg, disposables) => { + checkProposedApiEnabled(extension); + return extHostFileSystemEvent.onDidDeleteFile(listener, thisArg, disposables); + }, + onDidRenameFiles: (listener, thisArg, disposables) => { checkProposedApiEnabled(extension); return extHostFileSystemEvent.onDidRenameFile(listener, thisArg, disposables); }, - onWillRenameFile: (listener: (e: vscode.FileWillRenameEvent) => any, thisArg?: any, disposables?: vscode.Disposable[]) => { + onWillCreateFiles: (listener: (e: vscode.FileWillCreateEvent) => any, thisArg?: any, disposables?: vscode.Disposable[]) => { + checkProposedApiEnabled(extension); + return extHostFileSystemEvent.getOnWillCreateFileEvent(extension)(listener, thisArg, disposables); + }, + onWillDeleteFiles: (listener: (e: vscode.FileWillDeleteEvent) => any, thisArg?: any, disposables?: vscode.Disposable[]) => { + checkProposedApiEnabled(extension); + return extHostFileSystemEvent.getOnWillDeleteFileEvent(extension)(listener, thisArg, disposables); + }, + onWillRenameFiles: (listener: (e: vscode.FileWillRenameEvent) => any, thisArg?: any, disposables?: vscode.Disposable[]) => { checkProposedApiEnabled(extension); return extHostFileSystemEvent.getOnWillRenameFileEvent(extension)(listener, thisArg, disposables); } @@ -770,6 +792,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }, removeBreakpoints(breakpoints: vscode.Breakpoint[]) { return undefined; + }, + asDebugSourceUri(source: vscode.DebugProtocolSource, session?: vscode.DebugSession): vscode.Uri { + return undefined; } }; @@ -836,6 +861,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I ConfigurationTarget: extHostTypes.ConfigurationTarget, DebugAdapterExecutable: extHostTypes.DebugAdapterExecutable, DebugAdapterServer: extHostTypes.DebugAdapterServer, + DebugAdapterInlineImplementation: extHostTypes.DebugAdapterInlineImplementation, DecorationRangeBehavior: extHostTypes.DecorationRangeBehavior, Diagnostic: extHostTypes.Diagnostic, DiagnosticRelatedInformation: extHostTypes.DiagnosticRelatedInformation, @@ -850,6 +876,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I EventEmitter: Emitter, ExtensionKind: extHostTypes.ExtensionKind, CustomExecution: extHostTypes.CustomExecution, + CustomExecution2: extHostTypes.CustomExecution, FileChangeType: extHostTypes.FileChangeType, FileSystemError: extHostTypes.FileSystemError, FileType: files.FileType, @@ -871,6 +898,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I RelativePattern: extHostTypes.RelativePattern, ResolvedAuthority: extHostTypes.ResolvedAuthority, RemoteAuthorityResolverError: extHostTypes.RemoteAuthorityResolverError, + SemanticTokensLegend: extHostTypes.SemanticTokensLegend, + SemanticTokensBuilder: extHostTypes.SemanticTokensBuilder, + SemanticTokens: extHostTypes.SemanticTokens, + SemanticTokensEdits: extHostTypes.SemanticTokensEdits, + SemanticTokensEdit: extHostTypes.SemanticTokensEdit, Selection: extHostTypes.Selection, SelectionRange: extHostTypes.SelectionRange, ShellExecution: extHostTypes.ShellExecution, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index fe402c9bce..620fb2fc68 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -45,7 +45,7 @@ import { ITerminalDimensions, IShellLaunchConfig } from 'vs/workbench/contrib/te import { ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions'; import { createExtHostContextProxyIdentifier as createExtId, createMainContextProxyIdentifier as createMainId, IRPCProtocol } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import * as search from 'vs/workbench/services/search/common/search'; -import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; +import { SaveReason } from 'vs/workbench/common/editor'; import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator'; // {{SQL CARBON EDIT}} @@ -74,7 +74,7 @@ export interface IStaticWorkspaceData { } export interface IWorkspaceData extends IStaticWorkspaceData { - folders: { uri: UriComponents, name: string, index: number }[]; + folders: { uri: UriComponents, name: string, index: number; }[]; } export interface IInitData { @@ -101,7 +101,7 @@ export interface IConfigurationInitData extends IConfigurationData { export interface IWorkspaceConfigurationChangeEventData { changedConfiguration: IConfigurationModel; - changedConfigurationByResource: { [folder: string]: IConfigurationModel }; + changedConfigurationByResource: { [folder: string]: IConfigurationModel; }; } export interface IExtHostContext extends IRPCProtocol { @@ -135,12 +135,20 @@ export interface CommentProviderFeatures { reactionHandler?: boolean; } +export type CommentThreadChanges = Partial<{ + range: IRange, + label: string, + contextValue: string, + comments: modes.Comment[], + collapseState: modes.CommentThreadCollapsibleState; +}>; + export interface MainThreadCommentsShape extends IDisposable { $registerCommentController(handle: number, id: string, label: string): void; $unregisterCommentController(handle: number): void; $updateCommentControllerFeatures(handle: number, features: CommentProviderFeatures): void; $createCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, extensionId: ExtensionIdentifier): modes.CommentThread | undefined; - $updateCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, label: string | undefined, contextValue: string | undefined, comments: modes.Comment[], collapseState: modes.CommentThreadCollapsibleState): void; + $updateCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, changes: CommentThreadChanges): void; $deleteCommentThread(handle: number, commentThreadHandle: number): void; $onDidCommentThreadsChange(handle: number, event: modes.CommentThreadChangedEvent): void; } @@ -161,13 +169,13 @@ export interface MainThreadDialogOpenOptions { canSelectFiles?: boolean; canSelectFolders?: boolean; canSelectMany?: boolean; - filters?: { [name: string]: string[] }; + filters?: { [name: string]: string[]; }; } export interface MainThreadDialogSaveOptions { defaultUri?: UriComponents; saveLabel?: string; - filters?: { [name: string]: string[] }; + filters?: { [name: string]: string[]; }; } export interface MainThreadDiaglogsShape extends IDisposable { @@ -250,8 +258,8 @@ export interface MainThreadTextEditorsShape extends IDisposable { } export interface MainThreadTreeViewsShape extends IDisposable { - $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean }): void; - $refresh(treeViewId: string, itemsToRefresh?: { [treeItemHandle: string]: ITreeItem }): Promise; + $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean; }): void; + $refresh(treeViewId: string, itemsToRefresh?: { [treeItemHandle: string]: ITreeItem; }): Promise; $reveal(treeViewId: string, treeItem: ITreeItem, parentChain: ITreeItem[], options: IRevealOptions): Promise; $setMessage(treeViewId: string, message: string): void; $setTitle(treeViewId: string, title: string): void; @@ -274,7 +282,7 @@ export interface MainThreadKeytarShape extends IDisposable { $setPassword(service: string, account: string, password: string): Promise; $deletePassword(service: string, account: string): Promise; $findPassword(service: string): Promise; - $findCredentials(service: string): Promise>; + $findCredentials(service: string): Promise>; } export interface IRegExpDto { @@ -317,7 +325,7 @@ export interface ILanguageConfigurationDto { }; } -export type GlobPattern = string | { base: string; pattern: string }; +export type GlobPattern = string | { base: string; pattern: string; }; export interface IDocumentFilterDto { $serialized: true; @@ -350,6 +358,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerOnTypeFormattingSupport(handle: number, selector: IDocumentFilterDto[], autoFormatTriggerCharacters: string[], extensionId: ExtensionIdentifier): void; $registerNavigateTypeSupport(handle: number): void; $registerRenameSupport(handle: number, selector: IDocumentFilterDto[], supportsResolveInitialValues: boolean): void; + $registerSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticTokensLegend): void; $registerSuggestSupport(handle: number, selector: IDocumentFilterDto[], triggerCharacters: string[], supportsResolveDetails: boolean, extensionId: ExtensionIdentifier): void; $registerSignatureHelpProvider(handle: number, selector: IDocumentFilterDto[], metadata: ISignatureHelpProviderMetadataDto): void; $registerDocumentLinkProvider(handle: number, selector: IDocumentFilterDto[], supportsResolve: boolean): void; @@ -396,7 +405,7 @@ export interface TerminalLaunchConfig { shellPath?: string; shellArgs?: string[] | string; cwd?: string | UriComponents; - env?: { [key: string]: string | null }; + env?: { [key: string]: string | null; }; waitOnExit?: boolean; strictEnv?: boolean; hideFromUser?: boolean; @@ -404,7 +413,7 @@ export interface TerminalLaunchConfig { } export interface MainThreadTerminalServiceShape extends IDisposable { - $createTerminal(config: TerminalLaunchConfig): Promise<{ id: number, name: string }>; + $createTerminal(config: TerminalLaunchConfig): Promise<{ id: number, name: string; }>; $dispose(terminalId: number): void; $hide(terminalId: number): void; $sendText(terminalId: number, text: string, addNewLine: boolean): void; @@ -416,7 +425,7 @@ export interface MainThreadTerminalServiceShape extends IDisposable { $sendProcessTitle(terminalId: number, title: string): void; $sendProcessData(terminalId: number, data: string): void; $sendProcessReady(terminalId: number, pid: number, cwd: string): void; - $sendProcessExit(terminalId: number, exitCode: number): void; + $sendProcessExit(terminalId: number, exitCode: number | undefined): void; $sendProcessInitialCwd(terminalId: number, cwd: string): void; $sendProcessCwd(terminalId: number, initialCwd: string): void; $sendOverrideDimensions(terminalId: number, dimensions: ITerminalDimensions | undefined): void; @@ -471,6 +480,8 @@ export interface TransferQuickPick extends BaseTransferQuickInput { matchOnDescription?: boolean; matchOnDetail?: boolean; + + sortByLabel?: boolean; } export interface TransferInputBox extends BaseTransferQuickInput { @@ -554,8 +565,7 @@ export interface MainThreadWebviewsShape extends IDisposable { $disposeWebview(handle: WebviewPanelHandle): void; $reveal(handle: WebviewPanelHandle, showOptions: WebviewPanelShowOptions): void; $setTitle(handle: WebviewPanelHandle, value: string): void; - $setState(handle: WebviewPanelHandle, state: modes.WebviewContentState): void; - $setIconPath(handle: WebviewPanelHandle, value: { light: UriComponents, dark: UriComponents } | undefined): void; + $setIconPath(handle: WebviewPanelHandle, value: { light: UriComponents, dark: UriComponents; } | undefined): void; $setHtml(handle: WebviewPanelHandle, value: string): void; $setOptions(handle: WebviewPanelHandle, options: modes.IWebviewOptions): void; @@ -567,6 +577,8 @@ export interface MainThreadWebviewsShape extends IDisposable { $registerEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void; $unregisterEditorProvider(viewType: string): void; + + $onEdit(handle: WebviewPanelHandle, editJson: any): void; } export interface WebviewPanelViewStateData { @@ -582,15 +594,22 @@ export interface ExtHostWebviewsShape { $onMissingCsp(handle: WebviewPanelHandle, extensionId: string): void; $onDidChangeWebviewPanelViewStates(newState: WebviewPanelViewStateData): void; $onDidDisposeWebviewPanel(handle: WebviewPanelHandle): Promise; + $deserializeWebviewPanel(newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, state: any, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise; - $resolveWebviewEditor(resource: UriComponents, newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise; - $save(handle: WebviewPanelHandle): Promise; + $resolveWebviewEditor(input: { resource: UriComponents, edits: readonly any[] }, newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise; + + $undoEdits(handle: WebviewPanelHandle, edits: readonly any[]): void; + $applyEdits(handle: WebviewPanelHandle, edits: readonly any[]): void; + + $onSave(handle: WebviewPanelHandle): Promise; + $onSaveAs(handle: WebviewPanelHandle, resource: UriComponents, targetResource: UriComponents): Promise; } export interface MainThreadUrlsShape extends IDisposable { $registerUriHandler(handle: number, extensionId: ExtensionIdentifier): Promise; $unregisterUriHandler(handle: number): Promise; - $createAppUri(extensionId: ExtensionIdentifier, options?: { payload?: Partial }): Promise; + $createAppUri(uri: UriComponents): Promise; + $proposedCreateAppUri(extensionId: ExtensionIdentifier, options?: { payload?: Partial; }): Promise; } export interface ExtHostUrlsShape { @@ -603,10 +622,10 @@ export interface ITextSearchComplete { export interface MainThreadWorkspaceShape extends IDisposable { $startFileSearch(includePattern: string | null, includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false | null, maxResults: number | null, token: CancellationToken): Promise; - $startTextSearch(query: search.IPatternInfo, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise; + $startTextSearch(query: search.IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise; $checkExists(folders: UriComponents[], includes: string[], token: CancellationToken): Promise; $saveAll(includeUntitled?: boolean): Promise; - $updateWorkspaceFolders(extensionName: string, index: number, deleteCount: number, workspaceFoldersToAdd: { uri: UriComponents, name?: string }[]): Promise; + $updateWorkspaceFolders(extensionName: string, index: number, deleteCount: number, workspaceFoldersToAdd: { uri: UriComponents, name?: string; }[]): Promise; $resolveProxy(url: string): Promise; } @@ -753,7 +772,7 @@ export interface IOpenUriOptions { export interface MainThreadWindowShape extends IDisposable { $getWindowVisibility(): Promise; - $openUri(uri: UriComponents, options: IOpenUriOptions): Promise; + $openUri(uri: UriComponents, uriString: string | undefined, options: IOpenUriOptions): Promise; $asExternalUri(uri: UriComponents, options: IOpenUriOptions): Promise; } @@ -761,7 +780,7 @@ export interface MainThreadWindowShape extends IDisposable { export interface ExtHostCommandsShape { $executeContributedCommand(id: string, ...args: any[]): Promise; - $getContributedCommandHandlerDescriptions(): Promise<{ [id: string]: string | ICommandHandlerDescription }>; + $getContributedCommandHandlerDescriptions(): Promise<{ [id: string]: string | ICommandHandlerDescription; }>; } export interface ExtHostConfigurationShape { @@ -896,7 +915,7 @@ export interface ExtHostExtensionServiceShape { $startExtensionHost(enabledExtensionIds: ExtensionIdentifier[]): Promise; $activateByEvent(activationEvent: string): Promise; $activate(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise; - $setRemoteEnvironment(env: { [key: string]: string | null }): Promise; + $setRemoteEnvironment(env: { [key: string]: string | null; }): Promise; $deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise; @@ -910,10 +929,11 @@ export interface FileSystemEvents { changed: UriComponents[]; deleted: UriComponents[]; } + export interface ExtHostFileSystemEventServiceShape { $onFileEvent(events: FileSystemEvents): void; - $onFileRename(oldUri: UriComponents, newUri: UriComponents): void; - $onWillRename(oldUri: UriComponents, newUri: UriComponents): Promise; + $onWillRunFileOperation(operation: files.FileOperation, target: UriComponents, source: UriComponents | undefined, timeout: number, token: CancellationToken): Promise; + $onDidRunFileOperation(operation: files.FileOperation, target: UriComponents, source: UriComponents | undefined): void; } export interface ObjectIdentifier { @@ -975,7 +995,7 @@ export interface ISuggestDataDto { [ISuggestDataDtoField.preselect]?: boolean; [ISuggestDataDtoField.insertText]?: string; [ISuggestDataDtoField.insertTextRules]?: modes.CompletionItemInsertTextRule; - [ISuggestDataDtoField.range]?: IRange; + [ISuggestDataDtoField.range]?: IRange | { insert: IRange, replace: IRange; }; [ISuggestDataDtoField.commitCharacters]?: string[]; [ISuggestDataDtoField.additionalTextEdits]?: ISingleEditOperation[]; [ISuggestDataDtoField.command]?: modes.Command; @@ -986,7 +1006,7 @@ export interface ISuggestDataDto { export interface ISuggestResultDto { x?: number; - a: IRange; + a: { insert: IRange, replace: IRange; }; b: ISuggestDataDto[]; c?: boolean; } @@ -1075,6 +1095,7 @@ export interface ICodeActionDto { command?: ICommandDto; kind?: string; isPreferred?: boolean; + disabled?: string; } export interface ICodeActionListDto { @@ -1109,6 +1130,8 @@ export interface ICodeLensDto { } export interface ICallHierarchyItemDto { + _sessionId: string; + _itemId: string; kind: modes.SymbolKind; name: string; detail?: string; @@ -1117,6 +1140,16 @@ export interface ICallHierarchyItemDto { selectionRange: IRange; } +export interface IIncomingCallDto { + from: ICallHierarchyItemDto; + fromRanges: IRange[]; +} + +export interface IOutgoingCallDto { + fromRanges: IRange[]; + to: ICallHierarchyItemDto; +} + export interface ExtHostLanguageFeaturesShape { $provideDocumentSymbols(handle: number, resource: UriComponents, token: CancellationToken): Promise; $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise; @@ -1139,6 +1172,8 @@ export interface ExtHostLanguageFeaturesShape { $releaseWorkspaceSymbols(handle: number, id: number): void; $provideRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string, token: CancellationToken): Promise; $resolveRenameLocation(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; + $provideSemanticTokens(handle: number, resource: UriComponents, ranges: IRange[] | null, previousResultId: number, token: CancellationToken): Promise; + $releaseSemanticTokens(handle: number, semanticColoringResultId: number): void; $provideCompletionItems(handle: number, resource: UriComponents, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise; $resolveCompletionItem(handle: number, resource: UriComponents, position: IPosition, id: ChainedCacheId, token: CancellationToken): Promise; $releaseCompletionItems(handle: number, id: number): void; @@ -1151,8 +1186,10 @@ export interface ExtHostLanguageFeaturesShape { $provideColorPresentations(handle: number, resource: UriComponents, colorInfo: IRawColorInfo, token: CancellationToken): Promise; $provideFoldingRanges(handle: number, resource: UriComponents, context: modes.FoldingContext, token: CancellationToken): Promise; $provideSelectionRanges(handle: number, resource: UriComponents, positions: IPosition[], token: CancellationToken): Promise; - $provideCallHierarchyIncomingCalls(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<[ICallHierarchyItemDto, IRange[]][] | undefined>; - $provideCallHierarchyOutgoingCalls(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<[ICallHierarchyItemDto, IRange[]][] | undefined>; + $prepareCallHierarchy(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; + $provideCallHierarchyIncomingCalls(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise; + $provideCallHierarchyOutgoingCalls(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise; + $releaseCallHierarchy(handle: number, sessionId: string): void; } export interface ExtHostQuickOpenShape { @@ -1171,7 +1208,7 @@ export interface IShellLaunchConfigDto { executable?: string; args?: string[] | string; cwd?: string | UriComponents; - env?: { [key: string]: string | null }; + env?: { [key: string]: string | null; }; } export interface IShellDefinitionDto { @@ -1190,8 +1227,8 @@ export interface ITerminalDimensionsDto { } export interface ExtHostTerminalServiceShape { - $acceptTerminalClosed(id: number): void; - $acceptTerminalOpened(id: number, name: string): void; + $acceptTerminalClosed(id: number, exitCode: number | undefined): void; + $acceptTerminalOpened(id: number, name: string, shellLaunchConfig: IShellLaunchConfigDto): void; $acceptActiveTerminalChanged(id: number | null): void; $acceptTerminalProcessId(id: number, processId: number): void; $acceptTerminalProcessData(id: number, data: string): void; @@ -1226,8 +1263,8 @@ export interface ExtHostTaskShape { $onDidStartTaskProcess(value: tasks.TaskProcessStartedDTO): void; $onDidEndTaskProcess(value: tasks.TaskProcessEndedDTO): void; $OnDidEndTask(execution: tasks.TaskExecutionDTO): void; - $resolveVariables(workspaceFolder: UriComponents, toResolve: { process?: { name: string; cwd?: string }, variables: string[] }): Promise<{ process?: string; variables: { [key: string]: string } }>; - $getDefaultShellAndArgs(): Thenable<{ shell: string, args: string[] | string | undefined }>; + $resolveVariables(workspaceFolder: UriComponents, toResolve: { process?: { name: string; cwd?: string; }, variables: string[]; }): Promise<{ process?: string; variables: { [key: string]: string; }; }>; + $getDefaultShellAndArgs(): Thenable<{ shell: string, args: string[] | string | undefined; }>; $jsonTasksSupported(): Thenable; } @@ -1314,7 +1351,7 @@ export interface DecorationRequest { } export type DecorationData = [number, boolean, string, string, ThemeColor]; -export type DecorationReply = { [id: number]: DecorationData }; +export type DecorationReply = { [id: number]: DecorationData; }; export interface ExtHostDecorationsShape { $provideDecorations(requests: DecorationRequest[], token: CancellationToken): Promise; diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index 91b750ebee..188180394c 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import * as vscode from 'vscode'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import * as types from 'vs/workbench/api/common/extHostTypes'; -import { IRawColorInfo, IWorkspaceEditDto, ICallHierarchyItemDto } from 'vs/workbench/api/common/extHost.protocol'; +import { IRawColorInfo, IWorkspaceEditDto, ICallHierarchyItemDto, IIncomingCallDto, IOutgoingCallDto } from 'vs/workbench/api/common/extHost.protocol'; import { ISingleEditOperation } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import * as search from 'vs/workbench/contrib/search/common/search'; @@ -19,10 +19,97 @@ import { ICommandsExecutor, OpenFolderAPICommand, DiffAPICommand, OpenAPICommand import { EditorGroupLayout } from 'vs/workbench/services/editor/common/editorGroupsService'; import { isFalsyOrEmpty } from 'vs/base/common/arrays'; import { IRange } from 'vs/editor/common/core/range'; +import { IPosition } from 'vs/editor/common/core/position'; + +//#region --- NEW world + +export class ApiCommandArgument { + + static readonly Uri = new ApiCommandArgument('uri', 'Uri of a text document', v => URI.isUri(v), v => v); + static readonly Position = new ApiCommandArgument('position', 'A position in a text document', v => types.Position.isPosition(v), typeConverters.Position.from); + + static readonly CallHierarchyItem = new ApiCommandArgument('item', 'A call hierarchy item', v => v instanceof types.CallHierarchyItem, typeConverters.CallHierarchyItem.to); + + constructor( + readonly name: string, + readonly description: string, + readonly validate: (v: V) => boolean, + readonly convert: (v: V) => O + ) { } +} + +export class ApiCommandResult { + + constructor( + readonly description: string, + readonly convert: (v: V) => O + ) { } +} + +export class ApiCommand { + + constructor( + readonly id: string, + readonly internalId: string, + readonly description: string, + readonly args: ApiCommandArgument[], + readonly result: ApiCommandResult + ) { } + + register(commands: ExtHostCommands): IDisposable { + + return commands.registerCommand(false, this.id, async (...apiArgs) => { + + const internalArgs = this.args.map((arg, i) => { + if (!arg.validate(apiArgs[i])) { + throw new Error(`Invalid argument '${arg.name}' when running '${this.id}', receieved: ${apiArgs[i]}`); + } + return arg.convert(apiArgs[i]); + }); + + const internalResult = await commands.executeCommand(this.internalId, ...internalArgs); + return this.result.convert(internalResult); + }, undefined, this._getCommandHandlerDesc()); + } + + private _getCommandHandlerDesc(): ICommandHandlerDescription { + return { + description: this.description, + args: this.args, + returns: this.result.description + }; + } +} + + +const newCommands: ApiCommand[] = [ + new ApiCommand( + 'vscode.prepareCallHierarchy', '_executePrepareCallHierarchy', 'Prepare call hierarchy at a position inside a document', + [ApiCommandArgument.Uri, ApiCommandArgument.Position], + new ApiCommandResult('A CallHierarchyItem or undefined', v => v.map(typeConverters.CallHierarchyItem.to)) + ), + new ApiCommand( + 'vscode.provideIncomingCalls', '_executeProvideIncomingCalls', 'Compute incoming calls for an item', + [ApiCommandArgument.CallHierarchyItem], + new ApiCommandResult('A CallHierarchyItem or undefined', v => v.map(typeConverters.CallHierarchyIncomingCall.to)) + ), + new ApiCommand( + 'vscode.provideOutgoingCalls', '_executeProvideOutgoingCalls', 'Compute outgoing calls for an item', + [ApiCommandArgument.CallHierarchyItem], + new ApiCommandResult('A CallHierarchyItem or undefined', v => v.map(typeConverters.CallHierarchyOutgoingCall.to)) + ), +]; + + +//#endregion + + +//#region OLD world export class ExtHostApiCommands { static register(commands: ExtHostCommands) { + newCommands.forEach(command => command.register(commands)); return new ExtHostApiCommands(commands).registerCommands(); } @@ -182,22 +269,6 @@ export class ExtHostApiCommands { ], returns: 'A promise that resolves to an array of DocumentLink-instances.' }); - this._register('vscode.executeCallHierarchyProviderIncomingCalls', this._executeCallHierarchyIncomingCallsProvider, { - description: 'Execute call hierarchy provider for incoming calls', - args: [ - { name: 'uri', description: 'Uri of a text document', constraint: URI }, - { name: 'position', description: 'Position in a text document', constraint: types.Position }, - ], - returns: 'A promise that resolves to an array of CallHierarchyIncomingCall-instances.' - }); - this._register('vscode.executeCallHierarchyProviderOutgoingCalls', this._executeCallHierarchyOutgoingCallsProvider, { - description: 'Execute call hierarchy provider for outgoing calls', - args: [ - { name: 'uri', description: 'Uri of a text document', constraint: URI }, - { name: 'position', description: 'Position in a text document', constraint: types.Position }, - ], - returns: 'A promise that resolves to an array of CallHierarchyOutgoingCall-instances.' - }); this._register('vscode.executeDocumentColorProvider', this._executeDocumentColorProvider, { description: 'Execute document color provider.', args: [ @@ -449,7 +520,7 @@ export class ExtHostApiCommands { }); } - private _executeColorPresentationProvider(color: types.Color, context: { uri: URI, range: types.Range }): Promise { + private _executeColorPresentationProvider(color: types.Color, context: { uri: URI, range: types.Range; }): Promise { const args = { resource: context.uri, color: typeConverters.Color.from(color), @@ -573,36 +644,6 @@ export class ExtHostApiCommands { return this._commands.executeCommand('_executeLinkProvider', resource) .then(tryMapWith(typeConverters.DocumentLink.to)); } - - private async _executeCallHierarchyIncomingCallsProvider(resource: URI, position: types.Position): Promise { - type IncomingCallDto = { - from: ICallHierarchyItemDto; - fromRanges: IRange[]; - }; - const args = { resource, position: typeConverters.Position.from(position) }; - const calls = await this._commands.executeCommand('_executeCallHierarchyIncomingCalls', args); - - const result: vscode.CallHierarchyIncomingCall[] = []; - for (const call of calls) { - result.push(new types.CallHierarchyIncomingCall(typeConverters.CallHierarchyItem.to(call.from), call.fromRanges.map(typeConverters.Range.to))); - } - return result; - } - - private async _executeCallHierarchyOutgoingCallsProvider(resource: URI, position: types.Position): Promise { - type OutgoingCallDto = { - fromRanges: IRange[]; - to: ICallHierarchyItemDto; - }; - const args = { resource, position: typeConverters.Position.from(position) }; - const calls = await this._commands.executeCommand('_executeCallHierarchyOutgoingCalls', args); - - const result: vscode.CallHierarchyOutgoingCall[] = []; - for (const call of calls) { - result.push(new types.CallHierarchyOutgoingCall(typeConverters.CallHierarchyItem.to(call.to), call.fromRanges.map(typeConverters.Range.to))); - } - return result; - } } function tryMapWith(f: (x: T) => R) { diff --git a/src/vs/workbench/api/common/extHostCommands.ts b/src/vs/workbench/api/common/extHostCommands.ts index b2d9a6905c..ade390377f 100644 --- a/src/vs/workbench/api/common/extHostCommands.ts +++ b/src/vs/workbench/api/common/extHostCommands.ts @@ -47,7 +47,7 @@ export class ExtHostCommands implements ExtHostCommandsShape { ) { this._proxy = extHostRpc.getProxy(MainContext.MainThreadCommands); this._logService = logService; - this._converter = new CommandsConverter(this); + this._converter = new CommandsConverter(this, logService); this._argumentProcessors = [ { processArgument(a) { @@ -218,14 +218,15 @@ export class ExtHostCommands implements ExtHostCommandsShape { export class CommandsConverter { private readonly _delegatingCommandId: string; - private readonly _commands: ExtHostCommands; private readonly _cache = new Map(); private _cachIdPool = 0; // --- conversion between internal and api commands - constructor(commands: ExtHostCommands) { + constructor( + private readonly _commands: ExtHostCommands, + private readonly _logService: ILogService + ) { this._delegatingCommandId = `_vscode_delegate_cmd_${Date.now().toString(36)}`; - this._commands = commands; this._commands.registerCommand(true, this._delegatingCommandId, this._executeConvertedCommand, this); } @@ -248,12 +249,16 @@ export class CommandsConverter { const id = ++this._cachIdPool; this._cache.set(id, command); - disposables.add(toDisposable(() => this._cache.delete(id))); + disposables.add(toDisposable(() => { + this._cache.delete(id); + this._logService.trace('CommandsConverter#DISPOSE', id); + })); result.$ident = id; result.id = this._delegatingCommandId; result.arguments = [id]; + this._logService.trace('CommandsConverter#CREATE', command.command, id); } return result; @@ -276,6 +281,8 @@ export class CommandsConverter { private _executeConvertedCommand(...args: any[]): Promise { const actualCmd = this._cache.get(args[0]); + this._logService.trace('CommandsConverter#EXECUTE', args[0], actualCmd ? actualCmd.command : 'MISSING'); + if (!actualCmd) { return Promise.reject('actual command NOT FOUND'); } diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index 31711a5c9e..28b6fb716c 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -16,7 +16,7 @@ import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import * as extHostTypeConverter from 'vs/workbench/api/common/extHostTypeConverters'; import * as types from 'vs/workbench/api/common/extHostTypes'; import * as vscode from 'vscode'; -import { ExtHostCommentsShape, IMainContext, MainContext, MainThreadCommentsShape } from './extHost.protocol'; +import { ExtHostCommentsShape, IMainContext, MainContext, MainThreadCommentsShape, CommentThreadChanges } from './extHost.protocol'; import { ExtHostCommands } from './extHostCommands'; type ProviderHandle = number; @@ -213,12 +213,21 @@ export class ExtHostComments implements ExtHostCommentsShape, IDisposable { } } +type CommentThreadModification = Partial<{ + range: vscode.Range, + label: string | undefined, + contextValue: string | undefined, + comments: vscode.Comment[], + collapsibleState: vscode.CommentThreadCollapsibleState +}>; export class ExtHostCommentThread implements vscode.CommentThread { private static _handlePool: number = 0; readonly handle = ExtHostCommentThread._handlePool++; public commentHandle: number = 0; + private modifications: CommentThreadModification = Object.create(null); + set threadId(id: string) { this._id = id; } @@ -245,6 +254,7 @@ export class ExtHostCommentThread implements vscode.CommentThread { set range(range: vscode.Range) { if (!range.isEqual(this._range)) { this._range = range; + this.modifications.range = range; this._onDidUpdateCommentThread.fire(); } } @@ -261,6 +271,7 @@ export class ExtHostCommentThread implements vscode.CommentThread { set label(label: string | undefined) { this._label = label; + this.modifications.label = label; this._onDidUpdateCommentThread.fire(); } @@ -272,6 +283,7 @@ export class ExtHostCommentThread implements vscode.CommentThread { set contextValue(context: string | undefined) { this._contextValue = context; + this.modifications.contextValue = context; this._onDidUpdateCommentThread.fire(); } @@ -281,6 +293,7 @@ export class ExtHostCommentThread implements vscode.CommentThread { set comments(newComments: vscode.Comment[]) { this._comments = newComments; + this.modifications.comments = newComments; this._onDidUpdateCommentThread.fire(); } @@ -292,6 +305,7 @@ export class ExtHostCommentThread implements vscode.CommentThread { set collapsibleState(newState: vscode.CommentThreadCollapsibleState) { this._collapseState = newState; + this.modifications.collapsibleState = newState; this._onDidUpdateCommentThread.fire(); } @@ -353,22 +367,34 @@ export class ExtHostCommentThread implements vscode.CommentThread { this._acceptInputDisposables.value = new DisposableStore(); } - const commentThreadRange = extHostTypeConverter.Range.from(this._range); - const label = this.label; - const contextValue = this.contextValue; - const comments = this._comments.map(cmt => { return convertToModeComment(this, this._commentController, cmt, this._commentsMap); }); - const collapsibleState = convertToCollapsibleState(this._collapseState); + const modified = (value: keyof CommentThreadModification): boolean => + Object.prototype.hasOwnProperty.call(this.modifications, value); + + const formattedModifications: CommentThreadChanges = {}; + if (modified('range')) { + formattedModifications.range = extHostTypeConverter.Range.from(this._range); + } + if (modified('label')) { + formattedModifications.label = this.label; + } + if (modified('contextValue')) { + formattedModifications.contextValue = this.contextValue; + } + if (modified('comments')) { + formattedModifications.comments = + this._comments.map(cmt => convertToModeComment(this, this._commentController, cmt, this._commentsMap)); + } + if (modified('collapsibleState')) { + formattedModifications.collapseState = convertToCollapsibleState(this._collapseState); + } + this.modifications = {}; this._proxy.$updateCommentThread( this._commentController.handle, this.handle, this._id!, this._uri, - commentThreadRange, - label, - contextValue, - comments, - collapsibleState + formattedModifications ); } diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index 476df18c51..0d0db96080 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -20,6 +20,7 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { Barrier } from 'vs/base/common/async'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { ILogService } from 'vs/platform/log/common/log'; function lookUp(tree: any, key: string) { if (key) { @@ -45,16 +46,19 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape { readonly _serviceBrand: undefined; private readonly _proxy: MainThreadConfigurationShape; + private readonly _logService: ILogService; private readonly _extHostWorkspace: ExtHostWorkspace; private readonly _barrier: Barrier; private _actual: ExtHostConfigProvider | null; constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, - @IExtHostWorkspace extHostWorkspace: IExtHostWorkspace + @IExtHostWorkspace extHostWorkspace: IExtHostWorkspace, + @ILogService logService: ILogService, ) { this._proxy = extHostRpc.getProxy(MainContext.MainThreadConfiguration); this._extHostWorkspace = extHostWorkspace; + this._logService = logService; this._barrier = new Barrier(); this._actual = null; } @@ -64,7 +68,7 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape { } $initializeConfiguration(data: IConfigurationInitData): void { - this._actual = new ExtHostConfigProvider(this._proxy, this._extHostWorkspace, data); + this._actual = new ExtHostConfigProvider(this._proxy, this._extHostWorkspace, data, this._logService); this._barrier.open(); } @@ -80,9 +84,11 @@ export class ExtHostConfigProvider { private readonly _extHostWorkspace: ExtHostWorkspace; private _configurationScopes: Map; private _configuration: Configuration; + private _logService: ILogService; - constructor(proxy: MainThreadConfigurationShape, extHostWorkspace: ExtHostWorkspace, data: IConfigurationInitData) { + constructor(proxy: MainThreadConfigurationShape, extHostWorkspace: ExtHostWorkspace, data: IConfigurationInitData, logService: ILogService) { this._proxy = proxy; + this._logService = logService; this._extHostWorkspace = extHostWorkspace; this._configuration = ExtHostConfigProvider.parse(data); this._configurationScopes = this._toMap(data.configurationScopes); @@ -236,13 +242,13 @@ export class ExtHostConfigProvider { const extensionIdText = extensionId ? `[${extensionId.value}] ` : ''; if (ConfigurationScope.RESOURCE === scope) { if (resource === undefined) { - console.warn(`${extensionIdText}Accessing a resource scoped configuration without providing a resource is not expected. To get the effective value for '${key}', provide the URI of a resource or 'null' for any resource.`); + this._logService.warn(`${extensionIdText}Accessing a resource scoped configuration without providing a resource is not expected. To get the effective value for '${key}', provide the URI of a resource or 'null' for any resource.`); } return; } if (ConfigurationScope.WINDOW === scope) { if (resource) { - console.warn(`${extensionIdText}Accessing a window scoped configuration for a resource is not expected. To associate '${key}' to a resource, define its scope to 'resource' in configuration contributions in 'package.json'.`); + this._logService.warn(`${extensionIdText}Accessing a window scoped configuration for a resource is not expected. To associate '${key}' to a resource, define its scope to 'resource' in configuration contributions in 'package.json'.`); } return; } diff --git a/src/vs/workbench/api/common/extHostCustomers.ts b/src/vs/workbench/api/common/extHostCustomers.ts index f762302f64..07c3aefea0 100644 --- a/src/vs/workbench/api/common/extHostCustomers.ts +++ b/src/vs/workbench/api/common/extHostCustomers.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IDisposable } from 'vs/base/common/lifecycle'; -import { IConstructorSignature1 } from 'vs/platform/instantiation/common/instantiation'; +import { IConstructorSignature1, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier'; @@ -13,12 +13,12 @@ export type IExtHostNamedCustomer = [ProxyIdentifier, export type IExtHostCustomerCtor = IConstructorSignature1; export function extHostNamedCustomer(id: ProxyIdentifier) { - return function (ctor: IExtHostCustomerCtor): void { + return function (ctor: { new(context: IExtHostContext, ...services: Services): T }): void { ExtHostCustomersRegistryImpl.INSTANCE.registerNamedCustomer(id, ctor); }; } -export function extHostCustomer(ctor: IExtHostCustomerCtor): void { +export function extHostCustomer(ctor: { new(context: IExtHostContext, ...services: Services): T }): void { ExtHostCustomersRegistryImpl.INSTANCE.registerCustomer(ctor); } diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index 6180477b18..77595d63a1 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -3,11 +3,35 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event } from 'vs/base/common/event'; -import { ExtHostDebugServiceShape } from 'vs/workbench/api/common/extHost.protocol'; +import * as path from 'vs/base/common/path'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { Event, Emitter } from 'vs/base/common/event'; +import { asPromise } from 'vs/base/common/async'; +import { + MainContext, MainThreadDebugServiceShape, ExtHostDebugServiceShape, DebugSessionUUID, + IBreakpointsDeltaDto, ISourceMultiBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto +} from 'vs/workbench/api/common/extHost.protocol'; +import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint, DebugAdapterServer, DebugAdapterExecutable, DataBreakpoint, DebugConsoleMode, DebugAdapterInlineImplementation } from 'vs/workbench/api/common/extHostTypes'; +import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter'; +import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; +import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; +import { ExtHostDocumentsAndEditors, IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { IDebuggerContribution, IConfig, IDebugAdapter, IDebugAdapterServer, IDebugAdapterExecutable, IAdapterDescriptor, IDebugAdapterImpl } from 'vs/workbench/contrib/debug/common/debug'; +import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/common/variableResolver'; +import { ExtHostConfigProvider, IExtHostConfiguration } from '../common/extHostConfiguration'; +import { convertToVSCPaths, convertToDAPaths, isDebuggerMainContribution } from 'vs/workbench/contrib/debug/common/debugUtils'; +import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; +import { ISignService } from 'vs/platform/sign/common/sign'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import * as vscode from 'vscode'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { withNullAsUndefined } from 'vs/base/common/types'; +import { IProcessEnvironment } from 'vs/base/common/platform'; export const IExtHostDebugService = createDecorator('IExtHostDebugService'); @@ -30,5 +54,1036 @@ export interface IExtHostDebugService extends ExtHostDebugServiceShape { registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider): vscode.Disposable; registerDebugAdapterDescriptorFactory(extension: IExtensionDescription, type: string, factory: vscode.DebugAdapterDescriptorFactory): vscode.Disposable; registerDebugAdapterTrackerFactory(type: string, factory: vscode.DebugAdapterTrackerFactory): vscode.Disposable; + asDebugSourceUri(source: vscode.DebugProtocolSource, session?: vscode.DebugSession): vscode.Uri; } +export class ExtHostDebugServiceBase implements IExtHostDebugService, ExtHostDebugServiceShape { + + readonly _serviceBrand: undefined; + + private _configProviderHandleCounter: number; + private _configProviders: ConfigProviderTuple[]; + + private _adapterFactoryHandleCounter: number; + private _adapterFactories: DescriptorFactoryTuple[]; + + private _trackerFactoryHandleCounter: number; + private _trackerFactories: TrackerFactoryTuple[]; + + private _debugServiceProxy: MainThreadDebugServiceShape; + private _debugSessions: Map = new Map(); + + private readonly _onDidStartDebugSession: Emitter; + get onDidStartDebugSession(): Event { return this._onDidStartDebugSession.event; } + + private readonly _onDidTerminateDebugSession: Emitter; + get onDidTerminateDebugSession(): Event { return this._onDidTerminateDebugSession.event; } + + private readonly _onDidChangeActiveDebugSession: Emitter; + get onDidChangeActiveDebugSession(): Event { return this._onDidChangeActiveDebugSession.event; } + + private _activeDebugSession: ExtHostDebugSession | undefined; + get activeDebugSession(): ExtHostDebugSession | undefined { return this._activeDebugSession; } + + private readonly _onDidReceiveDebugSessionCustomEvent: Emitter; + get onDidReceiveDebugSessionCustomEvent(): Event { return this._onDidReceiveDebugSessionCustomEvent.event; } + + private _activeDebugConsole: ExtHostDebugConsole; + get activeDebugConsole(): ExtHostDebugConsole { return this._activeDebugConsole; } + + private _breakpoints: Map; + private _breakpointEventsActive: boolean; + + private readonly _onDidChangeBreakpoints: Emitter; + + private _aexCommands: Map; + private _debugAdapters: Map; + private _debugAdaptersTrackers: Map; + + private _variableResolver: IConfigurationResolverService | undefined; + + private _signService: ISignService | undefined; + + + constructor( + @IExtHostRpcService extHostRpcService: IExtHostRpcService, + @IExtHostWorkspace private _workspaceService: IExtHostWorkspace, + @IExtHostExtensionService private _extensionService: IExtHostExtensionService, + @IExtHostDocumentsAndEditors private _editorsService: IExtHostDocumentsAndEditors, + @IExtHostConfiguration protected _configurationService: IExtHostConfiguration, + @IExtHostCommands private _commandService: IExtHostCommands + ) { + this._configProviderHandleCounter = 0; + this._configProviders = []; + + this._adapterFactoryHandleCounter = 0; + this._adapterFactories = []; + + this._trackerFactoryHandleCounter = 0; + this._trackerFactories = []; + + this._aexCommands = new Map(); + this._debugAdapters = new Map(); + this._debugAdaptersTrackers = new Map(); + + this._onDidStartDebugSession = new Emitter(); + this._onDidTerminateDebugSession = new Emitter(); + this._onDidChangeActiveDebugSession = new Emitter(); + this._onDidReceiveDebugSessionCustomEvent = new Emitter(); + + this._debugServiceProxy = extHostRpcService.getProxy(MainContext.MainThreadDebugService); + + this._onDidChangeBreakpoints = new Emitter({ + onFirstListenerAdd: () => { + this.startBreakpoints(); + } + }); + + this._activeDebugConsole = new ExtHostDebugConsole(this._debugServiceProxy); + + this._breakpoints = new Map(); + this._breakpointEventsActive = false; + + this._extensionService.getExtensionRegistry().then((extensionRegistry: ExtensionDescriptionRegistry) => { + extensionRegistry.onDidChange(_ => { + this.registerAllDebugTypes(extensionRegistry); + }); + this.registerAllDebugTypes(extensionRegistry); + }); + } + + public asDebugSourceUri(src: vscode.DebugProtocolSource, session?: vscode.DebugSession): URI { + + const source = src; + + if (typeof source.sourceReference === 'number') { + // src can be retrieved via DAP's "source" request + + let debug = `debug:${encodeURIComponent(source.path || '')}`; + let sep = '?'; + + if (session) { + debug += `${sep}session=${encodeURIComponent(session.id)}`; + sep = '&'; + } + + debug += `${sep}ref=${source.sourceReference}`; + + return URI.parse(debug); + } else if (source.path) { + // src is just a local file path + return URI.file(source.path); + } else { + throw new Error(`cannot create uri from DAP 'source' object; properties 'path' and 'sourceReference' are both missing.`); + } + } + + private registerAllDebugTypes(extensionRegistry: ExtensionDescriptionRegistry) { + + const debugTypes: string[] = []; + this._aexCommands.clear(); + + for (const ed of extensionRegistry.getAllExtensionDescriptions()) { + if (ed.contributes) { + const debuggers = ed.contributes['debuggers']; + if (debuggers && debuggers.length > 0) { + for (const dbg of debuggers) { + if (isDebuggerMainContribution(dbg)) { + debugTypes.push(dbg.type); + if (dbg.adapterExecutableCommand) { + this._aexCommands.set(dbg.type, dbg.adapterExecutableCommand); + } + } + } + } + } + } + + this._debugServiceProxy.$registerDebugTypes(debugTypes); + } + + // extension debug API + + get onDidChangeBreakpoints(): Event { + return this._onDidChangeBreakpoints.event; + } + + get breakpoints(): vscode.Breakpoint[] { + + this.startBreakpoints(); + + const result: vscode.Breakpoint[] = []; + this._breakpoints.forEach(bp => result.push(bp)); + return result; + } + + public addBreakpoints(breakpoints0: vscode.Breakpoint[]): Promise { + + this.startBreakpoints(); + + // filter only new breakpoints + const breakpoints = breakpoints0.filter(bp => { + const id = bp.id; + if (!this._breakpoints.has(id)) { + this._breakpoints.set(id, bp); + return true; + } + return false; + }); + + // send notification for added breakpoints + this.fireBreakpointChanges(breakpoints, [], []); + + // convert added breakpoints to DTOs + const dtos: Array = []; + const map = new Map(); + for (const bp of breakpoints) { + if (bp instanceof SourceBreakpoint) { + let dto = map.get(bp.location.uri.toString()); + if (!dto) { + dto = { + type: 'sourceMulti', + uri: bp.location.uri, + lines: [] + }; + map.set(bp.location.uri.toString(), dto); + dtos.push(dto); + } + dto.lines.push({ + id: bp.id, + enabled: bp.enabled, + condition: bp.condition, + hitCondition: bp.hitCondition, + logMessage: bp.logMessage, + line: bp.location.range.start.line, + character: bp.location.range.start.character + }); + } else if (bp instanceof FunctionBreakpoint) { + dtos.push({ + type: 'function', + id: bp.id, + enabled: bp.enabled, + hitCondition: bp.hitCondition, + logMessage: bp.logMessage, + condition: bp.condition, + functionName: bp.functionName + }); + } + } + + // send DTOs to VS Code + return this._debugServiceProxy.$registerBreakpoints(dtos); + } + + public removeBreakpoints(breakpoints0: vscode.Breakpoint[]): Promise { + + this.startBreakpoints(); + + // remove from array + const breakpoints = breakpoints0.filter(b => this._breakpoints.delete(b.id)); + + // send notification + this.fireBreakpointChanges([], breakpoints, []); + + // unregister with VS Code + const ids = breakpoints.filter(bp => bp instanceof SourceBreakpoint).map(bp => bp.id); + const fids = breakpoints.filter(bp => bp instanceof FunctionBreakpoint).map(bp => bp.id); + const dids = breakpoints.filter(bp => bp instanceof DataBreakpoint).map(bp => bp.id); + return this._debugServiceProxy.$unregisterBreakpoints(ids, fids, dids); + } + + public startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, options: vscode.DebugSessionOptions): Promise { + return this._debugServiceProxy.$startDebugging(folder ? folder.uri : undefined, nameOrConfig, { + parentSessionID: options.parentSession ? options.parentSession.id : undefined, + repl: options.consoleMode === DebugConsoleMode.MergeWithParent ? 'mergeWithParent' : 'separate' + }); + } + + public registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider): vscode.Disposable { + + if (!provider) { + return new Disposable(() => { }); + } + + if (provider.debugAdapterExecutable) { + console.error('DebugConfigurationProvider.debugAdapterExecutable is deprecated and will be removed soon; please use DebugAdapterDescriptorFactory.createDebugAdapterDescriptor instead.'); + } + + const handle = this._configProviderHandleCounter++; + this._configProviders.push({ type, handle, provider }); + + this._debugServiceProxy.$registerDebugConfigurationProvider(type, + !!provider.provideDebugConfigurations, + !!provider.resolveDebugConfiguration, + !!provider.debugAdapterExecutable, // TODO@AW: deprecated + handle); + + return new Disposable(() => { + this._configProviders = this._configProviders.filter(p => p.provider !== provider); // remove + this._debugServiceProxy.$unregisterDebugConfigurationProvider(handle); + }); + } + + public registerDebugAdapterDescriptorFactory(extension: IExtensionDescription, type: string, factory: vscode.DebugAdapterDescriptorFactory): vscode.Disposable { + + if (!factory) { + return new Disposable(() => { }); + } + + // a DebugAdapterDescriptorFactory can only be registered in the extension that contributes the debugger + if (!this.definesDebugType(extension, type)) { + throw new Error(`a DebugAdapterDescriptorFactory can only be registered from the extension that defines the '${type}' debugger.`); + } + + // make sure that only one factory for this type is registered + if (this.getAdapterFactoryByType(type)) { + throw new Error(`a DebugAdapterDescriptorFactory can only be registered once per a type.`); + } + + const handle = this._adapterFactoryHandleCounter++; + this._adapterFactories.push({ type, handle, factory }); + + this._debugServiceProxy.$registerDebugAdapterDescriptorFactory(type, handle); + + return new Disposable(() => { + this._adapterFactories = this._adapterFactories.filter(p => p.factory !== factory); // remove + this._debugServiceProxy.$unregisterDebugAdapterDescriptorFactory(handle); + }); + } + + public registerDebugAdapterTrackerFactory(type: string, factory: vscode.DebugAdapterTrackerFactory): vscode.Disposable { + + if (!factory) { + return new Disposable(() => { }); + } + + const handle = this._trackerFactoryHandleCounter++; + this._trackerFactories.push({ type, handle, factory }); + + return new Disposable(() => { + this._trackerFactories = this._trackerFactories.filter(p => p.factory !== factory); // remove + }); + } + + // RPC methods (ExtHostDebugServiceShape) + + public async $runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): Promise { + return Promise.resolve(undefined); + } + + protected createVariableResolver(folders: vscode.WorkspaceFolder[], editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfigProvider): AbstractVariableResolverService { + return new ExtHostVariableResolverService(folders, editorService, configurationService); + } + + public async $substituteVariables(folderUri: UriComponents | undefined, config: IConfig): Promise { + if (!this._variableResolver) { + const [workspaceFolders, configProvider] = await Promise.all([this._workspaceService.getWorkspaceFolders2(), this._configurationService.getConfigProvider()]); + this._variableResolver = this.createVariableResolver(workspaceFolders || [], this._editorsService, configProvider!); + } + let ws: IWorkspaceFolder | undefined; + const folder = await this.getFolder(folderUri); + if (folder) { + ws = { + uri: folder.uri, + name: folder.name, + index: folder.index, + toResource: () => { + throw new Error('Not implemented'); + } + }; + } + return this._variableResolver.resolveAny(ws, config); + } + + protected createDebugAdapter(adapter: IAdapterDescriptor, session: ExtHostDebugSession): AbstractDebugAdapter | undefined { + if (adapter.type === 'implementation') { + return new DirectDebugAdapter(adapter.implementation); + } + return undefined; + } + + protected createSignService(): ISignService | undefined { + return undefined; + } + + public async $startDASession(debugAdapterHandle: number, sessionDto: IDebugSessionDto): Promise { + const mythis = this; + + const session = await this.getSession(sessionDto); + + return this.getAdapterDescriptor(this.getAdapterFactoryByType(session.type), session).then(daDescriptor => { + + const adapter = this.convertToDto(daDescriptor); + + const da: AbstractDebugAdapter | undefined = this.createDebugAdapter(adapter, session); + + const debugAdapter = da; + + if (debugAdapter) { + this._debugAdapters.set(debugAdapterHandle, debugAdapter); + + return this.getDebugAdapterTrackers(session).then(tracker => { + + if (tracker) { + this._debugAdaptersTrackers.set(debugAdapterHandle, tracker); + } + + debugAdapter.onMessage(async message => { + + if (message.type === 'request' && (message).command === 'handshake') { + + const request = message; + + const response: DebugProtocol.Response = { + type: 'response', + seq: 0, + command: request.command, + request_seq: request.seq, + success: true + }; + + if (!this._signService) { + this._signService = this.createSignService(); + } + + try { + if (this._signService) { + const signature = await this._signService.sign(request.arguments.value); + response.body = { + signature: signature + }; + debugAdapter.sendResponse(response); + } else { + throw new Error('no signer'); + } + } catch (e) { + response.success = false; + response.message = e.message; + debugAdapter.sendResponse(response); + } + } else { + if (tracker && tracker.onDidSendMessage) { + tracker.onDidSendMessage(message); + } + + // DA -> VS Code + message = convertToVSCPaths(message, true); + + mythis._debugServiceProxy.$acceptDAMessage(debugAdapterHandle, message); + } + }); + debugAdapter.onError(err => { + if (tracker && tracker.onError) { + tracker.onError(err); + } + this._debugServiceProxy.$acceptDAError(debugAdapterHandle, err.name, err.message, err.stack); + }); + debugAdapter.onExit((code: number | null) => { + if (tracker && tracker.onExit) { + tracker.onExit(withNullAsUndefined(code), undefined); + } + this._debugServiceProxy.$acceptDAExit(debugAdapterHandle, withNullAsUndefined(code), undefined); + }); + + if (tracker && tracker.onWillStartSession) { + tracker.onWillStartSession(); + } + + return debugAdapter.startSession(); + }); + + } + return undefined; + }); + } + + public $sendDAMessage(debugAdapterHandle: number, message: DebugProtocol.ProtocolMessage): void { + + // VS Code -> DA + message = convertToDAPaths(message, false); + + const tracker = this._debugAdaptersTrackers.get(debugAdapterHandle); // TODO@AW: same handle? + if (tracker && tracker.onWillReceiveMessage) { + tracker.onWillReceiveMessage(message); + } + + const da = this._debugAdapters.get(debugAdapterHandle); + if (da) { + da.sendMessage(message); + } + } + + public $stopDASession(debugAdapterHandle: number): Promise { + + const tracker = this._debugAdaptersTrackers.get(debugAdapterHandle); + this._debugAdaptersTrackers.delete(debugAdapterHandle); + if (tracker && tracker.onWillStopSession) { + tracker.onWillStopSession(); + } + + const da = this._debugAdapters.get(debugAdapterHandle); + this._debugAdapters.delete(debugAdapterHandle); + if (da) { + return da.stopSession(); + } else { + return Promise.resolve(void 0); + } + } + + public $acceptBreakpointsDelta(delta: IBreakpointsDeltaDto): void { + + const a: vscode.Breakpoint[] = []; + const r: vscode.Breakpoint[] = []; + const c: vscode.Breakpoint[] = []; + + if (delta.added) { + for (const bpd of delta.added) { + const id = bpd.id; + if (id && !this._breakpoints.has(id)) { + let bp: vscode.Breakpoint; + if (bpd.type === 'function') { + bp = new FunctionBreakpoint(bpd.functionName, bpd.enabled, bpd.condition, bpd.hitCondition, bpd.logMessage); + } else if (bpd.type === 'data') { + bp = new DataBreakpoint(bpd.label, bpd.dataId, bpd.canPersist, bpd.enabled, bpd.hitCondition, bpd.condition, bpd.logMessage); + } else { + const uri = URI.revive(bpd.uri); + bp = new SourceBreakpoint(new Location(uri, new Position(bpd.line, bpd.character)), bpd.enabled, bpd.condition, bpd.hitCondition, bpd.logMessage); + } + (bp as any)._id = id; + this._breakpoints.set(id, bp); + a.push(bp); + } + } + } + + if (delta.removed) { + for (const id of delta.removed) { + const bp = this._breakpoints.get(id); + if (bp) { + this._breakpoints.delete(id); + r.push(bp); + } + } + } + + if (delta.changed) { + for (const bpd of delta.changed) { + if (bpd.id) { + const bp = this._breakpoints.get(bpd.id); + if (bp) { + if (bp instanceof FunctionBreakpoint && bpd.type === 'function') { + const fbp = bp; + fbp.enabled = bpd.enabled; + fbp.condition = bpd.condition; + fbp.hitCondition = bpd.hitCondition; + fbp.logMessage = bpd.logMessage; + fbp.functionName = bpd.functionName; + } else if (bp instanceof SourceBreakpoint && bpd.type === 'source') { + const sbp = bp; + sbp.enabled = bpd.enabled; + sbp.condition = bpd.condition; + sbp.hitCondition = bpd.hitCondition; + sbp.logMessage = bpd.logMessage; + sbp.location = new Location(URI.revive(bpd.uri), new Position(bpd.line, bpd.character)); + } + c.push(bp); + } + } + } + } + + this.fireBreakpointChanges(a, r, c); + } + + public $provideDebugConfigurations(configProviderHandle: number, folderUri: UriComponents | undefined, token: CancellationToken): Promise { + return asPromise(async () => { + const provider = this.getConfigProviderByHandle(configProviderHandle); + if (!provider) { + throw new Error('no DebugConfigurationProvider found'); + } + if (!provider.provideDebugConfigurations) { + throw new Error('DebugConfigurationProvider has no method provideDebugConfigurations'); + } + const folder = await this.getFolder(folderUri); + return provider.provideDebugConfigurations(folder, token); + }).then(debugConfigurations => { + if (!debugConfigurations) { + throw new Error('nothing returned from DebugConfigurationProvider.provideDebugConfigurations'); + } + return debugConfigurations; + }); + } + + public $resolveDebugConfiguration(configProviderHandle: number, folderUri: UriComponents | undefined, debugConfiguration: vscode.DebugConfiguration, token: CancellationToken): Promise { + return asPromise(async () => { + const provider = this.getConfigProviderByHandle(configProviderHandle); + if (!provider) { + throw new Error('no DebugConfigurationProvider found'); + } + if (!provider.resolveDebugConfiguration) { + throw new Error('DebugConfigurationProvider has no method resolveDebugConfiguration'); + } + const folder = await this.getFolder(folderUri); + return provider.resolveDebugConfiguration(folder, debugConfiguration, token); + }); + } + + // TODO@AW deprecated and legacy + public $legacyDebugAdapterExecutable(configProviderHandle: number, folderUri: UriComponents | undefined): Promise { + return asPromise(async () => { + const provider = this.getConfigProviderByHandle(configProviderHandle); + if (!provider) { + throw new Error('no DebugConfigurationProvider found'); + } + if (!provider.debugAdapterExecutable) { + throw new Error('DebugConfigurationProvider has no method debugAdapterExecutable'); + } + const folder = await this.getFolder(folderUri); + return provider.debugAdapterExecutable(folder, CancellationToken.None); + }).then(executable => { + if (!executable) { + throw new Error('nothing returned from DebugConfigurationProvider.debugAdapterExecutable'); + } + return this.convertToDto(executable); + }); + } + + public async $provideDebugAdapter(adapterProviderHandle: number, sessionDto: IDebugSessionDto): Promise { + const adapterProvider = this.getAdapterProviderByHandle(adapterProviderHandle); + if (!adapterProvider) { + return Promise.reject(new Error('no handler found')); + } + const session = await this.getSession(sessionDto); + return this.getAdapterDescriptor(adapterProvider, session).then(x => this.convertToDto(x)); + } + + public async $acceptDebugSessionStarted(sessionDto: IDebugSessionDto): Promise { + const session = await this.getSession(sessionDto); + this._onDidStartDebugSession.fire(session); + } + + public async $acceptDebugSessionTerminated(sessionDto: IDebugSessionDto): Promise { + const session = await this.getSession(sessionDto); + if (session) { + this._onDidTerminateDebugSession.fire(session); + this._debugSessions.delete(session.id); + } + } + + public async $acceptDebugSessionActiveChanged(sessionDto: IDebugSessionDto | undefined): Promise { + this._activeDebugSession = sessionDto ? await this.getSession(sessionDto) : undefined; + this._onDidChangeActiveDebugSession.fire(this._activeDebugSession); + } + + public async $acceptDebugSessionNameChanged(sessionDto: IDebugSessionDto, name: string): Promise { + const session = await this.getSession(sessionDto); + if (session) { + session._acceptNameChanged(name); + } + } + + public async $acceptDebugSessionCustomEvent(sessionDto: IDebugSessionDto, event: any): Promise { + const session = await this.getSession(sessionDto); + const ee: vscode.DebugSessionCustomEvent = { + session: session, + event: event.event, + body: event.body + }; + this._onDidReceiveDebugSessionCustomEvent.fire(ee); + } + + // private & dto helpers + + private convertToDto(x: vscode.DebugAdapterDescriptor | undefined): IAdapterDescriptor { + + if (x instanceof DebugAdapterExecutable) { + return { + type: 'executable', + command: x.command, + args: x.args, + options: x.options + }; + } else if (x instanceof DebugAdapterServer) { + return { + type: 'server', + port: x.port, + host: x.host + }; + } else if (x instanceof DebugAdapterInlineImplementation) { + return { + type: 'implementation', + implementation: x.implementation + }; + } else { + throw new Error('convertToDto unexpected type'); + } + } + + private getAdapterFactoryByType(type: string): vscode.DebugAdapterDescriptorFactory | undefined { + const results = this._adapterFactories.filter(p => p.type === type); + if (results.length > 0) { + return results[0].factory; + } + return undefined; + } + + private getAdapterProviderByHandle(handle: number): vscode.DebugAdapterDescriptorFactory | undefined { + const results = this._adapterFactories.filter(p => p.handle === handle); + if (results.length > 0) { + return results[0].factory; + } + return undefined; + } + + private getConfigProviderByHandle(handle: number): vscode.DebugConfigurationProvider | undefined { + const results = this._configProviders.filter(p => p.handle === handle); + if (results.length > 0) { + return results[0].provider; + } + return undefined; + } + + private definesDebugType(ed: IExtensionDescription, type: string) { + if (ed.contributes) { + const debuggers = ed.contributes['debuggers']; + if (debuggers && debuggers.length > 0) { + for (const dbg of debuggers) { + // only debugger contributions with a "label" are considered a "defining" debugger contribution + if (dbg.label && dbg.type) { + if (dbg.type === type) { + return true; + } + } + } + } + } + return false; + } + + private getDebugAdapterTrackers(session: ExtHostDebugSession): Promise { + + const config = session.configuration; + const type = config.type; + + const promises = this._trackerFactories + .filter(tuple => tuple.type === type || tuple.type === '*') + .map(tuple => asPromise>(() => tuple.factory.createDebugAdapterTracker(session)).then(p => p, err => null)); + + return Promise.race([ + Promise.all(promises).then(result => { + const trackers = result.filter(t => !!t); // filter null + if (trackers.length > 0) { + return new MultiTracker(trackers); + } + return undefined; + }), + new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + clearTimeout(timeout); + reject(new Error('timeout')); + }, 1000); + }) + ]).catch(err => { + // ignore errors + return undefined; + }); + } + + private async getAdapterDescriptor(adapterProvider: vscode.DebugAdapterDescriptorFactory | undefined, session: ExtHostDebugSession): Promise { + + // a "debugServer" attribute in the launch config takes precedence + const serverPort = session.configuration.debugServer; + if (typeof serverPort === 'number') { + return Promise.resolve(new DebugAdapterServer(serverPort)); + } + + // TODO@AW legacy + const pair = this._configProviders.filter(p => p.type === session.type).pop(); + if (pair && pair.provider.debugAdapterExecutable) { + const func = pair.provider.debugAdapterExecutable; + return asPromise(() => func(session.workspaceFolder, CancellationToken.None)).then(executable => { + if (executable) { + return executable; + } + return undefined; + }); + } + + if (adapterProvider) { + const extensionRegistry = await this._extensionService.getExtensionRegistry(); + return asPromise(() => adapterProvider.createDebugAdapterDescriptor(session, this.daExecutableFromPackage(session, extensionRegistry))).then(daDescriptor => { + if (daDescriptor) { + return daDescriptor; + } + return undefined; + }); + } + + // try deprecated command based extension API "adapterExecutableCommand" to determine the executable + // TODO@AW legacy + const aex = this._aexCommands.get(session.type); + if (aex) { + const folder = session.workspaceFolder; + const rootFolder = folder ? folder.uri.toString() : undefined; + return this._commandService.executeCommand(aex, rootFolder).then((ae: { command: string, args: string[] }) => { + return new DebugAdapterExecutable(ae.command, ae.args || []); + }); + } + + // fallback: use executable information from package.json + const extensionRegistry = await this._extensionService.getExtensionRegistry(); + return Promise.resolve(this.daExecutableFromPackage(session, extensionRegistry)); + } + + protected daExecutableFromPackage(session: ExtHostDebugSession, extensionRegistry: ExtensionDescriptionRegistry): DebugAdapterExecutable | undefined { + return undefined; + } + + private startBreakpoints() { + if (!this._breakpointEventsActive) { + this._breakpointEventsActive = true; + this._debugServiceProxy.$startBreakpointEvents(); + } + } + + private fireBreakpointChanges(added: vscode.Breakpoint[], removed: vscode.Breakpoint[], changed: vscode.Breakpoint[]) { + if (added.length > 0 || removed.length > 0 || changed.length > 0) { + this._onDidChangeBreakpoints.fire(Object.freeze({ + added, + removed, + changed, + })); + } + } + + private async getSession(dto: IDebugSessionDto): Promise { + if (dto) { + if (typeof dto === 'string') { + const ds = this._debugSessions.get(dto); + if (ds) { + return ds; + } + } else { + let ds = this._debugSessions.get(dto.id); + if (!ds) { + const folder = await this.getFolder(dto.folderUri); + ds = new ExtHostDebugSession(this._debugServiceProxy, dto.id, dto.type, dto.name, folder, dto.configuration); + this._debugSessions.set(ds.id, ds); + this._debugServiceProxy.$sessionCached(ds.id); + } + return ds; + } + } + throw new Error('cannot find session'); + } + + private getFolder(_folderUri: UriComponents | undefined): Promise { + if (_folderUri) { + const folderURI = URI.revive(_folderUri); + return this._workspaceService.resolveWorkspaceFolder(folderURI); + } + return Promise.resolve(undefined); + } +} + +export class ExtHostDebugSession implements vscode.DebugSession { + + constructor( + private _debugServiceProxy: MainThreadDebugServiceShape, + private _id: DebugSessionUUID, + private _type: string, + private _name: string, + private _workspaceFolder: vscode.WorkspaceFolder | undefined, + private _configuration: vscode.DebugConfiguration) { + } + + public get id(): string { + return this._id; + } + + public get type(): string { + return this._type; + } + + public get name(): string { + return this._name; + } + + public set name(name: string) { + this._name = name; + this._debugServiceProxy.$setDebugSessionName(this._id, name); + } + + _acceptNameChanged(name: string) { + this._name = name; + } + + public get workspaceFolder(): vscode.WorkspaceFolder | undefined { + return this._workspaceFolder; + } + + public get configuration(): vscode.DebugConfiguration { + return this._configuration; + } + + public customRequest(command: string, args: any): Promise { + return this._debugServiceProxy.$customDebugAdapterRequest(this._id, command, args); + } +} + +export class ExtHostDebugConsole implements vscode.DebugConsole { + + private _debugServiceProxy: MainThreadDebugServiceShape; + + constructor(proxy: MainThreadDebugServiceShape) { + this._debugServiceProxy = proxy; + } + + append(value: string): void { + this._debugServiceProxy.$appendDebugConsole(value); + } + + appendLine(value: string): void { + this.append(value + '\n'); + } +} + +export class ExtHostVariableResolverService extends AbstractVariableResolverService { + + constructor(folders: vscode.WorkspaceFolder[], editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfigProvider, env?: IProcessEnvironment) { + super({ + getFolderUri: (folderName: string): URI | undefined => { + const found = folders.filter(f => f.name === folderName); + if (found && found.length > 0) { + return found[0].uri; + } + return undefined; + }, + getWorkspaceFolderCount: (): number => { + return folders.length; + }, + getConfigurationValue: (folderUri: URI, section: string): string | undefined => { + return configurationService.getConfiguration(undefined, folderUri).get(section); + }, + getExecPath: (): string | undefined => { + return env ? env['VSCODE_EXEC_PATH'] : undefined; + }, + getFilePath: (): string | undefined => { + const activeEditor = editorService.activeEditor(); + if (activeEditor) { + return path.normalize(activeEditor.document.uri.fsPath); + } + return undefined; + }, + getSelectedText: (): string | undefined => { + const activeEditor = editorService.activeEditor(); + if (activeEditor && !activeEditor.selection.isEmpty) { + return activeEditor.document.getText(activeEditor.selection); + } + return undefined; + }, + getLineNumber: (): string | undefined => { + const activeEditor = editorService.activeEditor(); + if (activeEditor) { + return String(activeEditor.selection.end.line + 1); + } + return undefined; + } + }, env); + } +} + +interface ConfigProviderTuple { + type: string; + handle: number; + provider: vscode.DebugConfigurationProvider; +} + +interface DescriptorFactoryTuple { + type: string; + handle: number; + factory: vscode.DebugAdapterDescriptorFactory; +} + +interface TrackerFactoryTuple { + type: string; + handle: number; + factory: vscode.DebugAdapterTrackerFactory; +} + +class MultiTracker implements vscode.DebugAdapterTracker { + + constructor(private trackers: vscode.DebugAdapterTracker[]) { + } + + onWillStartSession(): void { + this.trackers.forEach(t => t.onWillStartSession ? t.onWillStartSession() : undefined); + } + + onWillReceiveMessage(message: any): void { + this.trackers.forEach(t => t.onWillReceiveMessage ? t.onWillReceiveMessage(message) : undefined); + } + + onDidSendMessage(message: any): void { + this.trackers.forEach(t => t.onDidSendMessage ? t.onDidSendMessage(message) : undefined); + } + + onWillStopSession(): void { + this.trackers.forEach(t => t.onWillStopSession ? t.onWillStopSession() : undefined); + } + + onError(error: Error): void { + this.trackers.forEach(t => t.onError ? t.onError(error) : undefined); + } + + onExit(code: number, signal: string): void { + this.trackers.forEach(t => t.onExit ? t.onExit(code, signal) : undefined); + } +} + +/* + * Call directly into a debug adapter implementation + */ +class DirectDebugAdapter extends AbstractDebugAdapter { + + constructor(private implementation: vscode.DebugAdapter) { + super(); + + if (this.implementation.onSendMessage) { + implementation.onSendMessage((message: DebugProtocol.ProtocolMessage) => { + this.acceptMessage(message); + }); + } + } + + startSession(): Promise { + return Promise.resolve(undefined); + } + + sendMessage(message: DebugProtocol.ProtocolMessage): void { + if (this.implementation.handleMessage) { + this.implementation.handleMessage(message); + } + } + + stopSession(): Promise { + if (this.implementation.dispose) { + this.implementation.dispose(); + } + return Promise.resolve(undefined); + } +} + + +export class WorkerExtHostDebugService extends ExtHostDebugServiceBase { + constructor( + @IExtHostRpcService extHostRpcService: IExtHostRpcService, + @IExtHostWorkspace workspaceService: IExtHostWorkspace, + @IExtHostExtensionService extensionService: IExtHostExtensionService, + @IExtHostDocumentsAndEditors editorsService: IExtHostDocumentsAndEditors, + @IExtHostConfiguration configurationService: IExtHostConfiguration, + @IExtHostCommands commandService: IExtHostCommands + ) { + super(extHostRpcService, workspaceService, extensionService, editorsService, configurationService, commandService); + } +} diff --git a/src/vs/workbench/api/common/extHostDecorations.ts b/src/vs/workbench/api/common/extHostDecorations.ts index 507eaca490..6317bbe84e 100644 --- a/src/vs/workbench/api/common/extHostDecorations.ts +++ b/src/vs/workbench/api/common/extHostDecorations.ts @@ -12,6 +12,7 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { asArray } from 'vs/base/common/arrays'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { ILogService } from 'vs/platform/log/common/log'; interface ProviderData { provider: vscode.DecorationProvider; @@ -28,6 +29,7 @@ export class ExtHostDecorations implements IExtHostDecorations { constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, + @ILogService private readonly _logService: ILogService, ) { this._proxy = extHostRpc.getProxy(MainContext.MainThreadDecorations); } @@ -66,10 +68,10 @@ export class ExtHostDecorations implements IExtHostDecorations { Decoration.validate(data); result[id] = [data.priority, data.bubble, data.title, data.letter, data.color]; } catch (e) { - console.warn(`INVALID decoration from extension '${extensionId.value}': ${e}`); + this._logService.warn(`INVALID decoration from extension '${extensionId.value}': ${e}`); } }, err => { - console.error(err); + this._logService.error(err); }); })).then(() => { diff --git a/src/vs/workbench/api/common/extHostDiagnostics.ts b/src/vs/workbench/api/common/extHostDiagnostics.ts index 7c810fb08f..3c9292d5e2 100644 --- a/src/vs/workbench/api/common/extHostDiagnostics.ts +++ b/src/vs/workbench/api/common/extHostDiagnostics.ts @@ -13,6 +13,7 @@ import * as converter from './extHostTypeConverters'; import { mergeSort } from 'vs/base/common/arrays'; import { Event, Emitter } from 'vs/base/common/event'; import { keys } from 'vs/base/common/map'; +import { ILogService } from 'vs/platform/log/common/log'; export class DiagnosticCollection implements vscode.DiagnosticCollection { @@ -253,7 +254,7 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape { readonly onDidChangeDiagnostics: Event = Event.map(Event.debounce(this._onDidChangeDiagnostics.event, ExtHostDiagnostics._debouncer, 50), ExtHostDiagnostics._mapper); - constructor(mainContext: IMainContext) { + constructor(mainContext: IMainContext, @ILogService private readonly _logService: ILogService) { this._proxy = mainContext.getProxy(MainContext.MainThreadDiagnostics); } @@ -266,7 +267,7 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape { } else if (!_collections.has(name)) { owner = name; } else { - console.warn(`DiagnosticCollection with name '${name}' does already exist.`); + this._logService.warn(`DiagnosticCollection with name '${name}' does already exist.`); do { owner = name + ExtHostDiagnostics._idPool++; } while (_collections.has(owner)); diff --git a/src/vs/workbench/api/common/extHostDocumentData.ts b/src/vs/workbench/api/common/extHostDocumentData.ts index ebffa07ccf..fb883e475a 100644 --- a/src/vs/workbench/api/common/extHostDocumentData.ts +++ b/src/vs/workbench/api/common/extHostDocumentData.ts @@ -28,7 +28,6 @@ export class ExtHostDocumentData extends MirrorTextModel { private _languageId: string; private _isDirty: boolean; private _document?: vscode.TextDocument; - private _textLines: vscode.TextLine[] = []; private _isDisposed: boolean = false; constructor(proxy: MainThreadDocumentsShape, uri: URI, lines: string[], eol: string, @@ -130,33 +129,11 @@ export class ExtHostDocumentData extends MirrorTextModel { line = lineOrPosition; } - if (typeof line !== 'number' || line < 0 || line >= this._lines.length) { + if (typeof line !== 'number' || line < 0 || line >= this._lines.length || Math.floor(line) !== line) { throw new Error('Illegal value for `line`'); } - let result = this._textLines[line]; - if (!result || result.lineNumber !== line || result.text !== this._lines[line]) { - - const text = this._lines[line]; - const firstNonWhitespaceCharacterIndex = /^(\s*)/.exec(text)![1].length; - const range = new Range(line, 0, line, text.length); - const rangeIncludingLineBreak = line < this._lines.length - 1 - ? new Range(line, 0, line + 1, 0) - : range; - - result = Object.freeze({ - lineNumber: line, - range, - rangeIncludingLineBreak, - text, - firstNonWhitespaceCharacterIndex, //TODO@api, rename to 'leadingWhitespaceLength' - isEmptyOrWhitespace: firstNonWhitespaceCharacterIndex === text.length - }); - - this._textLines[line] = result; - } - - return result; + return new ExtHostDocumentLine(line, this._lines[line], line === this._lines.length - 1); } private _offsetAt(position: vscode.Position): number { @@ -239,8 +216,7 @@ export class ExtHostDocumentData extends MirrorTextModel { } else if (regExpLeadsToEndlessLoop(regexp)) { // use default when custom-regexp is bad - console.warn(`[getWordRangeAtPosition]: ignoring custom regexp '${regexp.source}' because it matches the empty string.`); - regexp = getWordDefinitionFor(this._languageId); + throw new Error(`[getWordRangeAtPosition]: ignoring custom regexp '${regexp.source}' because it matches the empty string.`); } const wordAtText = getWordAtText( @@ -256,3 +232,44 @@ export class ExtHostDocumentData extends MirrorTextModel { return undefined; } } + +class ExtHostDocumentLine implements vscode.TextLine { + + private readonly _line: number; + private readonly _text: string; + private readonly _isLastLine: boolean; + + constructor(line: number, text: string, isLastLine: boolean) { + this._line = line; + this._text = text; + this._isLastLine = isLastLine; + } + + public get lineNumber(): number { + return this._line; + } + + public get text(): string { + return this._text; + } + + public get range(): Range { + return new Range(this._line, 0, this._line, this._text.length); + } + + public get rangeIncludingLineBreak(): Range { + if (this._isLastLine) { + return this.range; + } + return new Range(this._line, 0, this._line + 1, 0); + } + + public get firstNonWhitespaceCharacterIndex(): number { + //TODO@api, rename to 'leadingWhitespaceLength' + return /^(\s*)/.exec(this._text)![1].length; + } + + public get isEmptyOrWhitespace(): boolean { + return this.firstNonWhitespaceCharacterIndex === this._text.length; + } +} diff --git a/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts b/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts index a5339ea50d..f9af051dae 100644 --- a/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts +++ b/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts @@ -11,7 +11,7 @@ import { ExtHostDocumentSaveParticipantShape, MainThreadTextEditorsShape, IResou import { TextEdit } from 'vs/workbench/api/common/extHostTypes'; import { Range, TextDocumentSaveReason, EndOfLine } from 'vs/workbench/api/common/extHostTypeConverters'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; -import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; +import { SaveReason } from 'vs/workbench/common/editor'; import * as vscode from 'vscode'; import { LinkedList } from 'vs/base/common/linkedList'; import { ILogService } from 'vs/platform/log/common/log'; diff --git a/src/vs/workbench/api/common/extHostDocuments.ts b/src/vs/workbench/api/common/extHostDocuments.ts index 298957e986..164b4d446c 100644 --- a/src/vs/workbench/api/common/extHostDocuments.ts +++ b/src/vs/workbench/api/common/extHostDocuments.ts @@ -12,6 +12,7 @@ import { ExtHostDocumentData, setWordDefinitionFor } from 'vs/workbench/api/comm import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import * as TypeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import * as vscode from 'vscode'; +import { assertIsDefined } from 'vs/base/common/types'; export class ExtHostDocuments implements ExtHostDocumentsShape { @@ -84,7 +85,7 @@ export class ExtHostDocuments implements ExtHostDocumentsShape { if (!promise) { promise = this._proxy.$tryOpenDocument(uri).then(() => { this._documentLoader.delete(uri.toString()); - return this._documentsAndEditors.getDocument(uri); + return assertIsDefined(this._documentsAndEditors.getDocument(uri)); }, err => { this._documentLoader.delete(uri.toString()); return Promise.reject(err); diff --git a/src/vs/workbench/api/common/extHostExtensionActivator.ts b/src/vs/workbench/api/common/extHostExtensionActivator.ts index be047c9bea..ff00ea50a5 100644 --- a/src/vs/workbench/api/common/extHostExtensionActivator.ts +++ b/src/vs/workbench/api/common/extHostExtensionActivator.ts @@ -9,6 +9,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ExtensionActivationError, MissingDependencyError } from 'vs/workbench/services/extensions/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; const NO_OP_VOID_PROMISE = Promise.resolve(undefined); @@ -182,7 +183,13 @@ export class ExtensionsActivator { */ private readonly _alreadyActivatedEvents: { [activationEvent: string]: boolean; }; - constructor(registry: ExtensionDescriptionRegistry, resolvedExtensions: ExtensionIdentifier[], hostExtensions: ExtensionIdentifier[], host: IExtensionsActivatorHost) { + constructor( + registry: ExtensionDescriptionRegistry, + resolvedExtensions: ExtensionIdentifier[], + hostExtensions: ExtensionIdentifier[], + host: IExtensionsActivatorHost, + @ILogService private readonly _logService: ILogService + ) { this._registry = registry; this._resolvedExtensionsSet = new Set(); resolvedExtensions.forEach((extensionId) => this._resolvedExtensionsSet.add(ExtensionIdentifier.toKey(extensionId))); @@ -308,7 +315,6 @@ export class ExtensionsActivator { } private _activateExtensions(extensions: ActivationIdAndReason[]): Promise { - // console.log('_activateExtensions: ', extensions.map(p => p.id.value)); if (extensions.length === 0) { return Promise.resolve(undefined); } @@ -335,9 +341,6 @@ export class ExtensionsActivator { const green = Object.keys(greenMap).map(id => greenMap[id]); - // console.log('greenExtensions: ', green.map(p => p.id.value)); - // console.log('redExtensions: ', red.map(p => p.id.value)); - if (red.length === 0) { // Finally reached only leafs! return Promise.all(green.map((p) => this._activateExtension(p.id, p.reason))).then(_ => undefined); @@ -362,8 +365,8 @@ export class ExtensionsActivator { const newlyActivatingExtension = this._host.actualActivateExtension(extensionId, reason).then(undefined, (err) => { this._host.onExtensionActivationError(extensionId, nls.localize('activationError', "Activating extension '{0}' failed: {1}.", extensionId.value, err.message)); - console.error('Activating extension `' + extensionId.value + '` failed: ', err.message); - console.log('Here is the error stack: ', err.stack); + this._logService.error(`Activating extension ${extensionId.value} failed due to an error:`); + this._logService.error(err); // Treat the extension as being empty return new FailedExtension(err); }).then((x: ActivatedExtension) => { diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index afcfa22e1e..a406a9082c 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -133,20 +133,26 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio const hostExtensions = new Set(); this._initData.hostExtensions.forEach((extensionId) => hostExtensions.add(ExtensionIdentifier.toKey(extensionId))); - this._activator = new ExtensionsActivator(this._registry, this._initData.resolvedExtensions, this._initData.hostExtensions, { - onExtensionActivationError: (extensionId: ExtensionIdentifier, error: ExtensionActivationError): void => { - this._mainThreadExtensionsProxy.$onExtensionActivationError(extensionId, error); - }, + this._activator = new ExtensionsActivator( + this._registry, + this._initData.resolvedExtensions, + this._initData.hostExtensions, + { + onExtensionActivationError: (extensionId: ExtensionIdentifier, error: ExtensionActivationError): void => { + this._mainThreadExtensionsProxy.$onExtensionActivationError(extensionId, error); + }, - actualActivateExtension: async (extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise => { - if (hostExtensions.has(ExtensionIdentifier.toKey(extensionId))) { - await this._mainThreadExtensionsProxy.$activateExtension(extensionId, reason); - return new HostExtension(); + actualActivateExtension: async (extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise => { + if (hostExtensions.has(ExtensionIdentifier.toKey(extensionId))) { + await this._mainThreadExtensionsProxy.$activateExtension(extensionId, reason); + return new HostExtension(); + } + const extensionDescription = this._registry.getExtensionDescription(extensionId)!; + return this._activateExtension(extensionDescription, reason); } - const extensionDescription = this._registry.getExtensionDescription(extensionId)!; - return this._activateExtension(extensionDescription, reason); - } - }); + }, + this._logService + ); this._extensionPathIndex = null; this._resolvers = Object.create(null); this._started = false; @@ -405,7 +411,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio // Handle "eager" activation extensions private _handleEagerExtensions(): Promise { this._activateByEvent('*', true).then(undefined, (err) => { - console.error(err); + this._logService.error(err); }); this._disposables.add(this._extHostWorkspace.onDidChangeWorkspace((e) => this._handleWorkspaceContainsEagerExtensions(e.added))); @@ -467,7 +473,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio // the file was found return ( this._activateById(extensionId, { startup: true, extensionId, activationEvent: `workspaceContains:${fileName}` }) - .then(undefined, err => console.error(err)) + .then(undefined, err => this._logService.error(err)) ); } } @@ -488,7 +494,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio const timer = setTimeout(async () => { tokenSource.cancel(); this._activateById(extensionId, { startup: true, extensionId, activationEvent: `workspaceContainsTimeout:${globPatterns.join(',')}` }) - .then(undefined, err => console.error(err)); + .then(undefined, err => this._logService.error(err)); }, AbstractExtHostExtensionService.WORKSPACE_CONTAINS_TIMEOUT); let exists: boolean = false; @@ -496,7 +502,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio exists = await searchP; } catch (err) { if (!errors.isPromiseCanceledError(err)) { - console.error(err); + this._logService.error(err); } } @@ -507,7 +513,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio // a file was found matching one of the glob patterns return ( this._activateById(extensionId, { startup: true, extensionId, activationEvent: `workspaceContains:${globPatterns.join(',')}` }) - .then(undefined, err => console.error(err)) + .then(undefined, err => this._logService.error(err)) ); } diff --git a/src/vs/workbench/api/common/extHostFileSystem.ts b/src/vs/workbench/api/common/extHostFileSystem.ts index fc1908ee2f..6af3598b40 100644 --- a/src/vs/workbench/api/common/extHostFileSystem.ts +++ b/src/vs/workbench/api/common/extHostFileSystem.ts @@ -44,6 +44,7 @@ class FsLinkProvider { const edges: Edge[] = []; let prevScheme: string | undefined; let prevState: State; + let lastState = State.LastKnownState; let nextState = State.LastKnownState; for (const scheme of schemes) { @@ -60,6 +61,8 @@ class FsLinkProvider { // keep creating new (next) states until the // end (and the BeforeColon-state) is reached if (pos + 1 === scheme.length) { + // Save the last state here, because we need to continue for the next scheme + lastState = nextState; nextState = State.BeforeColon; } else { nextState += 1; @@ -70,6 +73,8 @@ class FsLinkProvider { } prevScheme = scheme; + // Restore the last state + nextState = lastState; } // all link must match this pattern `:/` diff --git a/src/vs/workbench/api/common/extHostFileSystemEventService.ts b/src/vs/workbench/api/common/extHostFileSystemEventService.ts index e295d4f4b3..898d84ac90 100644 --- a/src/vs/workbench/api/common/extHostFileSystemEventService.ts +++ b/src/vs/workbench/api/common/extHostFileSystemEventService.ts @@ -3,16 +3,19 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { flatten } from 'vs/base/common/arrays'; -import { AsyncEmitter, Emitter, Event } from 'vs/base/common/event'; +import { AsyncEmitter, Emitter, Event, IWaitUntil } from 'vs/base/common/event'; import { IRelativePattern, parse } from 'vs/base/common/glob'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import * as vscode from 'vscode'; -import { ExtHostFileSystemEventServiceShape, FileSystemEvents, IMainContext, MainContext, IResourceFileEditDto, IResourceTextEditDto, MainThreadTextEditorsShape } from './extHost.protocol'; +import { ExtHostFileSystemEventServiceShape, FileSystemEvents, IMainContext, MainContext, MainThreadTextEditorsShape, IResourceFileEditDto, IResourceTextEditDto } from './extHost.protocol'; import * as typeConverter from './extHostTypeConverters'; import { Disposable, WorkspaceEdit } from './extHostTypes'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { FileOperation } from 'vs/platform/files/common/files'; +import { flatten } from 'vs/base/common/arrays'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { ILogService } from 'vs/platform/log/common/log'; class FileSystemWatcher implements vscode.FileSystemWatcher { @@ -96,87 +99,132 @@ class FileSystemWatcher implements vscode.FileSystemWatcher { } } -interface WillRenameListener { +interface IExtensionListener { extension: IExtensionDescription; - (e: vscode.FileWillRenameEvent): any; + (e: E): any; } export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServiceShape { - private readonly _onFileEvent = new Emitter(); + private readonly _onFileSystemEvent = new Emitter(); + private readonly _onDidRenameFile = new Emitter(); + private readonly _onDidCreateFile = new Emitter(); + private readonly _onDidDeleteFile = new Emitter(); private readonly _onWillRenameFile = new AsyncEmitter(); + private readonly _onWillCreateFile = new AsyncEmitter(); + private readonly _onWillDeleteFile = new AsyncEmitter(); readonly onDidRenameFile: Event = this._onDidRenameFile.event; + readonly onDidCreateFile: Event = this._onDidCreateFile.event; + readonly onDidDeleteFile: Event = this._onDidDeleteFile.event; + constructor( mainContext: IMainContext, + private readonly _logService: ILogService, private readonly _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors, private readonly _mainThreadTextEditors: MainThreadTextEditorsShape = mainContext.getProxy(MainContext.MainThreadTextEditors) ) { // } - public createFileSystemWatcher(globPattern: string | IRelativePattern, ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean): vscode.FileSystemWatcher { - return new FileSystemWatcher(this._onFileEvent.event, globPattern, ignoreCreateEvents, ignoreChangeEvents, ignoreDeleteEvents); + //--- file events + + createFileSystemWatcher(globPattern: string | IRelativePattern, ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean): vscode.FileSystemWatcher { + return new FileSystemWatcher(this._onFileSystemEvent.event, globPattern, ignoreCreateEvents, ignoreChangeEvents, ignoreDeleteEvents); } $onFileEvent(events: FileSystemEvents) { - this._onFileEvent.fire(events); + this._onFileSystemEvent.fire(events); } - $onFileRename(oldUri: UriComponents, newUri: UriComponents) { - this._onDidRenameFile.fire(Object.freeze({ oldUri: URI.revive(oldUri), newUri: URI.revive(newUri) })); + + //--- file operations + + $onDidRunFileOperation(operation: FileOperation, target: UriComponents, source: UriComponents | undefined): void { + switch (operation) { + case FileOperation.MOVE: + this._onDidRenameFile.fire(Object.freeze({ files: [{ oldUri: URI.revive(source!), newUri: URI.revive(target) }] })); + break; + case FileOperation.DELETE: + this._onDidDeleteFile.fire(Object.freeze({ files: [URI.revive(target)] })); + break; + case FileOperation.CREATE: + this._onDidCreateFile.fire(Object.freeze({ files: [URI.revive(target)] })); + break; + default: + //ignore, dont send + } } + getOnWillRenameFileEvent(extension: IExtensionDescription): Event { + return this._createWillExecuteEvent(extension, this._onWillRenameFile); + } + + getOnWillCreateFileEvent(extension: IExtensionDescription): Event { + return this._createWillExecuteEvent(extension, this._onWillCreateFile); + } + + getOnWillDeleteFileEvent(extension: IExtensionDescription): Event { + return this._createWillExecuteEvent(extension, this._onWillDeleteFile); + } + + private _createWillExecuteEvent(extension: IExtensionDescription, emitter: AsyncEmitter): Event { return (listener, thisArg, disposables) => { - const wrappedListener: WillRenameListener = ((e: vscode.FileWillRenameEvent) => { - listener.call(thisArg, e); - }); + const wrappedListener: IExtensionListener = function wrapped(e: E) { listener.call(thisArg, e); }; wrappedListener.extension = extension; - return this._onWillRenameFile.event(wrappedListener, undefined, disposables); + return emitter.event(wrappedListener, undefined, disposables); }; } - $onWillRename(oldUriDto: UriComponents, newUriDto: UriComponents): Promise { - const oldUri = URI.revive(oldUriDto); - const newUri = URI.revive(newUriDto); + async $onWillRunFileOperation(operation: FileOperation, target: UriComponents, source: UriComponents | undefined, timeout: number, token: CancellationToken): Promise { + switch (operation) { + case FileOperation.MOVE: + await this._fireWillEvent(this._onWillRenameFile, { files: [{ oldUri: URI.revive(source!), newUri: URI.revive(target) }] }, timeout, token); + break; + case FileOperation.DELETE: + await this._fireWillEvent(this._onWillDeleteFile, { files: [URI.revive(target)] }, timeout, token); + break; + case FileOperation.CREATE: + await this._fireWillEvent(this._onWillCreateFile, { files: [URI.revive(target)] }, timeout, token); + break; + default: + //ignore, dont send + } + } + + private async _fireWillEvent(emitter: AsyncEmitter, data: Omit, timeout: number, token: CancellationToken): Promise { const edits: WorkspaceEdit[] = []; - return Promise.resolve(this._onWillRenameFile.fireAsync((bucket, _listener) => { - return { - oldUri, - newUri, - waitUntil: (thenable: Promise): void => { - if (Object.isFrozen(bucket)) { - throw new TypeError('waitUntil cannot be called async'); - } - const index = bucket.length; - const wrappedThenable = Promise.resolve(thenable).then(result => { - // ignore all results except for WorkspaceEdits. Those - // are stored in a spare array - if (result instanceof WorkspaceEdit) { - edits[index] = result; - } - }); - bucket.push(wrappedThenable); - } - }; - }).then((): any => { - if (edits.length === 0) { - return undefined; + + await emitter.fireAsync(data, token, async (thenable, listener: IExtensionListener) => { + // ignore all results except for WorkspaceEdits. Those are stored in an array. + const now = Date.now(); + const result = await Promise.resolve(thenable); + if (result instanceof WorkspaceEdit) { + edits.push(result); } + + if (Date.now() - now > timeout) { + this._logService.warn('SLOW file-participant', listener.extension?.identifier); + } + }); + + if (token.isCancellationRequested) { + return; + } + + if (edits.length > 0) { // flatten all WorkspaceEdits collected via waitUntil-call // and apply them in one go. const allEdits = new Array>(); for (let edit of edits) { - if (edit) { // sparse array - let { edits } = typeConverter.WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors); - allEdits.push(edits); - } + let { edits } = typeConverter.WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors); + allEdits.push(edits); } return this._mainThreadTextEditors.$tryApplyWorkspaceEdit({ edits: flatten(allEdits) }); - })); + } } } diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 26e006ae98..8560d7f70c 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -7,7 +7,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { mixin } from 'vs/base/common/objects'; import * as vscode from 'vscode'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; -import { Range, Disposable, CompletionList, SnippetString, CodeActionKind, SymbolInformation, DocumentSymbol } from 'vs/workbench/api/common/extHostTypes'; +import { Range, Disposable, CompletionList, SnippetString, CodeActionKind, SymbolInformation, DocumentSymbol, SemanticTokensEdits } from 'vs/workbench/api/common/extHostTypes'; import { ISingleEditOperation } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; @@ -26,6 +26,9 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IURITransformer } from 'vs/base/common/uriIpc'; import { DisposableStore, dispose } from 'vs/base/common/lifecycle'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { encodeSemanticTokensDto } from 'vs/workbench/api/common/shared/semanticTokens'; +import { IdGenerator } from 'vs/base/common/idGenerator'; // --- adapter @@ -348,7 +351,7 @@ class CodeActionAdapter { only: context.only ? new CodeActionKind(context.only) : undefined }; - return asPromise(() => this._provider.provideCodeActions(doc, ran, codeActionContext, token)).then(commandsOrActions => { + return asPromise(() => this._provider.provideCodeActions(doc, ran, codeActionContext, token)).then((commandsOrActions): extHostProtocol.ICodeActionListDto | undefined => { if (!isNonEmptyArray(commandsOrActions) || token.isCancellationRequested) { return undefined; } @@ -386,11 +389,12 @@ class CodeActionAdapter { edit: candidate.edit && typeConvert.WorkspaceEdit.from(candidate.edit), kind: candidate.kind && candidate.kind.value, isPreferred: candidate.isPreferred, + disabled: candidate.disabled }); } } - return { cacheId, actions }; + return { cacheId, actions }; }); } @@ -471,13 +475,13 @@ class OnTypeFormattingAdapter { class NavigateTypeAdapter { - private readonly _symbolCache: { [id: number]: vscode.SymbolInformation } = Object.create(null); - private readonly _resultCache: { [id: number]: [number, number] } = Object.create(null); - private readonly _provider: vscode.WorkspaceSymbolProvider; + private readonly _symbolCache = new Map(); + private readonly _resultCache = new Map(); - constructor(provider: vscode.WorkspaceSymbolProvider) { - this._provider = provider; - } + constructor( + private readonly _provider: vscode.WorkspaceSymbolProvider, + private readonly _logService: ILogService + ) { } provideWorkspaceSymbols(search: string, token: CancellationToken): Promise { const result: extHostProtocol.IWorkspaceSymbolsDto = extHostProtocol.IdObject.mixin({ symbols: [] }); @@ -489,44 +493,42 @@ class NavigateTypeAdapter { continue; } if (!item.name) { - console.warn('INVALID SymbolInformation, lacks name', item); + this._logService.warn('INVALID SymbolInformation, lacks name', item); continue; } const symbol = extHostProtocol.IdObject.mixin(typeConvert.WorkspaceSymbol.from(item)); - this._symbolCache[symbol._id!] = item; + this._symbolCache.set(symbol._id!, item); result.symbols.push(symbol); } } }).then(() => { if (result.symbols.length > 0) { - this._resultCache[result._id!] = [result.symbols[0]._id!, result.symbols[result.symbols.length - 1]._id!]; + this._resultCache.set(result._id!, [result.symbols[0]._id!, result.symbols[result.symbols.length - 1]._id!]); } return result; }); } - resolveWorkspaceSymbol(symbol: extHostProtocol.IWorkspaceSymbolDto, token: CancellationToken): Promise { - + async resolveWorkspaceSymbol(symbol: extHostProtocol.IWorkspaceSymbolDto, token: CancellationToken): Promise { if (typeof this._provider.resolveWorkspaceSymbol !== 'function') { - return Promise.resolve(symbol); + return symbol; } - const item = this._symbolCache[symbol._id!]; + const item = this._symbolCache.get(symbol._id!); if (item) { - return asPromise(() => this._provider.resolveWorkspaceSymbol!(item, token)).then(value => { - return value && mixin(symbol, typeConvert.WorkspaceSymbol.from(value), true); - }); + const value = await asPromise(() => this._provider.resolveWorkspaceSymbol!(item, token)); + return value && mixin(symbol, typeConvert.WorkspaceSymbol.from(value), true); } - return Promise.resolve(undefined); + return undefined; } releaseWorkspaceSymbols(id: number): any { - const range = this._resultCache[id]; + const range = this._resultCache.get(id); if (range) { for (let [from, to] = range; from <= to; from++) { - delete this._symbolCache[from]; + this._symbolCache.delete(from); } - delete this._resultCache[id]; + this._resultCache.delete(id); } } } @@ -539,7 +541,8 @@ class RenameAdapter { constructor( private readonly _documents: ExtHostDocuments, - private readonly _provider: vscode.RenameProvider + private readonly _provider: vscode.RenameProvider, + private readonly _logService: ILogService ) { } provideRenameEdits(resource: URI, position: IPosition, newName: string, token: CancellationToken): Promise { @@ -588,7 +591,7 @@ class RenameAdapter { return undefined; } if (range.start.line > pos.line || range.end.line < pos.line) { - console.warn('INVALID rename location: position line must be within range start/end lines'); + this._logService.warn('INVALID rename location: position line must be within range start/end lines'); return undefined; } return { range: typeConvert.Range.from(range), text }; @@ -613,30 +616,148 @@ class RenameAdapter { } } +class SemanticTokensPreviousResult { + constructor( + public readonly resultId: string | undefined, + public readonly tokens?: Uint32Array, + ) { } +} + +export class SemanticTokensAdapter { + + private readonly _previousResults: Map; + private _nextResultId = 1; + + constructor( + private readonly _documents: ExtHostDocuments, + private readonly _provider: vscode.SemanticTokensProvider, + ) { + this._previousResults = new Map(); + } + + provideSemanticTokens(resource: URI, ranges: IRange[] | null, previousResultId: number, token: CancellationToken): Promise { + const doc = this._documents.getDocument(resource); + const previousResult = (previousResultId !== 0 ? this._previousResults.get(previousResultId) : null); + const opts: vscode.SemanticTokensRequestOptions = { + ranges: (Array.isArray(ranges) && ranges.length > 0 ? ranges.map(typeConvert.Range.to) : undefined), + previousResultId: (previousResult ? previousResult.resultId : undefined) + }; + return asPromise(() => this._provider.provideSemanticTokens(doc, opts, token)).then(value => { + if (!value) { + return null; + } + if (previousResult) { + this._previousResults.delete(previousResultId); + } + return this._send(SemanticTokensAdapter._convertToEdits(previousResult, value), value); + }); + } + + async releaseSemanticColoring(semanticColoringResultId: number): Promise { + this._previousResults.delete(semanticColoringResultId); + } + + private static _isSemanticTokens(v: vscode.SemanticTokens | vscode.SemanticTokensEdits): v is vscode.SemanticTokens { + return v && !!((v as vscode.SemanticTokens).data); + } + + private static _isSemanticTokensEdits(v: vscode.SemanticTokens | vscode.SemanticTokensEdits): v is vscode.SemanticTokensEdits { + return v && Array.isArray((v as vscode.SemanticTokensEdits).edits); + } + + private static _convertToEdits(previousResult: SemanticTokensPreviousResult | null | undefined, newResult: vscode.SemanticTokens | vscode.SemanticTokensEdits): vscode.SemanticTokens | vscode.SemanticTokensEdits { + if (!SemanticTokensAdapter._isSemanticTokens(newResult)) { + return newResult; + } + if (!previousResult || !previousResult.tokens) { + return newResult; + } + const oldData = previousResult.tokens; + const oldLength = oldData.length; + const newData = newResult.data; + const newLength = newData.length; + + let commonPrefixLength = 0; + const maxCommonPrefixLength = Math.min(oldLength, newLength); + while (commonPrefixLength < maxCommonPrefixLength && oldData[commonPrefixLength] === newData[commonPrefixLength]) { + commonPrefixLength++; + } + + if (commonPrefixLength === oldLength && commonPrefixLength === newLength) { + // complete overlap! + return new SemanticTokensEdits([], newResult.resultId); + } + + let commonSuffixLength = 0; + const maxCommonSuffixLength = maxCommonPrefixLength - commonPrefixLength; + while (commonSuffixLength < maxCommonSuffixLength && oldData[oldLength - commonSuffixLength - 1] === newData[newLength - commonSuffixLength - 1]) { + commonSuffixLength++; + } + + return new SemanticTokensEdits([{ + start: commonPrefixLength, + deleteCount: (oldLength - commonPrefixLength - commonSuffixLength), + data: newData.subarray(commonPrefixLength, newLength - commonSuffixLength) + }], newResult.resultId); + } + + private _send(value: vscode.SemanticTokens | vscode.SemanticTokensEdits, original: vscode.SemanticTokens | vscode.SemanticTokensEdits): VSBuffer | null { + if (SemanticTokensAdapter._isSemanticTokens(value)) { + const myId = this._nextResultId++; + this._previousResults.set(myId, new SemanticTokensPreviousResult(value.resultId, value.data)); + return encodeSemanticTokensDto({ + id: myId, + type: 'full', + data: value.data + }); + } + + if (SemanticTokensAdapter._isSemanticTokensEdits(value)) { + const myId = this._nextResultId++; + if (SemanticTokensAdapter._isSemanticTokens(original)) { + // store the original + this._previousResults.set(myId, new SemanticTokensPreviousResult(original.resultId, original.data)); + } else { + this._previousResults.set(myId, new SemanticTokensPreviousResult(value.resultId)); + } + return encodeSemanticTokensDto({ + id: myId, + type: 'delta', + deltas: (value.edits || []).map(edit => ({ start: edit.start, deleteCount: edit.deleteCount, data: edit.data })) + }); + } + + return null; + } +} + class SuggestAdapter { static supportsResolving(provider: vscode.CompletionItemProvider): boolean { return typeof provider.resolveCompletionItem === 'function'; } - private _documents: ExtHostDocuments; - private _commands: CommandsConverter; - private _provider: vscode.CompletionItemProvider; - private _cache = new Cache('CompletionItem'); private _disposables = new Map(); - constructor(documents: ExtHostDocuments, commands: CommandsConverter, provider: vscode.CompletionItemProvider) { - this._documents = documents; - this._commands = commands; - this._provider = provider; - } + constructor( + private readonly _documents: ExtHostDocuments, + private readonly _commands: CommandsConverter, + private readonly _provider: vscode.CompletionItemProvider, + private readonly _logService: ILogService + ) { } provideCompletionItems(resource: URI, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise { const doc = this._documents.getDocument(resource); const pos = typeConvert.Position.to(position); + // The default insert/replace ranges. It's important to compute them + // before asynchronously asking the provider for its results. See + // https://github.com/microsoft/vscode/issues/83400#issuecomment-546851421 + const replaceRange = doc.getWordRangeAtPosition(pos) || new Range(pos, pos); + const insertRange = replaceRange.with({ end: pos }); + return asPromise(() => this._provider.provideCompletionItems(doc, pos, token, typeConvert.CompletionContext.to(context))).then(value => { if (!value) { @@ -657,14 +778,10 @@ class SuggestAdapter { const disposables = new DisposableStore(); this._disposables.set(pid, disposables); - // the default text edit range - const wordRangeBeforePos = (doc.getWordRangeAtPosition(pos) as Range || new Range(pos, pos)) - .with({ end: pos }); - const result: extHostProtocol.ISuggestResultDto = { x: pid, b: [], - a: typeConvert.Range.from(wordRangeBeforePos), + a: { replace: typeConvert.Range.from(replaceRange), insert: typeConvert.Range.from(insertRange) }, c: list.isIncomplete || undefined }; @@ -711,7 +828,7 @@ class SuggestAdapter { private _convertCompletionItem(item: vscode.CompletionItem, position: vscode.Position, id: extHostProtocol.ChainedCacheId): extHostProtocol.ISuggestDataDto | undefined { if (typeof item.label !== 'string' || item.label.length === 0) { - console.warn('INVALID text edit -> must have at least a label'); + this._logService.warn('INVALID text edit -> must have at least a label'); return undefined; } @@ -751,21 +868,46 @@ class SuggestAdapter { } // 'overwrite[Before|After]'-logic - let range: vscode.Range | undefined; + let range: vscode.Range | { inserting: vscode.Range, replacing: vscode.Range; } | undefined; if (item.textEdit) { range = item.textEdit.range; } else if (item.range) { range = item.range; + } else if (item.range2) { + range = item.range2; } - result[extHostProtocol.ISuggestDataDtoField.range] = typeConvert.Range.from(range); - if (range && (!range.isSingleLine || range.start.line !== position.line)) { - console.warn('INVALID text edit -> must be single line and on the same line'); - return undefined; + if (range) { + if (Range.isRange(range)) { + if (!SuggestAdapter._isValidRangeForCompletion(range, position)) { + this._logService.trace('INVALID range -> must be single line and on the same line'); + return undefined; + } + result[extHostProtocol.ISuggestDataDtoField.range] = typeConvert.Range.from(range); + + } else { + if ( + !SuggestAdapter._isValidRangeForCompletion(range.inserting, position) + || !SuggestAdapter._isValidRangeForCompletion(range.replacing, position) + || !range.inserting.start.isEqual(range.replacing.start) + || !range.replacing.contains(range.inserting) + ) { + this._logService.trace('INVALID range -> must be single line, on the same line, insert range must be a prefix of replace range'); + return undefined; + } + result[extHostProtocol.ISuggestDataDtoField.range] = { + insert: typeConvert.Range.from(range.inserting), + replace: typeConvert.Range.from(range.replacing) + }; + } } return result; } + + private static _isValidRangeForCompletion(range: vscode.Range, position: vscode.Position): boolean { + return range.isSingleLine || range.start.line === position.line; + } } class SignatureHelpAdapter { @@ -966,7 +1108,8 @@ class SelectionRangeAdapter { constructor( private readonly _documents: ExtHostDocuments, - private readonly _provider: vscode.SelectionRangeProvider + private readonly _provider: vscode.SelectionRangeProvider, + private readonly _logService: ILogService ) { } provideSelectionRanges(resource: URI, pos: IPosition[], token: CancellationToken): Promise { @@ -978,7 +1121,7 @@ class SelectionRangeAdapter { return []; } if (allProviderRanges.length !== positions.length) { - console.warn('BAD selection ranges, provider must return ranges for each position'); + this._logService.warn('BAD selection ranges, provider must return ranges for each position'); return []; } @@ -1009,37 +1152,95 @@ class SelectionRangeAdapter { class CallHierarchyAdapter { + private readonly _idPool = new IdGenerator(''); + private readonly _cache = new Map>(); + constructor( private readonly _documents: ExtHostDocuments, - private readonly _provider: vscode.CallHierarchyItemProvider + private readonly _provider: vscode.CallHierarchyProvider ) { } - async provideCallsTo(uri: URI, position: IPosition, token: CancellationToken): Promise<[extHostProtocol.ICallHierarchyItemDto, IRange[]][] | undefined> { + async prepareSession(uri: URI, position: IPosition, token: CancellationToken): Promise { const doc = this._documents.getDocument(uri); const pos = typeConvert.Position.to(position); - const calls = await this._provider.provideCallHierarchyIncomingCalls(doc, pos, token); - if (!calls) { + + const item = await this._provider.prepareCallHierarchy(doc, pos, token); + if (!item) { return undefined; } - return calls.map(call => (<[extHostProtocol.ICallHierarchyItemDto, IRange[]]>[typeConvert.CallHierarchyItem.from(call.from), call.fromRanges.map(typeConvert.Range.from)])); + const sessionId = this._idPool.nextId(); + + this._cache.set(sessionId, new Map()); + return this._cacheAndConvertItem(sessionId, item); } - async provideCallsFrom(uri: URI, position: IPosition, token: CancellationToken): Promise<[extHostProtocol.ICallHierarchyItemDto, IRange[]][] | undefined> { - const doc = this._documents.getDocument(uri); - const pos = typeConvert.Position.to(position); - const calls = await this._provider.provideCallHierarchyOutgoingCalls(doc, pos, token); + async provideCallsTo(sessionId: string, itemId: string, token: CancellationToken): Promise { + const item = this._itemFromCache(sessionId, itemId); + if (!item) { + throw new Error('missing call hierarchy item'); + } + const calls = await this._provider.provideCallHierarchyIncomingCalls(item, token); if (!calls) { return undefined; } - return calls.map(call => (<[extHostProtocol.ICallHierarchyItemDto, IRange[]]>[typeConvert.CallHierarchyItem.from(call.to), call.fromRanges.map(typeConvert.Range.from)])); + return calls.map(call => { + return { + from: this._cacheAndConvertItem(sessionId, call.from), + fromRanges: call.fromRanges.map(r => typeConvert.Range.from(r)) + }; + }); + } + + async provideCallsFrom(sessionId: string, itemId: string, token: CancellationToken): Promise { + const item = this._itemFromCache(sessionId, itemId); + if (!item) { + throw new Error('missing call hierarchy item'); + } + const calls = await this._provider.provideCallHierarchyOutgoingCalls(item, token); + if (!calls) { + return undefined; + } + return calls.map(call => { + return { + to: this._cacheAndConvertItem(sessionId, call.to), + fromRanges: call.fromRanges.map(r => typeConvert.Range.from(r)) + }; + }); + } + + releaseSession(sessionId: string): void { + this._cache.delete(sessionId.charAt(0)); + } + + private _cacheAndConvertItem(itemOrSessionId: string, item: vscode.CallHierarchyItem): extHostProtocol.ICallHierarchyItemDto { + const sessionId = itemOrSessionId.charAt(0); + const map = this._cache.get(sessionId)!; + const dto: extHostProtocol.ICallHierarchyItemDto = { + _sessionId: sessionId, + _itemId: map.size.toString(36), + name: item.name, + detail: item.detail, + kind: typeConvert.SymbolKind.from(item.kind), + uri: item.uri, + range: typeConvert.Range.from(item.range), + selectionRange: typeConvert.Range.from(item.selectionRange), + }; + map.set(dto._itemId, item); + return dto; + } + + private _itemFromCache(sessionId: string, itemId: string): vscode.CallHierarchyItem | undefined { + const map = this._cache.get(sessionId); + return map && map.get(itemId); } } type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | HoverAdapter | DocumentHighlightAdapter | ReferenceAdapter | CodeActionAdapter | DocumentFormattingAdapter | RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter - | SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter | TypeDefinitionAdapter - | ColorProviderAdapter | FoldingProviderAdapter | DeclarationAdapter | SelectionRangeAdapter | CallHierarchyAdapter; + | SemanticTokensAdapter | SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter + | ImplementationAdapter | TypeDefinitionAdapter | ColorProviderAdapter | FoldingProviderAdapter + | DeclarationAdapter | SelectionRangeAdapter | CallHierarchyAdapter; class AdapterData { constructor( @@ -1119,7 +1320,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF return ExtHostLanguageFeatures._handlePool++; } - private _withAdapter(handle: number, ctor: { new(...args: any[]): A }, callback: (adapter: A, extension: IExtensionDescription | undefined) => Promise, fallbackValue: R): Promise { + private _withAdapter(handle: number, ctor: { new(...args: any[]): A; }, callback: (adapter: A, extension: IExtensionDescription | undefined) => Promise, fallbackValue: R): Promise { const data = this._adapter.get(handle); if (!data) { return Promise.resolve(fallbackValue); @@ -1330,7 +1531,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF // --- navigate types registerWorkspaceSymbolProvider(extension: IExtensionDescription, provider: vscode.WorkspaceSymbolProvider): vscode.Disposable { - const handle = this._addNewAdapter(new NavigateTypeAdapter(provider), extension); + const handle = this._addNewAdapter(new NavigateTypeAdapter(provider, this._logService), extension); this._proxy.$registerNavigateTypeSupport(handle); return this._createDisposable(handle); } @@ -1350,7 +1551,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF // --- rename registerRenameProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.RenameProvider): vscode.Disposable { - const handle = this._addNewAdapter(new RenameAdapter(this._documents, provider), extension); + const handle = this._addNewAdapter(new RenameAdapter(this._documents, provider, this._logService), extension); this._proxy.$registerRenameSupport(handle, this._transformDocumentSelector(selector), RenameAdapter.supportsResolving(provider)); return this._createDisposable(handle); } @@ -1363,10 +1564,28 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF return this._withAdapter(handle, RenameAdapter, adapter => adapter.resolveRenameLocation(URI.revive(resource), position, token), undefined); } + //#region semantic coloring + + registerSemanticTokensProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.SemanticTokensProvider, legend: vscode.SemanticTokensLegend): vscode.Disposable { + const handle = this._addNewAdapter(new SemanticTokensAdapter(this._documents, provider), extension); + this._proxy.$registerSemanticTokensProvider(handle, this._transformDocumentSelector(selector), legend); + return this._createDisposable(handle); + } + + $provideSemanticTokens(handle: number, resource: UriComponents, ranges: IRange[] | null, previousResultId: number, token: CancellationToken): Promise { + return this._withAdapter(handle, SemanticTokensAdapter, adapter => adapter.provideSemanticTokens(URI.revive(resource), ranges, previousResultId, token), null); + } + + $releaseSemanticTokens(handle: number, semanticColoringResultId: number): void { + this._withAdapter(handle, SemanticTokensAdapter, adapter => adapter.releaseSemanticColoring(semanticColoringResultId), undefined); + } + + //#endregion + // --- suggestion registerCompletionItemProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.CompletionItemProvider, triggerCharacters: string[]): vscode.Disposable { - const handle = this._addNewAdapter(new SuggestAdapter(this._documents, this._commands.converter, provider), extension); + const handle = this._addNewAdapter(new SuggestAdapter(this._documents, this._commands.converter, provider, this._logService), extension); this._proxy.$registerSuggestSupport(handle, this._transformDocumentSelector(selector), triggerCharacters, SuggestAdapter.supportsResolving(provider), extension.identifier); return this._createDisposable(handle); } @@ -1450,7 +1669,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF // --- smart select registerSelectionRangeProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.SelectionRangeProvider): vscode.Disposable { - const handle = this._addNewAdapter(new SelectionRangeAdapter(this._documents, provider), extension); + const handle = this._addNewAdapter(new SelectionRangeAdapter(this._documents, provider, this._logService), extension); this._proxy.$registerSelectionRangeProvider(handle, this._transformDocumentSelector(selector)); return this._createDisposable(handle); } @@ -1461,18 +1680,26 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF // --- call hierarchy - registerCallHierarchyProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.CallHierarchyItemProvider): vscode.Disposable { + registerCallHierarchyProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.CallHierarchyProvider): vscode.Disposable { const handle = this._addNewAdapter(new CallHierarchyAdapter(this._documents, provider), extension); this._proxy.$registerCallHierarchyProvider(handle, this._transformDocumentSelector(selector)); return this._createDisposable(handle); } - $provideCallHierarchyIncomingCalls(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<[extHostProtocol.ICallHierarchyItemDto, IRange[]][] | undefined> { - return this._withAdapter(handle, CallHierarchyAdapter, adapter => adapter.provideCallsTo(URI.revive(resource), position, token), undefined); + $prepareCallHierarchy(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise { + return this._withAdapter(handle, CallHierarchyAdapter, adapter => Promise.resolve(adapter.prepareSession(URI.revive(resource), position, token)), undefined); } - $provideCallHierarchyOutgoingCalls(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<[extHostProtocol.ICallHierarchyItemDto, IRange[]][] | undefined> { - return this._withAdapter(handle, CallHierarchyAdapter, adapter => adapter.provideCallsFrom(URI.revive(resource), position, token), undefined); + $provideCallHierarchyIncomingCalls(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise { + return this._withAdapter(handle, CallHierarchyAdapter, adapter => adapter.provideCallsTo(sessionId, itemId, token), undefined); + } + + $provideCallHierarchyOutgoingCalls(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise { + return this._withAdapter(handle, CallHierarchyAdapter, adapter => adapter.provideCallsFrom(sessionId, itemId, token), undefined); + } + + $releaseCallHierarchy(handle: number, sessionId: string): void { + this._withAdapter(handle, CallHierarchyAdapter, adapter => Promise.resolve(adapter.releaseSession(sessionId)), undefined); } // --- configuration diff --git a/src/vs/workbench/api/common/extHostMessageService.ts b/src/vs/workbench/api/common/extHostMessageService.ts index db03a9f4d6..f28190fc82 100644 --- a/src/vs/workbench/api/common/extHostMessageService.ts +++ b/src/vs/workbench/api/common/extHostMessageService.ts @@ -7,6 +7,7 @@ import Severity from 'vs/base/common/severity'; import * as vscode from 'vscode'; import { MainContext, MainThreadMessageServiceShape, MainThreadMessageOptions, IMainContext } from './extHost.protocol'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; function isMessageItem(item: any): item is vscode.MessageItem { return item && item.title; @@ -16,7 +17,10 @@ export class ExtHostMessageService { private _proxy: MainThreadMessageServiceShape; - constructor(mainContext: IMainContext) { + constructor( + mainContext: IMainContext, + @ILogService private readonly _logService: ILogService + ) { this._proxy = mainContext.getProxy(MainContext.MainThreadMessageService); } @@ -45,7 +49,7 @@ export class ExtHostMessageService { let { title, isCloseAffordance } = command; commands.push({ title, isCloseAffordance: !!isCloseAffordance, handle }); } else { - console.warn('Invalid message item:', command); + this._logService.warn('Invalid message item:', command); } } diff --git a/src/vs/workbench/api/common/extHostQuickOpen.ts b/src/vs/workbench/api/common/extHostQuickOpen.ts index 68c953e298..5b087491c8 100644 --- a/src/vs/workbench/api/common/extHostQuickOpen.ts +++ b/src/vs/workbench/api/common/extHostQuickOpen.ts @@ -135,7 +135,7 @@ export class ExtHostQuickOpen implements ExtHostQuickOpenShape { // ---- input - showInput(options?: InputBoxOptions, token: CancellationToken = CancellationToken.None): Promise { + showInput(options?: InputBoxOptions, token: CancellationToken = CancellationToken.None): Promise { // global validate fn used in callback below this._validateInput = options ? options.validateInput : undefined; @@ -160,7 +160,7 @@ export class ExtHostQuickOpen implements ExtHostQuickOpenShape { // ---- workspace folder picker showWorkspaceFolderPick(options?: WorkspaceFolderPickOptions, token = CancellationToken.None): Promise { - return this._commands.executeCommand('_workbench.pickWorkspaceFolder', [options]).then(async (selectedFolder: WorkspaceFolder) => { + return this._commands.executeCommand('_workbench.pickWorkspaceFolder', [options]).then(async (selectedFolder: WorkspaceFolder) => { if (!selectedFolder) { return undefined; } @@ -485,6 +485,7 @@ class ExtHostQuickPick extends ExtHostQuickInput implem private _canSelectMany = false; private _matchOnDescription = true; private _matchOnDetail = true; + private _sortByLabel = true; private _activeItems: T[] = []; private readonly _onDidChangeActiveEmitter = new Emitter(); private _selectedItems: T[] = []; @@ -550,6 +551,15 @@ class ExtHostQuickPick extends ExtHostQuickInput implem this.update({ matchOnDetail }); } + get sortByLabel() { + return this._sortByLabel; + } + + set sortByLabel(sortByLabel: boolean) { + this._sortByLabel = sortByLabel; + this.update({ sortByLabel }); + } + get activeItems() { return this._activeItems; } diff --git a/src/vs/workbench/api/common/extHostRequireInterceptor.ts b/src/vs/workbench/api/common/extHostRequireInterceptor.ts index 669f1e4725..67209e8096 100644 --- a/src/vs/workbench/api/common/extHostRequireInterceptor.ts +++ b/src/vs/workbench/api/common/extHostRequireInterceptor.ts @@ -18,6 +18,7 @@ import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitData import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; import { platform } from 'vs/base/common/process'; +import { ILogService } from 'vs/platform/log/common/log'; import { AzdataNodeModuleFactory, SqlopsNodeModuleFactory } from 'sql/workbench/api/common/extHostRequireInterceptor'; // {{SQL CARBON EDIT}} import { IExtensionApiFactory as sqlIApiFactory } from 'sql/workbench/api/common/sqlExtHost.api.impl'; // {{SQL CARBON EDIT}} @@ -44,7 +45,8 @@ export abstract class RequireInterceptor { @IInstantiationService private readonly _instaService: IInstantiationService, @IExtHostConfiguration private readonly _extHostConfiguration: IExtHostConfiguration, @IExtHostExtensionService private readonly _extHostExtensionService: IExtHostExtensionService, - @IExtHostInitDataService private readonly _initData: IExtHostInitDataService + @IExtHostInitDataService private readonly _initData: IExtHostInitDataService, + @ILogService private readonly _logService: ILogService, ) { this._factories = new Map(); this._alternatives = []; @@ -57,7 +59,7 @@ export abstract class RequireInterceptor { const configProvider = await this._extHostConfiguration.getConfigProvider(); const extensionPaths = await this._extHostExtensionService.getExtensionPathIndex(); - this.register(new VSCodeNodeModuleFactory(this._apiFactory.vscode, extensionPaths, this._extensionRegistry, configProvider)); // {{SQL CARBON EDIT}} // add node module + this.register(new VSCodeNodeModuleFactory(this._apiFactory.vscode, extensionPaths, this._extensionRegistry, configProvider, this._logService)); // {{SQL CARBON EDIT}} // add node module this.register(new AzdataNodeModuleFactory(this._apiFactory.azdata, extensionPaths)); // {{SQL CARBON EDIT}} // add node module this.register(new SqlopsNodeModuleFactory(this._apiFactory.sqlops, extensionPaths)); // {{SQL CARBON EDIT}} // add node module this.register(this._instaService.createInstance(KeytarNodeModuleFactory)); @@ -96,7 +98,8 @@ class VSCodeNodeModuleFactory implements INodeModuleFactory { private readonly _apiFactory: IExtensionApiFactory, private readonly _extensionPaths: TernarySearchTree, private readonly _extensionRegistry: ExtensionDescriptionRegistry, - private readonly _configProvider: ExtHostConfigProvider + private readonly _configProvider: ExtHostConfigProvider, + private readonly _logService: ILogService, ) { } @@ -117,7 +120,7 @@ class VSCodeNodeModuleFactory implements INodeModuleFactory { if (!this._defaultApiImpl) { let extensionPathsPretty = ''; this._extensionPaths.forEach((value, index) => extensionPathsPretty += `\t${index} -> ${value.identifier.value}\n`); - console.warn(`Could not identify extension for 'vscode' require call from ${parent.fsPath}. These are the extension path mappings: \n${extensionPathsPretty}`); + this._logService.warn(`Could not identify extension for 'vscode' require call from ${parent.fsPath}. These are the extension path mappings: \n${extensionPathsPretty}`); this._defaultApiImpl = this._apiFactory(nullExtensionDescription, this._extensionRegistry, this._configProvider); } return this._defaultApiImpl; @@ -247,9 +250,9 @@ class OpenNodeModuleFactory implements INodeModuleFactory { return this.callOriginal(target, options); } if (uri.scheme === 'http' || uri.scheme === 'https') { - return mainThreadWindow.$openUri(uri, { allowTunneling: true }); + return mainThreadWindow.$openUri(uri, target, { allowTunneling: true }); } else if (uri.scheme === 'mailto' || uri.scheme === this._appUriScheme) { - return mainThreadWindow.$openUri(uri, {}); + return mainThreadWindow.$openUri(uri, target, {}); } return this.callOriginal(target, options); }; diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index a2f1fe8e27..658c93c135 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -180,8 +180,7 @@ export class ExtHostSCMInputBox implements vscode.SourceControlInputBox { } if (fn && typeof fn !== 'function') { - console.warn('Invalid SCM input box validation function'); - return; + throw new Error(`[${this._extension.identifier.value}]: Invalid SCM input box validation function`); } this._validateInput = fn; diff --git a/src/vs/workbench/api/common/extHostSearch.ts b/src/vs/workbench/api/common/extHostSearch.ts index 90fd4a5df3..6543a942d5 100644 --- a/src/vs/workbench/api/common/extHostSearch.ts +++ b/src/vs/workbench/api/common/extHostSearch.ts @@ -3,10 +3,17 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import * as vscode from 'vscode'; -import { ExtHostSearchShape } from '../common/extHost.protocol'; +import { ExtHostSearchShape, MainThreadSearchShape, MainContext } from '../common/extHost.protocol'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { FileSearchManager } from 'vs/workbench/services/search/common/fileSearchManager'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IRawFileQuery, ISearchCompleteStats, IFileQuery, IRawTextQuery, IRawQuery, ITextQuery, IFolderQuery } from 'vs/workbench/services/search/common/search'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { TextSearchManager } from 'vs/workbench/services/search/common/textSearchManager'; export interface IExtHostSearch extends ExtHostSearchShape { registerTextSearchProvider(scheme: string, provider: vscode.TextSearchProvider): IDisposable; @@ -14,3 +21,111 @@ export interface IExtHostSearch extends ExtHostSearchShape { } export const IExtHostSearch = createDecorator('IExtHostSearch'); + +export class ExtHostSearch implements ExtHostSearchShape { + + protected readonly _proxy: MainThreadSearchShape = this.extHostRpc.getProxy(MainContext.MainThreadSearch); + protected _handlePool: number = 0; + + private readonly _textSearchProvider = new Map(); + private readonly _textSearchUsedSchemes = new Set(); + private readonly _fileSearchProvider = new Map(); + private readonly _fileSearchUsedSchemes = new Set(); + + private readonly _fileSearchManager = new FileSearchManager(); + + constructor( + @IExtHostRpcService private extHostRpc: IExtHostRpcService, + @IURITransformerService protected _uriTransformer: IURITransformerService, + @ILogService protected _logService: ILogService + ) { } + + protected _transformScheme(scheme: string): string { + return this._uriTransformer.transformOutgoingScheme(scheme); + } + + registerTextSearchProvider(scheme: string, provider: vscode.TextSearchProvider): IDisposable { + if (this._textSearchUsedSchemes.has(scheme)) { + throw new Error(`a text search provider for the scheme '${scheme}' is already registered`); + } + + this._textSearchUsedSchemes.add(scheme); + const handle = this._handlePool++; + this._textSearchProvider.set(handle, provider); + this._proxy.$registerTextSearchProvider(handle, this._transformScheme(scheme)); + return toDisposable(() => { + this._textSearchUsedSchemes.delete(scheme); + this._textSearchProvider.delete(handle); + this._proxy.$unregisterProvider(handle); + }); + } + + registerFileSearchProvider(scheme: string, provider: vscode.FileSearchProvider): IDisposable { + if (this._fileSearchUsedSchemes.has(scheme)) { + throw new Error(`a file search provider for the scheme '${scheme}' is already registered`); + } + + this._fileSearchUsedSchemes.add(scheme); + const handle = this._handlePool++; + this._fileSearchProvider.set(handle, provider); + this._proxy.$registerFileSearchProvider(handle, this._transformScheme(scheme)); + return toDisposable(() => { + this._fileSearchUsedSchemes.delete(scheme); + this._fileSearchProvider.delete(handle); + this._proxy.$unregisterProvider(handle); + }); + } + + $provideFileSearchResults(handle: number, session: number, rawQuery: IRawFileQuery, token: vscode.CancellationToken): Promise { + const query = reviveQuery(rawQuery); + const provider = this._fileSearchProvider.get(handle); + if (provider) { + return this._fileSearchManager.fileSearch(query, provider, batch => { + this._proxy.$handleFileMatch(handle, session, batch.map(p => p.resource)); + }, token); + } else { + throw new Error('unknown provider: ' + handle); + } + } + + $clearCache(cacheKey: string): Promise { + this._fileSearchManager.clearCache(cacheKey); + + return Promise.resolve(undefined); + } + + $provideTextSearchResults(handle: number, session: number, rawQuery: IRawTextQuery, token: vscode.CancellationToken): Promise { + const provider = this._textSearchProvider.get(handle); + if (!provider || !provider.provideTextSearchResults) { + throw new Error(`Unknown provider ${handle}`); + } + + const query = reviveQuery(rawQuery); + const engine = this.createTextSearchManager(query, provider); + return engine.search(progress => this._proxy.$handleTextMatch(handle, session, progress), token); + } + + protected createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProvider): TextSearchManager { + return new TextSearchManager(query, provider, { + readdir: resource => Promise.resolve([]), // TODO@rob implement + toCanonicalName: encoding => encoding + }); + } +} + +export function reviveQuery(rawQuery: U): U extends IRawTextQuery ? ITextQuery : IFileQuery { + return { + ...rawQuery, // TODO@rob ??? + ...{ + folderQueries: rawQuery.folderQueries && rawQuery.folderQueries.map(reviveFolderQuery), + extraFileResources: rawQuery.extraFileResources && rawQuery.extraFileResources.map(components => URI.revive(components)) + } + }; +} + +function reviveFolderQuery(rawFolderQuery: IFolderQuery): IFolderQuery { + return { + ...rawFolderQuery, + folder: URI.revive(rawFolderQuery.folder) + }; +} diff --git a/src/vs/workbench/api/common/extHostTask.ts b/src/vs/workbench/api/common/extHostTask.ts index 417bc4b63e..e41b8ac5d8 100644 --- a/src/vs/workbench/api/common/extHostTask.ts +++ b/src/vs/workbench/api/common/extHostTask.ts @@ -24,6 +24,7 @@ import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitData import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Schemas } from 'vs/base/common/network'; import * as Platform from 'vs/base/common/platform'; +import { ILogService } from 'vs/platform/log/common/log'; export interface IExtHostTask extends ExtHostTaskShape { @@ -374,6 +375,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { protected readonly _editorService: IExtHostDocumentsAndEditors; protected readonly _configurationService: IExtHostConfiguration; protected readonly _terminalService: IExtHostTerminalService; + protected readonly _logService: ILogService; protected _handleCounter: number; protected _handlers: Map; protected _taskExecutions: Map; @@ -393,7 +395,8 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { @IExtHostWorkspace workspaceService: IExtHostWorkspace, @IExtHostDocumentsAndEditors editorService: IExtHostDocumentsAndEditors, @IExtHostConfiguration configurationService: IExtHostConfiguration, - @IExtHostTerminalService extHostTerminalService: IExtHostTerminalService + @IExtHostTerminalService extHostTerminalService: IExtHostTerminalService, + @ILogService logService: ILogService ) { this._proxy = extHostRpc.getProxy(MainContext.MainThreadTask); this._workspaceProvider = workspaceService; @@ -406,6 +409,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { this._providedCustomExecutions2 = new Map(); this._notProvidedCustomExecutions = new Set(); this._activeCustomExecutions2 = new Map(); + this._logService = logService; } public registerTaskProvider(extension: IExtensionDescription, type: string, provider: vscode.TaskProvider): vscode.Disposable { @@ -457,6 +461,10 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { return this._onDidExecuteTask.event; } + protected async resolveDefinition(uri: number | UriComponents | undefined, definition: vscode.TaskDefinition | undefined): Promise { + return definition; + } + public async $onDidStartTask(execution: tasks.TaskExecutionDTO, terminalId: number): Promise { const customExecution: types.CustomExecution | undefined = this._providedCustomExecutions2.get(execution.id); if (customExecution) { @@ -466,7 +474,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { // Clone the custom execution to keep the original untouched. This is important for multiple runs of the same task. this._activeCustomExecutions2.set(execution.id, customExecution); - this._terminalService.attachPtyToTerminal(terminalId, await customExecution.callback()); + this._terminalService.attachPtyToTerminal(terminalId, await customExecution.callback(await this.resolveDefinition(execution.task?.source.scope, execution.task?.definition))); } this._lastStartedTask = execution.id; @@ -657,9 +665,10 @@ export class WorkerExtHostTask extends ExtHostTaskBase { @IExtHostWorkspace workspaceService: IExtHostWorkspace, @IExtHostDocumentsAndEditors editorService: IExtHostDocumentsAndEditors, @IExtHostConfiguration configurationService: IExtHostConfiguration, - @IExtHostTerminalService extHostTerminalService: IExtHostTerminalService + @IExtHostTerminalService extHostTerminalService: IExtHostTerminalService, + @ILogService logService: ILogService ) { - super(extHostRpc, initData, workspaceService, editorService, configurationService, extHostTerminalService); + super(extHostRpc, initData, workspaceService, editorService, configurationService, extHostTerminalService, logService); if (initData.remote.isRemote && initData.remote.authority) { this.registerTaskSystem(Schemas.vscodeRemote, { scheme: Schemas.vscodeRemote, @@ -692,7 +701,7 @@ export class WorkerExtHostTask extends ExtHostTaskBase { if (value) { for (let task of value) { if (!task.definition || !validTypes[task.definition.type]) { - console.warn(`The task [${task.source}, ${task.name}] uses an undefined task type. The task will be ignored in the future.`); + this._logService.warn(`The task [${task.source}, ${task.name}] uses an undefined task type. The task will be ignored in the future.`); } const taskDTO: tasks.TaskDTO | undefined = TaskDTO.from(task, handler.extension); @@ -703,7 +712,7 @@ export class WorkerExtHostTask extends ExtHostTaskBase { // is invoked, we have to be able to map it back to our data. taskIdPromises.push(this.addCustomExecution(taskDTO, task, true)); } else { - console.warn('Only custom execution tasks supported.'); + this._logService.warn('Only custom execution tasks supported.'); } } } @@ -717,7 +726,7 @@ export class WorkerExtHostTask extends ExtHostTaskBase { if (CustomExecutionDTO.is(resolvedTaskDTO.execution)) { return resolvedTaskDTO; } else { - console.warn('Only custom execution tasks supported.'); + this._logService.warn('Only custom execution tasks supported.'); } return undefined; } diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 40d279aa7a..f093e0444e 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -9,9 +9,10 @@ import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShap import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { EXT_HOST_CREATION_DELAY, ITerminalChildProcess, ITerminalDimensions } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalChildProcess, ITerminalDimensions, EXT_HOST_CREATION_DELAY } from 'vs/workbench/contrib/terminal/common/terminal'; import { timeout } from 'vs/base/common/async'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/terminalDataBuffering'; export interface IExtHostTerminalService extends ExtHostTerminalServiceShape { @@ -96,15 +97,18 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi private _cols: number | undefined; private _pidPromiseComplete: ((value: number | undefined) => any) | undefined; private _rows: number | undefined; + private _exitStatus: vscode.TerminalExitStatus | undefined; public isOpen: boolean = false; constructor( proxy: MainThreadTerminalServiceShape, + private readonly _creationOptions: vscode.TerminalOptions | vscode.ExtensionTerminalOptions, private _name?: string, id?: number ) { super(proxy, id); + this._creationOptions = Object.freeze(this._creationOptions); this._pidPromise = new Promise(c => this._pidPromiseComplete = c); } @@ -137,6 +141,10 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi this._name = name; } + public get exitStatus(): vscode.TerminalExitStatus | undefined { + return this._exitStatus; + } + public get dimensions(): vscode.TerminalDimensions | undefined { if (this._cols === undefined || this._rows === undefined) { return undefined; @@ -147,6 +155,10 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi }; } + public setExitCode(code: number | undefined) { + this._exitStatus = Object.freeze({ code }); + } + public setDimensions(cols: number, rows: number): boolean { if (cols === this._cols && rows === this._rows) { // Nothing changed @@ -161,6 +173,10 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi return this._pidPromise; } + public get creationOptions(): Readonly { + return this._creationOptions; + } + public sendText(text: string, addNewLine: boolean = true): void { this._checkDisposed(); this._queueApiRequest(this._proxy.$sendText, [text, addNewLine]); @@ -209,8 +225,8 @@ class ApiRequest { export class ExtHostPseudoterminal implements ITerminalChildProcess { private readonly _onProcessData = new Emitter(); public readonly onProcessData: Event = this._onProcessData.event; - private readonly _onProcessExit = new Emitter(); - public readonly onProcessExit: Event = this._onProcessExit.event; + private readonly _onProcessExit = new Emitter(); + public readonly onProcessExit: Event = this._onProcessExit.event; private readonly _onProcessReady = new Emitter<{ pid: number, cwd: string }>(); public get onProcessReady(): Event<{ pid: number, cwd: string }> { return this._onProcessReady.event; } private readonly _onProcessTitleChanged = new Emitter(); @@ -252,7 +268,9 @@ export class ExtHostPseudoterminal implements ITerminalChildProcess { // Attach the listeners this._pty.onDidWrite(e => this._onProcessData.fire(e)); if (this._pty.onDidClose) { - this._pty.onDidClose(e => this._onProcessExit.fire(e || 0)); + this._pty.onDidClose((e: number | void = undefined) => { + this._onProcessExit.fire(e === void 0 ? undefined : e as number); // {{SQL CARBON EDIT}} strict-null-checks + }); } if (this._pty.onDidOverrideDimensions) { this._pty.onDidOverrideDimensions(e => this._onProcessOverrideDimensions.fire(e ? { cols: e.columns, rows: e.rows } : undefined)); // {{SQL CARBONEDIT}} strict-null-checks @@ -270,6 +288,7 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ protected _activeTerminal: ExtHostTerminal | undefined; protected _terminals: ExtHostTerminal[] = []; protected _terminalProcesses: { [id: number]: ITerminalChildProcess } = {}; + protected _extensionTerminalAwaitingStart: { [id: number]: { initialDimensions: ITerminalDimensionsDto | undefined } | undefined } = {}; protected _getTerminalPromises: { [id: number]: Promise } = {}; public get activeTerminal(): ExtHostTerminal | undefined { return this._activeTerminal; } @@ -286,9 +305,13 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ protected readonly _onDidWriteTerminalData: Emitter; public get onDidWriteTerminalData(): Event { return this._onDidWriteTerminalData && this._onDidWriteTerminalData.event; } + private readonly _bufferer: TerminalDataBufferer; + constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService ) { + this._bufferer = new TerminalDataBufferer(); + this._proxy = extHostRpc.getProxy(MainContext.MainThreadTerminalService); this._onDidWriteTerminalData = new Emitter({ onFirstListenerAdd: () => this._proxy.$startSendingDataEvents(), @@ -305,7 +328,7 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ public abstract $acceptWorkspacePermissionsChanged(isAllowed: boolean): void; public createExtensionTerminal(options: vscode.ExtensionTerminalOptions): vscode.Terminal { - const terminal = new ExtHostTerminal(this._proxy, options.name); + const terminal = new ExtHostTerminal(this._proxy, options, options.name); const p = new ExtHostPseudoterminal(options.pty); terminal.createExtensionTerminal().then(id => this._setupExtHostProcessListeners(id, p)); this._terminals.push(terminal); @@ -364,7 +387,7 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ if (this._terminalProcesses[id]) { // Extension pty terminal only - when virtual process resize fires it means that the // terminal's maximum dimensions changed - this._terminalProcesses[id].resize(cols, rows); + this._terminalProcesses[id]?.resize(cols, rows); } } @@ -376,16 +399,17 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ } } - public async $acceptTerminalClosed(id: number): Promise { + public async $acceptTerminalClosed(id: number, exitCode: number | undefined): Promise { await this._getTerminalByIdEventually(id); const index = this._getTerminalObjectIndexById(this.terminals, id); if (index !== null) { const terminal = this._terminals.splice(index, 1)[0]; + terminal.setExitCode(exitCode); this._onDidCloseTerminal.fire(terminal); } } - public $acceptTerminalOpened(id: number, name: string): void { + public $acceptTerminalOpened(id: number, name: string, shellLaunchConfigDto: IShellLaunchConfigDto): void { const index = this._getTerminalObjectIndexById(this._terminals, id); if (index !== null) { // The terminal has already been created (via createTerminal*), only fire the event @@ -394,7 +418,14 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ return; } - const terminal = new ExtHostTerminal(this._proxy, name, id); + const creationOptions: vscode.TerminalOptions = { + name: shellLaunchConfigDto.name, + shellPath: shellLaunchConfigDto.executable, + shellArgs: shellLaunchConfigDto.args, + cwd: typeof shellLaunchConfigDto.cwd === 'string' ? shellLaunchConfigDto.cwd : URI.revive(shellLaunchConfigDto.cwd), + env: shellLaunchConfigDto.env + }; + const terminal = new ExtHostTerminal(this._proxy, creationOptions, name, id); this._terminals.push(terminal); this._onDidOpenTerminal.fire(terminal); terminal.isOpen = true; @@ -407,22 +438,6 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ } } - public performTerminalIdAction(id: number, callback: (terminal: ExtHostTerminal) => void): void { - // TODO: Use await this._getTerminalByIdEventually(id); - let terminal = this._getTerminalById(id); - if (terminal) { - callback(terminal); - } else { - // Retry one more time in case the terminal has not yet been initialized. - setTimeout(() => { - terminal = this._getTerminalById(id); - if (terminal) { - callback(terminal); - } - }, EXT_HOST_CREATION_DELAY * 2); - } - } - public async $startExtensionTerminal(id: number, initialDimensions: ITerminalDimensionsDto | undefined): Promise { // Make sure the ExtHostTerminal exists so onDidOpenTerminal has fired before we call // Pseudoterminal.start @@ -448,37 +463,42 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ } await openPromise; - // Processes should be initialized here for normal virtual process terminals, however for - // tasks they are responsible for attaching the virtual process to a terminal so this - // function may be called before tasks is able to attach to the terminal. - let retries = 5; - while (retries-- > 0) { - if (this._terminalProcesses[id]) { - (this._terminalProcesses[id] as ExtHostPseudoterminal).startSendingEvents(initialDimensions); - return; - } - await timeout(50); + if (this._terminalProcesses[id]) { + (this._terminalProcesses[id] as ExtHostPseudoterminal).startSendingEvents(initialDimensions); + } else { + // Defer startSendingEvents call to when _setupExtHostProcessListeners is called + this._extensionTerminalAwaitingStart[id] = { initialDimensions }; } + } protected _setupExtHostProcessListeners(id: number, p: ITerminalChildProcess): void { p.onProcessReady((e: { pid: number, cwd: string }) => this._proxy.$sendProcessReady(id, e.pid, e.cwd)); p.onProcessTitleChanged(title => this._proxy.$sendProcessTitle(id, title)); - p.onProcessData(data => this._proxy.$sendProcessData(id, data)); + + // Buffer data events to reduce the amount of messages going to the renderer + this._bufferer.startBuffering(id, p.onProcessData, this._proxy.$sendProcessData); p.onProcessExit(exitCode => this._onProcessExit(id, exitCode)); + if (p.onProcessOverrideDimensions) { p.onProcessOverrideDimensions(e => this._proxy.$sendOverrideDimensions(id, e)); } this._terminalProcesses[id] = p; + + const awaitingStart = this._extensionTerminalAwaitingStart[id]; + if (awaitingStart && p instanceof ExtHostPseudoterminal) { + p.startSendingEvents(awaitingStart.initialDimensions); + delete this._extensionTerminalAwaitingStart[id]; + } } public $acceptProcessInput(id: number, data: string): void { - this._terminalProcesses[id].input(data); + this._terminalProcesses[id]?.input(data); } public $acceptProcessResize(id: number, cols: number, rows: number): void { try { - this._terminalProcesses[id].resize(cols, rows); + this._terminalProcesses[id]?.resize(cols, rows); } catch (error) { // We tried to write to a closed pipe / channel. if (error.code !== 'EPIPE' && error.code !== 'ERR_IPC_CHANNEL_CLOSED') { @@ -488,24 +508,27 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ } public $acceptProcessShutdown(id: number, immediate: boolean): void { - this._terminalProcesses[id].shutdown(immediate); + this._terminalProcesses[id]?.shutdown(immediate); } public $acceptProcessRequestInitialCwd(id: number): void { - this._terminalProcesses[id].getInitialCwd().then(initialCwd => this._proxy.$sendProcessInitialCwd(id, initialCwd)); + this._terminalProcesses[id]?.getInitialCwd().then(initialCwd => this._proxy.$sendProcessInitialCwd(id, initialCwd)); } public $acceptProcessRequestCwd(id: number): void { - this._terminalProcesses[id].getCwd().then(cwd => this._proxy.$sendProcessCwd(id, cwd)); + this._terminalProcesses[id]?.getCwd().then(cwd => this._proxy.$sendProcessCwd(id, cwd)); } public $acceptProcessRequestLatency(id: number): number { return id; } - private _onProcessExit(id: number, exitCode: number): void { + private _onProcessExit(id: number, exitCode: number | undefined): void { + this._bufferer.stopBuffering(id); + // Remove process reference delete this._terminalProcesses[id]; + delete this._extensionTerminalAwaitingStart[id]; // Send exit event to main side this._proxy.$sendProcessExit(id, exitCode); @@ -515,10 +538,6 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ private _getTerminalByIdEventually(id: number, retries: number = 5): Promise { if (!this._getTerminalPromises[id]) { this._getTerminalPromises[id] = this._createGetTerminalPromise(id, retries); - } else { - this._getTerminalPromises[id].then(c => { - return this._createGetTerminalPromise(id, retries); - }); } return this._getTerminalPromises[id]; } @@ -536,7 +555,7 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ } else { // This should only be needed immediately after createTerminalRenderer is called as // the ExtHostTerminal has not yet been iniitalized - timeout(200).then(() => c(this._createGetTerminalPromise(id, retries - 1))); + timeout(EXT_HOST_CREATION_DELAY * 2).then(() => c(this._createGetTerminalPromise(id, retries - 1))); } }); } diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index d1147a7ec7..38992389a0 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -95,12 +95,10 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { get onDidChangeVisibility() { return treeView.onDidChangeVisibility; }, get message() { return treeView.message; }, set message(message: string) { - checkProposedApiEnabled(extension); treeView.message = message; }, get title() { return treeView.title; }, set title(title: string) { - checkProposedApiEnabled(extension); treeView.title = title; }, reveal: (element: T, options?: IRevealOptions): Promise => { @@ -202,7 +200,13 @@ export class ExtHostTreeView extends Disposable { private refreshPromise: Promise = Promise.resolve(); private refreshQueue: Promise = Promise.resolve(); - constructor(private viewId: string, options: vscode.TreeViewOptions, private proxy: MainThreadTreeViewsShape, private commands: CommandsConverter, private logService: ILogService, private extension: IExtensionDescription) { + constructor( + private viewId: string, options: vscode.TreeViewOptions, + private proxy: MainThreadTreeViewsShape, + private commands: CommandsConverter, + private logService: ILogService, + private extension: IExtensionDescription + ) { super(); if (extension.contributes && extension.contributes.views) { for (const location in extension.contributes.views) { @@ -257,7 +261,7 @@ export class ExtHostTreeView extends Disposable { getChildren(parentHandle: TreeItemHandle | Root): Promise { const parentElement = parentHandle ? this.getExtensionElement(parentHandle) : undefined; if (parentHandle && !parentElement) { - console.error(`No tree item with id \'${parentHandle}\' found.`); + this.logService.error(`No tree item with id \'${parentHandle}\' found.`); return Promise.resolve([]); } @@ -425,7 +429,7 @@ export class ExtHostTreeView extends Disposable { // check if an ancestor of extElement is already in the elements to update list let currentNode: TreeNode | undefined = elementNode; while (currentNode && currentNode.parent && !elementsToUpdate.has(currentNode.parent.item.handle)) { - const parentElement = this.elements.get(currentNode.parent.item.handle); + const parentElement: T | undefined = this.elements.get(currentNode.parent.item.handle); currentNode = parentElement ? this.nodes.get(parentElement) : undefined; } if (currentNode && !currentNode.parent) { diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 2b9bb7da9f..6ea563c076 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -13,9 +13,9 @@ import { EndOfLineSequence, TrackedRangeStickiness } from 'vs/editor/common/mode import * as vscode from 'vscode'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ProgressLocation as MainProgressLocation } from 'vs/platform/progress/common/progress'; -import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; +import { SaveReason } from 'vs/workbench/common/editor'; import { IPosition } from 'vs/editor/common/core/position'; -import { IRange } from 'vs/editor/common/core/range'; +import * as editorRange from 'vs/editor/common/core/range'; import { ISelection } from 'vs/editor/common/core/selection'; import * as htmlContent from 'vs/base/common/htmlContent'; import * as languageSelector from 'vs/editor/common/modes/languageSelector'; @@ -31,7 +31,6 @@ import { LogLevel as _MainLogLevel } from 'vs/platform/log/common/log'; import { coalesce, isNonEmptyArray } from 'vs/base/common/arrays'; import { RenderLineNumbersType } from 'vs/editor/common/config/editorOptions'; - export interface PositionLike { line: number; character: number; @@ -68,9 +67,9 @@ export namespace Selection { export namespace Range { export function from(range: undefined): undefined; - export function from(range: RangeLike): IRange; - export function from(range: RangeLike | undefined): IRange | undefined; - export function from(range: RangeLike | undefined): IRange | undefined { + export function from(range: RangeLike): editorRange.IRange; + export function from(range: RangeLike | undefined): editorRange.IRange | undefined; + export function from(range: RangeLike | undefined): editorRange.IRange | undefined { if (!range) { return undefined; } @@ -84,9 +83,9 @@ export namespace Range { } export function to(range: undefined): types.Range; - export function to(range: IRange): types.Range; - export function to(range: IRange | undefined): types.Range | undefined; - export function to(range: IRange | undefined): types.Range | undefined { + export function to(range: editorRange.IRange): types.Range; + export function to(range: editorRange.IRange | undefined): types.Range | undefined; + export function to(range: editorRange.IRange | undefined): types.Range | undefined { if (!range) { return undefined; } @@ -260,7 +259,7 @@ export namespace MarkdownString { const collectUri = (href: string): string => { try { - let uri = URI.parse(href); + let uri = URI.parse(href, true); uri = uri.with({ query: _uriMassage(uri.query, resUris) }); resUris[href] = uri; } catch (e) { @@ -290,16 +289,23 @@ export namespace MarkdownString { if (!data) { return part; } + let changed = false; data = cloneAndChange(data, value => { if (URI.isUri(value)) { const key = `__uri_${Math.random().toString(16).slice(2, 8)}`; bucket[key] = value; + changed = true; return key; } else { return undefined; } }); - return encodeURIComponent(JSON.stringify(data)); + + if (!changed) { + return part; + } + + return JSON.stringify(data); } export function to(value: htmlContent.IMarkdownString): vscode.MarkdownString { @@ -627,19 +633,8 @@ export namespace DocumentSymbol { export namespace CallHierarchyItem { - export function from(item: vscode.CallHierarchyItem): extHostProtocol.ICallHierarchyItemDto { - return { - name: item.name, - detail: item.detail, - kind: SymbolKind.from(item.kind), - uri: item.uri, - range: Range.from(item.range), - selectionRange: Range.from(item.selectionRange), - }; - } - - export function to(item: extHostProtocol.ICallHierarchyItemDto): vscode.CallHierarchyItem { - return new types.CallHierarchyItem( + export function to(item: extHostProtocol.ICallHierarchyItemDto): types.CallHierarchyItem { + const result = new types.CallHierarchyItem( SymbolKind.to(item.kind), item.name, item.detail || '', @@ -647,6 +642,31 @@ export namespace CallHierarchyItem { Range.to(item.range), Range.to(item.selectionRange) ); + + result._sessionId = item._sessionId; + result._itemId = item._itemId; + + return result; + } +} + +export namespace CallHierarchyIncomingCall { + + export function to(item: extHostProtocol.IIncomingCallDto): types.CallHierarchyIncomingCall { + return new types.CallHierarchyIncomingCall( + CallHierarchyItem.to(item.from), + item.fromRanges.map(r => Range.to(r)) + ); + } +} + +export namespace CallHierarchyOutgoingCall { + + export function to(item: extHostProtocol.IOutgoingCallDto): types.CallHierarchyOutgoingCall { + return new types.CallHierarchyOutgoingCall( + CallHierarchyItem.to(item.to), + item.fromRanges.map(r => Range.to(r)) + ); } } @@ -821,14 +841,15 @@ export namespace CompletionItem { result.filterText = suggestion.filterText; result.preselect = suggestion.preselect; result.commitCharacters = suggestion.commitCharacters; - result.range = Range.to(suggestion.range); + result.range = editorRange.Range.isIRange(suggestion.range) ? Range.to(suggestion.range) : undefined; + result.range2 = editorRange.Range.isIRange(suggestion.range) ? undefined : { inserting: Range.to(suggestion.range.insert), replacing: Range.to(suggestion.range.replace) }; result.keepWhitespace = typeof suggestion.insertTextRules === 'undefined' ? false : Boolean(suggestion.insertTextRules & modes.CompletionItemInsertTextRule.KeepWhitespace); // 'inserText'-logic if (typeof suggestion.insertTextRules !== 'undefined' && suggestion.insertTextRules & modes.CompletionItemInsertTextRule.InsertAsSnippet) { result.insertText = new types.SnippetString(suggestion.insertText); } else { result.insertText = suggestion.insertText; - result.textEdit = new types.TextEdit(result.range, result.insertText); + result.textEdit = result.range instanceof types.Range ? new types.TextEdit(result.range, result.insertText) : undefined; } // TODO additionalEdits, command @@ -903,7 +924,7 @@ export namespace DocumentLink { let target: URI | undefined = undefined; if (link.url) { try { - target = typeof link.url === 'string' ? URI.parse(link.url) : URI.revive(link.url); + target = typeof link.url === 'string' ? URI.parse(link.url, true) : URI.revive(link.url); } catch (err) { // ignore } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index d5c66e025a..931c01e33b 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -29,8 +29,8 @@ function es5ClassCompat(target: Function): any { @es5ClassCompat export class Disposable { - static from(...inDisposables: { dispose(): any }[]): Disposable { - let disposables: ReadonlyArray<{ dispose(): any }> | undefined = inDisposables; + static from(...inDisposables: { dispose(): any; }[]): Disposable { + let disposables: ReadonlyArray<{ dispose(): any; }> | undefined = inDisposables; return new Disposable(function () { if (disposables) { for (const disposable of disposables) { @@ -333,9 +333,9 @@ export class Range { return this._start.line === this._end.line; } - with(change: { start?: Position, end?: Position }): Range; + with(change: { start?: Position, end?: Position; }): Range; with(start?: Position, end?: Position): Range; - with(startOrChange: Position | undefined | { start?: Position, end?: Position }, end: Position = this.end): Range { + with(startOrChange: Position | undefined | { start?: Position, end?: Position; }, end: Position = this.end): Range { if (startOrChange === null || end === null) { throw illegalArgument(); @@ -589,15 +589,15 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { private _edits = new Array(); - renameFile(from: vscode.Uri, to: vscode.Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean }): void { + renameFile(from: vscode.Uri, to: vscode.Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean; }): void { this._edits.push({ _type: 1, from, to, options }); } - createFile(uri: vscode.Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean }): void { + createFile(uri: vscode.Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean; }): void { this._edits.push({ _type: 1, from: undefined, to: uri, options }); } - deleteFile(uri: vscode.Uri, options?: { recursive?: boolean, ignoreIfNotExists?: boolean }): void { + deleteFile(uri: vscode.Uri, options?: { recursive?: boolean, ignoreIfNotExists?: boolean; }): void { this._edits.push({ _type: 1, from: uri, to: undefined, options }); } @@ -740,6 +740,18 @@ export class SnippetString { return this; } + appendChoice(values: string[], number: number = this._tabstop++): SnippetString { + const value = SnippetString._escape(values.toString()); + + this.value += '${'; + this.value += number; + this.value += '|'; + this.value += value; + this.value += '|}'; + + return this; + } + appendVariable(name: string, defaultValue?: string | ((snippet: SnippetString) => any)): SnippetString { if (typeof defaultValue === 'function') { @@ -1142,6 +1154,10 @@ export class SelectionRange { } export class CallHierarchyItem { + + _sessionId?: string; + _itemId?: string; + kind: SymbolKind; name: string; detail?: string; @@ -1344,6 +1360,7 @@ export class CompletionItem implements vscode.CompletionItem { insertText?: string | SnippetString; keepWhitespace?: boolean; range?: Range; + range2?: Range | { inserting: Range; replacing: Range; }; commitCharacters?: string[]; textEdit?: TextEdit; additionalTextEdits?: TextEdit[]; @@ -1495,7 +1512,7 @@ export class Color { } } -export type IColorFormat = string | { opaque: string, transparent: string }; +export type IColorFormat = string | { opaque: string, transparent: string; }; @es5ClassCompat export class ColorInformation { @@ -1776,20 +1793,20 @@ export enum TaskScope { Workspace = 2 } -export class CustomExecution implements vscode.CustomExecution { - private _callback: () => Thenable; - constructor(callback: () => Thenable) { +export class CustomExecution implements vscode.CustomExecution2 { + private _callback: (resolvedDefintion?: vscode.TaskDefinition) => Thenable; + constructor(callback: (resolvedDefintion?: vscode.TaskDefinition) => Thenable) { this._callback = callback; } public computeId(): string { return 'customExecution' + generateUuid(); } - public set callback(value: () => Thenable) { + public set callback(value: (resolvedDefintion?: vscode.TaskDefinition) => Thenable) { this._callback = value; } - public get callback(): (() => Thenable) { + public get callback(): ((resolvedDefintion?: vscode.TaskDefinition) => Thenable) { return this._callback; } } @@ -2050,13 +2067,13 @@ export class TreeItem { label?: string | vscode.TreeItemLabel; resourceUri?: URI; - iconPath?: string | URI | { light: string | URI; dark: string | URI }; + iconPath?: string | URI | { light: string | URI; dark: string | URI; }; command?: vscode.Command; contextValue?: string; tooltip?: string; - constructor(label: string | vscode.TreeItemLabel, collapsibleState?: vscode.TreeItemCollapsibleState) - constructor(resourceUri: URI, collapsibleState?: vscode.TreeItemCollapsibleState) + constructor(label: string | vscode.TreeItemLabel, collapsibleState?: vscode.TreeItemCollapsibleState); + constructor(resourceUri: URI, collapsibleState?: vscode.TreeItemCollapsibleState); constructor(arg1: string | vscode.TreeItemLabel | URI, public collapsibleState: vscode.TreeItemCollapsibleState = TreeItemCollapsibleState.None) { if (URI.isUri(arg1)) { this.resourceUri = arg1; @@ -2233,16 +2250,14 @@ export class DebugAdapterServer implements vscode.DebugAdapterServer { } } -/* @es5ClassCompat -export class DebugAdapterImplementation implements vscode.DebugAdapterImplementation { - readonly implementation: any; +export class DebugAdapterInlineImplementation implements vscode.DebugAdapterInlineImplementation { + readonly implementation: vscode.DebugAdapter; - constructor(transport: any) { - this.implementation = transport; + constructor(impl: vscode.DebugAdapter) { + this.implementation = impl; } } -*/ export enum LogLevel { Trace = 1, @@ -2351,6 +2366,91 @@ export enum CommentMode { //#endregion +//#region Semantic Coloring + +export class SemanticTokensLegend { + public readonly tokenTypes: string[]; + public readonly tokenModifiers: string[]; + + constructor(tokenTypes: string[], tokenModifiers: string[]) { + this.tokenTypes = tokenTypes; + this.tokenModifiers = tokenModifiers; + } +} + +export class SemanticTokensBuilder { + + private _prevLine: number; + private _prevChar: number; + private _data: number[]; + private _dataLen: number; + + constructor() { + this._prevLine = 0; + this._prevChar = 0; + this._data = []; + this._dataLen = 0; + } + + public push(line: number, char: number, length: number, tokenType: number, tokenModifiers: number): void { + let pushLine = line; + let pushChar = char; + if (this._dataLen > 0) { + pushLine -= this._prevLine; + if (pushLine === 0) { + pushChar -= this._prevChar; + } + } + + this._data[this._dataLen++] = pushLine; + this._data[this._dataLen++] = pushChar; + this._data[this._dataLen++] = length; + this._data[this._dataLen++] = tokenType; + this._data[this._dataLen++] = tokenModifiers; + + this._prevLine = line; + this._prevChar = char; + } + + public build(): Uint32Array { + return new Uint32Array(this._data); + } +} + +export class SemanticTokens { + readonly resultId?: string; + readonly data: Uint32Array; + + constructor(data: Uint32Array, resultId?: string) { + this.resultId = resultId; + this.data = data; + } +} + +export class SemanticTokensEdit { + readonly start: number; + readonly deleteCount: number; + readonly data?: Uint32Array; + + constructor(start: number, deleteCount: number, data?: Uint32Array) { + this.start = start; + this.deleteCount = deleteCount; + this.data = data; + } +} + +export class SemanticTokensEdits { + readonly resultId?: string; + readonly edits: SemanticTokensEdit[]; + + constructor(edits: SemanticTokensEdit[], resultId?: string) { + this.resultId = resultId; + this.edits = edits; + } +} + +//#endregion + //#region debug export enum DebugConsoleMode { /** diff --git a/src/vs/workbench/api/common/extHostUrls.ts b/src/vs/workbench/api/common/extHostUrls.ts index ad5d57893a..5ea42ebb96 100644 --- a/src/vs/workbench/api/common/extHostUrls.ts +++ b/src/vs/workbench/api/common/extHostUrls.ts @@ -56,7 +56,11 @@ export class ExtHostUrls implements ExtHostUrlsShape { return Promise.resolve(undefined); } - async createAppUri(extensionId: ExtensionIdentifier, options?: vscode.AppUriOptions): Promise { - return URI.revive(await this._proxy.$createAppUri(extensionId, options)); + async createAppUri(uri: URI): Promise { + return URI.revive(await this._proxy.$createAppUri(uri)); + } + + async proposedCreateAppUri(extensionId: ExtensionIdentifier, options?: vscode.AppUriOptions): Promise { + return URI.revive(await this._proxy.$proposedCreateAppUri(extensionId, options)); } } diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index a89670b110..bf1f7917dc 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -4,17 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { assertIsDefined } from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import * as modes from 'vs/editor/common/modes'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; import * as vscode from 'vscode'; import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelViewStateData } from './extHost.protocol'; -import { Disposable } from './extHostTypes'; +import { Disposable as VSCodeDisposable } from './extHostTypes'; type IconPath = URI | { light: URI, dark: URI }; @@ -33,6 +36,7 @@ export class ExtHostWebview implements vscode.Webview { private readonly _initData: WebviewInitData, private readonly _workspace: IExtHostWorkspace | undefined, private readonly _extension: IExtensionDescription, + private readonly _logService: ILogService, ) { } public dispose() { @@ -61,7 +65,7 @@ export class ExtHostWebview implements vscode.Webview { if (this._initData.isExtensionDevelopmentDebug && !this._hasCalledAsWebviewUri) { if (/(["'])vscode-resource:([^\s'"]+?)(["'])/i.test(value)) { this._hasCalledAsWebviewUri = true; - console.warn(`${this._extension.identifier.value} created a webview that appears to use the vscode-resource scheme directly. Please migrate to use the 'webview.asWebviewUri' api instead: https://aka.ms/vscode-webview-use-aswebviewuri`); + this._logService.warn(`${this._extension.identifier.value} created a webview that appears to use the vscode-resource scheme directly. Please migrate to use the 'webview.asWebviewUri' api instead: https://aka.ms/vscode-webview-use-aswebviewuri`); } } this._proxy.$setHtml(this._handle, value); @@ -91,7 +95,7 @@ export class ExtHostWebview implements vscode.Webview { } } -export class ExtHostWebviewEditor implements vscode.WebviewEditor { +export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPanel { private readonly _handle: WebviewPanelHandle; private readonly _proxy: MainThreadWebviewsShape; @@ -107,12 +111,14 @@ export class ExtHostWebviewEditor implements vscode.WebviewEditor { _isDisposed: boolean = false; - readonly _onDisposeEmitter = new Emitter(); + readonly _onDisposeEmitter = this._register(new Emitter()); public readonly onDidDispose: Event = this._onDisposeEmitter.event; - readonly _onDidChangeViewStateEmitter = new Emitter(); + readonly _onDidChangeViewStateEmitter = this._register(new Emitter()); public readonly onDidChangeViewState: Event = this._onDidChangeViewStateEmitter.event; + public _capabilities?: vscode.WebviewEditorCapabilities; + constructor( handle: WebviewPanelHandle, proxy: MainThreadWebviewsShape, @@ -122,6 +128,7 @@ export class ExtHostWebviewEditor implements vscode.WebviewEditor { editorOptions: vscode.WebviewPanelOptions, webview: ExtHostWebview ) { + super(); this._handle = handle; this._proxy = proxy; this._viewType = viewType; @@ -135,16 +142,12 @@ export class ExtHostWebviewEditor implements vscode.WebviewEditor { if (this._isDisposed) { return; } - this._isDisposed = true; this._onDisposeEmitter.fire(); - this._proxy.$disposeWebview(this._handle); - this._webview.dispose(); - this._onDisposeEmitter.dispose(); - this._onDidChangeViewStateEmitter.dispose(); + super.dispose(); } get webview() { @@ -223,18 +226,6 @@ export class ExtHostWebviewEditor implements vscode.WebviewEditor { this._visible = value; } - private readonly _onWillSave = new Emitter<{ waitUntil: (thenable: Thenable) => void }>(); - public readonly onWillSave = this._onWillSave.event; - - async _save(): Promise { - const waitingOn: Thenable[] = []; - this._onWillSave.fire({ - waitUntil: (thenable: Thenable): void => { waitingOn.push(thenable); }, - }); - const result = await Promise.all(waitingOn); - return result.every(x => x); - } - public postMessage(message: any): Promise { this.assertNotDisposed(); return this._proxy.$postMessage(this._handle, message); @@ -248,6 +239,32 @@ export class ExtHostWebviewEditor implements vscode.WebviewEditor { }); } + _setCapabilities(capabilities: vscode.WebviewEditorCapabilities) { + this._capabilities = capabilities; + if (capabilities.editingCapability) { + this._register(capabilities.editingCapability.onEdit(edit => { + this._proxy.$onEdit(this._handle, edit); + })); + } + } + + _undoEdits(edits: readonly any[]): void { + assertIsDefined(this._capabilities).editingCapability?.undoEdits(edits); + } + + _redoEdits(edits: readonly any[]): void { + assertIsDefined(this._capabilities).editingCapability?.applyEdits(edits); + } + + async _onSave(): Promise { + await assertIsDefined(this._capabilities).editingCapability?.save(); + } + + + async _onSaveAs(resource: vscode.Uri, targetResource: vscode.Uri): Promise { + await assertIsDefined(this._capabilities).editingCapability?.saveAs(resource, targetResource); + } + private assertNotDisposed() { if (this._isDisposed) { throw new Error('Webview is disposed'); @@ -270,6 +287,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { mainContext: IMainContext, private readonly initData: WebviewInitData, private readonly workspace: IExtHostWorkspace | undefined, + private readonly _logService: ILogService, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadWebviews); } @@ -290,7 +308,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { const handle = ExtHostWebviews.newHandle(); this._proxy.$createWebviewPanel({ id: extension.identifier, location: extension.extensionLocation }, handle, viewType, title, webviewShowOptions, convertWebviewOptions(extension, this.workspace, options)); - const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, extension); + const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, extension, this._logService); const panel = new ExtHostWebviewEditor(handle, this._proxy, viewType, title, viewColumn, options, webview); this._webviewPanels.set(handle, panel); return panel; @@ -308,7 +326,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { this._serializers.set(viewType, { serializer, extension }); this._proxy.$registerSerializer(viewType); - return new Disposable(() => { + return new VSCodeDisposable(() => { this._serializers.delete(viewType); this._proxy.$unregisterSerializer(viewType); }); @@ -327,7 +345,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { this._editorProviders.set(viewType, { extension, provider, }); this._proxy.$registerEditorProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType, options || {}); - return new Disposable(() => { + return new VSCodeDisposable(() => { this._editorProviders.delete(viewType); this._proxy.$unregisterEditorProvider(viewType); }); @@ -347,7 +365,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { _handle: WebviewPanelHandle, extensionId: string ): void { - console.warn(`${extensionId} created a webview without a content security policy: https://aka.ms/vscode-webview-missing-csp`); + this._logService.warn(`${extensionId} created a webview without a content security policy: https://aka.ms/vscode-webview-missing-csp`); } public $onDidChangeWebviewPanelViewStates(newStates: WebviewPanelViewStateData): void { @@ -385,16 +403,15 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { } } - $onDidDisposeWebviewPanel(handle: WebviewPanelHandle): Promise { + async $onDidDisposeWebviewPanel(handle: WebviewPanelHandle): Promise { const panel = this.getWebviewPanel(handle); if (panel) { panel.dispose(); this._webviewPanels.delete(handle); } - return Promise.resolve(undefined); } - $deserializeWebviewPanel( + async $deserializeWebviewPanel( webviewHandle: WebviewPanelHandle, viewType: string, title: string, @@ -404,22 +421,18 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { ): Promise { const entry = this._serializers.get(viewType); if (!entry) { - return Promise.reject(new Error(`No serializer found for '${viewType}'`)); + throw new Error(`No serializer found for '${viewType}'`); } const { serializer, extension } = entry; - const webview = new ExtHostWebview(webviewHandle, this._proxy, options, this.initData, this.workspace, extension); + const webview = new ExtHostWebview(webviewHandle, this._proxy, options, this.initData, this.workspace, extension, this._logService); const revivedPanel = new ExtHostWebviewEditor(webviewHandle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview); this._webviewPanels.set(webviewHandle, revivedPanel); - return Promise.resolve(serializer.deserializeWebviewPanel(revivedPanel, state)); - } - - private getWebviewPanel(handle: WebviewPanelHandle): ExtHostWebviewEditor | undefined { - return this._webviewPanels.get(handle); + await serializer.deserializeWebviewPanel(revivedPanel, state); } async $resolveWebviewEditor( - resource: UriComponents, + input: { resource: UriComponents, edits: readonly any[] }, handle: WebviewPanelHandle, viewType: string, title: string, @@ -430,19 +443,42 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { if (!entry) { return Promise.reject(new Error(`No provider found for '${viewType}'`)); } + const { provider, extension } = entry; - const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, extension); + const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, extension, this._logService); const revivedPanel = new ExtHostWebviewEditor(handle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview); this._webviewPanels.set(handle, revivedPanel); - return Promise.resolve(provider.resolveWebviewEditor(URI.revive(resource), revivedPanel)); + const capabilities = await provider.resolveWebviewEditor({ resource: URI.revive(input.resource) }, revivedPanel); + revivedPanel._setCapabilities(capabilities); + + // TODO: the first set of edits should likely be passed when resolving + if (input.edits.length) { + revivedPanel._redoEdits(input.edits); + } } - async $save(handle: WebviewPanelHandle): Promise { + $undoEdits(handle: WebviewPanelHandle, edits: readonly any[]): void { const panel = this.getWebviewPanel(handle); - if (panel) { - return panel._save(); - } - return false; + panel?._undoEdits(edits); + } + + $applyEdits(handle: WebviewPanelHandle, edits: readonly any[]): void { + const panel = this.getWebviewPanel(handle); + panel?._redoEdits(edits); + } + + async $onSave(handle: WebviewPanelHandle): Promise { + const panel = this.getWebviewPanel(handle); + return panel?._onSave(); + } + + async $onSaveAs(handle: WebviewPanelHandle, resource: UriComponents, targetResource: UriComponents): Promise { + const panel = this.getWebviewPanel(handle); + return panel?._onSaveAs(URI.revive(resource), URI.revive(targetResource)); + } + + private getWebviewPanel(handle: WebviewPanelHandle): ExtHostWebviewEditor | undefined { + return this._webviewPanels.get(handle); } } @@ -462,7 +498,7 @@ function getDefaultLocalResourceRoots( workspace: IExtHostWorkspace | undefined, ): URI[] { return [ - ...(workspace && workspace.getWorkspaceFolders() || []).map(x => x.uri), + ...(workspace?.getWorkspaceFolders() || []).map(x => x.uri), extension.extensionLocation, ]; } diff --git a/src/vs/workbench/api/common/extHostWindow.ts b/src/vs/workbench/api/common/extHostWindow.ts index fd2958617a..a86e553749 100644 --- a/src/vs/workbench/api/common/extHostWindow.ts +++ b/src/vs/workbench/api/common/extHostWindow.ts @@ -39,7 +39,9 @@ export class ExtHostWindow implements ExtHostWindowShape { } openUri(stringOrUri: string | URI, options: IOpenUriOptions): Promise { + let uriAsString: string | undefined; if (typeof stringOrUri === 'string') { + uriAsString = stringOrUri; try { stringOrUri = URI.parse(stringOrUri); } catch (e) { @@ -51,7 +53,7 @@ export class ExtHostWindow implements ExtHostWindowShape { } else if (stringOrUri.scheme === Schemas.command) { return Promise.reject(`Invalid scheme '${stringOrUri.scheme}'`); } - return this._proxy.$openUri(stringOrUri, options); + return this._proxy.$openUri(stringOrUri, uriAsString, options); } async asExternalUri(uri: URI, options: IOpenUriOptions): Promise { diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index 227891e6e7..cc5d6519d7 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -3,31 +3,30 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { join } from 'vs/base/common/path'; import { delta as arrayDelta, mapArrayOrNot } from 'vs/base/common/arrays'; +import { Barrier } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { TernarySearchTree } from 'vs/base/common/map'; +import { Schemas } from 'vs/base/common/network'; import { Counter } from 'vs/base/common/numbers'; -import { basenameOrAuthority, dirname, isEqual, relativePath, basename } from 'vs/base/common/resources'; +import { basename, basenameOrAuthority, dirname, isEqual, relativePath } from 'vs/base/common/resources'; import { compare } from 'vs/base/common/strings'; +import { withUndefinedAsNull } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { Severity } from 'vs/platform/notification/common/notification'; -import { IRawFileMatch2, resultIsMatch } from 'vs/workbench/services/search/common/search'; import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { Range, RelativePattern } from 'vs/workbench/api/common/extHostTypes'; import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder'; +import { IRawFileMatch2, resultIsMatch } from 'vs/workbench/services/search/common/search'; import * as vscode from 'vscode'; -import { ExtHostWorkspaceShape, IWorkspaceData, MainThreadMessageServiceShape, MainThreadWorkspaceShape, MainContext } from './extHost.protocol'; -import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { Barrier } from 'vs/base/common/async'; -import { Schemas } from 'vs/base/common/network'; -import { withUndefinedAsNull } from 'vs/base/common/types'; -import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { ExtHostWorkspaceShape, IWorkspaceData, MainContext, MainThreadMessageServiceShape, MainThreadWorkspaceShape } from './extHost.protocol'; export interface IExtHostWorkspaceProvider { getWorkspaceFolder2(uri: vscode.Uri, resolveParent?: boolean): Promise; @@ -419,19 +418,6 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac findFiles(include: string | RelativePattern | undefined, exclude: vscode.GlobPattern | null | undefined, maxResults: number | undefined, extensionId: ExtensionIdentifier, token: vscode.CancellationToken = CancellationToken.None): Promise { this._logService.trace(`extHostWorkspace#findFiles: fileSearch, extension: ${extensionId.value}, entryPoint: findFiles`); - let includePattern: string | undefined; - let includeFolder: URI | undefined; - if (include) { - if (typeof include === 'string') { - includePattern = include; - } else { - includePattern = include.pattern; - - // include.base must be an absolute path - includeFolder = include.baseFolder || URI.file(include.base); - } - } - let excludePatternOrDisregardExcludes: string | false | undefined = undefined; if (exclude === null) { excludePatternOrDisregardExcludes = false; @@ -447,9 +433,10 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac return Promise.resolve([]); } + const { includePattern, folder } = parseSearchInclude(include); return this._proxy.$startFileSearch( withUndefinedAsNull(includePattern), - withUndefinedAsNull(includeFolder), + withUndefinedAsNull(folder), withUndefinedAsNull(excludePatternOrDisregardExcludes), withUndefinedAsNull(maxResults), token @@ -457,19 +444,11 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac .then(data => Array.isArray(data) ? data.map(d => URI.revive(d)) : []); } - findTextInFiles(query: vscode.TextSearchQuery, options: vscode.FindTextInFilesOptions, callback: (result: vscode.TextSearchResult) => void, extensionId: ExtensionIdentifier, token: vscode.CancellationToken = CancellationToken.None): Promise { + async findTextInFiles(query: vscode.TextSearchQuery, options: vscode.FindTextInFilesOptions, callback: (result: vscode.TextSearchResult) => void, extensionId: ExtensionIdentifier, token: vscode.CancellationToken = CancellationToken.None): Promise { this._logService.trace(`extHostWorkspace#findTextInFiles: textSearch, extension: ${extensionId.value}, entryPoint: findTextInFiles`); const requestId = this._requestIdProvider.getNext(); - const globPatternToString = (pattern: vscode.GlobPattern | string) => { - if (typeof pattern === 'string') { - return pattern; - } - - return join(pattern.base, pattern.pattern); - }; - const previewOptions: vscode.TextSearchPreviewOptions = typeof options.previewOptions === 'undefined' ? { matchLines: 100, @@ -477,6 +456,19 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac } : options.previewOptions; + let includePattern: string | undefined; + let folder: URI | undefined; + if (options.include) { + if (typeof options.include === 'string') { + includePattern = options.include; + } else { + includePattern = options.include.pattern; + folder = (options.include as RelativePattern).baseFolder || URI.file(options.include.base); + } + } + + const excludePattern = (typeof options.exclude === 'string') ? options.exclude : + options.exclude ? options.exclude.pattern : undefined; const queryOptions: ITextQueryBuilderOptions = { ignoreSymlinks: typeof options.followSymlinks === 'boolean' ? !options.followSymlinks : undefined, disregardIgnoreFiles: typeof options.useIgnoreFiles === 'boolean' ? !options.useIgnoreFiles : undefined, @@ -488,8 +480,8 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac afterContext: options.afterContext, beforeContext: options.beforeContext, - includePattern: options.include && globPatternToString(options.include), - excludePattern: options.exclude ? globPatternToString(options.exclude) : undefined + includePattern: includePattern, + excludePattern: excludePattern }; const isCanceled = false; @@ -525,16 +517,22 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac }; if (token.isCancellationRequested) { - return Promise.resolve(undefined); + return {}; } - return this._proxy.$startTextSearch(query, queryOptions, requestId, token).then(result => { + try { + const result = await this._proxy.$startTextSearch( + query, + withUndefinedAsNull(folder), + queryOptions, + requestId, + token); delete this._activeSearchCallbacks[requestId]; - return result; - }, err => { + return result || {}; + } catch (err) { delete this._activeSearchCallbacks[requestId]; - return Promise.reject(err); - }); + throw err; + } } $handleTextSearchResult(result: IRawFileMatch2, requestId: number): void { @@ -554,3 +552,23 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac export const IExtHostWorkspace = createDecorator('IExtHostWorkspace'); export interface IExtHostWorkspace extends ExtHostWorkspace, ExtHostWorkspaceShape, IExtHostWorkspaceProvider { } + +function parseSearchInclude(include: RelativePattern | string | undefined): { includePattern?: string, folder?: URI } { + let includePattern: string | undefined; + let includeFolder: URI | undefined; + if (include) { + if (typeof include === 'string') { + includePattern = include; + } else { + includePattern = include.pattern; + + // include.base must be an absolute path + includeFolder = include.baseFolder || URI.file(include.base); + } + } + + return { + includePattern: includePattern, + folder: includeFolder + }; +} diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index f4427b6035..4b4859ac32 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -13,6 +13,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { MenuId, MenuRegistry, ILocalizedString, IMenuItem } from 'vs/platform/actions/common/actions'; import { URI } from 'vs/base/common/uri'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; namespace schema { @@ -352,10 +353,14 @@ commandsExtensionPoint.setHandler(extensions => { const { icon, enablement, category, title, command } = userFriendlyCommand; - let absoluteIcon: { dark: URI; light?: URI; } | undefined; + let absoluteIcon: { dark: URI; light?: URI; } | ThemeIcon | undefined; if (icon) { if (typeof icon === 'string') { - absoluteIcon = { dark: resources.joinPath(extension.description.extensionLocation, icon) }; + if (extension.description.enableProposedApi) { + absoluteIcon = ThemeIcon.fromString(icon) || { dark: resources.joinPath(extension.description.extensionLocation, icon) }; + } else { + absoluteIcon = { dark: resources.joinPath(extension.description.extensionLocation, icon) }; + } } else { absoluteIcon = { dark: resources.joinPath(extension.description.extensionLocation, icon.dark), @@ -372,7 +377,7 @@ commandsExtensionPoint.setHandler(extensions => { title, category, precondition: ContextKeyExpr.deserialize(enablement), - iconLocation: absoluteIcon + icon: absoluteIcon }); _commandRegistrations.add(registration); } diff --git a/src/vs/workbench/api/common/shared/semanticTokens.ts b/src/vs/workbench/api/common/shared/semanticTokens.ts new file mode 100644 index 0000000000..cecc816f78 --- /dev/null +++ b/src/vs/workbench/api/common/shared/semanticTokens.ts @@ -0,0 +1,113 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { VSBuffer } from 'vs/base/common/buffer'; + +export interface IFullSemanticTokensDto { + id: number; + type: 'full'; + data: Uint32Array; +} + +export interface IDeltaSemanticTokensDto { + id: number; + type: 'delta'; + deltas: { start: number; deleteCount: number; data?: Uint32Array; }[]; +} + +export type ISemanticTokensDto = IFullSemanticTokensDto | IDeltaSemanticTokensDto; + +const enum EncodedSemanticTokensType { + Full = 1, + Delta = 2 +} + +export function encodeSemanticTokensDto(semanticTokens: ISemanticTokensDto): VSBuffer { + const buff = VSBuffer.alloc(encodedSize2(semanticTokens)); + let offset = 0; + buff.writeUInt32BE(semanticTokens.id, offset); offset += 4; + if (semanticTokens.type === 'full') { + buff.writeUInt8(EncodedSemanticTokensType.Full, offset); offset += 1; + buff.writeUInt32BE(semanticTokens.data.length, offset); offset += 4; + for (const uint of semanticTokens.data) { + buff.writeUInt32BE(uint, offset); offset += 4; + } + } else { + buff.writeUInt8(EncodedSemanticTokensType.Delta, offset); offset += 1; + buff.writeUInt32BE(semanticTokens.deltas.length, offset); offset += 4; + for (const delta of semanticTokens.deltas) { + buff.writeUInt32BE(delta.start, offset); offset += 4; + buff.writeUInt32BE(delta.deleteCount, offset); offset += 4; + if (delta.data) { + buff.writeUInt32BE(delta.data.length, offset); offset += 4; + for (const uint of delta.data) { + buff.writeUInt32BE(uint, offset); offset += 4; + } + } else { + buff.writeUInt32BE(0, offset); offset += 4; + } + } + } + return buff; +} + +function encodedSize2(semanticTokens: ISemanticTokensDto): number { + let result = 0; + result += 4; // id + result += 1; // type + if (semanticTokens.type === 'full') { + result += 4; // data length + result += semanticTokens.data.byteLength; + } else { + result += 4; // delta count + for (const delta of semanticTokens.deltas) { + result += 4; // start + result += 4; // deleteCount + result += 4; // data length + if (delta.data) { + result += delta.data.byteLength; + } + } + } + return result; +} + +export function decodeSemanticTokensDto(buff: VSBuffer): ISemanticTokensDto { + let offset = 0; + const id = buff.readUInt32BE(offset); offset += 4; + const type: EncodedSemanticTokensType = buff.readUInt8(offset); offset += 1; + if (type === EncodedSemanticTokensType.Full) { + const length = buff.readUInt32BE(offset); offset += 4; + const data = new Uint32Array(length); + for (let j = 0; j < length; j++) { + data[j] = buff.readUInt32BE(offset); offset += 4; + } + return { + id: id, + type: 'full', + data: data + }; + } + const deltaCount = buff.readUInt32BE(offset); offset += 4; + let deltas: { start: number; deleteCount: number; data?: Uint32Array; }[] = []; + for (let i = 0; i < deltaCount; i++) { + const start = buff.readUInt32BE(offset); offset += 4; + const deleteCount = buff.readUInt32BE(offset); offset += 4; + const length = buff.readUInt32BE(offset); offset += 4; + let data: Uint32Array | undefined; + if (length > 0) { + data = new Uint32Array(length); + for (let j = 0; j < length; j++) { + data[j] = buff.readUInt32BE(offset); offset += 4; + } + } + deltas[i] = { start, deleteCount, data }; + } + return { + id: id, + type: 'delta', + deltas: deltas + }; +} diff --git a/src/vs/workbench/api/node/extHost.services.ts b/src/vs/workbench/api/node/extHost.services.ts index e864993b01..deeb5c441d 100644 --- a/src/vs/workbench/api/node/extHost.services.ts +++ b/src/vs/workbench/api/node/extHost.services.ts @@ -18,7 +18,7 @@ import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminal // import { ExtHostDebugService } from 'vs/workbench/api/node/extHostDebugService'; // import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService'; import { IExtHostSearch } from 'vs/workbench/api/common/extHostSearch'; -import { ExtHostSearch } from 'vs/workbench/api/node/extHostSearch'; +import { NativeExtHostSearch } from 'vs/workbench/api/node/extHostSearch'; import { ExtensionStoragePaths } from 'vs/workbench/api/node/extHostStoragePaths'; import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; @@ -38,7 +38,7 @@ registerSingleton(IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors); registerSingleton(IExtHostTerminalService, ExtHostTerminalService); // registerSingleton(IExtHostTask, ExtHostTask); {{SQL CABON EDIT}} disable exthost tasks // registerSingleton(IExtHostDebugService, ExtHostDebugService); {{SQL CARBON EDIT}} remove debug service -registerSingleton(IExtHostSearch, ExtHostSearch); +registerSingleton(IExtHostSearch, NativeExtHostSearch); registerSingleton(IExtensionStoragePaths, ExtensionStoragePaths); registerSingleton(IExtHostExtensionService, ExtHostExtensionService); registerSingleton(IExtHostStorage, ExtHostStorage); diff --git a/src/vs/workbench/api/node/extHostCLIServer.ts b/src/vs/workbench/api/node/extHostCLIServer.ts index bbb1a3d66d..43fd5516a3 100644 --- a/src/vs/workbench/api/node/extHostCLIServer.ts +++ b/src/vs/workbench/api/node/extHostCLIServer.ts @@ -101,9 +101,6 @@ export class CLIServer { for (const s of folderURIs) { try { urisToOpen.push({ folderUri: URI.parse(s) }); - if (!addMode && !forceReuseWindow) { - forceNewWindow = true; - } } catch (e) { // ignore } @@ -114,9 +111,6 @@ export class CLIServer { try { if (hasWorkspaceFileExtension(s)) { urisToOpen.push({ workspaceUri: URI.parse(s) }); - if (!forceReuseWindow) { - forceNewWindow = true; - } } else { urisToOpen.push({ fileUri: URI.parse(s) }); } @@ -127,7 +121,8 @@ export class CLIServer { } if (urisToOpen.length) { const waitMarkerFileURI = waitMarkerFilePath ? URI.file(waitMarkerFilePath) : undefined; - const windowOpenArgs: INativeOpenWindowOptions = { forceNewWindow, diffMode, addMode, gotoLineMode, forceReuseWindow, waitMarkerFileURI }; + const preferNewWindow = !forceReuseWindow && !waitMarkerFileURI && !addMode; + const windowOpenArgs: INativeOpenWindowOptions = { forceNewWindow, diffMode, addMode, gotoLineMode, forceReuseWindow, preferNewWindow, waitMarkerFileURI }; this._commands.executeCommand('_files.windowOpen', urisToOpen, windowOpenArgs); } res.writeHead(200); diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index a77f96210b..29668be492 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -3,330 +3,70 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'vs/base/common/path'; -import { Schemas } from 'vs/base/common/network'; -import { URI, UriComponents } from 'vs/base/common/uri'; -import { Event, Emitter } from 'vs/base/common/event'; -import { asPromise } from 'vs/base/common/async'; import * as nls from 'vs/nls'; -import { - MainContext, MainThreadDebugServiceShape, ExtHostDebugServiceShape, DebugSessionUUID, - IBreakpointsDeltaDto, ISourceMultiBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto -} from 'vs/workbench/api/common/extHost.protocol'; import * as vscode from 'vscode'; -import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint, DebugAdapterServer, DebugAdapterExecutable, DataBreakpoint, DebugConsoleMode } from 'vs/workbench/api/common/extHostTypes'; +import { DebugAdapterExecutable } from 'vs/workbench/api/common/extHostTypes'; import { ExecutableDebugAdapter, SocketDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter'; import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter'; import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; -import { ExtHostDocumentsAndEditors, IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; -import { IDebuggerContribution, IConfig, IDebugAdapter, IDebugAdapterServer, IDebugAdapterExecutable, IAdapterDescriptor } from 'vs/workbench/contrib/debug/common/debug'; -import { hasChildProcesses, prepareCommand, runInExternalTerminal } from 'vs/workbench/contrib/debug/node/terminals'; -import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/common/variableResolver'; -import { ExtHostConfigProvider, IExtHostConfiguration } from '../common/extHostConfiguration'; -import { convertToVSCPaths, convertToDAPaths, isDebuggerMainContribution } from 'vs/workbench/contrib/debug/common/debugUtils'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { IAdapterDescriptor } from 'vs/workbench/contrib/debug/common/debug'; +import { IExtHostConfiguration, ExtHostConfigProvider } from '../common/extHostConfiguration'; import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; -import { IProcessEnvironment } from 'vs/base/common/platform'; -import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { SignService } from 'vs/platform/sign/node/signService'; -import { ISignService } from 'vs/platform/sign/common/sign'; import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; -import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService'; +import { ExtHostDebugServiceBase, ExtHostDebugSession, ExtHostVariableResolverService } from 'vs/workbench/api/common/extHostDebugService'; +import { ISignService } from 'vs/platform/sign/common/sign'; +import { SignService } from 'vs/platform/sign/node/signService'; +import { hasChildProcesses, prepareCommand, runInExternalTerminal } from 'vs/workbench/contrib/debug/node/terminals'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/common/variableResolver'; +import { IProcessEnvironment } from 'vs/base/common/platform'; -export class ExtHostDebugService implements IExtHostDebugService, ExtHostDebugServiceShape { + +export class ExtHostDebugService extends ExtHostDebugServiceBase { readonly _serviceBrand: undefined; - private _configProviderHandleCounter: number; - private _configProviders: ConfigProviderTuple[]; - - private _adapterFactoryHandleCounter: number; - private _adapterFactories: DescriptorFactoryTuple[]; - - private _trackerFactoryHandleCounter: number; - private _trackerFactories: TrackerFactoryTuple[]; - - private _debugServiceProxy: MainThreadDebugServiceShape; - private _debugSessions: Map = new Map(); - - private readonly _onDidStartDebugSession: Emitter; - get onDidStartDebugSession(): Event { return this._onDidStartDebugSession.event; } - - private readonly _onDidTerminateDebugSession: Emitter; - get onDidTerminateDebugSession(): Event { return this._onDidTerminateDebugSession.event; } - - private readonly _onDidChangeActiveDebugSession: Emitter; - get onDidChangeActiveDebugSession(): Event { return this._onDidChangeActiveDebugSession.event; } - - private _activeDebugSession: ExtHostDebugSession | undefined; - get activeDebugSession(): ExtHostDebugSession | undefined { return this._activeDebugSession; } - - private readonly _onDidReceiveDebugSessionCustomEvent: Emitter; - get onDidReceiveDebugSessionCustomEvent(): Event { return this._onDidReceiveDebugSessionCustomEvent.event; } - - private _activeDebugConsole: ExtHostDebugConsole; - get activeDebugConsole(): ExtHostDebugConsole { return this._activeDebugConsole; } - - private _breakpoints: Map; - private _breakpointEventsActive: boolean; - - private readonly _onDidChangeBreakpoints: Emitter; - - private _aexCommands: Map; - private _debugAdapters: Map; - private _debugAdaptersTrackers: Map; - - private _variableResolver: IConfigurationResolverService | undefined; - private _integratedTerminalInstance?: vscode.Terminal; private _terminalDisposedListener: IDisposable | undefined; - private _signService: ISignService | undefined; - - constructor( @IExtHostRpcService extHostRpcService: IExtHostRpcService, - @IExtHostWorkspace private _workspaceService: IExtHostWorkspace, - @IExtHostExtensionService private _extensionService: IExtHostExtensionService, - @IExtHostDocumentsAndEditors private _editorsService: IExtHostDocumentsAndEditors, - @IExtHostConfiguration private _configurationService: IExtHostConfiguration, + @IExtHostWorkspace workspaceService: IExtHostWorkspace, + @IExtHostExtensionService extensionService: IExtHostExtensionService, + @IExtHostDocumentsAndEditors editorsService: IExtHostDocumentsAndEditors, + @IExtHostConfiguration configurationService: IExtHostConfiguration, @IExtHostTerminalService private _terminalService: IExtHostTerminalService, - @IExtHostCommands private _commandService: IExtHostCommands + @IExtHostCommands commandService: IExtHostCommands ) { - this._configProviderHandleCounter = 0; - this._configProviders = []; - - this._adapterFactoryHandleCounter = 0; - this._adapterFactories = []; - - this._trackerFactoryHandleCounter = 0; - this._trackerFactories = []; - - this._aexCommands = new Map(); - this._debugAdapters = new Map(); - this._debugAdaptersTrackers = new Map(); - - this._onDidStartDebugSession = new Emitter(); - this._onDidTerminateDebugSession = new Emitter(); - this._onDidChangeActiveDebugSession = new Emitter(); - this._onDidReceiveDebugSessionCustomEvent = new Emitter(); - - this._debugServiceProxy = extHostRpcService.getProxy(MainContext.MainThreadDebugService); - - this._onDidChangeBreakpoints = new Emitter({ - onFirstListenerAdd: () => { - this.startBreakpoints(); - } - }); - - this._activeDebugConsole = new ExtHostDebugConsole(this._debugServiceProxy); - - this._breakpoints = new Map(); - this._breakpointEventsActive = false; - - this._extensionService.getExtensionRegistry().then((extensionRegistry: ExtensionDescriptionRegistry) => { - extensionRegistry.onDidChange(_ => { - this.registerAllDebugTypes(extensionRegistry); - }); - this.registerAllDebugTypes(extensionRegistry); - }); + super(extHostRpcService, workspaceService, extensionService, editorsService, configurationService, commandService); } - private registerAllDebugTypes(extensionRegistry: ExtensionDescriptionRegistry) { - - const debugTypes: string[] = []; - this._aexCommands.clear(); - - for (const ed of extensionRegistry.getAllExtensionDescriptions()) { - if (ed.contributes) { - const debuggers = ed.contributes['debuggers']; - if (debuggers && debuggers.length > 0) { - for (const dbg of debuggers) { - if (isDebuggerMainContribution(dbg)) { - debugTypes.push(dbg.type); - if (dbg.adapterExecutableCommand) { - this._aexCommands.set(dbg.type, dbg.adapterExecutableCommand); - } - } - } - } - } + protected createDebugAdapter(adapter: IAdapterDescriptor, session: ExtHostDebugSession): AbstractDebugAdapter | undefined { + switch (adapter.type) { + case 'server': + return new SocketDebugAdapter(adapter); + case 'executable': + return new ExecutableDebugAdapter(adapter, session.type); } - - this._debugServiceProxy.$registerDebugTypes(debugTypes); + return super.createDebugAdapter(adapter, session); } - // extension debug API - - get onDidChangeBreakpoints(): Event { - return this._onDidChangeBreakpoints.event; - } - - get breakpoints(): vscode.Breakpoint[] { - - this.startBreakpoints(); - - const result: vscode.Breakpoint[] = []; - this._breakpoints.forEach(bp => result.push(bp)); - return result; - } - - public addBreakpoints(breakpoints0: vscode.Breakpoint[]): Promise { - - this.startBreakpoints(); - - // filter only new breakpoints - const breakpoints = breakpoints0.filter(bp => { - const id = bp.id; - if (!this._breakpoints.has(id)) { - this._breakpoints.set(id, bp); - return true; - } - return false; - }); - - // send notification for added breakpoints - this.fireBreakpointChanges(breakpoints, [], []); - - // convert added breakpoints to DTOs - const dtos: Array = []; - const map = new Map(); - for (const bp of breakpoints) { - if (bp instanceof SourceBreakpoint) { - let dto = map.get(bp.location.uri.toString()); - if (!dto) { - dto = { - type: 'sourceMulti', - uri: bp.location.uri, - lines: [] - }; - map.set(bp.location.uri.toString(), dto); - dtos.push(dto); - } - dto.lines.push({ - id: bp.id, - enabled: bp.enabled, - condition: bp.condition, - hitCondition: bp.hitCondition, - logMessage: bp.logMessage, - line: bp.location.range.start.line, - character: bp.location.range.start.character - }); - } else if (bp instanceof FunctionBreakpoint) { - dtos.push({ - type: 'function', - id: bp.id, - enabled: bp.enabled, - hitCondition: bp.hitCondition, - logMessage: bp.logMessage, - condition: bp.condition, - functionName: bp.functionName - }); - } + protected daExecutableFromPackage(session: ExtHostDebugSession, extensionRegistry: ExtensionDescriptionRegistry): DebugAdapterExecutable | undefined { + const dae = ExecutableDebugAdapter.platformAdapterExecutable(extensionRegistry.getAllExtensionDescriptions(), session.type); + if (dae) { + return new DebugAdapterExecutable(dae.command, dae.args, dae.options); } - - // send DTOs to VS Code - return this._debugServiceProxy.$registerBreakpoints(dtos); + return undefined; } - public removeBreakpoints(breakpoints0: vscode.Breakpoint[]): Promise { - - this.startBreakpoints(); - - // remove from array - const breakpoints = breakpoints0.filter(b => this._breakpoints.delete(b.id)); - - // send notification - this.fireBreakpointChanges([], breakpoints, []); - - // unregister with VS Code - const ids = breakpoints.filter(bp => bp instanceof SourceBreakpoint).map(bp => bp.id); - const fids = breakpoints.filter(bp => bp instanceof FunctionBreakpoint).map(bp => bp.id); - const dids = breakpoints.filter(bp => bp instanceof DataBreakpoint).map(bp => bp.id); - return this._debugServiceProxy.$unregisterBreakpoints(ids, fids, dids); + protected createSignService(): ISignService | undefined { + return new SignService(); } - public startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, options: vscode.DebugSessionOptions): Promise { - return this._debugServiceProxy.$startDebugging(folder ? folder.uri : undefined, nameOrConfig, { - parentSessionID: options.parentSession ? options.parentSession.id : undefined, - repl: options.consoleMode === DebugConsoleMode.MergeWithParent ? 'mergeWithParent' : 'separate' - }); - } - - public registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider): vscode.Disposable { - - if (!provider) { - return new Disposable(() => { }); - } - - if (provider.debugAdapterExecutable) { - console.error('DebugConfigurationProvider.debugAdapterExecutable is deprecated and will be removed soon; please use DebugAdapterDescriptorFactory.createDebugAdapterDescriptor instead.'); - } - - const handle = this._configProviderHandleCounter++; - this._configProviders.push({ type, handle, provider }); - - this._debugServiceProxy.$registerDebugConfigurationProvider(type, - !!provider.provideDebugConfigurations, - !!provider.resolveDebugConfiguration, - !!provider.debugAdapterExecutable, // TODO@AW: deprecated - handle); - - return new Disposable(() => { - this._configProviders = this._configProviders.filter(p => p.provider !== provider); // remove - this._debugServiceProxy.$unregisterDebugConfigurationProvider(handle); - }); - } - - public registerDebugAdapterDescriptorFactory(extension: IExtensionDescription, type: string, factory: vscode.DebugAdapterDescriptorFactory): vscode.Disposable { - - if (!factory) { - return new Disposable(() => { }); - } - - // a DebugAdapterDescriptorFactory can only be registered in the extension that contributes the debugger - if (!this.definesDebugType(extension, type)) { - throw new Error(`a DebugAdapterDescriptorFactory can only be registered from the extension that defines the '${type}' debugger.`); - } - - // make sure that only one factory for this type is registered - if (this.getAdapterFactoryByType(type)) { - throw new Error(`a DebugAdapterDescriptorFactory can only be registered once per a type.`); - } - - const handle = this._adapterFactoryHandleCounter++; - this._adapterFactories.push({ type, handle, factory }); - - this._debugServiceProxy.$registerDebugAdapterDescriptorFactory(type, handle); - - return new Disposable(() => { - this._adapterFactories = this._adapterFactories.filter(p => p.factory !== factory); // remove - this._debugServiceProxy.$unregisterDebugAdapterDescriptorFactory(handle); - }); - } - - public registerDebugAdapterTrackerFactory(type: string, factory: vscode.DebugAdapterTrackerFactory): vscode.Disposable { - - if (!factory) { - return new Disposable(() => { }); - } - - const handle = this._trackerFactoryHandleCounter++; - this._trackerFactories.push({ type, handle, factory }); - - return new Disposable(() => { - this._trackerFactories = this._trackerFactories.filter(p => p.factory !== factory); // remove - }); - } - - // RPC methods (ExtHostDebugServiceShape) - public async $runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): Promise { if (args.kind === 'integrated') { @@ -340,776 +80,48 @@ export class ExtHostDebugService implements IExtHostDebugService, ExtHostDebugSe }); } - return new Promise(resolve => { - if (this._integratedTerminalInstance) { - this._integratedTerminalInstance.processId.then(pid => { - resolve(hasChildProcesses(pid)); - }, err => { - resolve(true); - }); - } else { - resolve(true); - } - }).then(async needNewTerminal => { + let needNewTerminal = true; // be pessimistic + if (this._integratedTerminalInstance) { + const pid = await this._integratedTerminalInstance.processId; + needNewTerminal = await hasChildProcesses(pid); // if no processes running in terminal reuse terminal + } - const configProvider = await this._configurationService.getConfigProvider(); - const shell = this._terminalService.getDefaultShell(true, configProvider); + const configProvider = await this._configurationService.getConfigProvider(); + const shell = this._terminalService.getDefaultShell(true, configProvider); - if (needNewTerminal || !this._integratedTerminalInstance) { - const options: vscode.TerminalOptions = { - shellPath: shell, - // shellArgs: this._terminalService._getDefaultShellArgs(configProvider), - cwd: args.cwd, - name: args.title || nls.localize('debug.terminal.title', "debuggee"), - env: args.env - }; - delete args.cwd; - delete args.env; - this._integratedTerminalInstance = this._terminalService.createTerminalFromOptions(options); - } - const terminal: vscode.Terminal = this._integratedTerminalInstance; + if (needNewTerminal || !this._integratedTerminalInstance) { - terminal.show(); + const options: vscode.TerminalOptions = { + shellPath: shell, + // shellArgs: this._terminalService._getDefaultShellArgs(configProvider), + cwd: args.cwd, + name: args.title || nls.localize('debug.terminal.title', "debuggee"), + env: args.env + }; + delete args.cwd; + delete args.env; + this._integratedTerminalInstance = this._terminalService.createTerminalFromOptions(options); + } - return this._integratedTerminalInstance.processId.then(shellProcessId => { + const terminal = this._integratedTerminalInstance; - const command = prepareCommand(args, shell, configProvider); + terminal.show(); - terminal.sendText(command, true); + const shellProcessId = await this._integratedTerminalInstance.processId; + const command = prepareCommand(args, shell, configProvider); + terminal.sendText(command, true); - return shellProcessId; - }); - }); + return shellProcessId; } else if (args.kind === 'external') { runInExternalTerminal(args, await this._configurationService.getConfigProvider()); } - return Promise.resolve(undefined); + return super.$runInTerminal(args); } - public async $substituteVariables(folderUri: UriComponents | undefined, config: IConfig): Promise { - if (!this._variableResolver) { - const [workspaceFolders, configProvider] = await Promise.all([this._workspaceService.getWorkspaceFolders2(), this._configurationService.getConfigProvider()]); - this._variableResolver = new ExtHostVariableResolverService(workspaceFolders || [], this._editorsService, configProvider); - } - let ws: IWorkspaceFolder | undefined; - const folder = await this.getFolder(folderUri); - if (folder) { - ws = { - uri: folder.uri, - name: folder.name, - index: folder.index, - toResource: () => { - throw new Error('Not implemented'); - } - }; - } - return this._variableResolver.resolveAny(ws, config); + protected createVariableResolver(folders: vscode.WorkspaceFolder[], editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfigProvider): AbstractVariableResolverService { + return new ExtHostVariableResolverService(folders, editorService, configurationService, process.env as IProcessEnvironment); } - public async $startDASession(debugAdapterHandle: number, sessionDto: IDebugSessionDto): Promise { - const mythis = this; - - const session = await this.getSession(sessionDto); - - return this.getAdapterDescriptor(this.getAdapterFactoryByType(session.type), session).then(daDescriptor => { - - const adapter = this.convertToDto(daDescriptor); - let da: AbstractDebugAdapter | undefined = undefined; - - switch (adapter.type) { - - case 'server': - da = new SocketDebugAdapter(adapter); - break; - - case 'executable': - da = new ExecutableDebugAdapter(adapter, session.type); - break; - - case 'implementation': - da = new DirectDebugAdapter(adapter.implementation); - break; - - default: - break; - } - - const debugAdapter = da; - - if (debugAdapter) { - this._debugAdapters.set(debugAdapterHandle, debugAdapter); - - return this.getDebugAdapterTrackers(session).then(tracker => { - - if (tracker) { - this._debugAdaptersTrackers.set(debugAdapterHandle, tracker); - } - - debugAdapter.onMessage(async message => { - - if (message.type === 'request' && (message).command === 'handshake') { - - const request = message; - - const response: DebugProtocol.Response = { - type: 'response', - seq: 0, - command: request.command, - request_seq: request.seq, - success: true - }; - - if (!this._signService) { - this._signService = new SignService(); - } - - try { - const signature = await this._signService.sign(request.arguments.value); - response.body = { - signature: signature - }; - debugAdapter.sendResponse(response); - } catch (e) { - response.success = false; - response.message = e.message; - debugAdapter.sendResponse(response); - } - } else { - if (tracker && tracker.onDidSendMessage) { - tracker.onDidSendMessage(message); - } - - // DA -> VS Code - message = convertToVSCPaths(message, true); - - mythis._debugServiceProxy.$acceptDAMessage(debugAdapterHandle, message); - } - }); - debugAdapter.onError(err => { - if (tracker && tracker.onError) { - tracker.onError(err); - } - this._debugServiceProxy.$acceptDAError(debugAdapterHandle, err.name, err.message, err.stack); - }); - debugAdapter.onExit((code: number) => { - if (tracker && tracker.onExit) { - tracker.onExit(code, undefined); - } - this._debugServiceProxy.$acceptDAExit(debugAdapterHandle, code, undefined); - }); - - if (tracker && tracker.onWillStartSession) { - tracker.onWillStartSession(); - } - - return debugAdapter.startSession(); - }); - - } - return undefined; - }); - } - - public $sendDAMessage(debugAdapterHandle: number, message: DebugProtocol.ProtocolMessage): void { - - // VS Code -> DA - message = convertToDAPaths(message, false); - - const tracker = this._debugAdaptersTrackers.get(debugAdapterHandle); // TODO@AW: same handle? - if (tracker && tracker.onWillReceiveMessage) { - tracker.onWillReceiveMessage(message); - } - - const da = this._debugAdapters.get(debugAdapterHandle); - if (da) { - da.sendMessage(message); - } - } - - public $stopDASession(debugAdapterHandle: number): Promise { - - const tracker = this._debugAdaptersTrackers.get(debugAdapterHandle); - this._debugAdaptersTrackers.delete(debugAdapterHandle); - if (tracker && tracker.onWillStopSession) { - tracker.onWillStopSession(); - } - - const da = this._debugAdapters.get(debugAdapterHandle); - this._debugAdapters.delete(debugAdapterHandle); - if (da) { - return da.stopSession(); - } else { - return Promise.resolve(void 0); - } - } - - public $acceptBreakpointsDelta(delta: IBreakpointsDeltaDto): void { - - const a: vscode.Breakpoint[] = []; - const r: vscode.Breakpoint[] = []; - const c: vscode.Breakpoint[] = []; - - if (delta.added) { - for (const bpd of delta.added) { - const id = bpd.id; - if (id && !this._breakpoints.has(id)) { - let bp: vscode.Breakpoint; - if (bpd.type === 'function') { - bp = new FunctionBreakpoint(bpd.functionName, bpd.enabled, bpd.condition, bpd.hitCondition, bpd.logMessage); - } else if (bpd.type === 'data') { - bp = new DataBreakpoint(bpd.label, bpd.dataId, bpd.canPersist, bpd.enabled, bpd.hitCondition, bpd.condition, bpd.logMessage); - } else { - const uri = URI.revive(bpd.uri); - bp = new SourceBreakpoint(new Location(uri, new Position(bpd.line, bpd.character)), bpd.enabled, bpd.condition, bpd.hitCondition, bpd.logMessage); - } - (bp as any)._id = id; - this._breakpoints.set(id, bp); - a.push(bp); - } - } - } - - if (delta.removed) { - for (const id of delta.removed) { - const bp = this._breakpoints.get(id); - if (bp) { - this._breakpoints.delete(id); - r.push(bp); - } - } - } - - if (delta.changed) { - for (const bpd of delta.changed) { - if (bpd.id) { - const bp = this._breakpoints.get(bpd.id); - if (bp) { - if (bp instanceof FunctionBreakpoint && bpd.type === 'function') { - const fbp = bp; - fbp.enabled = bpd.enabled; - fbp.condition = bpd.condition; - fbp.hitCondition = bpd.hitCondition; - fbp.logMessage = bpd.logMessage; - fbp.functionName = bpd.functionName; - } else if (bp instanceof SourceBreakpoint && bpd.type === 'source') { - const sbp = bp; - sbp.enabled = bpd.enabled; - sbp.condition = bpd.condition; - sbp.hitCondition = bpd.hitCondition; - sbp.logMessage = bpd.logMessage; - sbp.location = new Location(URI.revive(bpd.uri), new Position(bpd.line, bpd.character)); - } - c.push(bp); - } - } - } - } - - this.fireBreakpointChanges(a, r, c); - } - - public $provideDebugConfigurations(configProviderHandle: number, folderUri: UriComponents | undefined, token: CancellationToken): Promise { - return asPromise(async () => { - const provider = this.getConfigProviderByHandle(configProviderHandle); - if (!provider) { - throw new Error('no DebugConfigurationProvider found'); - } - if (!provider.provideDebugConfigurations) { - throw new Error('DebugConfigurationProvider has no method provideDebugConfigurations'); - } - const folder = await this.getFolder(folderUri); - return provider.provideDebugConfigurations(folder, token); - }).then(debugConfigurations => { - if (!debugConfigurations) { - throw new Error('nothing returned from DebugConfigurationProvider.provideDebugConfigurations'); - } - return debugConfigurations; - }); - } - - public $resolveDebugConfiguration(configProviderHandle: number, folderUri: UriComponents | undefined, debugConfiguration: vscode.DebugConfiguration, token: CancellationToken): Promise { - return asPromise(async () => { - const provider = this.getConfigProviderByHandle(configProviderHandle); - if (!provider) { - throw new Error('no DebugConfigurationProvider found'); - } - if (!provider.resolveDebugConfiguration) { - throw new Error('DebugConfigurationProvider has no method resolveDebugConfiguration'); - } - const folder = await this.getFolder(folderUri); - return provider.resolveDebugConfiguration(folder, debugConfiguration, token); - }); - } - - // TODO@AW deprecated and legacy - public $legacyDebugAdapterExecutable(configProviderHandle: number, folderUri: UriComponents | undefined): Promise { - return asPromise(async () => { - const provider = this.getConfigProviderByHandle(configProviderHandle); - if (!provider) { - throw new Error('no DebugConfigurationProvider found'); - } - if (!provider.debugAdapterExecutable) { - throw new Error('DebugConfigurationProvider has no method debugAdapterExecutable'); - } - const folder = await this.getFolder(folderUri); - return provider.debugAdapterExecutable(folder, CancellationToken.None); - }).then(executable => { - if (!executable) { - throw new Error('nothing returned from DebugConfigurationProvider.debugAdapterExecutable'); - } - return this.convertToDto(executable); - }); - } - - public async $provideDebugAdapter(adapterProviderHandle: number, sessionDto: IDebugSessionDto): Promise { - const adapterProvider = this.getAdapterProviderByHandle(adapterProviderHandle); - if (!adapterProvider) { - return Promise.reject(new Error('no handler found')); - } - const session = await this.getSession(sessionDto); - return this.getAdapterDescriptor(adapterProvider, session).then(x => this.convertToDto(x)); - } - - public async $acceptDebugSessionStarted(sessionDto: IDebugSessionDto): Promise { - const session = await this.getSession(sessionDto); - this._onDidStartDebugSession.fire(session); - } - - public async $acceptDebugSessionTerminated(sessionDto: IDebugSessionDto): Promise { - const session = await this.getSession(sessionDto); - if (session) { - this._onDidTerminateDebugSession.fire(session); - this._debugSessions.delete(session.id); - } - } - - public async $acceptDebugSessionActiveChanged(sessionDto: IDebugSessionDto | undefined): Promise { - this._activeDebugSession = sessionDto ? await this.getSession(sessionDto) : undefined; - this._onDidChangeActiveDebugSession.fire(this._activeDebugSession); - } - - public async $acceptDebugSessionNameChanged(sessionDto: IDebugSessionDto, name: string): Promise { - const session = await this.getSession(sessionDto); - if (session) { - session._acceptNameChanged(name); - } - } - - public async $acceptDebugSessionCustomEvent(sessionDto: IDebugSessionDto, event: any): Promise { - const session = await this.getSession(sessionDto); - const ee: vscode.DebugSessionCustomEvent = { - session: session, - event: event.event, - body: event.body - }; - this._onDidReceiveDebugSessionCustomEvent.fire(ee); - } - - // private & dto helpers - - private convertToDto(x: vscode.DebugAdapterDescriptor | undefined): IAdapterDescriptor { - if (x instanceof DebugAdapterExecutable) { - return { - type: 'executable', - command: x.command, - args: x.args, - options: x.options - }; - } else if (x instanceof DebugAdapterServer) { - return { - type: 'server', - port: x.port, - host: x.host - }; - } else /* if (x instanceof DebugAdapterImplementation) { - return { - type: 'implementation', - implementation: x.implementation - }; - } else */ { - throw new Error('convertToDto unexpected type'); - } - } - - private getAdapterFactoryByType(type: string): vscode.DebugAdapterDescriptorFactory | undefined { - const results = this._adapterFactories.filter(p => p.type === type); - if (results.length > 0) { - return results[0].factory; - } - return undefined; - } - - private getAdapterProviderByHandle(handle: number): vscode.DebugAdapterDescriptorFactory | undefined { - const results = this._adapterFactories.filter(p => p.handle === handle); - if (results.length > 0) { - return results[0].factory; - } - return undefined; - } - - private getConfigProviderByHandle(handle: number): vscode.DebugConfigurationProvider | undefined { - const results = this._configProviders.filter(p => p.handle === handle); - if (results.length > 0) { - return results[0].provider; - } - return undefined; - } - - private definesDebugType(ed: IExtensionDescription, type: string) { - if (ed.contributes) { - const debuggers = ed.contributes['debuggers']; - if (debuggers && debuggers.length > 0) { - for (const dbg of debuggers) { - // only debugger contributions with a "label" are considered a "defining" debugger contribution - if (dbg.label && dbg.type) { - if (dbg.type === type) { - return true; - } - } - } - } - } - return false; - } - - private getDebugAdapterTrackers(session: ExtHostDebugSession): Promise { - - const config = session.configuration; - const type = config.type; - - const promises = this._trackerFactories - .filter(tuple => tuple.type === type || tuple.type === '*') - .map(tuple => asPromise>(() => tuple.factory.createDebugAdapterTracker(session)).then(p => p, err => null)); - - return Promise.race([ - Promise.all(promises).then(result => { - const trackers = result.filter(t => !!t); // filter null - if (trackers.length > 0) { - return new MultiTracker(trackers); - } - return undefined; - }), - new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - clearTimeout(timeout); - reject(new Error('timeout')); - }, 1000); - }) - ]).catch(err => { - // ignore errors - return undefined; - }); - } - - private async getAdapterDescriptor(adapterProvider: vscode.DebugAdapterDescriptorFactory | undefined, session: ExtHostDebugSession): Promise { - - // a "debugServer" attribute in the launch config takes precedence - const serverPort = session.configuration.debugServer; - if (typeof serverPort === 'number') { - return Promise.resolve(new DebugAdapterServer(serverPort)); - } - - // TODO@AW legacy - const pair = this._configProviders.filter(p => p.type === session.type).pop(); - if (pair && pair.provider.debugAdapterExecutable) { - const func = pair.provider.debugAdapterExecutable; - return asPromise(() => func(session.workspaceFolder, CancellationToken.None)).then(executable => { - if (executable) { - return executable; - } - return undefined; - }); - } - - if (adapterProvider) { - const extensionRegistry = await this._extensionService.getExtensionRegistry(); - return asPromise(() => adapterProvider.createDebugAdapterDescriptor(session, this.daExecutableFromPackage(session, extensionRegistry))).then(daDescriptor => { - if (daDescriptor) { - return daDescriptor; - } - return undefined; - }); - } - - // try deprecated command based extension API "adapterExecutableCommand" to determine the executable - // TODO@AW legacy - const aex = this._aexCommands.get(session.type); - if (aex) { - const folder = session.workspaceFolder; - const rootFolder = folder ? folder.uri.toString() : undefined; - return this._commandService.executeCommand(aex, rootFolder).then((ae: { command: string, args: string[] }) => { - return new DebugAdapterExecutable(ae.command, ae.args || []); - }); - } - - // fallback: use executable information from package.json - const extensionRegistry = await this._extensionService.getExtensionRegistry(); - return Promise.resolve(this.daExecutableFromPackage(session, extensionRegistry)); - } - - private daExecutableFromPackage(session: ExtHostDebugSession, extensionRegistry: ExtensionDescriptionRegistry): DebugAdapterExecutable | undefined { - const dae = ExecutableDebugAdapter.platformAdapterExecutable(extensionRegistry.getAllExtensionDescriptions(), session.type); - if (dae) { - return new DebugAdapterExecutable(dae.command, dae.args, dae.options); - } - return undefined; - } - - private startBreakpoints() { - if (!this._breakpointEventsActive) { - this._breakpointEventsActive = true; - this._debugServiceProxy.$startBreakpointEvents(); - } - } - - private fireBreakpointChanges(added: vscode.Breakpoint[], removed: vscode.Breakpoint[], changed: vscode.Breakpoint[]) { - if (added.length > 0 || removed.length > 0 || changed.length > 0) { - this._onDidChangeBreakpoints.fire(Object.freeze({ - added, - removed, - changed, - })); - } - } - - private async getSession(dto: IDebugSessionDto): Promise { - if (dto) { - if (typeof dto === 'string') { - const ds = this._debugSessions.get(dto); - if (ds) { - return ds; - } - } else { - let ds = this._debugSessions.get(dto.id); - if (!ds) { - const folder = await this.getFolder(dto.folderUri); - ds = new ExtHostDebugSession(this._debugServiceProxy, dto.id, dto.type, dto.name, folder, dto.configuration); - this._debugSessions.set(ds.id, ds); - this._debugServiceProxy.$sessionCached(ds.id); - } - return ds; - } - } - throw new Error('cannot find session'); - } - - private getFolder(_folderUri: UriComponents | undefined): Promise { - if (_folderUri) { - const folderURI = URI.revive(_folderUri); - return this._workspaceService.resolveWorkspaceFolder(folderURI); - } - return Promise.resolve(undefined); - } -} - -export class ExtHostDebugSession implements vscode.DebugSession { - - constructor( - private _debugServiceProxy: MainThreadDebugServiceShape, - private _id: DebugSessionUUID, - private _type: string, - private _name: string, - private _workspaceFolder: vscode.WorkspaceFolder | undefined, - private _configuration: vscode.DebugConfiguration) { - } - - public get id(): string { - return this._id; - } - - public get type(): string { - return this._type; - } - - public get name(): string { - return this._name; - } - - public set name(name: string) { - this._name = name; - this._debugServiceProxy.$setDebugSessionName(this._id, name); - } - - _acceptNameChanged(name: string) { - this._name = name; - } - - public get workspaceFolder(): vscode.WorkspaceFolder | undefined { - return this._workspaceFolder; - } - - public get configuration(): vscode.DebugConfiguration { - return this._configuration; - } - - public customRequest(command: string, args: any): Promise { - return this._debugServiceProxy.$customDebugAdapterRequest(this._id, command, args); - } -} - -export class ExtHostDebugConsole implements vscode.DebugConsole { - - private _debugServiceProxy: MainThreadDebugServiceShape; - - constructor(proxy: MainThreadDebugServiceShape) { - this._debugServiceProxy = proxy; - } - - append(value: string): void { - this._debugServiceProxy.$appendDebugConsole(value); - } - - appendLine(value: string): void { - this.append(value + '\n'); - } -} - -export class ExtHostVariableResolverService extends AbstractVariableResolverService { - - constructor(folders: vscode.WorkspaceFolder[], editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfigProvider) { - super({ - getFolderUri: (folderName: string): URI | undefined => { - const found = folders.filter(f => f.name === folderName); - if (found && found.length > 0) { - return found[0].uri; - } - return undefined; - }, - getWorkspaceFolderCount: (): number => { - return folders.length; - }, - getConfigurationValue: (folderUri: URI, section: string): string | undefined => { - return configurationService.getConfiguration(undefined, folderUri).get(section); - }, - getExecPath: (): string | undefined => { - return process.env['VSCODE_EXEC_PATH']; - }, - getFilePath: (): string | undefined => { - const activeEditor = editorService.activeEditor(); - if (activeEditor) { - const resource = activeEditor.document.uri; - if (resource.scheme === Schemas.file) { - return path.normalize(resource.fsPath); - } - } - return undefined; - }, - getSelectedText: (): string | undefined => { - const activeEditor = editorService.activeEditor(); - if (activeEditor && !activeEditor.selection.isEmpty) { - return activeEditor.document.getText(activeEditor.selection); - } - return undefined; - }, - getLineNumber: (): string | undefined => { - const activeEditor = editorService.activeEditor(); - if (activeEditor) { - return String(activeEditor.selection.end.line + 1); - } - return undefined; - } - }, process.env as IProcessEnvironment); - } -} - -interface ConfigProviderTuple { - type: string; - handle: number; - provider: vscode.DebugConfigurationProvider; -} - -interface DescriptorFactoryTuple { - type: string; - handle: number; - factory: vscode.DebugAdapterDescriptorFactory; -} - -interface TrackerFactoryTuple { - type: string; - handle: number; - factory: vscode.DebugAdapterTrackerFactory; -} - -class MultiTracker implements vscode.DebugAdapterTracker { - - constructor(private trackers: vscode.DebugAdapterTracker[]) { - } - - onWillStartSession(): void { - this.trackers.forEach(t => t.onWillStartSession ? t.onWillStartSession() : undefined); - } - - onWillReceiveMessage(message: any): void { - this.trackers.forEach(t => t.onWillReceiveMessage ? t.onWillReceiveMessage(message) : undefined); - } - - onDidSendMessage(message: any): void { - this.trackers.forEach(t => t.onDidSendMessage ? t.onDidSendMessage(message) : undefined); - } - - onWillStopSession(): void { - this.trackers.forEach(t => t.onWillStopSession ? t.onWillStopSession() : undefined); - } - - onError(error: Error): void { - this.trackers.forEach(t => t.onError ? t.onError(error) : undefined); - } - - onExit(code: number, signal: string): void { - this.trackers.forEach(t => t.onExit ? t.onExit(code, signal) : undefined); - } -} - -interface IDapTransport { - start(cb: (msg: DebugProtocol.ProtocolMessage) => void, errorcb: (event: DebugProtocol.Event) => void): void; - send(message: DebugProtocol.ProtocolMessage): void; - stop(): void; -} - -class DirectDebugAdapter extends AbstractDebugAdapter implements IDapTransport { - - - private _sendUp!: (msg: DebugProtocol.ProtocolMessage) => void; - - constructor(implementation: any) { - super(); - if (implementation.__setTransport) { - implementation.__setTransport(this); - } - } - - // IDapTransport - start(cb: (msg: DebugProtocol.ProtocolMessage) => void, errorcb: (event: DebugProtocol.Event) => void) { - this._sendUp = cb; - } - - // AbstractDebugAdapter - startSession(): Promise { - return Promise.resolve(undefined); - } - - // AbstractDebugAdapter - // VSCode -> DA - sendMessage(message: DebugProtocol.ProtocolMessage): void { - this._sendUp(message); - } - - // AbstractDebugAdapter - stopSession(): Promise { - this.stop(); - return Promise.resolve(undefined); - } - - // IDapTransport - // DA -> VSCode - send(message: DebugProtocol.ProtocolMessage) { - this.acceptMessage(message); - } - - // IDapTransport - stop(): void { - throw new Error('Method not implemented.'); - } } diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index e3e0cd936b..5e66335bb0 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -72,6 +72,7 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { return nativeProcessSend.apply(process, args); } mainThreadConsole.$logExtensionHostMessage(args[0]); + return false; }; } diff --git a/src/vs/workbench/api/node/extHostSearch.ts b/src/vs/workbench/api/node/extHostSearch.ts index 94a5194023..cb9669501d 100644 --- a/src/vs/workbench/api/node/extHostSearch.ts +++ b/src/vs/workbench/api/node/extHostSearch.ts @@ -3,47 +3,37 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken } from 'vs/base/common/cancellation'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { URI, UriComponents } from 'vs/base/common/uri'; +import { URI } from 'vs/base/common/uri'; import * as pfs from 'vs/base/node/pfs'; import { ILogService } from 'vs/platform/log/common/log'; -import { IFileQuery, IFolderQuery, IRawFileQuery, IRawQuery, IRawTextQuery, ISearchCompleteStats, ITextQuery, isSerializedFileMatch, ISerializedSearchProgressItem } from 'vs/workbench/services/search/common/search'; -import { FileSearchManager } from 'vs/workbench/services/search/node/fileSearchManager'; +import { IFileQuery, IRawFileQuery, ISearchCompleteStats, isSerializedFileMatch, ISerializedSearchProgressItem, ITextQuery } from 'vs/workbench/services/search/common/search'; import { SearchService } from 'vs/workbench/services/search/node/rawSearchService'; import { RipgrepSearchProvider } from 'vs/workbench/services/search/node/ripgrepSearchProvider'; import { OutputChannel } from 'vs/workbench/services/search/node/ripgrepSearchUtils'; -import { TextSearchManager } from 'vs/workbench/services/search/node/textSearchManager'; import * as vscode from 'vscode'; -import { ExtHostSearchShape, MainContext, MainThreadSearchShape } from '../common/extHost.protocol'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { ExtHostSearch, reviveQuery } from 'vs/workbench/api/common/extHostSearch'; +import { Schemas } from 'vs/base/common/network'; +import { NativeTextSearchManager } from 'vs/workbench/services/search/node/textSearchManager'; +import { TextSearchManager } from 'vs/workbench/services/search/common/textSearchManager'; -export class ExtHostSearch implements ExtHostSearchShape { +export class NativeExtHostSearch extends ExtHostSearch { - private readonly _proxy: MainThreadSearchShape; - private readonly _textSearchProvider = new Map(); - private readonly _textSearchUsedSchemes = new Set(); - private readonly _fileSearchProvider = new Map(); - private readonly _fileSearchUsedSchemes = new Set(); - private _handlePool: number = 0; + protected _pfs: typeof pfs = pfs; // allow extending for tests private _internalFileSearchHandle: number = -1; private _internalFileSearchProvider: SearchService | null = null; - private _fileSearchManager: FileSearchManager; - - protected _pfs: typeof pfs = pfs; // allow extending for tests - constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, @IExtHostInitDataService initData: IExtHostInitDataService, - @IURITransformerService private _uriTransformer: IURITransformerService, - @ILogService private _logService: ILogService, + @IURITransformerService _uriTransformer: IURITransformerService, + @ILogService _logService: ILogService, ) { - this._proxy = extHostRpc.getProxy(MainContext.MainThreadSearch); - this._fileSearchManager = new FileSearchManager(); + super(extHostRpc, _uriTransformer, _logService); if (initData.remote.isRemote && initData.remote.authority) { this._registerEHSearchProviders(); @@ -52,47 +42,11 @@ export class ExtHostSearch implements ExtHostSearchShape { private _registerEHSearchProviders(): void { const outputChannel = new OutputChannel(this._logService); - this.registerTextSearchProvider('file', new RipgrepSearchProvider(outputChannel)); - this.registerInternalFileSearchProvider('file', new SearchService()); + this.registerTextSearchProvider(Schemas.file, new RipgrepSearchProvider(outputChannel)); + this.registerInternalFileSearchProvider(Schemas.file, new SearchService()); } - private _transformScheme(scheme: string): string { - return this._uriTransformer.transformOutgoingScheme(scheme); - } - - registerTextSearchProvider(scheme: string, provider: vscode.TextSearchProvider): IDisposable { - if (this._textSearchUsedSchemes.has(scheme)) { - throw new Error(`a text search provider for the scheme '${scheme}' is already registered`); - } - - this._textSearchUsedSchemes.add(scheme); - const handle = this._handlePool++; - this._textSearchProvider.set(handle, provider); - this._proxy.$registerTextSearchProvider(handle, this._transformScheme(scheme)); - return toDisposable(() => { - this._textSearchUsedSchemes.delete(scheme); - this._textSearchProvider.delete(handle); - this._proxy.$unregisterProvider(handle); - }); - } - - registerFileSearchProvider(scheme: string, provider: vscode.FileSearchProvider): IDisposable { - if (this._fileSearchUsedSchemes.has(scheme)) { - throw new Error(`a file search provider for the scheme '${scheme}' is already registered`); - } - - this._fileSearchUsedSchemes.add(scheme); - const handle = this._handlePool++; - this._fileSearchProvider.set(handle, provider); - this._proxy.$registerFileSearchProvider(handle, this._transformScheme(scheme)); - return toDisposable(() => { - this._fileSearchUsedSchemes.delete(scheme); - this._fileSearchProvider.delete(handle); - this._proxy.$unregisterProvider(handle); - }); - } - - registerInternalFileSearchProvider(scheme: string, provider: SearchService): IDisposable { + private registerInternalFileSearchProvider(scheme: string, provider: SearchService): IDisposable { const handle = this._handlePool++; this._internalFileSearchProvider = provider; this._internalFileSearchHandle = handle; @@ -103,23 +57,16 @@ export class ExtHostSearch implements ExtHostSearchShape { }); } - $provideFileSearchResults(handle: number, session: number, rawQuery: IRawFileQuery, token: CancellationToken): Promise { + $provideFileSearchResults(handle: number, session: number, rawQuery: IRawFileQuery, token: vscode.CancellationToken): Promise { const query = reviveQuery(rawQuery); if (handle === this._internalFileSearchHandle) { return this.doInternalFileSearch(handle, session, query, token); - } else { - const provider = this._fileSearchProvider.get(handle); - if (provider) { - return this._fileSearchManager.fileSearch(query, provider, batch => { - this._proxy.$handleFileMatch(handle, session, batch.map(p => p.resource)); - }, token); - } else { - throw new Error('unknown provider: ' + handle); - } } + + return super.$provideFileSearchResults(handle, session, rawQuery, token); } - private doInternalFileSearch(handle: number, session: number, rawQuery: IFileQuery, token: CancellationToken): Promise { + private doInternalFileSearch(handle: number, session: number, rawQuery: IFileQuery, token: vscode.CancellationToken): Promise { const onResult = (ev: ISerializedSearchProgressItem) => { if (isSerializedFileMatch(ev)) { ev = [ev]; @@ -147,37 +94,11 @@ export class ExtHostSearch implements ExtHostSearchShape { this._internalFileSearchProvider.clearCache(cacheKey); } - this._fileSearchManager.clearCache(cacheKey); - - return Promise.resolve(undefined); + return super.$clearCache(cacheKey); } - $provideTextSearchResults(handle: number, session: number, rawQuery: IRawTextQuery, token: CancellationToken): Promise { - const provider = this._textSearchProvider.get(handle); - if (!provider || !provider.provideTextSearchResults) { - throw new Error(`Unknown provider ${handle}`); - } - - const query = reviveQuery(rawQuery); - const engine = new TextSearchManager(query, provider, this._pfs); - return engine.search(progress => this._proxy.$handleTextMatch(handle, session, progress), token); + protected createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProvider): TextSearchManager { + return new NativeTextSearchManager(query, provider); } } -function reviveQuery(rawQuery: U): U extends IRawTextQuery ? ITextQuery : IFileQuery { - return { - ...rawQuery, // TODO - ...{ - folderQueries: rawQuery.folderQueries && rawQuery.folderQueries.map(reviveFolderQuery), - extraFileResources: rawQuery.extraFileResources && rawQuery.extraFileResources.map(components => URI.revive(components)) - } - }; -} - -function reviveFolderQuery(rawFolderQuery: IFolderQuery): IFolderQuery { - return { - ...rawFolderQuery, - folder: URI.revive(rawFolderQuery.folder) - }; -} - diff --git a/src/vs/workbench/api/node/extHostStoragePaths.ts b/src/vs/workbench/api/node/extHostStoragePaths.ts index 19cf948cc3..df911131c6 100644 --- a/src/vs/workbench/api/node/extHostStoragePaths.ts +++ b/src/vs/workbench/api/node/extHostStoragePaths.ts @@ -11,6 +11,7 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions' import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { ILogService } from 'vs/platform/log/common/log'; export class ExtensionStoragePaths implements IExtensionStoragePaths { @@ -22,7 +23,10 @@ export class ExtensionStoragePaths implements IExtensionStoragePaths { readonly whenReady: Promise; private _value?: string; - constructor(@IExtHostInitDataService initData: IExtHostInitDataService) { + constructor( + @IExtHostInitDataService initData: IExtHostInitDataService, + @ILogService private readonly _logService: ILogService, + ) { this._workspace = withNullAsUndefined(initData.workspace); this._environment = initData.environment; this.whenReady = this._getOrCreateWorkspaceStoragePath().then(value => this._value = value); @@ -69,7 +73,7 @@ export class ExtensionStoragePaths implements IExtensionStoragePaths { return storagePath; } catch (e) { - console.error(e); + this._logService.error(e); return undefined; } } diff --git a/src/vs/workbench/api/node/extHostTask.ts b/src/vs/workbench/api/node/extHostTask.ts index e1f3430781..0e451c8706 100644 --- a/src/vs/workbench/api/node/extHostTask.ts +++ b/src/vs/workbench/api/node/extHostTask.ts @@ -5,24 +5,28 @@ // import * as path from 'vs/base/common/path'; -import { /*URI,*/ UriComponents } from 'vs/base/common/uri'; +import { URI, UriComponents } from 'vs/base/common/uri'; // import { win32 } from 'vs/base/node/processes'; import * as types from 'vs/workbench/api/common/extHostTypes'; import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import * as vscode from 'vscode'; import * as tasks from '../common/shared/tasks'; -// import { ExtHostVariableResolverService } from 'vs/workbench/api/node/extHostDebugService'; +import * as Objects from 'vs/base/common/objects'; +import { ExtHostVariableResolverService } from 'vs/workbench/api/common/extHostDebugService'; import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; -// import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { ExtHostTaskBase, TaskHandleDTO, TaskDTO, CustomExecutionDTO, HandlerData } from 'vs/workbench/api/common/extHostTask'; import { Schemas } from 'vs/base/common/network'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IProcessEnvironment } from 'vs/base/common/platform'; export class ExtHostTask extends ExtHostTaskBase { + private _variableResolver: ExtHostVariableResolverService | undefined; constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, @@ -30,9 +34,10 @@ export class ExtHostTask extends ExtHostTaskBase { @IExtHostWorkspace workspaceService: IExtHostWorkspace, @IExtHostDocumentsAndEditors editorService: IExtHostDocumentsAndEditors, @IExtHostConfiguration configurationService: IExtHostConfiguration, - @IExtHostTerminalService extHostTerminalService: IExtHostTerminalService + @IExtHostTerminalService extHostTerminalService: IExtHostTerminalService, + @ILogService logService: ILogService ) { - super(extHostRpc, initData, workspaceService, editorService, configurationService, extHostTerminalService); + super(extHostRpc, initData, workspaceService, editorService, configurationService, extHostTerminalService, logService); if (initData.remote.isRemote && initData.remote.authority) { this.registerTaskSystem(Schemas.vscodeRemote, { scheme: Schemas.vscodeRemote, @@ -69,7 +74,7 @@ export class ExtHostTask extends ExtHostTaskBase { if (value) { for (let task of value) { if (!task.definition || !validTypes[task.definition.type]) { - console.warn(`The task [${task.source}, ${task.name}] uses an undefined task type. The task will be ignored in the future.`); + this._logService.warn(`The task [${task.source}, ${task.name}] uses an undefined task type. The task will be ignored in the future.`); } const taskDTO: tasks.TaskDTO | undefined = TaskDTO.from(task, handler.extension); @@ -95,9 +100,42 @@ export class ExtHostTask extends ExtHostTaskBase { return resolvedTaskDTO; } + private async getVariableResolver(workspaceFolders: vscode.WorkspaceFolder[]): Promise { + if (this._variableResolver === undefined) { + const configProvider = await this._configurationService.getConfigProvider(); + this._variableResolver = new ExtHostVariableResolverService(workspaceFolders, this._editorService, configProvider, process.env as IProcessEnvironment); + } + return this._variableResolver; + } + + protected async resolveDefinition(uri: number | UriComponents | undefined, definition: vscode.TaskDefinition | undefined): Promise { + if (!uri || (typeof uri === 'number') || !definition) { + return definition; + } + const workspaceFolder = await this._workspaceProvider.resolveWorkspaceFolder(URI.revive(uri)); + const workspaceFolders = await this._workspaceProvider.getWorkspaceFolders2(); + if (!workspaceFolders || !workspaceFolder) { + return definition; + } + const resolver = await this.getVariableResolver(workspaceFolders); + const ws: IWorkspaceFolder = { + uri: workspaceFolder.uri, + name: workspaceFolder.name, + index: workspaceFolder.index, + toResource: () => { + throw new Error('Not implemented'); + } + }; + const resolvedDefinition = Objects.deepClone(definition); + for (const key in resolvedDefinition) { + resolvedDefinition[key] = resolver.resolve(ws, resolvedDefinition[key]); + } + + return resolvedDefinition; + } + public async $resolveVariables(uriComponents: UriComponents, toResolve: { process?: { name: string; cwd?: string; path?: string }, variables: string[] }): Promise<{ process?: string, variables: { [key: string]: string; } }> { - /*const configProvider = await this._configurationService.getConfigProvider(); - const uri: URI = URI.revive(uriComponents); + /*const uri: URI = URI.revive(uriComponents); const result = { process: undefined as string, variables: Object.create(null) @@ -107,7 +145,7 @@ export class ExtHostTask extends ExtHostTaskBase { if (!workspaceFolders || !workspaceFolder) { throw new Error('Unexpected: Tasks can only be run in a workspace folder'); } - const resolver = new ExtHostVariableResolverService(workspaceFolders, this._editorService, configProvider); + const resolver = await this.getVariableResolver(workspaceFolders); const ws: IWorkspaceFolder = { uri: workspaceFolder.uri, name: workspaceFolder.name, diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index e0c74f33f5..e2f96f5efe 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -16,7 +16,7 @@ import { IShellLaunchConfig, ITerminalEnvironment } from 'vs/workbench/contrib/t import { TerminalProcess } from 'vs/workbench/contrib/terminal/node/terminalProcess'; import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { ExtHostVariableResolverService } from 'vs/workbench/api/node/extHostDebugService'; +import { ExtHostVariableResolverService } from 'vs/workbench/api/common/extHostDebugService'; import { ExtHostDocumentsAndEditors, IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { getSystemShell, detectAvailableShells } from 'vs/workbench/contrib/terminal/node/terminal'; import { getMainProcessParentEnv } from 'vs/workbench/contrib/terminal/node/terminalEnvironment'; @@ -45,14 +45,14 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { } public createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal { - const terminal = new ExtHostTerminal(this._proxy, name); + const terminal = new ExtHostTerminal(this._proxy, { name, shellPath, shellArgs }, name); terminal.create(shellPath, shellArgs); this._terminals.push(terminal); return terminal; } public createTerminalFromOptions(options: vscode.TerminalOptions): vscode.Terminal { - const terminal = new ExtHostTerminal(this._proxy, options.name); + const terminal = new ExtHostTerminal(this._proxy, options, options.name); terminal.create(options.shellPath, options.shellArgs, options.cwd, options.env, /*options.waitOnExit*/ undefined, options.strictEnv, options.hideFromUser); this._terminals.push(terminal); return terminal; @@ -120,7 +120,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { private async _updateVariableResolver(): Promise { const configProvider = await this._extHostConfiguration.getConfigProvider(); const workspaceFolders = await this._extHostWorkspace.getWorkspaceFolders2(); - this._variableResolver = new ExtHostVariableResolverService(workspaceFolders || [], this._extHostDocumentsAndEditors, configProvider); + this._variableResolver = new ExtHostVariableResolverService(workspaceFolders || [], this._extHostDocumentsAndEditors, configProvider, process.env as platform.IProcessEnvironment); } public async $spawnExtHostProcess(id: number, shellLaunchConfigDto: IShellLaunchConfigDto, activeWorkspaceRootUriComponents: UriComponents | undefined, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise { diff --git a/src/vs/workbench/browser/actions.ts b/src/vs/workbench/browser/actions.ts index e796de7bcb..1e36b69aea 100644 --- a/src/vs/workbench/browser/actions.ts +++ b/src/vs/workbench/browser/actions.ts @@ -7,7 +7,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IAction } from 'vs/base/common/actions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { ITree, IActionProvider } from 'vs/base/parts/tree/browser/tree'; -import { IInstantiationService, IConstructorSignature0, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, IConstructorSignature0, ServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation'; /** * The action bar contributor allows to add actions to an actionbar in a given context. @@ -134,7 +134,7 @@ export interface IActionBarRegistry { * Registers an Actionbar contributor. It will be called to contribute actions to all the action bars * that are used in the Workbench in the given scope. */ - registerActionBarContributor(scope: string, ctor: IConstructorSignature0): void; + registerActionBarContributor(scope: string, ctor: { new(...services: Services): ActionBarContributor }): void; /** * Returns an array of registered action bar contributors known to the workbench for the given scope. diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts index db02240757..864ff68b54 100644 --- a/src/vs/workbench/browser/actions/developerActions.ts +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -152,7 +152,7 @@ class ToggleScreencastModeAction extends Action { } })); - const onKeyDown = domEvent(container, 'keydown', true); + const onKeyDown = domEvent(window, 'keydown', true); let keyboardTimeout: IDisposable = Disposable.None; let length = 0; @@ -214,9 +214,9 @@ class LogStorageAction extends Action { const developerCategory = nls.localize('developer', "Developer"); const registry = Registry.as(Extensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(InspectContextKeysAction, InspectContextKeysAction.ID, InspectContextKeysAction.LABEL), 'Developer: Inspect Context Keys', developerCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleScreencastModeAction, ToggleScreencastModeAction.ID, ToggleScreencastModeAction.LABEL), 'Developer: Toggle Screencast Mode', developerCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(LogStorageAction, LogStorageAction.ID, LogStorageAction.LABEL), 'Developer: Log Storage Database Contents', developerCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(InspectContextKeysAction, InspectContextKeysAction.ID, InspectContextKeysAction.LABEL), 'Developer: Inspect Context Keys', developerCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleScreencastModeAction, ToggleScreencastModeAction.ID, ToggleScreencastModeAction.LABEL), 'Developer: Toggle Screencast Mode', developerCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(LogStorageAction, LogStorageAction.ID, LogStorageAction.LABEL), 'Developer: Log Storage Database Contents', developerCategory); // Screencast Mode const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); diff --git a/src/vs/workbench/browser/actions/helpActions.ts b/src/vs/workbench/browser/actions/helpActions.ts index 7c901cfb84..43f94c2656 100644 --- a/src/vs/workbench/browser/actions/helpActions.ts +++ b/src/vs/workbench/browser/actions/helpActions.ts @@ -248,39 +248,39 @@ const registry = Registry.as(Extensions.WorkbenchActio const helpCategory = nls.localize('help', "Help"); if (KeybindingsReferenceAction.AVAILABLE) { - registry.registerWorkbenchAction(new SyncActionDescriptor(KeybindingsReferenceAction, KeybindingsReferenceAction.ID, KeybindingsReferenceAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_R) }), 'Help: Keyboard Shortcuts Reference', helpCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(KeybindingsReferenceAction, KeybindingsReferenceAction.ID, KeybindingsReferenceAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_R) }), 'Help: Keyboard Shortcuts Reference', helpCategory); } if (OpenDocumentationUrlAction.AVAILABLE) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenDocumentationUrlAction, OpenDocumentationUrlAction.ID, OpenDocumentationUrlAction.LABEL), 'Help: Documentation', helpCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenDocumentationUrlAction, OpenDocumentationUrlAction.ID, OpenDocumentationUrlAction.LABEL), 'Help: Documentation', helpCategory); } if (OpenIntroductoryVideosUrlAction.AVAILABLE) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenIntroductoryVideosUrlAction, OpenIntroductoryVideosUrlAction.ID, OpenIntroductoryVideosUrlAction.LABEL), 'Help: Introductory Videos', helpCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenIntroductoryVideosUrlAction, OpenIntroductoryVideosUrlAction.ID, OpenIntroductoryVideosUrlAction.LABEL), 'Help: Introductory Videos', helpCategory); } if (OpenTipsAndTricksUrlAction.AVAILABLE) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenTipsAndTricksUrlAction, OpenTipsAndTricksUrlAction.ID, OpenTipsAndTricksUrlAction.LABEL), 'Help: Tips and Tricks', helpCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenTipsAndTricksUrlAction, OpenTipsAndTricksUrlAction.ID, OpenTipsAndTricksUrlAction.LABEL), 'Help: Tips and Tricks', helpCategory); } if (OpenNewsletterSignupUrlAction.AVAILABLE) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNewsletterSignupUrlAction, OpenNewsletterSignupUrlAction.ID, OpenNewsletterSignupUrlAction.LABEL), 'Help: Tips and Tricks', helpCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenNewsletterSignupUrlAction, OpenNewsletterSignupUrlAction.ID, OpenNewsletterSignupUrlAction.LABEL), 'Help: Tips and Tricks', helpCategory); } if (OpenTwitterUrlAction.AVAILABLE) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenTwitterUrlAction, OpenTwitterUrlAction.ID, OpenTwitterUrlAction.LABEL), 'Help: Join Us on Twitter', helpCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenTwitterUrlAction, OpenTwitterUrlAction.ID, OpenTwitterUrlAction.LABEL), 'Help: Join Us on Twitter', helpCategory); } if (OpenRequestFeatureUrlAction.AVAILABLE) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenRequestFeatureUrlAction, OpenRequestFeatureUrlAction.ID, OpenRequestFeatureUrlAction.LABEL), 'Help: Search Feature Requests', helpCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenRequestFeatureUrlAction, OpenRequestFeatureUrlAction.ID, OpenRequestFeatureUrlAction.LABEL), 'Help: Search Feature Requests', helpCategory); } if (OpenLicenseUrlAction.AVAILABLE) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenLicenseUrlAction, OpenLicenseUrlAction.ID, OpenLicenseUrlAction.LABEL), 'Help: View License', helpCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenLicenseUrlAction, OpenLicenseUrlAction.ID, OpenLicenseUrlAction.LABEL), 'Help: View License', helpCategory); } if (OpenPrivacyStatementUrlAction.AVAILABE) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPrivacyStatementUrlAction, OpenPrivacyStatementUrlAction.ID, OpenPrivacyStatementUrlAction.LABEL), 'Help: Privacy Statement', helpCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenPrivacyStatementUrlAction, OpenPrivacyStatementUrlAction.ID, OpenPrivacyStatementUrlAction.LABEL), 'Help: Privacy Statement', helpCategory); } // --- Menu Registration @@ -297,15 +297,6 @@ if (OpenDocumentationUrlAction.AVAILABLE) { order: 3 }); } -/* // {{SQL CARBON EDIT}} - Disable unused menu item -MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '1_welcome', - command: { - id: 'update.showCurrentReleaseNotes', - title: nls.localize({ key: 'miReleaseNotes', comment: ['&& denotes a mnemonic'] }, "&&Release Notes") - }, - order: 4 -}); // Reference if (KeybindingsReferenceAction.AVAILABLE) { @@ -362,7 +353,7 @@ if (OpenRequestFeatureUrlAction.AVAILABLE) { }, order: 2 }); -}*/ +} // Legal if (OpenLicenseUrlAction.AVAILABLE) { diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index fc21cc0785..d2aad3396f 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -56,7 +56,7 @@ export class ToggleActivityBarVisibilityAction extends Action { } } -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, ToggleActivityBarVisibilityAction.LABEL), 'View: Toggle Activity Bar Visibility', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, ToggleActivityBarVisibilityAction.LABEL), 'View: Toggle Activity Bar Visibility', viewCategory); MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '2_workbench_layout', @@ -91,7 +91,7 @@ class ToggleCenteredLayout extends Action { } } -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleCenteredLayout, ToggleCenteredLayout.ID, ToggleCenteredLayout.LABEL), 'View: Toggle Centered Layout', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleCenteredLayout, ToggleCenteredLayout.ID, ToggleCenteredLayout.LABEL), 'View: Toggle Centered Layout', viewCategory); MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '1_toggle_view', @@ -143,7 +143,7 @@ export class ToggleEditorLayoutAction extends Action { } const group = viewCategory; -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleEditorLayoutAction, ToggleEditorLayoutAction.ID, ToggleEditorLayoutAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_0, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_0 } }), 'View: Toggle Vertical/Horizontal Editor Layout', group); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleEditorLayoutAction, ToggleEditorLayoutAction.ID, ToggleEditorLayoutAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_0, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_0 } }), 'View: Toggle Vertical/Horizontal Editor Layout', group); MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { group: 'z_flip', @@ -186,7 +186,7 @@ export class ToggleSidebarPositionAction extends Action { } } -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleSidebarPositionAction, ToggleSidebarPositionAction.ID, ToggleSidebarPositionAction.LABEL), 'View: Toggle Side Bar Position', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleSidebarPositionAction, ToggleSidebarPositionAction.ID, ToggleSidebarPositionAction.LABEL), 'View: Toggle Side Bar Position', viewCategory); MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '3_workbench_layout_move', @@ -231,7 +231,7 @@ export class ToggleEditorVisibilityAction extends Action { } } -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleEditorVisibilityAction, ToggleEditorVisibilityAction.ID, ToggleEditorVisibilityAction.LABEL), 'View: Toggle Editor Area Visibility', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleEditorVisibilityAction, ToggleEditorVisibilityAction.ID, ToggleEditorVisibilityAction.LABEL), 'View: Toggle Editor Area Visibility', viewCategory); MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '2_workbench_layout', @@ -266,7 +266,7 @@ export class ToggleSidebarVisibilityAction extends Action { } } -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleSidebarVisibilityAction, ToggleSidebarVisibilityAction.ID, ToggleSidebarVisibilityAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_B }), 'View: Toggle Side Bar Visibility', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleSidebarVisibilityAction, ToggleSidebarVisibilityAction.ID, ToggleSidebarVisibilityAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_B }), 'View: Toggle Side Bar Visibility', viewCategory); MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { group: '2_appearance', @@ -313,7 +313,7 @@ export class ToggleStatusbarVisibilityAction extends Action { } } -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleStatusbarVisibilityAction, ToggleStatusbarVisibilityAction.ID, ToggleStatusbarVisibilityAction.LABEL), 'View: Toggle Status Bar Visibility', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleStatusbarVisibilityAction, ToggleStatusbarVisibilityAction.ID, ToggleStatusbarVisibilityAction.LABEL), 'View: Toggle Status Bar Visibility', viewCategory); MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '2_workbench_layout', @@ -350,7 +350,7 @@ class ToggleTabsVisibilityAction extends Action { } } -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleTabsVisibilityAction, ToggleTabsVisibilityAction.ID, ToggleTabsVisibilityAction.LABEL, { +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleTabsVisibilityAction, ToggleTabsVisibilityAction.ID, ToggleTabsVisibilityAction.LABEL, { primary: undefined, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_W, }, linux: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_W, } @@ -379,7 +379,7 @@ class ToggleZenMode extends Action { } } -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleZenMode, ToggleZenMode.ID, ToggleZenMode.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_Z) }), 'View: Toggle Zen Mode', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleZenMode, ToggleZenMode.ID, ToggleZenMode.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_Z) }), 'View: Toggle Zen Mode', viewCategory); MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '1_toggle_view', @@ -442,7 +442,7 @@ export class ToggleMenuBarAction extends Action { } if (isWindows || isLinux || isWeb) { - registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleMenuBarAction, ToggleMenuBarAction.ID, ToggleMenuBarAction.LABEL), 'View: Toggle Menu Bar', viewCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleMenuBarAction, ToggleMenuBarAction.ID, ToggleMenuBarAction.LABEL), 'View: Toggle Menu Bar', viewCategory); } MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { @@ -529,5 +529,5 @@ export class DecreaseViewSizeAction extends BaseResizeViewAction { } } -registry.registerWorkbenchAction(new SyncActionDescriptor(IncreaseViewSizeAction, IncreaseViewSizeAction.ID, IncreaseViewSizeAction.LABEL, undefined), 'View: Increase Current View Size', viewCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(DecreaseViewSizeAction, DecreaseViewSizeAction.ID, DecreaseViewSizeAction.LABEL, undefined), 'View: Decrease Current View Size', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(IncreaseViewSizeAction, IncreaseViewSizeAction.ID, IncreaseViewSizeAction.LABEL, undefined), 'View: Increase Current View Size', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(DecreaseViewSizeAction, DecreaseViewSizeAction.ID, DecreaseViewSizeAction.LABEL, undefined), 'View: Decrease Current View Size', viewCategory); diff --git a/src/vs/workbench/browser/actions/listCommands.ts b/src/vs/workbench/browser/actions/listCommands.ts index db5f82dbc4..ba6418a543 100644 --- a/src/vs/workbench/browser/actions/listCommands.ts +++ b/src/vs/workbench/browser/actions/listCommands.ts @@ -872,10 +872,9 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ }); KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'list.scrollLeft', + id: '84256', weight: KeybindingWeight.WorkbenchContrib, when: WorkbenchListFocusContextKey, - primary: KeyMod.CtrlCmd | KeyCode.LeftArrow, handler: accessor => { const focused = accessor.get(IListService).lastFocusedList; @@ -891,7 +890,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'list.scrollRight', weight: KeybindingWeight.WorkbenchContrib, when: WorkbenchListFocusContextKey, - primary: KeyMod.CtrlCmd | KeyCode.RightArrow, handler: accessor => { const focused = accessor.get(IListService).lastFocusedList; diff --git a/src/vs/workbench/browser/actions/navigationActions.ts b/src/vs/workbench/browser/actions/navigationActions.ts index d697091687..8aa734cb4e 100644 --- a/src/vs/workbench/browser/actions/navigationActions.ts +++ b/src/vs/workbench/browser/actions/navigationActions.ts @@ -280,7 +280,7 @@ class NavigateDownAction extends BaseNavigationAction { const registry = Registry.as(Extensions.WorkbenchActions); const viewCategory = nls.localize('view', "View"); -registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateUpAction, NavigateUpAction.ID, NavigateUpAction.LABEL, undefined), 'View: Navigate to the View Above', viewCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateDownAction, NavigateDownAction.ID, NavigateDownAction.LABEL, undefined), 'View: Navigate to the View Below', viewCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateLeftAction, NavigateLeftAction.ID, NavigateLeftAction.LABEL, undefined), 'View: Navigate to the View on the Left', viewCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateRightAction, NavigateRightAction.ID, NavigateRightAction.LABEL, undefined), 'View: Navigate to the View on the Right', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NavigateUpAction, NavigateUpAction.ID, NavigateUpAction.LABEL, undefined), 'View: Navigate to the View Above', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NavigateDownAction, NavigateDownAction.ID, NavigateDownAction.LABEL, undefined), 'View: Navigate to the View Below', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NavigateLeftAction, NavigateLeftAction.ID, NavigateLeftAction.LABEL, undefined), 'View: Navigate to the View on the Left', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NavigateRightAction, NavigateRightAction.ID, NavigateRightAction.LABEL, undefined), 'View: Navigate to the View on the Right', viewCategory); diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts index bc5a96f7f7..665e962eb9 100644 --- a/src/vs/workbench/browser/actions/windowActions.ts +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -269,18 +269,18 @@ const registry = Registry.as(Extensions.WorkbenchActio // --- Actions Registration const fileCategory = nls.localize('file', "File"); -registry.registerWorkbenchAction(new SyncActionDescriptor(NewWindowAction, NewWindowAction.ID, NewWindowAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_N }), 'New Window'); -registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenRecentAction, QuickOpenRecentAction.ID, QuickOpenRecentAction.LABEL), 'File: Quick Open Recent...', fileCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenRecentAction, OpenRecentAction.ID, OpenRecentAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_R, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_R } }), 'File: Open Recent...', fileCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NewWindowAction, NewWindowAction.ID, NewWindowAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_N }), 'New Window'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenRecentAction, QuickOpenRecentAction.ID, QuickOpenRecentAction.LABEL), 'File: Quick Open Recent...', fileCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenRecentAction, OpenRecentAction.ID, OpenRecentAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_R, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_R } }), 'File: Open Recent...', fileCategory); const viewCategory = nls.localize('view', "View"); -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleFullScreenAction, ToggleFullScreenAction.ID, ToggleFullScreenAction.LABEL, { primary: KeyCode.F11, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_F } }), 'View: Toggle Full Screen', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleFullScreenAction, ToggleFullScreenAction.ID, ToggleFullScreenAction.LABEL, { primary: KeyCode.F11, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_F } }), 'View: Toggle Full Screen', viewCategory); const developerCategory = nls.localize('developer', "Developer"); -registry.registerWorkbenchAction(new SyncActionDescriptor(ReloadWindowAction, ReloadWindowAction.ID, ReloadWindowAction.LABEL), 'Developer: Reload Window', developerCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ReloadWindowAction, ReloadWindowAction.ID, ReloadWindowAction.LABEL), 'Developer: Reload Window', developerCategory); const helpCategory = nls.localize('help', "Help"); -registry.registerWorkbenchAction(new SyncActionDescriptor(ShowAboutDialogAction, ShowAboutDialogAction.ID, ShowAboutDialogAction.LABEL), `Help: About`, helpCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowAboutDialogAction, ShowAboutDialogAction.ID, ShowAboutDialogAction.LABEL), `Help: About`, helpCategory); // --- Commands/Keybindings Registration diff --git a/src/vs/workbench/browser/actions/workspaceActions.ts b/src/vs/workbench/browser/actions/workspaceActions.ts index 0916b16193..0d56d6bfbf 100644 --- a/src/vs/workbench/browser/actions/workspaceActions.ts +++ b/src/vs/workbench/browser/actions/workspaceActions.ts @@ -22,7 +22,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspacesService, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; export class OpenFileAction extends Action { @@ -213,7 +213,7 @@ export class SaveWorkspaceAsAction extends Action { async run(): Promise { const configPathUri = await this.workspaceEditingService.pickNewWorkspacePath(); - if (configPathUri) { + if (configPathUri && hasWorkspaceFileExtension(configPathUri)) { switch (this.contextService.getWorkbenchState()) { case WorkbenchState.EMPTY: case WorkbenchState.FOLDER: @@ -259,11 +259,11 @@ export class DuplicateWorkspaceInNewWindowAction extends Action { const registry = Registry.as(Extensions.WorkbenchActions); const workspacesCategory = nls.localize('workspaces', "Workspaces"); -registry.registerWorkbenchAction(new SyncActionDescriptor(AddRootFolderAction, AddRootFolderAction.ID, AddRootFolderAction.LABEL), 'Workspaces: Add Folder to Workspace...', workspacesCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalRemoveRootFolderAction, GlobalRemoveRootFolderAction.ID, GlobalRemoveRootFolderAction.LABEL), 'Workspaces: Remove Folder from Workspace...', workspacesCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(CloseWorkspaceAction, CloseWorkspaceAction.ID, CloseWorkspaceAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_F) }), 'Workspaces: Close Workspace', workspacesCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(SaveWorkspaceAsAction, SaveWorkspaceAsAction.ID, SaveWorkspaceAsAction.LABEL), 'Workspaces: Save Workspace As...', workspacesCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(DuplicateWorkspaceInNewWindowAction, DuplicateWorkspaceInNewWindowAction.ID, DuplicateWorkspaceInNewWindowAction.LABEL), 'Workspaces: Duplicate Workspace in New Window', workspacesCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(AddRootFolderAction, AddRootFolderAction.ID, AddRootFolderAction.LABEL), 'Workspaces: Add Folder to Workspace...', workspacesCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(GlobalRemoveRootFolderAction, GlobalRemoveRootFolderAction.ID, GlobalRemoveRootFolderAction.LABEL), 'Workspaces: Remove Folder from Workspace...', workspacesCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(CloseWorkspaceAction, CloseWorkspaceAction.ID, CloseWorkspaceAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_F) }), 'Workspaces: Close Workspace', workspacesCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(SaveWorkspaceAsAction, SaveWorkspaceAsAction.ID, SaveWorkspaceAsAction.LABEL), 'Workspaces: Save Workspace As...', workspacesCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(DuplicateWorkspaceInNewWindowAction, DuplicateWorkspaceInNewWindowAction.ID, DuplicateWorkspaceInNewWindowAction.LABEL), 'Workspaces: Duplicate Workspace in New Window', workspacesCategory); // --- Menu Registration diff --git a/src/vs/workbench/browser/composite.ts b/src/vs/workbench/browser/composite.ts index a472d79a12..f96a254895 100644 --- a/src/vs/workbench/browser/composite.ts +++ b/src/vs/workbench/browser/composite.ts @@ -31,11 +31,11 @@ import { find } from 'vs/base/common/arrays'; */ export abstract class Composite extends Component implements IComposite { - private readonly _onTitleAreaUpdate: Emitter = this._register(new Emitter()); - readonly onTitleAreaUpdate: Event = this._onTitleAreaUpdate.event; + private readonly _onTitleAreaUpdate = this._register(new Emitter()); + readonly onTitleAreaUpdate = this._onTitleAreaUpdate.event; - private readonly _onDidChangeVisibility: Emitter = this._register(new Emitter()); - readonly onDidChangeVisibility: Event = this._onDidChangeVisibility.event; + private readonly _onDidChangeVisibility = this._register(new Emitter()); + readonly onDidChangeVisibility = this._onDidChangeVisibility.event; private _onDidFocus: Emitter | undefined; get onDidFocus(): Event { @@ -250,11 +250,11 @@ export abstract class CompositeDescriptor { export abstract class CompositeRegistry extends Disposable { - private readonly _onDidRegister: Emitter> = this._register(new Emitter>()); - get onDidRegister(): Event> { return this._onDidRegister.event; } + private readonly _onDidRegister = this._register(new Emitter>()); + readonly onDidRegister = this._onDidRegister.event; - private readonly _onDidDeregister: Emitter> = this._register(new Emitter>()); - get onDidDeregister(): Event> { return this._onDidDeregister.event; } + private readonly _onDidDeregister = this._register(new Emitter>()); + readonly onDidDeregister = this._onDidDeregister.event; private composites: CompositeDescriptor[] = []; diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index ff6b983fc1..17919e7177 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContext } from 'vs/platform/contextkey/common/contextkeys'; import { IWindowsConfiguration } from 'vs/platform/windows/common/windows'; -import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorIsSaveableContext, toResource, SideBySideEditor, EditorAreaVisibleContext } from 'vs/workbench/common/editor'; +import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorIsReadonlyContext, EditorAreaVisibleContext, DirtyWorkingCopiesContext } from 'vs/workbench/common/editor'; import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom'; import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -21,8 +21,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { isMacintosh, isLinux, isWindows, isWeb } from 'vs/base/common/platform'; import { PanelPositionContext } from 'vs/workbench/common/panel'; import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; -import { IFileService } from 'vs/platform/files/common/files'; -import { Schemas } from 'vs/base/common/network'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; export const IsMacContext = new RawContextKey('isMac', isMacintosh); export const IsLinuxContext = new RawContextKey('isLinux', isLinux); @@ -51,8 +50,10 @@ export const IsFullscreenContext = new RawContextKey('isFullscreen', fa export class WorkbenchContextKeysHandler extends Disposable { private inputFocusedContext: IContextKey; + private dirtyWorkingCopiesContext: IContextKey; + private activeEditorContext: IContextKey; - private activeEditorIsSaveable: IContextKey; + private activeEditorIsReadonly: IContextKey; private activeEditorGroupEmpty: IContextKey; private activeEditorGroupIndex: IContextKey; @@ -75,19 +76,18 @@ export class WorkbenchContextKeysHandler extends Disposable { private panelPositionContext: IContextKey; constructor( - @IContextKeyService private contextKeyService: IContextKeyService, - @IWorkspaceContextService private contextService: IWorkspaceContextService, - @IConfigurationService private configurationService: IConfigurationService, - @IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService, - @IEditorService private editorService: IEditorService, - @IEditorGroupsService private editorGroupService: IEditorGroupsService, - @IWorkbenchLayoutService private layoutService: IWorkbenchLayoutService, - @IViewletService private viewletService: IViewletService, - @IFileService private fileService: IFileService + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IEditorService private readonly editorService: IEditorService, + @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @IViewletService private readonly viewletService: IViewletService, + @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService ) { super(); - // Platform IsMacContext.bindTo(this.contextKeyService); IsLinuxContext.bindTo(this.contextKeyService); @@ -107,7 +107,7 @@ export class WorkbenchContextKeysHandler extends Disposable { // Editors this.activeEditorContext = ActiveEditorContext.bindTo(this.contextKeyService); - this.activeEditorIsSaveable = ActiveEditorIsSaveableContext.bindTo(this.contextKeyService); + this.activeEditorIsReadonly = ActiveEditorIsReadonlyContext.bindTo(this.contextKeyService); this.editorsVisibleContext = EditorsVisibleContext.bindTo(this.contextKeyService); this.textCompareEditorVisibleContext = TextCompareEditorVisibleContext.bindTo(this.contextKeyService); this.textCompareEditorActiveContext = TextCompareEditorActiveContext.bindTo(this.contextKeyService); @@ -116,6 +116,9 @@ export class WorkbenchContextKeysHandler extends Disposable { this.activeEditorGroupLast = ActiveEditorGroupLastContext.bindTo(this.contextKeyService); this.multipleEditorGroupsContext = MultipleEditorGroupsContext.bindTo(this.contextKeyService); + // Working Copies + this.dirtyWorkingCopiesContext = DirtyWorkingCopiesContext.bindTo(this.contextKeyService); + // Inputs this.inputFocusedContext = InputFocusedContext.bindTo(this.contextKeyService); @@ -183,6 +186,8 @@ export class WorkbenchContextKeysHandler extends Disposable { this._register(this.viewletService.onDidViewletOpen(() => this.updateSideBarContextKeys())); this._register(this.layoutService.onPartVisibilityChange(() => this.editorAreaVisibleContext.set(this.layoutService.isVisible(Parts.EDITOR_PART)))); + + this._register(this.workingCopyService.onDidChangeDirty(w => this.dirtyWorkingCopiesContext.set(w.isDirty() || this.workingCopyService.hasDirty))); } private updateEditorContextKeys(): void { @@ -217,13 +222,10 @@ export class WorkbenchContextKeysHandler extends Disposable { if (activeControl) { this.activeEditorContext.set(activeControl.getId()); - - const resource = toResource(activeControl.input, { supportSideBySide: SideBySideEditor.MASTER }); - const canSave = resource ? this.fileService.canHandleResource(resource) || resource.scheme === Schemas.untitled : false; - this.activeEditorIsSaveable.set(canSave); + this.activeEditorIsReadonly.set(activeControl.input.isReadonly()); } else { this.activeEditorContext.reset(); - this.activeEditorIsSaveable.reset(); + this.activeEditorIsReadonly.reset(); } } diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index 6e88607f3a..80f25c474d 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -12,7 +12,7 @@ import { URI } from 'vs/base/common/uri'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { Schemas } from 'vs/base/common/network'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { DefaultEndOfLine } from 'vs/editor/common/model'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEditorViewState } from 'vs/editor/common/editorCommon'; @@ -20,18 +20,18 @@ import { DataTransfers } from 'vs/base/browser/dnd'; import { DragMouseEvent } from 'vs/base/browser/mouseEvent'; import { normalizeDriveLetter } from 'vs/base/common/labels'; import { MIME_BINARY } from 'vs/base/common/mime'; -import { isWindows, isLinux, isWeb } from 'vs/base/common/platform'; +import { isWindows, isWeb } from 'vs/base/common/platform'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorIdentifier, GroupIdentifier } from 'vs/workbench/common/editor'; import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; import { Disposable } from 'vs/base/common/lifecycle'; -import { addDisposableListener, EventType } from 'vs/base/browser/dom'; +import { addDisposableListener, EventType, asDomUri } from 'vs/base/browser/dom'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; import { withNullAsUndefined } from 'vs/base/common/types'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { isStandalone } from 'vs/base/browser/browser'; export interface IDraggedResource { resource: URI; @@ -163,7 +163,7 @@ export class ResourcesDropHandler { @IWorkspacesService private readonly workspacesService: IWorkspacesService, @ITextFileService private readonly textFileService: ITextFileService, @IBackupFileService private readonly backupFileService: IBackupFileService, - @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService, @IEditorService private readonly editorService: IEditorService, @IConfigurationService private readonly configurationService: IConfigurationService, @IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService, @@ -234,7 +234,7 @@ export class ResourcesDropHandler { // Untitled: always ensure that we open a new untitled for each file we drop if (droppedDirtyEditor.resource.scheme === Schemas.untitled) { - droppedDirtyEditor.resource = this.untitledEditorService.createOrGet().getResource(); + droppedDirtyEditor.resource = this.untitledTextEditorService.createOrGet().getResource(); } // Return early if the resource is already dirty in target or opened already @@ -324,20 +324,14 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources: return obj; }); - const firstSource = sources[0]; - // Text: allows to paste into text-capable areas const lineDelimiter = isWindows ? '\r\n' : '\n'; event.dataTransfer.setData(DataTransfers.TEXT, sources.map(source => source.resource.scheme === Schemas.file ? normalize(normalizeDriveLetter(source.resource.fsPath)) : source.resource.toString()).join(lineDelimiter)); - const envService = accessor.get(IWorkbenchEnvironmentService); - const hasRemote = !!envService.configuration.remoteAuthority; - if ( - !(isLinux && hasRemote) && // Not supported on linux remote due to chrome limitation https://github.com/microsoft/vscode-remote-release/issues/849 - !isWeb // Does not seem to work anymore when running from web, the file ends up being empty (and PWA crashes) - ) { - // Download URL: enables support to drag a tab as file to desktop (only single file supported) - event.dataTransfer.setData(DataTransfers.DOWNLOAD_URL, [MIME_BINARY, basename(firstSource.resource), firstSource.resource.toString()].join(':')); + // Download URL: enables support to drag a tab as file to desktop (only single file supported) + // Disabled for PWA web due to: https://github.com/microsoft/vscode/issues/83441 + if (!sources[0].isDirectory && (!isWeb || !isStandalone)) { + event.dataTransfer.setData(DataTransfers.DOWNLOAD_URL, [MIME_BINARY, basename(sources[0].resource), asDomUri(sources[0].resource).toString()].join(':')); } // Resource URLs: allows to drop multiple resources to a target in VS Code (not directories) @@ -482,3 +476,23 @@ export class DragAndDropObserver extends Disposable { })); } } + +export function containsDragType(event: DragEvent, ...dragTypesToFind: string[]): boolean { + if (!event.dataTransfer) { + return false; + } + + const dragTypes = event.dataTransfer.types; + const lowercaseDragTypes: string[] = []; + for (let i = 0; i < dragTypes.length; i++) { + lowercaseDragTypes.push(dragTypes[i].toLowerCase()); // somehow the types are lowercase + } + + for (const dragType of dragTypesToFind) { + if (lowercaseDragTypes.indexOf(dragType.toLowerCase()) >= 0) { + return true; + } + } + + return false; +} diff --git a/src/vs/workbench/browser/editor.ts b/src/vs/workbench/browser/editor.ts index e636641866..1ebad388aa 100644 --- a/src/vs/workbench/browser/editor.ts +++ b/src/vs/workbench/browser/editor.ts @@ -7,7 +7,7 @@ import { EditorInput } from 'vs/workbench/common/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { IConstructorSignature0, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IConstructorSignature0, IInstantiationService, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { find } from 'vs/base/common/arrays'; export interface IEditorDescriptor { @@ -54,6 +54,14 @@ export interface IEditorRegistry { */ export class EditorDescriptor implements IEditorDescriptor { + public static create( + ctor: { new(...services: Services): BaseEditor }, + id: string, + name: string + ): EditorDescriptor { + return new EditorDescriptor(ctor as IConstructorSignature0, id, name); + } + constructor( private readonly ctor: IConstructorSignature0, private readonly id: string, diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index 0e186d8746..3f346c9251 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -13,7 +13,7 @@ import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IDecorationsService, IResourceDecorationChangeEvent } from 'vs/workbench/services/decorations/browser/decorations'; import { Schemas } from 'vs/base/common/network'; import { FileKind, FILES_ASSOCIATIONS_CONFIG, IFileService } from 'vs/platform/files/common/files'; @@ -22,13 +22,13 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { Event, Emitter } from 'vs/base/common/event'; import { ILabelService } from 'vs/platform/label/common/label'; import { getIconClasses, detectModeId } from 'vs/editor/common/services/getIconClasses'; -import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, dispose, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { withNullAsUndefined } from 'vs/base/common/types'; export interface IResourceLabelProps { resource?: URI; - name?: string; + name?: string | string[]; description?: string; } @@ -41,6 +41,7 @@ export interface IResourceLabelOptions extends IIconLabelValueOptions { export interface IFileLabelOptions extends IResourceLabelOptions { hideLabel?: boolean; hidePath?: boolean; + readonly parentCount?: number; } export interface IResourceLabel extends IDisposable { @@ -164,7 +165,7 @@ export class ResourceLabels extends Disposable { const label: IResourceLabel = { element: widget.element, onDidRender: widget.onDidRender, - setLabel: (label?: string, description?: string, options?: IIconLabelValueOptions) => widget.setLabel(label, description, options), + setLabel: (label: string, description?: string, options?: IIconLabelValueOptions) => widget.setLabel(label, description, options), setResource: (label: IResourceLabelProps, options?: IResourceLabelOptions) => widget.setResource(label, options), setEditor: (editor: IEditorInput, options?: IResourceLabelOptions) => widget.setEditor(editor, options), setFile: (resource: URI, options?: IFileLabelOptions) => widget.setFile(resource, options), @@ -241,6 +242,8 @@ class ResourceLabelWidget extends IconLabel { private _onDidRender = this._register(new Emitter()); readonly onDidRender: Event = this._onDidRender.event; + private readonly renderDisposables = this._register(new DisposableStore()); + private label?: IResourceLabelProps; private options?: IResourceLabelOptions; private computedIconClasses?: string[]; @@ -257,7 +260,7 @@ class ResourceLabelWidget extends IconLabel { @IModelService private readonly modelService: IModelService, @IDecorationsService private readonly decorationsService: IDecorationsService, @ILabelService private readonly labelService: ILabelService, - @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService ) { super(container, options); @@ -365,7 +368,7 @@ class ResourceLabelWidget extends IconLabel { setEditor(editor: IEditorInput, options?: IResourceLabelOptions): void { this.setResource({ resource: toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }), - name: withNullAsUndefined(editor.getName()), + name: editor.getName(), description: editor.getDescription(options ? options.descriptionVerbosity : undefined) }, options); } @@ -387,7 +390,7 @@ class ResourceLabelWidget extends IconLabel { } let description: string | undefined; - const hidePath = (options && options.hidePath) || (resource.scheme === Schemas.untitled && !this.untitledEditorService.hasAssociatedFilePath(resource)); + const hidePath = (options && options.hidePath) || (resource.scheme === Schemas.untitled && !this.untitledTextEditorService.hasAssociatedFilePath(resource)); if (!hidePath) { description = this.labelService.getUriLabel(resources.dirname(resource), { relative: true }); } @@ -402,7 +405,7 @@ class ResourceLabelWidget extends IconLabel { this.computedIconClasses = undefined; this.computedPathLabel = undefined; - this.setLabel(); + this.setLabel(''); } private render(clearIconCache: boolean): void { @@ -434,11 +437,15 @@ class ResourceLabelWidget extends IconLabel { return; } + this.renderDisposables.clear(); + const iconLabelOptions: IIconLabelValueOptions & { extraClasses: string[] } = { title: '', italic: this.options && this.options.italic, matches: this.options && this.options.matches, - extraClasses: [] + extraClasses: [], + separator: this.options?.separator, + domId: this.options?.domId }; const resource = this.label.resource; @@ -472,6 +479,9 @@ class ResourceLabelWidget extends IconLabel { ); if (deco) { + + this.renderDisposables.add(deco); + if (deco.tooltip) { iconLabelOptions.title = `${iconLabelOptions.title} • ${deco.tooltip}`; } @@ -486,7 +496,7 @@ class ResourceLabelWidget extends IconLabel { } } - this.setLabel(label, this.label.description, iconLabelOptions); + this.setLabel(label || '', this.label.description, iconLabelOptions); this._onDidRender.fire(); } diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 7bc5480bbe..7f34fec989 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -5,7 +5,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; -import { EventType, addDisposableListener, addClass, removeClass, isAncestor, getClientArea, position, size, Dimension } from 'vs/base/browser/dom'; +import { EventType, addDisposableListener, addClass, removeClass, isAncestor, getClientArea, Dimension, toggleClass, position, size } from 'vs/base/browser/dom'; import { onDidChangeFullscreen, isFullscreen } from 'vs/base/browser/browser'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -38,6 +38,8 @@ import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { coalesce } from 'vs/base/common/arrays'; import { assertIsDefined } from 'vs/base/common/types'; import { INotificationService, NotificationsFilter } from 'vs/platform/notification/common/notification'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { WINDOW_ACTIVE_BORDER, WINDOW_INACTIVE_BORDER } from 'vs/workbench/common/theme'; enum Settings { ACTIVITYBAR_VISIBLE = 'workbench.activityBar.visible', @@ -74,7 +76,8 @@ enum Classes { EDITOR_HIDDEN = 'noeditorarea', PANEL_HIDDEN = 'nopanel', STATUSBAR_HIDDEN = 'nostatusbar', - FULLSCREEN = 'fullscreen' + FULLSCREEN = 'fullscreen', + WINDOW_BORDER = 'border' } export abstract class Layout extends Disposable implements IWorkbenchLayoutService { @@ -92,6 +95,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private readonly _onCenteredLayoutChange: Emitter = this._register(new Emitter()); readonly onCenteredLayoutChange: Event = this._onCenteredLayoutChange.event; + private readonly _onMaximizeChange: Emitter = this._register(new Emitter()); + readonly onMaximizeChange: Event = this._onMaximizeChange.event; + private readonly _onPanelPositionChange: Emitter = this._register(new Emitter()); readonly onPanelPositionChange: Event = this._onPanelPositionChange.event; @@ -135,9 +141,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private contextService!: IWorkspaceContextService; private backupFileService!: IBackupFileService; private notificationService!: INotificationService; + private themeService!: IThemeService; protected readonly state = { fullscreen: false, + maximized: false, + hasFocus: false, + windowBorder: false, menuBar: { visibility: 'default' as MenuBarVisibility, @@ -204,6 +214,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.contextService = accessor.get(IWorkspaceContextService); this.storageService = accessor.get(IStorageService); this.backupFileService = accessor.get(IBackupFileService); + this.themeService = accessor.get(IThemeService); // Parts this.editorService = accessor.get(IEditorService); @@ -257,6 +268,12 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if ((isWindows || isLinux || isWeb) && getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') { this._register(this.titleService.onMenubarVisibilityChange(visible => this.onMenubarToggled(visible))); } + + // Theme changes + this._register(this.themeService.onThemeChange(theme => this.updateStyles())); + + // Window focus changes + this._register(this.hostService.onDidChangeFocus(e => this.onWindowFocusChanged(e))); } private onMenubarToggled(visible: boolean) { @@ -291,12 +308,23 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Propagate to grid this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); + this.updateWindowBorder(true); + this.layout(); // handle title bar when fullscreen changes } this._onFullscreenChange.fire(this.state.fullscreen); } + private onWindowFocusChanged(hasFocus: boolean): void { + if (this.state.hasFocus === hasFocus) { + return; + } + + this.state.hasFocus = hasFocus; + this.updateWindowBorder(); + } + private doUpdateLayoutConfiguration(skipLayout?: boolean): void { // Sidebar position @@ -366,6 +394,44 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.layout(); } + private updateWindowBorder(skipLayout: boolean = false) { + if (isWeb || getTitleBarStyle(this.configurationService, this.environmentService) !== 'custom') { + return; + } + + const theme = this.themeService.getTheme(); + + const activeBorder = theme.getColor(WINDOW_ACTIVE_BORDER); + const inactiveBorder = theme.getColor(WINDOW_INACTIVE_BORDER); + + let windowBorder = false; + if (!this.state.fullscreen && !this.state.maximized && (activeBorder || inactiveBorder)) { + windowBorder = true; + + // If one color is missing, just fallback to the other one + const borderColor = this.state.hasFocus + ? activeBorder ?? inactiveBorder + : inactiveBorder ?? activeBorder; + this.container.style.setProperty('--window-border-color', borderColor ? borderColor.toString() : 'transparent'); + } + + if (windowBorder === this.state.windowBorder) { + return; + } + + this.state.windowBorder = windowBorder; + + toggleClass(this.container, Classes.WINDOW_BORDER, windowBorder); + + if (!skipLayout) { + this.layout(); + } + } + + private updateStyles() { + this.updateWindowBorder(); + } + private initLayoutState(lifecycleService: ILifecycleService, fileService: IFileService): void { // Fullscreen @@ -442,6 +508,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Zen mode enablement this.state.zenMode.restore = this.storageService.getBoolean(Storage.ZEN_MODE_ENABLED, StorageScope.WORKSPACE, false) && this.configurationService.getValue(Settings.ZEN_MODE_RESTORE); + this.state.hasFocus = this.hostService.hasFocus; + + // Window border + this.updateWindowBorder(true); + } private resolveEditorsToOpen(fileService: IFileService): Promise | IResourceEditor[] { @@ -682,6 +753,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (config.silentNotifications) { this.notificationService.setFilter(NotificationsFilter.ERROR); } + this.state.zenMode.transitionDisposables.add(this.configurationService.onDidChangeConfiguration(c => { + const silentNotificationsKey = 'zenMode.silentNotifications'; + if (c.affectsConfiguration(silentNotificationsKey)) { + const filter = this.configurationService.getValue(silentNotificationsKey) ? NotificationsFilter.ERROR : NotificationsFilter.OFF; + this.notificationService.setFilter(filter); + } + })); if (config.centerLayout) { this.centerEditorLayout(true, true); @@ -826,9 +904,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi })); } + getClientArea(): Dimension { + return getClientArea(this.parent); + } + layout(): void { if (!this.disposed) { - this._dimension = getClientArea(this.parent); + this._dimension = this.getClientArea(); position(this.container, 0, 0, 0, 0, 'relative'); size(this.container, this._dimension.width, this._dimension.height); @@ -1079,6 +1161,14 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } } + hasWindowBorder(): boolean { + return this.state.windowBorder; + } + + getWindowBorderRadius(): string | undefined { + return this.state.windowBorder && isMacintosh ? '5px' : undefined; + } + isPanelMaximized(): boolean { if (!this.workbenchGrid) { return false; @@ -1168,8 +1258,23 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this._onPanelPositionChange.fire(positionToString(this.state.panel.position)); } + isWindowMaximized() { + return this.state.maximized; + } + + updateWindowMaximizedState(maximized: boolean) { + if (this.state.maximized === maximized) { + return; + } + + this.state.maximized = maximized; + + this.updateWindowBorder(); + this._onMaximizeChange.fire(maximized); + } + private createGridDescriptor(): ISerializedGrid { - const workbenchDimensions = getClientArea(this.parent); + const workbenchDimensions = this.getClientArea(); const width = this.storageService.getNumber(Storage.GRID_WIDTH, StorageScope.GLOBAL, workbenchDimensions.width); const height = this.storageService.getNumber(Storage.GRID_HEIGHT, StorageScope.GLOBAL, workbenchDimensions.height); // At some point, we will not fall back to old keys from legacy layout, but for now, let's migrate the keys diff --git a/src/vs/workbench/browser/media/part.css b/src/vs/workbench/browser/media/part.css index a1fbb92ece..c26d2ec77e 100644 --- a/src/vs/workbench/browser/media/part.css +++ b/src/vs/workbench/browser/media/part.css @@ -39,8 +39,7 @@ font-size: 11px; cursor: default; font-weight: normal; - -webkit-margin-before: 0; - -webkit-margin-after: 0; + margin: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; @@ -72,6 +71,10 @@ display: none; } +.monaco-workbench .part > .title > .title-actions .action-label.codicon { + color: inherit; +} + .monaco-workbench .part > .content { /* {{SQL CARBON EDIT}} */ font-size: 12px; diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css index b7bb971a62..724cac1d07 100644 --- a/src/vs/workbench/browser/media/style.css +++ b/src/vs/workbench/browser/media/style.css @@ -37,12 +37,17 @@ body { overflow: hidden; font-size: 11px; user-select: none; + -webkit-user-select: none; } body.web { position: fixed; /* prevent bounce effect */ } +.monaco-workbench.web { + touch-action: initial; /* reenable touch events on workbench */ +} + @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .monaco-workbench { @@ -53,6 +58,15 @@ body.web { overflow: hidden; } +.monaco-workbench.border:not(.fullscreen) { + box-sizing: border-box; + border: 1px solid var(--window-border-color); +} + +.monaco-workbench.border.mac { + border-radius: 5px; +} + .monaco-workbench img { border: 0; } @@ -97,15 +111,18 @@ body.web { .monaco-workbench.monaco-font-aliasing-antialiased { -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } .monaco-workbench.monaco-font-aliasing-none { -webkit-font-smoothing: none; + -moz-osx-font-smoothing: unset; } @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { .monaco-workbench.monaco-font-aliasing-auto { -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } } @@ -150,14 +167,40 @@ body.web { .monaco-workbench.linux .monaco-menu .monaco-action-bar.vertical .submenu-indicator { height: 100%; - -webkit-mask-size: 10px 10px; mask-size: 10px 10px; + -webkit-mask-size: 10px 10px; } .monaco-workbench .monaco-menu .action-item { cursor: default; } +/* Custom Dropdown (select) Arrows */ + +.monaco-workbench select { + -webkit-appearance: none; + -moz-appearance: none; +} + +.monaco-workbench .select-container { + position: relative; +} + +.monaco-workbench .select-container:after { + content: "\eab4"; + font-family: codicon; + font-size: 14px; + width: 14px; + height: 14px; + line-height: 14px; + position: absolute; + top: 0; + bottom: 0; + right: 6px; + margin: auto; + pointer-events: none; +} + /* START Keyboard Focus Indication Styles */ .monaco-workbench [tabindex="0"]:focus, diff --git a/src/vs/workbench/browser/panel.ts b/src/vs/workbench/browser/panel.ts index 5a21c9ade6..1e232bc194 100644 --- a/src/vs/workbench/browser/panel.ts +++ b/src/vs/workbench/browser/panel.ts @@ -9,7 +9,7 @@ import { Composite, CompositeDescriptor, CompositeRegistry } from 'vs/workbench/ import { Action } from 'vs/base/common/actions'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; -import { IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation'; +import { IConstructorSignature0, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { isAncestor } from 'vs/base/browser/dom'; import { assertIsDefined } from 'vs/base/common/types'; @@ -20,7 +20,11 @@ export abstract class Panel extends Composite implements IPanel { } */ export class PanelDescriptor extends CompositeDescriptor { - constructor(ctor: IConstructorSignature0, id: string, name: string, cssClass?: string, order?: number, _commandId?: string) { + public static create(ctor: { new(...services: Services): Panel }, id: string, name: string, cssClass?: string, order?: number, _commandId?: string): PanelDescriptor { + return new PanelDescriptor(ctor as IConstructorSignature0, id, name, cssClass, order, _commandId); + } + + private constructor(ctor: IConstructorSignature0, id: string, name: string, cssClass?: string, order?: number, _commandId?: string) { super(ctor, id, name, cssClass, order, _commandId); } } diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts index 420c082694..fd648557bc 100644 --- a/src/vs/workbench/browser/part.ts +++ b/src/vs/workbench/browser/part.ts @@ -46,7 +46,7 @@ export abstract class Part extends Component implements ISerializableView { private options: IPartOptions, themeService: IThemeService, storageService: IStorageService, - layoutService: IWorkbenchLayoutService + protected readonly layoutService: IWorkbenchLayoutService ) { super(id, themeService, storageService); diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index a5b34d7d78..8d6d2136ad 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -22,7 +22,7 @@ import { ActivityAction, ActivityActionViewItem, ICompositeBar, ICompositeBarCol import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { IActivity } from 'vs/workbench/common/activity'; -import { ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme'; +import { ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_ACTIVE_FOCUS_BORDER, ACTIVITY_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme'; import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -33,15 +33,45 @@ export class ViewletActivityAction extends ActivityAction { private static readonly preventDoubleClickDelay = 300; - private lastRun: number = 0; + private readonly viewletService: IViewletService; + private readonly layoutService: IWorkbenchLayoutService; + private readonly telemetryService: ITelemetryService; + + private lastRun: number; constructor( activity: IActivity, - @IViewletService private readonly viewletService: IViewletService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, - @ITelemetryService private readonly telemetryService: ITelemetryService + @IViewletService viewletService: IViewletService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ITelemetryService telemetryService: ITelemetryService ) { + ViewletActivityAction.generateIconCSS(activity); super(activity); + + this.lastRun = 0; + this.viewletService = viewletService; + this.layoutService = layoutService; + this.telemetryService = telemetryService; + } + + private static generateIconCSS(activity: IActivity): void { + if (activity.iconUrl) { + activity.cssClass = activity.cssClass || `activity-${activity.id.replace(/\./g, '-')}`; + const iconClass = `.monaco-workbench .activitybar .monaco-action-bar .action-label.${activity.cssClass}`; + DOM.createCSSRule(iconClass, ` + mask: ${DOM.asCSSUrl(activity.iconUrl)} no-repeat 50% 50%; + mask-size: 24px; + -webkit-mask: ${DOM.asCSSUrl(activity.iconUrl)} no-repeat 50% 50%; + -webkit-mask-size: 24px; + `); + } + } + + setActivity(activity: IActivity): void { + if (activity.iconUrl && this.activity.cssClass !== activity.cssClass) { + ViewletActivityAction.generateIconCSS(activity); + } + this.activity = activity; } async run(event: any): Promise { @@ -170,16 +200,7 @@ export class PlaceHolderViewletActivityAction extends ViewletActivityAction { @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @ITelemetryService telemetryService: ITelemetryService ) { - super({ id, name: id, cssClass: `extensionViewlet-placeholder-${id.replace(/\./g, '-')}` }, viewletService, layoutService, telemetryService); - - if (iconUrl) { - const iconClass = `.monaco-workbench .activitybar .monaco-action-bar .action-label.${this.class}`; // Generate Placeholder CSS to show the icon in the activity bar - DOM.createCSSRule(iconClass, `-webkit-mask: ${DOM.asCSSUrl(iconUrl)} no-repeat 50% 50%; -webkit-mask-size: 24px;`); - } - } - - setActivity(activity: IActivity): void { - this.activity = activity; + super({ id, name: id, iconUrl }, viewletService, layoutService, telemetryService); } } @@ -262,19 +283,24 @@ export class NextSideBarViewAction extends SwitchSideBarViewAction { } const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(PreviousSideBarViewAction, PreviousSideBarViewAction.ID, PreviousSideBarViewAction.LABEL), 'View: Previous Side Bar View', nls.localize('view', "View")); -registry.registerWorkbenchAction(new SyncActionDescriptor(NextSideBarViewAction, NextSideBarViewAction.ID, NextSideBarViewAction.LABEL), 'View: Next Side Bar View', nls.localize('view', "View")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(PreviousSideBarViewAction, PreviousSideBarViewAction.ID, PreviousSideBarViewAction.LABEL), 'View: Previous Side Bar View', nls.localize('view', "View")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NextSideBarViewAction, NextSideBarViewAction.ID, NextSideBarViewAction.LABEL), 'View: Next Side Bar View', nls.localize('view', "View")); registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const activeForegroundColor = theme.getColor(ACTIVITY_BAR_FOREGROUND); if (activeForegroundColor) { collector.addRule(` - .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.active .action-label, - .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus .action-label, - .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:hover .action-label { + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.active .action-label:not(.codicon), + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus .action-label:not(.codicon), + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:hover .action-label:not(.codicon) { background-color: ${activeForegroundColor} !important; } + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.active .action-label.codicon, + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus .action-label.codicon, + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:hover .action-label.codicon { + color: ${activeForegroundColor} !important; + } `); } @@ -287,6 +313,20 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { `); } + const activeFocusBorderColor = theme.getColor(ACTIVITY_BAR_ACTIVE_FOCUS_BORDER); + if (activeFocusBorderColor) { + collector.addRule(` + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:focus::before { + visibility: hidden; + } + + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:focus .active-item-indicator:before { + visibility: visible; + border-left-color: ${activeFocusBorderColor}; + } + `); + } + const activeBackgroundColor = theme.getColor(ACTIVITY_BAR_ACTIVE_BACKGROUND); if (activeBackgroundColor) { collector.addRule(` diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index e85e702866..7b47da9c84 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -11,7 +11,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { Part } from 'vs/workbench/browser/part'; import { GlobalActivityActionViewItem, ViewletActivityAction, ToggleViewletAction, PlaceHolderToggleCompositePinnedAction, PlaceHolderViewletActivityAction } from 'vs/workbench/browser/parts/activitybar/activitybarActions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { IBadge } from 'vs/workbench/services/activity/common/activity'; +import { IBadge, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IWorkbenchLayoutService, Parts, Position as SideBarPosition } from 'vs/workbench/services/layout/browser/layoutService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; @@ -24,7 +24,7 @@ import { Dimension, addClass, removeNode } from 'vs/base/browser/dom'; import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { ToggleCompositePinnedAction, ICompositeBarColors, ActivityAction } from 'vs/workbench/browser/parts/compositeBarActions'; +import { ToggleCompositePinnedAction, ICompositeBarColors, ActivityAction, ICompositeActivity } from 'vs/workbench/browser/parts/compositeBarActions'; import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; import { IViewsService, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer, TEST_VIEW_CONTAINER_ID, IViewDescriptorCollection } from 'vs/workbench/common/views'; import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -68,6 +68,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { private globalActivityAction: ActivityAction | undefined; private globalActivityActionBar: ActionBar | undefined; + private globalActivity: ICompositeActivity[] = []; private customMenubar: CustomMenubarControl | undefined; private menubar: HTMLElement | undefined; @@ -83,7 +84,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { constructor( @IViewletService private readonly viewletService: IViewletService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IThemeService themeService: IThemeService, @IStorageService private readonly storageService: IStorageService, @IExtensionService private readonly extensionService: IExtensionService, @@ -202,18 +203,68 @@ export class ActivitybarPart extends Part implements IActivityBarService { } if (viewletOrActionId === GLOBAL_ACTIVITY_ID) { - return this.showGlobalActivity(badge, clazz); + return this.showGlobalActivity(badge, clazz, priority); } return Disposable.None; } - private showGlobalActivity(badge: IBadge, clazz?: string): IDisposable { + private showGlobalActivity(badge: IBadge, clazz?: string, priority?: number): IDisposable { + if (typeof priority !== 'number') { + priority = 0; + } + const activity: ICompositeActivity = { badge, clazz, priority }; + + for (let i = 0; i <= this.globalActivity.length; i++) { + if (i === this.globalActivity.length) { + this.globalActivity.push(activity); + break; + } else if (this.globalActivity[i].priority <= priority) { + this.globalActivity.splice(i, 0, activity); + break; + } + } + this.updateGlobalActivity(); + + return toDisposable(() => this.removeGlobalActivity(activity)); + } + + private removeGlobalActivity(activity: ICompositeActivity): void { + const index = this.globalActivity.indexOf(activity); + if (index !== -1) { + this.globalActivity.splice(index, 1); + this.updateGlobalActivity(); + } + } + + private updateGlobalActivity(): void { const globalActivityAction = assertIsDefined(this.globalActivityAction); + if (this.globalActivity.length) { + const [{ badge, clazz, priority }] = this.globalActivity; + if (badge instanceof NumberBadge && this.globalActivity.length > 1) { + const cumulativeNumberBadge = this.getCumulativeNumberBadge(priority); + globalActivityAction.setBadge(cumulativeNumberBadge); + } else { + globalActivityAction.setBadge(badge, clazz); + } + } else { + globalActivityAction.setBadge(undefined); + } + } - globalActivityAction.setBadge(badge, clazz); - - return toDisposable(() => globalActivityAction.setBadge(undefined)); + private getCumulativeNumberBadge(priority: number): NumberBadge { + const numberActivities = this.globalActivity.filter(activity => activity.badge instanceof NumberBadge && activity.priority === priority); + let number = numberActivities.reduce((result, activity) => { return result + (activity.badge).number; }, 0); + let descriptorFn = (): string => { + return numberActivities.reduce((result, activity, index) => { + result = result + (activity.badge).getDescription(); + if (index < numberActivities.length - 1) { + result = result + '\n'; + } + return result; + }, ''); + }; + return new NumberBadge(number, descriptorFn); } private uninstallMenubar() { @@ -306,7 +357,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.globalActivityAction = new ActivityAction({ id: 'workbench.actions.manage', name: nls.localize('manage', "Manage"), - cssClass: 'update-activity' + cssClass: 'codicon-settings-gear' }); this.globalActivityActionBar.push(this.globalActivityAction); @@ -450,6 +501,9 @@ export class ActivitybarPart extends Part implements IActivityBarService { if (this.globalActivityActionBar) { availableHeight -= (this.globalActivityActionBar.viewItems.length * ActivitybarPart.ACTION_HEIGHT); // adjust height for global actions showing } + if (this.menubar) { + availableHeight -= this.menubar.clientHeight; + } this.compositeBar.layout(new Dimension(width, availableHeight)); } diff --git a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css index 91c9d27e92..96ffa37af6 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css @@ -6,7 +6,7 @@ .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item { display: block; position: relative; - padding: 5px 0; + margin-bottom: 4px; } .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-label { @@ -14,23 +14,32 @@ z-index: 1; display: flex; overflow: hidden; - height: 40px; - line-height: 40px; + height: 48px; margin-right: 0; - padding: 0 0 0 48px; box-sizing: border-box; + +} + +.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-label:not(.codicon) { font-size: 15px; + line-height: 40px; + padding: 0 0 0 48px; +} + +.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-label.codicon { + font-size: 24px; + align-items: center; + justify-content: center; } .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before, .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before { content: ""; position: absolute; - top: 9px; - height: 32px; + top: 0; z-index: 1; - top: 5px; - height: 40px; + top: 0; + height: 100%; width: 0; border-left: 2px solid; } @@ -41,7 +50,7 @@ } .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:focus .active-item-indicator:before { - border-left: none; /* don't show active border + focus at the same time, focus takes priority */ + visibility: hidden; /* don't show active border + focus at the same time, focus takes priority */ } /* Hides active elements in high contrast mode */ @@ -53,20 +62,14 @@ border-left: none !important; /* no focus feedback when using mouse */ } +.monaco-workbench .activitybar.left > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before, .monaco-workbench .activitybar.left > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before{ left: 0; } -.monaco-workbench .activitybar.left > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before { - left: 1px; -} - +.monaco-workbench .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before, .monaco-workbench .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before { - right: 1px; -} - -.monaco-workbench .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before { - right: 2px; + right: 0; } /* Hides outline on HC as focus is handled by border */ @@ -79,16 +82,22 @@ .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .badge { position: absolute; z-index: 1; - top: 5px; + top: 0; + bottom: 0; + margin: auto; left: 0; overflow: hidden; - width: 50px; - height: 40px; + width: 100%; + height: 100%; +} + +.monaco-workbench.border .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .active-item-indicator { + left: -2px; } .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .badge .badge-content { position: absolute; - top: 20px; + top: 24px; right: 8px; font-size: 9px; font-weight: 600; @@ -102,9 +111,9 @@ /* Right aligned */ -.monaco-workbench .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .action-label { +.monaco-workbench .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .action-label:not(.codicon) { margin-left: 0; - padding: 0 50px 0 0; + padding: 0 48px 0 0; background-position: calc(100% - 9px) center; } diff --git a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css index 388dbf3390..3c2705d257 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css @@ -7,6 +7,21 @@ width: 48px; } +.monaco-workbench.windows.chromium .part.activitybar, +.monaco-workbench.linux.chromium .part.activitybar { + /* + * Explicitly put the part onto its own layer to help Chrome to + * render the content with LCD-anti-aliasing. By partioning the + * workbench into multiple layers, we can ensure that a bad + * behaving part is not making another part fallback to greyscale + * rendering. + * + * macOS: does not render LCD-anti-aliased. + */ + transform: translate3d(0px, 0px, 0px); + overflow: visible; /* when a new layer is created, we need to set overflow visible to avoid clipping the menubar */ +} + .monaco-workbench .activitybar > .content { height: 100%; display: flex; @@ -23,14 +38,6 @@ outline: 0 !important; /* activity bar indicates focus custom */ } -.monaco-workbench .activitybar > .content > .composite-bar > .monaco-action-bar .action-label.toggle-more { - -webkit-mask: url('ellipsis-activity-bar.svg') no-repeat 50% 50%; -} - -.monaco-workbench .activitybar .global-activity .monaco-action-bar .action-label.update-activity { - -webkit-mask: url('settings-activity-bar.svg') no-repeat 50% 50%; -} - .monaco-workbench .activitybar > .content > .composite-bar { margin-bottom: auto; } @@ -39,3 +46,8 @@ width: 100%; height: 35px; } + +.monaco-workbench .activitybar .menubar.compact .toolbar-toggle-more { + width: 100%; + height: 35px; +} diff --git a/src/vs/workbench/browser/parts/activitybar/media/ellipsis-activity-bar.svg b/src/vs/workbench/browser/parts/activitybar/media/ellipsis-activity-bar.svg deleted file mode 100644 index 6729ca3c90..0000000000 --- a/src/vs/workbench/browser/parts/activitybar/media/ellipsis-activity-bar.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/parts/activitybar/media/settings-activity-bar.svg b/src/vs/workbench/browser/parts/activitybar/media/settings-activity-bar.svg deleted file mode 100644 index 5d5fbb8ea5..0000000000 --- a/src/vs/workbench/browser/parts/activitybar/media/settings-activity-bar.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index fec6fa3bdd..e500122030 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -158,7 +158,13 @@ export class ActivityActionViewItem extends BaseActionViewItem { if (this.label) { if (this.options.icon) { const foreground = this._action.checked ? colors.activeBackgroundColor || colors.activeForegroundColor : colors.inactiveBackgroundColor || colors.inactiveForegroundColor; - this.label.style.backgroundColor = foreground ? foreground.toString() : ''; + if (this.activity.iconUrl) { + // Apply background color to activity bar item provided with iconUrls + this.label.style.backgroundColor = foreground ? foreground.toString() : ''; + } else { + // Apply foreground color to activity bar items provided with codicons + this.label.style.color = foreground ? foreground.toString() : ''; + } } else { const foreground = this._action.checked ? colors.activeForegroundColor : colors.inactiveForegroundColor; const borderBottomColor = this._action.checked ? colors.activeBorderBottomColor : null; @@ -233,6 +239,7 @@ export class ActivityActionViewItem extends BaseActionViewItem { this.updateLabel(); this.updateTitle(this.activity.name); this.updateBadge(); + this.updateStyles(); } protected updateBadge(): void { @@ -313,6 +320,11 @@ export class ActivityActionViewItem extends BaseActionViewItem { dom.addClass(this.label, this.activity.cssClass); } + if (this.options.icon && !this.activity.iconUrl) { + // Only apply codicon class to activity bar icon items without iconUrl + dom.addClass(this.label, 'codicon'); + } + if (!this.options.icon) { this.label.textContent = this.getAction().label; } @@ -346,7 +358,7 @@ export class CompositeOverflowActivityAction extends ActivityAction { super({ id: 'additionalComposites.action', name: nls.localize('additionalViews', "Additional Views"), - cssClass: 'toggle-more' + cssClass: 'codicon-more' }); } @@ -481,11 +493,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem { activityName = this.compositeActivityAction.activity.name; } - this.compositeActivity = { - id: this.compositeActivityAction.activity.id, - cssClass: this.compositeActivityAction.activity.cssClass, - name: activityName - }; + this.compositeActivity = { ...this.compositeActivityAction.activity, ... { name: activityName } }; } return this.compositeActivity; diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 3884885d31..f63db5ab33 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -168,7 +168,7 @@ export abstract class CompositePart extends Part { // Instantiate composite from registry otherwise const compositeDescriptor = this.registry.getComposite(id); if (compositeDescriptor) { - const compositeProgressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, this.progressBar, compositeDescriptor.id, !!isActive); + const compositeProgressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, assertIsDefined(this.progressBar), compositeDescriptor.id, !!isActive); const compositeInstantiationService = this.instantiationService.createChild(new ServiceCollection( [IEditorProgressService, compositeProgressIndicator] // provide the editor progress service for any editors instantiated within the composite )); @@ -285,7 +285,7 @@ export abstract class CompositePart extends Part { if (this.activeComposite && this.activeComposite.getId() === compositeId) { // Title - this.updateTitle(this.activeComposite.getId(), this.activeComposite.getTitle() || undefined); + this.updateTitle(this.activeComposite.getId(), this.activeComposite.getTitle()); // Actions const actionsBinding = this.collectCompositeActions(this.activeComposite); diff --git a/src/vs/workbench/browser/parts/editor/baseEditor.ts b/src/vs/workbench/browser/parts/editor/baseEditor.ts index f722d1f4d8..74defd79e0 100644 --- a/src/vs/workbench/browser/parts/editor/baseEditor.ts +++ b/src/vs/workbench/browser/parts/editor/baseEditor.ts @@ -78,11 +78,9 @@ export abstract class BaseEditor extends Panel implements IEditor { * The provided cancellation token should be used to test if the operation * was cancelled. */ - setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { + async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { this._input = input; this._options = options; - - return Promise.resolve(); } /** @@ -112,6 +110,8 @@ export abstract class BaseEditor extends Panel implements IEditor { this.createEditor(parent); } + onHide() { } + /** * Called to create the editor in the parent HTMLElement. */ diff --git a/src/vs/workbench/browser/parts/editor/binaryEditor.ts b/src/vs/workbench/browser/parts/editor/binaryEditor.ts index e983819609..c59a36eba8 100644 --- a/src/vs/workbench/browser/parts/editor/binaryEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryEditor.ts @@ -56,7 +56,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { this.callbacks = callbacks; } - getTitle() { + getTitle(): string { return this.input ? this.input.getName() : nls.localize('binaryEditor', "Binary Viewer"); } @@ -93,7 +93,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { } const [binaryContainer, scrollbar] = assertAllDefined(this.binaryContainer, this.scrollbar); - this.resourceViewerContext = ResourceViewer.show({ name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag(), mime: model.getMime() }, binaryContainer, scrollbar, { + this.resourceViewerContext = ResourceViewer.show({ name: model.getName(), resource: model.resource, size: model.getSize(), etag: model.getETag(), mime: model.getMime() }, binaryContainer, scrollbar, { openInternalClb: () => this.handleOpenInternalCallback(input, options), openExternalClb: this.environmentService.configuration.remoteAuthority ? undefined : resource => this.callbacks.openExternal(resource), metadataClb: meta => this.handleMetadataChanged(meta) diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts index 6b2d52784c..923b0e0a38 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts @@ -167,135 +167,161 @@ Registry.as(Extensions.Configuration).registerConfigurat type: 'boolean', default: true }, - 'breadcrumbs.filteredTypes.file': { + 'breadcrumbs.showFiles': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.file', "When set to `false` breadcrumbs never show `file`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.file', "When enabled breadcrumbs show `file`-symbols.") }, - 'breadcrumbs.filteredTypes.module': { + 'breadcrumbs.showModules': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.module', "When set to `false` breadcrumbs never show `module`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.module', "When enabled breadcrumbs show `module`-symbols.") }, - 'breadcrumbs.filteredTypes.namespace': { + 'breadcrumbs.showNamespaces': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.namespace', "When set to `false` breadcrumbs never show `namespace`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.namespace', "When enabled breadcrumbs show `namespace`-symbols.") }, - 'breadcrumbs.filteredTypes.package': { + 'breadcrumbs.showPackages': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.package', "When set to `false` breadcrumbs never show `package`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.package', "When enabled breadcrumbs show `package`-symbols.") }, - 'breadcrumbs.filteredTypes.class': { + 'breadcrumbs.showClasses': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.class', "When set to `false` breadcrumbs never show `class`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.class', "When enabled breadcrumbs show `class`-symbols.") }, - 'breadcrumbs.filteredTypes.method': { + 'breadcrumbs.showMethods': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.method', "When set to `false` breadcrumbs never show `method`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.method', "When enabled breadcrumbs show `method`-symbols.") }, - 'breadcrumbs.filteredTypes.property': { + 'breadcrumbs.showProperties': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.property', "When set to `false` breadcrumbs never show `property`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.property', "When enabled breadcrumbs show `property`-symbols.") }, - 'breadcrumbs.filteredTypes.field': { + 'breadcrumbs.showFields': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.field', "When set to `false` breadcrumbs never show `field`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.field', "When enabled breadcrumbs show `field`-symbols.") }, - 'breadcrumbs.filteredTypes.constructor': { + 'breadcrumbs.showConstructors': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.constructor', "When set to `false` breadcrumbs never show `constructor`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.constructor', "When enabled breadcrumbs show `constructor`-symbols.") }, - 'breadcrumbs.filteredTypes.enum': { + 'breadcrumbs.showEnums': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.enum', "When set to `false` breadcrumbs never show `enum`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.enum', "When enabled breadcrumbs show `enum`-symbols.") }, - 'breadcrumbs.filteredTypes.interface': { + 'breadcrumbs.showInterfaces': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.interface', "When set to `false` breadcrumbs never show `interface`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.interface', "When enabled breadcrumbs show `interface`-symbols.") }, - 'breadcrumbs.filteredTypes.function': { + 'breadcrumbs.showFunctions': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.function', "When set to `false` breadcrumbs never show `function`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.function', "When enabled breadcrumbs show `function`-symbols.") }, - 'breadcrumbs.filteredTypes.variable': { + 'breadcrumbs.showVariables': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.variable', "When set to `false` breadcrumbs never show `variable`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.variable', "When enabled breadcrumbs show `variable`-symbols.") }, - 'breadcrumbs.filteredTypes.constant': { + 'breadcrumbs.showConstants': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.constant', "When set to `false` breadcrumbs never show `constant`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.constant', "When enabled breadcrumbs show `constant`-symbols.") }, - 'breadcrumbs.filteredTypes.string': { + 'breadcrumbs.showStrings': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.string', "When set to `false` breadcrumbs never show `string`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.string', "When enabled breadcrumbs show `string`-symbols.") }, - 'breadcrumbs.filteredTypes.number': { + 'breadcrumbs.showNumbers': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.number', "When set to `false` breadcrumbs never show `number`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.number', "When enabled breadcrumbs show `number`-symbols.") }, - 'breadcrumbs.filteredTypes.boolean': { + 'breadcrumbs.showBooleans': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.boolean', "When set to `false` breadcrumbs never show `boolean`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.boolean', "When enabled breadcrumbs show `boolean`-symbols.") }, - 'breadcrumbs.filteredTypes.array': { + 'breadcrumbs.showArrays': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.array', "When set to `false` breadcrumbs never show `array`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.array', "When enabled breadcrumbs show `array`-symbols.") }, - 'breadcrumbs.filteredTypes.object': { + 'breadcrumbs.showObjects': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.object', "When set to `false` breadcrumbs never show `object`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.object', "When enabled breadcrumbs show `object`-symbols.") }, - 'breadcrumbs.filteredTypes.key': { + 'breadcrumbs.showKeys': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.key', "When set to `false` breadcrumbs never show `key`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.key', "When enabled breadcrumbs show `key`-symbols.") }, - 'breadcrumbs.filteredTypes.null': { + 'breadcrumbs.showNull': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.null', "When set to `false` breadcrumbs never show `null`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.null', "When enabled breadcrumbs show `null`-symbols.") }, - 'breadcrumbs.filteredTypes.enumMember': { + 'breadcrumbs.showEnumMembers': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.enumMember', "When set to `false` breadcrumbs never show `enumMember`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.enumMember', "When enabled breadcrumbs show `enumMember`-symbols.") }, - 'breadcrumbs.filteredTypes.struct': { + 'breadcrumbs.showStructs': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.struct', "When set to `false` breadcrumbs never show `struct`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.struct', "When enabled breadcrumbs show `struct`-symbols.") }, - 'breadcrumbs.filteredTypes.event': { + 'breadcrumbs.showEvents': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.event', "When set to `false` breadcrumbs never show `event`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.event', "When enabled breadcrumbs show `event`-symbols.") }, - 'breadcrumbs.filteredTypes.operator': { + 'breadcrumbs.showOperators': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.operator', "When set to `false` breadcrumbs never show `operator`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.operator', "When enabled breadcrumbs show `operator`-symbols.") }, - 'breadcrumbs.filteredTypes.typeParameter': { + 'breadcrumbs.showTypeParameters': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.typeParameter', "When set to `false` breadcrumbs never show `typeParameter`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.typeParameter', "When enabled breadcrumbs show `typeParameter`-symbols.") } } }); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 8051d8445b..8dec032b03 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -47,6 +47,7 @@ import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; import { onDidChangeZoomLevel } from 'vs/base/browser/browser'; import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; import { ILabelService } from 'vs/platform/label/common/label'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; class Item extends BreadcrumbsItem { @@ -102,7 +103,7 @@ class Item extends BreadcrumbsItem { } else if (this.element instanceof OutlineGroup) { // provider let label = new IconLabel(container); - label.setLabel(this.element.provider.displayName); + label.setLabel(this.element.provider.displayName || ''); this._disposables.add(label); } else if (this.element instanceof OutlineElement) { @@ -168,6 +169,7 @@ export class BreadcrumbsControl { @IThemeService private readonly _themeService: IThemeService, @IQuickOpenService private readonly _quickOpenService: IQuickOpenService, @IConfigurationService private readonly _configurationService: IConfigurationService, + @ITextResourceConfigurationService private readonly _textResourceConfigurationService: ITextResourceConfigurationService, @IFileService private readonly _fileService: IFileService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @ILabelService private readonly _labelService: ILabelService, @@ -246,7 +248,12 @@ export class BreadcrumbsControl { const uri = input.getResource()!; const editor = this._getActiveCodeEditor(); - const model = new EditorBreadcrumbsModel(uri, editor, this._configurationService, this._workspaceService); + const model = new EditorBreadcrumbsModel( + uri, editor, + this._configurationService, + this._textResourceConfigurationService, + this._workspaceService + ); dom.toggleClass(this.domNode, 'relative-path', model.isRelative()); dom.toggleClass(this.domNode, 'backslash-path', this._labelService.getSeparator(uri.scheme, uri.authority) === '\\'); @@ -375,7 +382,7 @@ export class BreadcrumbsControl { editorViewState = withNullAsUndefined(editor.saveViewState()); } const { symbol } = data.target; - editor.revealRangeInCenter(symbol.range, ScrollType.Smooth); + editor.revealRangeInCenterIfOutsideViewport(symbol.range, ScrollType.Smooth); editorDecorations = editor.deltaDecorations(editorDecorations, [{ range: symbol.range, options: { diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts index 0b76064787..165dd96fd4 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts @@ -6,7 +6,7 @@ import { equals } from 'vs/base/common/arrays'; import { TimeoutTimer } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { size } from 'vs/base/common/collections'; +import { size, values } from 'vs/base/common/collections'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -14,7 +14,7 @@ import { isEqual, dirname } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IPosition } from 'vs/editor/common/core/position'; -import { DocumentSymbolProviderRegistry, SymbolKinds } from 'vs/editor/common/modes'; +import { DocumentSymbolProviderRegistry } from 'vs/editor/common/modes'; import { OutlineElement, OutlineGroup, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { Schemas } from 'vs/base/common/network'; @@ -22,6 +22,9 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs'; import { FileKind } from 'vs/platform/files/common/files'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { OutlineFilter } from 'vs/editor/contrib/documentSymbols/outlineTree'; +import { ITextModel } from 'vs/editor/common/model'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; export class FileElement { constructor( @@ -52,9 +55,9 @@ export class EditorBreadcrumbsModel { private readonly _uri: URI, private readonly _editor: ICodeEditor | undefined, @IConfigurationService private readonly _configurationService: IConfigurationService, + @ITextResourceConfigurationService private readonly _textResourceConfigurationService: ITextResourceConfigurationService, @IWorkspaceContextService workspaceService: IWorkspaceContextService, ) { - this._cfgFilePath = BreadcrumbsConfig.FilePath.bindTo(_configurationService); this._cfgSymbolPath = BreadcrumbsConfig.SymbolPath.bindTo(_configurationService); @@ -139,8 +142,19 @@ export class EditorBreadcrumbsModel { // update when config changes (re-render) this._disposables.add(this._configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('breadcrumbs.filteredTypes')) { + if (e.affectsConfiguration('breadcrumbs')) { this._updateOutline(true); + return; + } + if (this._editor && this._editor.getModel()) { + const editorModel = this._editor.getModel() as ITextModel; + const languageName = editorModel.getLanguageIdentifier().language; + + // Checking for changes in the current language override config. + // We can't be more specific than this because the ConfigurationChangeEvent(e) only includes the first part of the root path + if (e.affectsConfiguration(`[${languageName}]`)) { + this._updateOutline(true); + } } })); @@ -214,7 +228,7 @@ export class EditorBreadcrumbsModel { } let item: OutlineGroup | OutlineElement | undefined = model.getItemEnclosingPosition(position); if (!item) { - return [model]; + return this._getOutlineElementsRoot(model); } let chain: Array = []; while (item) { @@ -231,17 +245,34 @@ export class EditorBreadcrumbsModel { let result: Array = []; for (let i = chain.length - 1; i >= 0; i--) { let element = chain[i]; - if ( - element instanceof OutlineElement - && !this._configurationService.getValue(`breadcrumbs.filteredTypes.${SymbolKinds.toString(element.symbol.kind)}`) - ) { + if (this._isFiltered(element)) { break; } result.push(element); } + if (result.length === 0) { + return this._getOutlineElementsRoot(model); + } return result; } + private _getOutlineElementsRoot(model: OutlineModel): (OutlineModel | OutlineGroup | OutlineElement)[] { + return values(model.children).every(e => this._isFiltered(e)) ? [] : [model]; + } + + private _isFiltered(element: TreeElement): boolean { + if (element instanceof OutlineElement) { + const key = `breadcrumbs.${OutlineFilter.kindToConfigName[element.symbol.kind]}`; + let uri: URI | undefined; + if (this._editor && this._editor.getModel()) { + const model = this._editor.getModel() as ITextModel; + uri = model.uri; + } + return !this._textResourceConfigurationService.getValue(uri, key); + } + return false; + } + private _updateOutlineElements(elements: Array): void { if (!equals(elements, this._outlineElements, EditorBreadcrumbsModel._outlineElementEquals)) { this._outlineElements = elements; diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts index 34614e1ae8..89ed66d0a4 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -374,12 +374,15 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker { const labels = this._instantiationService.createInstance(ResourceLabels, DEFAULT_LABELS_CONTAINER /* TODO@Jo visibility propagation */); this._disposables.add(labels); - return this._instantiationService.createInstance(WorkbenchAsyncDataTree, 'BreadcrumbsFilePicker', container, new FileVirtualDelegate(), [this._instantiationService.createInstance(FileRenderer, labels)], this._instantiationService.createInstance(FileDataSource), { + return this._instantiationService.createInstance>(WorkbenchAsyncDataTree, 'BreadcrumbsFilePicker', container, new FileVirtualDelegate(), [this._instantiationService.createInstance(FileRenderer, labels)], this._instantiationService.createInstance(FileDataSource), { multipleSelectionSupport: false, sorter: new FileSorter(), filter: this._instantiationService.createInstance(FileFilter), identityProvider: new FileIdentityProvider(), - keyboardNavigationLabelProvider: new FileNavigationLabelProvider() + keyboardNavigationLabelProvider: new FileNavigationLabelProvider(), + overrideStyles: { + listBackground: breadcrumbsPickerBackground + } }); } @@ -438,7 +441,7 @@ export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker { } protected _createTree(container: HTMLElement) { - return this._instantiationService.createInstance( + return this._instantiationService.createInstance>( WorkbenchDataTree, 'BreadcrumbsOutlinePicker', container, @@ -452,7 +455,7 @@ export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker { sorter: new OutlineItemComparator(this._getOutlineItemCompareType()), identityProvider: new OutlineIdentityProvider(), keyboardNavigationLabelProvider: new OutlineNavigationLabelProvider(), - filter: this._instantiationService.createInstance(OutlineFilter, 'breadcrumbs.filteredTypes') + filter: this._instantiationService.createInstance(OutlineFilter, 'breadcrumbs') } ); } @@ -468,14 +471,10 @@ export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker { const tree = this._tree as WorkbenchDataTree; tree.setInput(model); - let focusElement: TreeElement; - if (element === model) { - focusElement = tree.navigate().first(); - } else { - focusElement = element; + if (element !== model) { + tree.reveal(element, 0.5); + tree.setFocus([element], this._fakeEvent); } - tree.reveal(focusElement, 0.5); - tree.setFocus([focusElement], this._fakeEvent); tree.domFocus(); return Promise.resolve(); diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 24ee3f3ca3..8be11b4334 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -13,11 +13,11 @@ import { EditorInput, IEditorInputFactory, SideBySideEditorInput, IEditorInputFa import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; -import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor'; -import { ITextFileService, SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/textfiles'; +import { SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/textfiles'; import { BinaryResourceDiffEditor } from 'vs/workbench/browser/parts/editor/binaryDiffEditor'; import { ChangeEncodingAction, ChangeEOLAction, ChangeModeAction, EditorStatus } from 'vs/workbench/browser/parts/editor/editorStatus'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; @@ -52,24 +52,26 @@ import { toLocalResource } from 'vs/base/common/resources'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { withNullAsUndefined } from 'vs/base/common/types'; -import { registerAndGetAmdImageURL } from 'vs/base/common/amd'; +import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { EditorAutoSave } from 'vs/workbench/browser/parts/editor/editorAutoSave'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; // Register String Editor Registry.as(EditorExtensions.Editors).registerEditor( - new EditorDescriptor( + EditorDescriptor.create( TextResourceEditor, TextResourceEditor.ID, nls.localize('textEditor', "Text Editor"), ), [ - new SyncDescriptor(UntitledEditorInput), + new SyncDescriptor(UntitledTextEditorInput), new SyncDescriptor(ResourceEditorInput) ] ); // Register Text Diff Editor Registry.as(EditorExtensions.Editors).registerEditor( - new EditorDescriptor( + EditorDescriptor.create( TextDiffEditor, TextDiffEditor.ID, nls.localize('textDiffEditor', "Text Diff Editor") @@ -81,7 +83,7 @@ Registry.as(EditorExtensions.Editors).registerEditor( // Register Binary Resource Diff Editor Registry.as(EditorExtensions.Editors).registerEditor( - new EditorDescriptor( + EditorDescriptor.create( BinaryResourceDiffEditor, BinaryResourceDiffEditor.ID, nls.localize('binaryDiffEditor', "Binary Diff Editor") @@ -92,7 +94,7 @@ Registry.as(EditorExtensions.Editors).registerEditor( ); Registry.as(EditorExtensions.Editors).registerEditor( - new EditorDescriptor( + EditorDescriptor.create( SideBySideEditor, SideBySideEditor.ID, nls.localize('sideBySideEditor', "Side by Side Editor") @@ -102,7 +104,7 @@ Registry.as(EditorExtensions.Editors).registerEditor( ] ); -interface ISerializedUntitledEditorInput { +interface ISerializedUntitledTextEditorInput { resource: string; resourceJSON: object; modeId: string | undefined; @@ -110,48 +112,48 @@ interface ISerializedUntitledEditorInput { } // Register Editor Input Factory -class UntitledEditorInputFactory implements IEditorInputFactory { +class UntitledTextEditorInputFactory implements IEditorInputFactory { constructor( - @ITextFileService private readonly textFileService: ITextFileService, + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService ) { } serialize(editorInput: EditorInput): string | undefined { - if (!this.textFileService.isHotExitEnabled) { + if (!this.filesConfigurationService.isHotExitEnabled) { return undefined; // never restore untitled unless hot exit is enabled } - const untitledEditorInput = editorInput; + const untitledTextEditorInput = editorInput; - let resource = untitledEditorInput.getResource(); - if (untitledEditorInput.hasAssociatedFilePath) { + let resource = untitledTextEditorInput.getResource(); + if (untitledTextEditorInput.hasAssociatedFilePath) { resource = toLocalResource(resource, this.environmentService.configuration.remoteAuthority); // untitled with associated file path use the local schema } - const serialized: ISerializedUntitledEditorInput = { + const serialized: ISerializedUntitledTextEditorInput = { resource: resource.toString(), // Keep for backwards compatibility resourceJSON: resource.toJSON(), - modeId: untitledEditorInput.getMode(), - encoding: untitledEditorInput.getEncoding() + modeId: untitledTextEditorInput.getMode(), + encoding: untitledTextEditorInput.getEncoding() }; return JSON.stringify(serialized); } - deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): UntitledEditorInput { - return instantiationService.invokeFunction(accessor => { - const deserialized: ISerializedUntitledEditorInput = JSON.parse(serializedEditorInput); + deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): UntitledTextEditorInput { + return instantiationService.invokeFunction(accessor => { + const deserialized: ISerializedUntitledTextEditorInput = JSON.parse(serializedEditorInput); const resource = !!deserialized.resourceJSON ? URI.revive(deserialized.resourceJSON) : URI.parse(deserialized.resource); const mode = deserialized.modeId; const encoding = deserialized.encoding; - return accessor.get(IEditorService).createInput({ resource, mode, encoding, forceUntitled: true }) as UntitledEditorInput; + return accessor.get(IEditorService).createInput({ resource, mode, encoding, forceUntitled: true }) as UntitledTextEditorInput; }); } } -Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(UntitledEditorInput.ID, UntitledEditorInputFactory); +Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(UntitledTextEditorInput.ID, UntitledTextEditorInputFactory); interface ISerializedSideBySideEditorInput { name: string; @@ -223,13 +225,16 @@ registerEditorContribution(OpenWorkspaceButtonContribution.ID, OpenWorkspaceButt // Register Editor Status Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(EditorStatus, LifecyclePhase.Ready); +// Register Editor Auto Save +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(EditorAutoSave, LifecyclePhase.Ready); + // Register Status Actions const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(ChangeModeAction, ChangeModeAction.ID, ChangeModeAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_M) }), 'Change Language Mode'); -registry.registerWorkbenchAction(new SyncActionDescriptor(ChangeEOLAction, ChangeEOLAction.ID, ChangeEOLAction.LABEL), 'Change End of Line Sequence'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ChangeModeAction, ChangeModeAction.ID, ChangeModeAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_M) }), 'Change Language Mode'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ChangeEOLAction, ChangeEOLAction.ID, ChangeEOLAction.LABEL), 'Change End of Line Sequence'); if (Object.keys(SUPPORTED_ENCODINGS).length > 1) { - registry.registerWorkbenchAction(new SyncActionDescriptor(ChangeEncodingAction, ChangeEncodingAction.ID, ChangeEncodingAction.LABEL), 'Change File Encoding'); + registry.registerWorkbenchAction(SyncActionDescriptor.create(ChangeEncodingAction, ChangeEncodingAction.ID, ChangeEncodingAction.LABEL), 'Change File Encoding'); } export class QuickOpenActionContributor extends ActionBarContributor { @@ -278,7 +283,7 @@ const editorPickerContextKey = 'inEditorsPicker'; const editorPickerContext = ContextKeyExpr.and(inQuickOpenContext, ContextKeyExpr.has(editorPickerContextKey)); Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - new QuickOpenHandlerDescriptor( + QuickOpenHandlerDescriptor.create( ActiveEditorGroupPicker, ActiveEditorGroupPicker.ID, editorCommands.NAVIGATE_IN_ACTIVE_GROUP_PREFIX, @@ -294,7 +299,7 @@ Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpen ); Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - new QuickOpenHandlerDescriptor( + QuickOpenHandlerDescriptor.create( AllEditorsPicker, AllEditorsPicker.ID, editorCommands.NAVIGATE_ALL_EDITORS_GROUP_PREFIX, @@ -311,84 +316,84 @@ Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpen // Register Editor Actions const category = nls.localize('view', "View"); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextEditorInGroup, OpenNextEditorInGroup.ID, OpenNextEditorInGroup.LABEL), 'View: Open Next Editor in Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousEditorInGroup, OpenPreviousEditorInGroup.ID, OpenPreviousEditorInGroup.LABEL), 'View: Open Previous Editor in Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenLastEditorInGroup, OpenLastEditorInGroup.ID, OpenLastEditorInGroup.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_0, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_9], mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_0, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_9] } }), 'View: Open Last Editor in Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFirstEditorInGroup, OpenFirstEditorInGroup.ID, OpenFirstEditorInGroup.LABEL), 'View: Open First Editor in Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextRecentlyUsedEditorAction, OpenNextRecentlyUsedEditorAction.ID, OpenNextRecentlyUsedEditorAction.LABEL), 'View: Open Next Recently Used Editor', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction.ID, OpenPreviousRecentlyUsedEditorAction.LABEL), 'View: Open Previous Recently Used Editor', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(ShowAllEditorsAction, ShowAllEditorsAction.ID, ShowAllEditorsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_P), mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Tab } }), 'View: Show All Editors', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(ShowEditorsInActiveGroupAction, ShowEditorsInActiveGroupAction.ID, ShowEditorsInActiveGroupAction.LABEL), 'View: Show Editors in Active Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextEditor, OpenNextEditor.ID, OpenNextEditor.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.PageDown, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_CLOSE_SQUARE_BRACKET] } }), 'View: Open Next Editor', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousEditor, OpenPreviousEditor.ID, OpenPreviousEditor.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.PageUp, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_OPEN_SQUARE_BRACKET] } }), 'View: Open Previous Editor', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(ReopenClosedEditorAction, ReopenClosedEditorAction.ID, ReopenClosedEditorAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_T }), 'View: Reopen Closed Editor', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(ClearRecentFilesAction, ClearRecentFilesAction.ID, ClearRecentFilesAction.LABEL), 'File: Clear Recently Opened', nls.localize('file', "File")); -registry.registerWorkbenchAction(new SyncActionDescriptor(CloseAllEditorsAction, CloseAllEditorsAction.ID, CloseAllEditorsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_W) }), 'View: Close All Editors', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(CloseAllEditorGroupsAction, CloseAllEditorGroupsAction.ID, CloseAllEditorGroupsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_W) }), 'View: Close All Editor Groups', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(CloseLeftEditorsInGroupAction, CloseLeftEditorsInGroupAction.ID, CloseLeftEditorsInGroupAction.LABEL), 'View: Close Editors to the Left in Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(CloseEditorsInOtherGroupsAction, CloseEditorsInOtherGroupsAction.ID, CloseEditorsInOtherGroupsAction.LABEL), 'View: Close Editors in Other Groups', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(CloseEditorInAllGroupsAction, CloseEditorInAllGroupsAction.ID, CloseEditorInAllGroupsAction.LABEL), 'View: Close Editor in All Groups', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorAction, SplitEditorAction.ID, SplitEditorAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_BACKSLASH }), 'View: Split Editor', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorOrthogonalAction, SplitEditorOrthogonalAction.ID, SplitEditorOrthogonalAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.US_BACKSLASH) }), 'View: Split Editor Orthogonal', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorLeftAction, SplitEditorLeftAction.ID, SplitEditorLeftAction.LABEL), 'View: Split Editor Left', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorRightAction, SplitEditorRightAction.ID, SplitEditorRightAction.LABEL), 'View: Split Editor Right', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorUpAction, SplitEditorUpAction.ID, SplitEditorUpAction.LABEL), 'Split Editor Up', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorDownAction, SplitEditorDownAction.ID, SplitEditorDownAction.LABEL), 'View: Split Editor Down', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(JoinTwoGroupsAction, JoinTwoGroupsAction.ID, JoinTwoGroupsAction.LABEL), 'View: Join Editor Group with Next Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(JoinAllGroupsAction, JoinAllGroupsAction.ID, JoinAllGroupsAction.LABEL), 'View: Join All Editor Groups', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateBetweenGroupsAction, NavigateBetweenGroupsAction.ID, NavigateBetweenGroupsAction.LABEL), 'View: Navigate Between Editor Groups', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(ResetGroupSizesAction, ResetGroupSizesAction.ID, ResetGroupSizesAction.LABEL), 'View: Reset Editor Group Sizes', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleGroupSizesAction, ToggleGroupSizesAction.ID, ToggleGroupSizesAction.LABEL), 'View: Toggle Editor Group Sizes', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MaximizeGroupAction, MaximizeGroupAction.ID, MaximizeGroupAction.LABEL), 'View: Maximize Editor Group and Hide Side Bar', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MinimizeOtherGroupsAction, MinimizeOtherGroupsAction.ID, MinimizeOtherGroupsAction.LABEL), 'View: Maximize Editor Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorLeftInGroupAction, MoveEditorLeftInGroupAction.ID, MoveEditorLeftInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.PageUp, mac: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.LeftArrow) } }), 'View: Move Editor Left', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorRightInGroupAction, MoveEditorRightInGroupAction.ID, MoveEditorRightInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.PageDown, mac: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.RightArrow) } }), 'View: Move Editor Right', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MoveGroupLeftAction, MoveGroupLeftAction.ID, MoveGroupLeftAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.LeftArrow) }), 'View: Move Editor Group Left', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MoveGroupRightAction, MoveGroupRightAction.ID, MoveGroupRightAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.RightArrow) }), 'View: Move Editor Group Right', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MoveGroupUpAction, MoveGroupUpAction.ID, MoveGroupUpAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.UpArrow) }), 'View: Move Editor Group Up', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MoveGroupDownAction, MoveGroupDownAction.ID, MoveGroupDownAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.DownArrow) }), 'View: Move Editor Group Down', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToPreviousGroupAction, MoveEditorToPreviousGroupAction.ID, MoveEditorToPreviousGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.LeftArrow } }), 'View: Move Editor into Previous Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToNextGroupAction, MoveEditorToNextGroupAction.ID, MoveEditorToNextGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.RightArrow } }), 'View: Move Editor into Next Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToFirstGroupAction, MoveEditorToFirstGroupAction.ID, MoveEditorToFirstGroupAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_1, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_1 } }), 'View: Move Editor into First Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToLastGroupAction, MoveEditorToLastGroupAction.ID, MoveEditorToLastGroupAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_9, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_9 } }), 'View: Move Editor into Last Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToLeftGroupAction, MoveEditorToLeftGroupAction.ID, MoveEditorToLeftGroupAction.LABEL), 'View: Move Editor into Left Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToRightGroupAction, MoveEditorToRightGroupAction.ID, MoveEditorToRightGroupAction.LABEL), 'View: Move Editor into Right Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToAboveGroupAction, MoveEditorToAboveGroupAction.ID, MoveEditorToAboveGroupAction.LABEL), 'View: Move Editor into Above Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToBelowGroupAction, MoveEditorToBelowGroupAction.ID, MoveEditorToBelowGroupAction.LABEL), 'View: Move Editor into Below Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(FocusActiveGroupAction, FocusActiveGroupAction.ID, FocusActiveGroupAction.LABEL), 'View: Focus Active Editor Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(FocusFirstGroupAction, FocusFirstGroupAction.ID, FocusFirstGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_1 }), 'View: Focus First Editor Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(FocusLastGroupAction, FocusLastGroupAction.ID, FocusLastGroupAction.LABEL), 'View: Focus Last Editor Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousGroup, FocusPreviousGroup.ID, FocusPreviousGroup.LABEL), 'View: Focus Previous Editor Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextGroup, FocusNextGroup.ID, FocusNextGroup.LABEL), 'View: Focus Next Editor Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(FocusLeftGroup, FocusLeftGroup.ID, FocusLeftGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.LeftArrow) }), 'View: Focus Left Editor Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(FocusRightGroup, FocusRightGroup.ID, FocusRightGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.RightArrow) }), 'View: Focus Right Editor Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(FocusAboveGroup, FocusAboveGroup.ID, FocusAboveGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.UpArrow) }), 'View: Focus Above Editor Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(FocusBelowGroup, FocusBelowGroup.ID, FocusBelowGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.DownArrow) }), 'View: Focus Below Editor Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(NewEditorGroupLeftAction, NewEditorGroupLeftAction.ID, NewEditorGroupLeftAction.LABEL), 'View: New Editor Group to the Left', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(NewEditorGroupRightAction, NewEditorGroupRightAction.ID, NewEditorGroupRightAction.LABEL), 'View: New Editor Group to the Right', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(NewEditorGroupAboveAction, NewEditorGroupAboveAction.ID, NewEditorGroupAboveAction.LABEL), 'View: New Editor Group Above', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(NewEditorGroupBelowAction, NewEditorGroupBelowAction.ID, NewEditorGroupBelowAction.LABEL), 'View: New Editor Group Below', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateForwardAction, NavigateForwardAction.ID, NavigateForwardAction.LABEL, { primary: 0, win: { primary: KeyMod.Alt | KeyCode.RightArrow }, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_MINUS } }), 'Go Forward'); -registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateBackwardsAction, NavigateBackwardsAction.ID, NavigateBackwardsAction.LABEL, { primary: 0, win: { primary: KeyMod.Alt | KeyCode.LeftArrow }, mac: { primary: KeyMod.WinCtrl | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_MINUS } }), 'Go Back'); -registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateToLastEditLocationAction, NavigateToLastEditLocationAction.ID, NavigateToLastEditLocationAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_Q) }), 'Go to Last Edit Location'); -registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateLastAction, NavigateLastAction.ID, NavigateLastAction.LABEL), 'Go Last'); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousEditorFromHistoryAction, OpenPreviousEditorFromHistoryAction.ID, OpenPreviousEditorFromHistoryAction.LABEL), 'Open Previous Editor from History'); -registry.registerWorkbenchAction(new SyncActionDescriptor(ClearEditorHistoryAction, ClearEditorHistoryAction.ID, ClearEditorHistoryAction.LABEL), 'Clear Editor History'); -registry.registerWorkbenchAction(new SyncActionDescriptor(RevertAndCloseEditorAction, RevertAndCloseEditorAction.ID, RevertAndCloseEditorAction.LABEL), 'View: Revert and Close Editor', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutSingleAction, EditorLayoutSingleAction.ID, EditorLayoutSingleAction.LABEL), 'View: Single Column Editor Layout', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutTwoColumnsAction, EditorLayoutTwoColumnsAction.ID, EditorLayoutTwoColumnsAction.LABEL), 'View: Two Columns Editor Layout', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutThreeColumnsAction, EditorLayoutThreeColumnsAction.ID, EditorLayoutThreeColumnsAction.LABEL), 'View: Three Columns Editor Layout', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutTwoRowsAction, EditorLayoutTwoRowsAction.ID, EditorLayoutTwoRowsAction.LABEL), 'View: Two Rows Editor Layout', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutThreeRowsAction, EditorLayoutThreeRowsAction.ID, EditorLayoutThreeRowsAction.LABEL), 'View: Three Rows Editor Layout', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutTwoByTwoGridAction, EditorLayoutTwoByTwoGridAction.ID, EditorLayoutTwoByTwoGridAction.LABEL), 'View: Grid Editor Layout (2x2)', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutTwoRowsRightAction, EditorLayoutTwoRowsRightAction.ID, EditorLayoutTwoRowsRightAction.LABEL), 'View: Two Rows Right Editor Layout', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoColumnsBottomAction.ID, EditorLayoutTwoColumnsBottomAction.LABEL), 'View: Two Columns Bottom Editor Layout', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenNextEditorInGroup, OpenNextEditorInGroup.ID, OpenNextEditorInGroup.LABEL), 'View: Open Next Editor in Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenPreviousEditorInGroup, OpenPreviousEditorInGroup.ID, OpenPreviousEditorInGroup.LABEL), 'View: Open Previous Editor in Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenLastEditorInGroup, OpenLastEditorInGroup.ID, OpenLastEditorInGroup.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_0, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_9], mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_0, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_9] } }), 'View: Open Last Editor in Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenFirstEditorInGroup, OpenFirstEditorInGroup.ID, OpenFirstEditorInGroup.LABEL), 'View: Open First Editor in Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenNextRecentlyUsedEditorAction, OpenNextRecentlyUsedEditorAction.ID, OpenNextRecentlyUsedEditorAction.LABEL), 'View: Open Next Recently Used Editor', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction.ID, OpenPreviousRecentlyUsedEditorAction.LABEL), 'View: Open Previous Recently Used Editor', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowAllEditorsAction, ShowAllEditorsAction.ID, ShowAllEditorsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_P), mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Tab } }), 'View: Show All Editors', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowEditorsInActiveGroupAction, ShowEditorsInActiveGroupAction.ID, ShowEditorsInActiveGroupAction.LABEL), 'View: Show Editors in Active Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenNextEditor, OpenNextEditor.ID, OpenNextEditor.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.PageDown, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_CLOSE_SQUARE_BRACKET] } }), 'View: Open Next Editor', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenPreviousEditor, OpenPreviousEditor.ID, OpenPreviousEditor.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.PageUp, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_OPEN_SQUARE_BRACKET] } }), 'View: Open Previous Editor', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ReopenClosedEditorAction, ReopenClosedEditorAction.ID, ReopenClosedEditorAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_T }), 'View: Reopen Closed Editor', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ClearRecentFilesAction, ClearRecentFilesAction.ID, ClearRecentFilesAction.LABEL), 'File: Clear Recently Opened', nls.localize('file', "File")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(CloseAllEditorsAction, CloseAllEditorsAction.ID, CloseAllEditorsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_W) }), 'View: Close All Editors', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(CloseAllEditorGroupsAction, CloseAllEditorGroupsAction.ID, CloseAllEditorGroupsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_W) }), 'View: Close All Editor Groups', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(CloseLeftEditorsInGroupAction, CloseLeftEditorsInGroupAction.ID, CloseLeftEditorsInGroupAction.LABEL), 'View: Close Editors to the Left in Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(CloseEditorsInOtherGroupsAction, CloseEditorsInOtherGroupsAction.ID, CloseEditorsInOtherGroupsAction.LABEL), 'View: Close Editors in Other Groups', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(CloseEditorInAllGroupsAction, CloseEditorInAllGroupsAction.ID, CloseEditorInAllGroupsAction.LABEL), 'View: Close Editor in All Groups', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(SplitEditorAction, SplitEditorAction.ID, SplitEditorAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_BACKSLASH }), 'View: Split Editor', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(SplitEditorOrthogonalAction, SplitEditorOrthogonalAction.ID, SplitEditorOrthogonalAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.US_BACKSLASH) }), 'View: Split Editor Orthogonal', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(SplitEditorLeftAction, SplitEditorLeftAction.ID, SplitEditorLeftAction.LABEL), 'View: Split Editor Left', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(SplitEditorRightAction, SplitEditorRightAction.ID, SplitEditorRightAction.LABEL), 'View: Split Editor Right', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(SplitEditorUpAction, SplitEditorUpAction.ID, SplitEditorUpAction.LABEL), 'Split Editor Up', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(SplitEditorDownAction, SplitEditorDownAction.ID, SplitEditorDownAction.LABEL), 'View: Split Editor Down', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(JoinTwoGroupsAction, JoinTwoGroupsAction.ID, JoinTwoGroupsAction.LABEL), 'View: Join Editor Group with Next Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(JoinAllGroupsAction, JoinAllGroupsAction.ID, JoinAllGroupsAction.LABEL), 'View: Join All Editor Groups', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NavigateBetweenGroupsAction, NavigateBetweenGroupsAction.ID, NavigateBetweenGroupsAction.LABEL), 'View: Navigate Between Editor Groups', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ResetGroupSizesAction, ResetGroupSizesAction.ID, ResetGroupSizesAction.LABEL), 'View: Reset Editor Group Sizes', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleGroupSizesAction, ToggleGroupSizesAction.ID, ToggleGroupSizesAction.LABEL), 'View: Toggle Editor Group Sizes', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MaximizeGroupAction, MaximizeGroupAction.ID, MaximizeGroupAction.LABEL), 'View: Maximize Editor Group and Hide Side Bar', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MinimizeOtherGroupsAction, MinimizeOtherGroupsAction.ID, MinimizeOtherGroupsAction.LABEL), 'View: Maximize Editor Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveEditorLeftInGroupAction, MoveEditorLeftInGroupAction.ID, MoveEditorLeftInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.PageUp, mac: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.LeftArrow) } }), 'View: Move Editor Left', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveEditorRightInGroupAction, MoveEditorRightInGroupAction.ID, MoveEditorRightInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.PageDown, mac: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.RightArrow) } }), 'View: Move Editor Right', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveGroupLeftAction, MoveGroupLeftAction.ID, MoveGroupLeftAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.LeftArrow) }), 'View: Move Editor Group Left', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveGroupRightAction, MoveGroupRightAction.ID, MoveGroupRightAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.RightArrow) }), 'View: Move Editor Group Right', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveGroupUpAction, MoveGroupUpAction.ID, MoveGroupUpAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.UpArrow) }), 'View: Move Editor Group Up', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveGroupDownAction, MoveGroupDownAction.ID, MoveGroupDownAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.DownArrow) }), 'View: Move Editor Group Down', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveEditorToPreviousGroupAction, MoveEditorToPreviousGroupAction.ID, MoveEditorToPreviousGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.LeftArrow } }), 'View: Move Editor into Previous Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveEditorToNextGroupAction, MoveEditorToNextGroupAction.ID, MoveEditorToNextGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.RightArrow } }), 'View: Move Editor into Next Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveEditorToFirstGroupAction, MoveEditorToFirstGroupAction.ID, MoveEditorToFirstGroupAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_1, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_1 } }), 'View: Move Editor into First Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveEditorToLastGroupAction, MoveEditorToLastGroupAction.ID, MoveEditorToLastGroupAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_9, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_9 } }), 'View: Move Editor into Last Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveEditorToLeftGroupAction, MoveEditorToLeftGroupAction.ID, MoveEditorToLeftGroupAction.LABEL), 'View: Move Editor into Left Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveEditorToRightGroupAction, MoveEditorToRightGroupAction.ID, MoveEditorToRightGroupAction.LABEL), 'View: Move Editor into Right Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveEditorToAboveGroupAction, MoveEditorToAboveGroupAction.ID, MoveEditorToAboveGroupAction.LABEL), 'View: Move Editor into Above Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveEditorToBelowGroupAction, MoveEditorToBelowGroupAction.ID, MoveEditorToBelowGroupAction.LABEL), 'View: Move Editor into Below Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusActiveGroupAction, FocusActiveGroupAction.ID, FocusActiveGroupAction.LABEL), 'View: Focus Active Editor Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusFirstGroupAction, FocusFirstGroupAction.ID, FocusFirstGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_1 }), 'View: Focus First Editor Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusLastGroupAction, FocusLastGroupAction.ID, FocusLastGroupAction.LABEL), 'View: Focus Last Editor Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusPreviousGroup, FocusPreviousGroup.ID, FocusPreviousGroup.LABEL), 'View: Focus Previous Editor Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusNextGroup, FocusNextGroup.ID, FocusNextGroup.LABEL), 'View: Focus Next Editor Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusLeftGroup, FocusLeftGroup.ID, FocusLeftGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.LeftArrow) }), 'View: Focus Left Editor Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusRightGroup, FocusRightGroup.ID, FocusRightGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.RightArrow) }), 'View: Focus Right Editor Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusAboveGroup, FocusAboveGroup.ID, FocusAboveGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.UpArrow) }), 'View: Focus Above Editor Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusBelowGroup, FocusBelowGroup.ID, FocusBelowGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.DownArrow) }), 'View: Focus Below Editor Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NewEditorGroupLeftAction, NewEditorGroupLeftAction.ID, NewEditorGroupLeftAction.LABEL), 'View: New Editor Group to the Left', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NewEditorGroupRightAction, NewEditorGroupRightAction.ID, NewEditorGroupRightAction.LABEL), 'View: New Editor Group to the Right', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NewEditorGroupAboveAction, NewEditorGroupAboveAction.ID, NewEditorGroupAboveAction.LABEL), 'View: New Editor Group Above', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NewEditorGroupBelowAction, NewEditorGroupBelowAction.ID, NewEditorGroupBelowAction.LABEL), 'View: New Editor Group Below', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NavigateForwardAction, NavigateForwardAction.ID, NavigateForwardAction.LABEL, { primary: 0, win: { primary: KeyMod.Alt | KeyCode.RightArrow }, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_MINUS } }), 'Go Forward'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NavigateBackwardsAction, NavigateBackwardsAction.ID, NavigateBackwardsAction.LABEL, { primary: 0, win: { primary: KeyMod.Alt | KeyCode.LeftArrow }, mac: { primary: KeyMod.WinCtrl | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_MINUS } }), 'Go Back'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NavigateToLastEditLocationAction, NavigateToLastEditLocationAction.ID, NavigateToLastEditLocationAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_Q) }), 'Go to Last Edit Location'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NavigateLastAction, NavigateLastAction.ID, NavigateLastAction.LABEL), 'Go Last'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenPreviousEditorFromHistoryAction, OpenPreviousEditorFromHistoryAction.ID, OpenPreviousEditorFromHistoryAction.LABEL), 'Open Previous Editor from History'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ClearEditorHistoryAction, ClearEditorHistoryAction.ID, ClearEditorHistoryAction.LABEL), 'Clear Editor History'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(RevertAndCloseEditorAction, RevertAndCloseEditorAction.ID, RevertAndCloseEditorAction.LABEL), 'View: Revert and Close Editor', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(EditorLayoutSingleAction, EditorLayoutSingleAction.ID, EditorLayoutSingleAction.LABEL), 'View: Single Column Editor Layout', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(EditorLayoutTwoColumnsAction, EditorLayoutTwoColumnsAction.ID, EditorLayoutTwoColumnsAction.LABEL), 'View: Two Columns Editor Layout', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(EditorLayoutThreeColumnsAction, EditorLayoutThreeColumnsAction.ID, EditorLayoutThreeColumnsAction.LABEL), 'View: Three Columns Editor Layout', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(EditorLayoutTwoRowsAction, EditorLayoutTwoRowsAction.ID, EditorLayoutTwoRowsAction.LABEL), 'View: Two Rows Editor Layout', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(EditorLayoutThreeRowsAction, EditorLayoutThreeRowsAction.ID, EditorLayoutThreeRowsAction.LABEL), 'View: Three Rows Editor Layout', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(EditorLayoutTwoByTwoGridAction, EditorLayoutTwoByTwoGridAction.ID, EditorLayoutTwoByTwoGridAction.LABEL), 'View: Grid Editor Layout (2x2)', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(EditorLayoutTwoRowsRightAction, EditorLayoutTwoRowsRightAction.ID, EditorLayoutTwoRowsRightAction.LABEL), 'View: Two Rows Right Editor Layout', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoColumnsBottomAction.ID, EditorLayoutTwoColumnsBottomAction.LABEL), 'View: Two Columns Bottom Editor Layout', category); // Register Editor Picker Actions including quick navigate support const openNextEditorKeybinding = { primary: KeyMod.CtrlCmd | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyCode.Tab } }; const openPreviousEditorKeybinding = { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Tab } }; -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction.ID, OpenNextRecentlyUsedEditorInGroupAction.LABEL, openNextEditorKeybinding), 'View: Open Next Recently Used Editor in Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousRecentlyUsedEditorInGroupAction, OpenPreviousRecentlyUsedEditorInGroupAction.ID, OpenPreviousRecentlyUsedEditorInGroupAction.LABEL, openPreviousEditorKeybinding), 'View: Open Previous Recently Used Editor in Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenNextRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction.ID, OpenNextRecentlyUsedEditorInGroupAction.LABEL, openNextEditorKeybinding), 'View: Open Next Recently Used Editor in Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenPreviousRecentlyUsedEditorInGroupAction, OpenPreviousRecentlyUsedEditorInGroupAction.ID, OpenPreviousRecentlyUsedEditorInGroupAction.LABEL, openPreviousEditorKeybinding), 'View: Open Previous Recently Used Editor in Group', category); const quickOpenNavigateNextInEditorPickerId = 'workbench.action.quickOpenNavigateNextInEditorPicker'; KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -416,12 +421,12 @@ editorCommands.setup(); // Touch Bar if (isMacintosh) { MenuRegistry.appendMenuItem(MenuId.TouchBarContext, { - command: { id: NavigateBackwardsAction.ID, title: NavigateBackwardsAction.LABEL, iconLocation: { dark: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/back-tb.png')) } }, + command: { id: NavigateBackwardsAction.ID, title: NavigateBackwardsAction.LABEL, icon: { dark: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/back-tb.png')) } }, group: 'navigation' }); MenuRegistry.appendMenuItem(MenuId.TouchBarContext, { - command: { id: NavigateForwardAction.ID, title: NavigateForwardAction.LABEL, iconLocation: { dark: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/forward-tb.png')) } }, + command: { id: NavigateForwardAction.ID, title: NavigateForwardAction.LABEL, icon: { dark: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/forward-tb.png')) } }, group: 'navigation' }); } @@ -451,17 +456,15 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands. MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeAll', "Close All") }, group: '5_close', order: 10, when: ContextKeyExpr.has('config.workbench.editor.showTabs') }); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: nls.localize('closeAllSaved', "Close Saved") }, group: '5_close', order: 20, when: ContextKeyExpr.has('config.workbench.editor.showTabs') }); -interface IEditorToolItem { id: string; title: string; iconDark: URI; iconLight: URI; } +interface IEditorToolItem { id: string; title: string; icon?: { dark?: URI; light?: URI; } | ThemeIcon; } -function appendEditorToolItem(primary: IEditorToolItem, when: ContextKeyExpr | undefined, order: number, alternative?: IEditorToolItem): void { +function appendEditorToolItem(primary: IEditorToolItem, when: ContextKeyExpr | undefined, order: number, alternative?: IEditorToolItem, precondition?: ContextKeyExpr | undefined): void { const item: IMenuItem = { command: { id: primary.id, title: primary.title, - iconLocation: { - dark: primary.iconDark, - light: primary.iconLight - } + icon: primary.icon, + precondition }, group: 'navigation', when, @@ -472,36 +475,26 @@ function appendEditorToolItem(primary: IEditorToolItem, when: ContextKeyExpr | u item.alt = { id: alternative.id, title: alternative.title, - iconLocation: { - dark: alternative.iconDark, - light: alternative.iconLight - } + icon: alternative.icon }; } MenuRegistry.appendMenuItem(MenuId.EditorTitle, item); } -const SPLIT_EDITOR_HORIZONTAL_DARK_ICON = URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/split-editor-horizontal-dark.svg')); -const SPLIT_EDITOR_HORIZONTAL_LIGHT_ICON = URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/split-editor-horizontal-light.svg')); -const SPLIT_EDITOR_VERTICAL_DARK_ICON = URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/split-editor-vertical-dark.svg')); -const SPLIT_EDITOR_VERTICAL_LIGHT_ICON = URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/split-editor-vertical-light.svg')); - // Editor Title Menu: Split Editor appendEditorToolItem( { id: SplitEditorAction.ID, title: nls.localize('splitEditorRight', "Split Editor Right"), - iconDark: SPLIT_EDITOR_HORIZONTAL_DARK_ICON, - iconLight: SPLIT_EDITOR_HORIZONTAL_LIGHT_ICON + icon: { id: 'codicon/split-horizontal' } }, ContextKeyExpr.not('splitEditorsVertically'), 100000, // towards the end { id: editorCommands.SPLIT_EDITOR_DOWN, title: nls.localize('splitEditorDown', "Split Editor Down"), - iconDark: SPLIT_EDITOR_VERTICAL_DARK_ICON, - iconLight: SPLIT_EDITOR_VERTICAL_LIGHT_ICON + icon: { id: 'codicon/split-vertical' } } ); @@ -509,37 +502,30 @@ appendEditorToolItem( { id: SplitEditorAction.ID, title: nls.localize('splitEditorDown', "Split Editor Down"), - iconDark: SPLIT_EDITOR_VERTICAL_DARK_ICON, - iconLight: SPLIT_EDITOR_VERTICAL_LIGHT_ICON + icon: { id: 'codicon/split-vertical' } }, ContextKeyExpr.has('splitEditorsVertically'), 100000, // towards the end { id: editorCommands.SPLIT_EDITOR_RIGHT, title: nls.localize('splitEditorRight', "Split Editor Right"), - iconDark: SPLIT_EDITOR_HORIZONTAL_DARK_ICON, - iconLight: SPLIT_EDITOR_HORIZONTAL_LIGHT_ICON + icon: { id: 'codicon/split-horizontal' } } ); -const CLOSE_ALL_DARK_ICON = URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/close-all-dark.svg')); -const CLOSE_ALL_LIGHT_ICON = URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/close-all-light.svg')); - // Editor Title Menu: Close Group (tabs disabled) appendEditorToolItem( { id: editorCommands.CLOSE_EDITOR_COMMAND_ID, title: nls.localize('close', "Close"), - iconDark: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/close-dark-alt.svg')), - iconLight: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/close-light-alt.svg')) + icon: { id: 'codicon/close' } }, ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ContextKeyExpr.not('groupActiveEditorDirty')), 1000000, // towards the far end { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeAll', "Close All"), - iconDark: CLOSE_ALL_DARK_ICON, - iconLight: CLOSE_ALL_LIGHT_ICON + icon: { id: 'codicon/close-all' } } ); @@ -547,16 +533,14 @@ appendEditorToolItem( { id: editorCommands.CLOSE_EDITOR_COMMAND_ID, title: nls.localize('close', "Close"), - iconDark: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/close-dirty-dark-alt.svg')), - iconLight: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/close-dirty-light-alt.svg')) + icon: { id: 'codicon/close-dirty' } }, ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ContextKeyExpr.has('groupActiveEditorDirty')), 1000000, // towards the far end { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeAll', "Close All"), - iconDark: CLOSE_ALL_DARK_ICON, - iconLight: CLOSE_ALL_LIGHT_ICON + icon: { id: 'codicon/close-all' } } ); @@ -565,8 +549,7 @@ appendEditorToolItem( { id: editorCommands.GOTO_PREVIOUS_CHANGE, title: nls.localize('navigate.prev.label', "Previous Change"), - iconDark: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/previous-diff-dark.svg')), - iconLight: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/previous-diff-light.svg')) + icon: { id: 'codicon/arrow-up' } }, TextCompareEditorActiveContext, 10 @@ -577,8 +560,7 @@ appendEditorToolItem( { id: editorCommands.GOTO_NEXT_CHANGE, title: nls.localize('navigate.next.label', "Next Change"), - iconDark: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/next-diff-dark.svg')), - iconLight: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/next-diff-light.svg')) + icon: { id: 'codicon/arrow-down' } }, TextCompareEditorActiveContext, 11 @@ -589,8 +571,7 @@ appendEditorToolItem( { id: editorCommands.TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE, title: nls.localize('ignoreTrimWhitespace.label', "Ignore Leading/Trailing Whitespace Differences"), - iconDark: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/paragraph-dark.svg')), - iconLight: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/paragraph-light.svg')) + icon: { id: 'codicon/whitespace' } }, ContextKeyExpr.and(TextCompareEditorActiveContext, ContextKeyExpr.notEquals('config.diffEditor.ignoreTrimWhitespace', true)), 20 @@ -601,8 +582,7 @@ appendEditorToolItem( { id: editorCommands.TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE, title: nls.localize('showTrimWhitespace.label', "Show Leading/Trailing Whitespace Differences"), - iconDark: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/paragraph-disabled-dark.svg')), - iconLight: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/paragraph-disabled-light.svg')) + icon: { id: 'codicon/whitespace~disabled' } }, ContextKeyExpr.and(TextCompareEditorActiveContext, ContextKeyExpr.notEquals('config.diffEditor.ignoreTrimWhitespace', false)), 20 diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 6bd5a275e2..9823fada24 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -5,7 +5,7 @@ import { GroupIdentifier, IWorkbenchEditorConfiguration, EditorOptions, TextEditorOptions, IEditorInput, IEditorIdentifier, IEditorCloseEvent, IEditor, IEditorPartOptions } from 'vs/workbench/common/editor'; import { EditorGroup } from 'vs/workbench/common/editor/editorGroup'; -import { IEditorGroup, GroupDirection, IAddGroupOptions, IMergeGroupOptions, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroup, GroupDirection, IAddGroupOptions, IMergeGroupOptions, GroupsOrder, GroupsArrangement } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Dimension } from 'vs/base/browser/dom'; import { Event } from 'vs/base/common/event'; @@ -104,6 +104,8 @@ export interface IEditorGroupsAccessor { copyGroup(group: IEditorGroupView | GroupIdentifier, location: IEditorGroupView | GroupIdentifier, direction: GroupDirection): IEditorGroupView; removeGroup(group: IEditorGroupView | GroupIdentifier): void; + + arrangeGroups(arrangement: GroupsArrangement, target?: IEditorGroupView | GroupIdentifier): void; } export interface IEditorGroupView extends IDisposable, ISerializableView, IEditorGroup { diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 7d3f4e0f6d..200e87d969 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import { mixin } from 'vs/base/common/objects'; -import { IEditorInput, EditorInput, IEditorIdentifier, ConfirmResult, IEditorCommandsContext, CloseDirection } from 'vs/workbench/common/editor'; +import { IEditorInput, EditorInput, IEditorIdentifier, IEditorCommandsContext, CloseDirection, SaveReason } from 'vs/workbench/common/editor'; import { QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel'; import { EditorQuickOpenEntry, EditorQuickOpenEntryGroup, IEditorQuickOpenEntry, QuickOpenAction } from 'vs/workbench/browser/quickopen'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; @@ -15,13 +15,15 @@ import { IResourceInput } from 'vs/platform/editor/common/editor'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { CLOSE_EDITOR_COMMAND_ID, NAVIGATE_ALL_EDITORS_GROUP_PREFIX, MOVE_ACTIVE_EDITOR_COMMAND_ID, NAVIGATE_IN_ACTIVE_GROUP_PREFIX, ActiveEditorMoveArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, mergeAllGroups } from 'vs/workbench/browser/parts/editor/editorCommands'; import { IEditorGroupsService, IEditorGroup, GroupsArrangement, EditorsOrder, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; +import { IFileDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { ResourceMap, values } from 'vs/base/common/map'; export class ExecuteCommandAction extends Action { @@ -416,7 +418,7 @@ export class OpenToSideFromQuickOpenAction extends Action { updateClass(): void { const preferredDirection = preferredSideBySideGroupDirection(this.configurationService); - this.class = (preferredDirection === GroupDirection.RIGHT) ? 'quick-open-sidebyside-vertical' : 'quick-open-sidebyside-horizontal'; + this.class = (preferredDirection === GroupDirection.RIGHT) ? 'codicon-split-horizontal' : 'codicon-split-vertical'; } run(context: any): Promise { @@ -425,7 +427,7 @@ export class OpenToSideFromQuickOpenAction extends Action { const input = entry.getInput(); if (input) { if (input instanceof EditorInput) { - return this.editorService.openEditor(input, entry.getOptions() || undefined, SIDE_GROUP); + return this.editorService.openEditor(input, entry.getOptions(), SIDE_GROUP); } const resourceInput = input as IResourceInput; @@ -505,7 +507,7 @@ export class CloseOneEditorAction extends Action { // Close specific editor in group if (typeof editorIndex === 'number') { - const editorAtIndex = group.getEditor(editorIndex); + const editorAtIndex = group.getEditorByIndex(editorIndex); if (editorAtIndex) { return group.closeEditor(editorAtIndex); } @@ -596,8 +598,10 @@ export abstract class BaseCloseAllAction extends Action { id: string, label: string, clazz: string | undefined, - private textFileService: ITextFileService, - protected editorGroupService: IEditorGroupsService + private workingCopyService: IWorkingCopyService, + private fileDialogService: IFileDialogService, + protected editorGroupService: IEditorGroupsService, + private editorService: IEditorService ) { super(id, label, clazz); } @@ -619,7 +623,7 @@ export abstract class BaseCloseAllAction extends Action { async run(): Promise { // Just close all if there are no dirty editors - if (!this.textFileService.isDirty()) { + if (!this.workingCopyService.hasDirty) { return this.doCloseAll(); } @@ -636,18 +640,32 @@ export abstract class BaseCloseAllAction extends Action { return undefined; })); - const confirm = await this.textFileService.confirmSave(); + const dirtyEditorsToConfirmByName = new Set(); + const dirtyEditorsToConfirmByResource = new ResourceMap(); + + for (const editor of this.editorService.editors) { + if (!editor.isDirty()) { + continue; // only interested in dirty editors + } + + const resource = editor.getResource(); + if (resource) { + dirtyEditorsToConfirmByResource.set(resource, true); + } else { + dirtyEditorsToConfirmByName.add(editor.getName()); + } + } + + const confirm = await this.fileDialogService.showSaveConfirm([...dirtyEditorsToConfirmByResource.keys(), ...values(dirtyEditorsToConfirmByName)]); if (confirm === ConfirmResult.CANCEL) { return; } let saveOrRevert: boolean; if (confirm === ConfirmResult.DONT_SAVE) { - await this.textFileService.revertAll(undefined, { soft: true }); - saveOrRevert = true; + saveOrRevert = await this.editorService.revertAll({ soft: true, includeUntitled: true }); } else { - const res = await this.textFileService.saveAll(true); - saveOrRevert = res.results.every(r => !!r.success); + saveOrRevert = await this.editorService.saveAll({ reason: SaveReason.EXPLICIT, includeUntitled: true }); } if (saveOrRevert) { @@ -666,10 +684,12 @@ export class CloseAllEditorsAction extends BaseCloseAllAction { constructor( id: string, label: string, - @ITextFileService textFileService: ITextFileService, - @IEditorGroupsService editorGroupService: IEditorGroupsService + @IWorkingCopyService workingCopyService: IWorkingCopyService, + @IFileDialogService fileDialogService: IFileDialogService, + @IEditorGroupsService editorGroupService: IEditorGroupsService, + @IEditorService editorService: IEditorService ) { - super(id, label, 'codicon-close-all', textFileService, editorGroupService); + super(id, label, 'codicon-close-all', workingCopyService, fileDialogService, editorGroupService, editorService); } protected doCloseAll(): Promise { @@ -685,10 +705,12 @@ export class CloseAllEditorGroupsAction extends BaseCloseAllAction { constructor( id: string, label: string, - @ITextFileService textFileService: ITextFileService, - @IEditorGroupsService editorGroupService: IEditorGroupsService + @IWorkingCopyService workingCopyService: IWorkingCopyService, + @IFileDialogService fileDialogService: IFileDialogService, + @IEditorGroupsService editorGroupService: IEditorGroupsService, + @IEditorService editorService: IEditorService ) { - super(id, label, undefined, textFileService, editorGroupService); + super(id, label, undefined, workingCopyService, fileDialogService, editorGroupService, editorService); } protected async doCloseAll(): Promise { @@ -1274,8 +1296,6 @@ export class BaseQuickOpenEditorInGroupAction extends Action { run(): Promise { const keys = this.keybindingService.lookupKeybindings(this.id); - - this.quickOpenService.show(NAVIGATE_IN_ACTIVE_GROUP_PREFIX, { quickNavigateConfiguration: { keybindings: keys } }); return Promise.resolve(true); diff --git a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts new file mode 100644 index 0000000000..65ba4b9979 --- /dev/null +++ b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts @@ -0,0 +1,86 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { SaveReason, IEditorIdentifier, IEditorInput, GroupIdentifier } from 'vs/workbench/common/editor'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { withNullAsUndefined } from 'vs/base/common/types'; + +export class EditorAutoSave extends Disposable implements IWorkbenchContribution { + + private lastActiveEditor: IEditorInput | undefined = undefined; + private lastActiveGroupId: GroupIdentifier | undefined = undefined; + private lastActiveEditorControlDisposable = this._register(new DisposableStore()); + + constructor( + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, + @IHostService private readonly hostService: IHostService, + @IEditorService private readonly editorService: IEditorService, + @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService + ) { + super(); + + this.registerListeners(); + } + + private registerListeners(): void { + this._register(this.hostService.onDidChangeFocus(focused => this.onWindowFocusChange(focused))); + this._register(this.editorService.onDidActiveEditorChange(() => this.onDidActiveEditorChange())); + } + + private onWindowFocusChange(focused: boolean): void { + if (!focused) { + this.maybeTriggerAutoSave(SaveReason.WINDOW_CHANGE); + } + } + + private onDidActiveEditorChange(): void { + + // Treat editor change like a focus change for our last active editor if any + if (this.lastActiveEditor && typeof this.lastActiveGroupId === 'number') { + this.maybeTriggerAutoSave(SaveReason.FOCUS_CHANGE, { groupId: this.lastActiveGroupId, editor: this.lastActiveEditor }); + } + + // Remember as last active + const activeGroup = this.editorGroupService.activeGroup; + const activeEditor = this.lastActiveEditor = withNullAsUndefined(activeGroup.activeEditor); + this.lastActiveGroupId = activeGroup.id; + + // Dispose previous active control listeners + this.lastActiveEditorControlDisposable.clear(); + + // Listen to focus changes on control for auto save + const activeEditorControl = this.editorService.activeControl; + if (activeEditor && activeEditorControl) { + this.lastActiveEditorControlDisposable.add(activeEditorControl.onDidBlur(() => { + this.maybeTriggerAutoSave(SaveReason.FOCUS_CHANGE, { groupId: activeGroup.id, editor: activeEditor }); + })); + } + } + + private maybeTriggerAutoSave(reason: SaveReason, editorIdentifier?: IEditorIdentifier): void { + if (editorIdentifier && (editorIdentifier.editor.isReadonly() || editorIdentifier.editor.isUntitled())) { + return; // no auto save for readonly or untitled editors + } + + // Determine if we need to save all. In case of a window focus change we also save if  + // auto save mode is configured to be ON_FOCUS_CHANGE (editor focus change) + const mode = this.filesConfigurationService.getAutoSaveMode(); + if ( + (reason === SaveReason.WINDOW_CHANGE && (mode === AutoSaveMode.ON_FOCUS_CHANGE || mode === AutoSaveMode.ON_WINDOW_CHANGE)) || + (reason === SaveReason.FOCUS_CHANGE && mode === AutoSaveMode.ON_FOCUS_CHANGE) + ) { + if (editorIdentifier) { + this.editorService.save(editorIdentifier, { reason }); + } else { + this.editorService.saveAll({ reason }); + } + } + } +} diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 70736c4cee..7378ebe74d 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -318,7 +318,7 @@ function registerOpenEditorAtIndexCommands(): void { const editorService = accessor.get(IEditorService); const activeControl = editorService.activeControl; if (activeControl) { - const editor = activeControl.group.getEditor(editorIndex); + const editor = activeControl.group.getEditorByIndex(editorIndex); if (editor) { editorService.openEditor(editor); } @@ -448,7 +448,7 @@ export function splitEditor(editorGroupService: IEditorGroupsService, direction: // Split editor (if it can be split) let editorToCopy: IEditorInput | undefined; if (context && typeof context.editorIndex === 'number') { - editorToCopy = sourceGroup.getEditor(context.editorIndex); + editorToCopy = sourceGroup.getEditorByIndex(context.editorIndex); } else { editorToCopy = types.withNullAsUndefined(sourceGroup.activeEditor); } @@ -548,7 +548,7 @@ function registerCloseEditorCommands() { if (group) { const editors = coalesce(contexts .filter(context => context.groupId === groupId) - .map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor)); + .map(context => typeof context.editorIndex === 'number' ? group.getEditorByIndex(context.editorIndex) : group.activeEditor)); return group.closeEditors(editors); } @@ -603,7 +603,7 @@ function registerCloseEditorCommands() { if (group) { const editors = contexts .filter(context => context.groupId === groupId) - .map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor); + .map(context => typeof context.editorIndex === 'number' ? group.getEditorByIndex(context.editorIndex) : group.activeEditor); const editorsToClose = group.editors.filter(e => editors.indexOf(e) === -1); if (group.activeEditor) { @@ -715,7 +715,7 @@ function resolveCommandsContext(editorGroupService: IEditorGroupsService, contex // Resolve from context let group = context && typeof context.groupId === 'number' ? editorGroupService.getGroup(context.groupId) : undefined; - let editor = group && context && typeof context.editorIndex === 'number' ? types.withNullAsUndefined(group.getEditor(context.editorIndex)) : undefined; + let editor = group && context && typeof context.editorIndex === 'number' ? types.withNullAsUndefined(group.getEditorByIndex(context.editorIndex)) : undefined; let control = group ? group.activeControl : undefined; // Fallback to active group as needed diff --git a/src/vs/workbench/browser/parts/editor/editorControl.ts b/src/vs/workbench/browser/parts/editor/editorControl.ts index ce8fd54436..daa1290452 100644 --- a/src/vs/workbench/browser/parts/editor/editorControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorControl.ts @@ -208,6 +208,7 @@ export class EditorControl extends Disposable { if (controlInstanceContainer) { this.parent.removeChild(controlInstanceContainer); hide(controlInstanceContainer); + this._activeControl.onHide(); } // Indicate to editor control diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index 183f21d822..5e8782e9bb 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -4,19 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/editordroptarget'; -import { LocalSelectionTransfer, DraggedEditorIdentifier, ResourcesDropHandler, DraggedEditorGroupIdentifier, DragAndDropObserver } from 'vs/workbench/browser/dnd'; +import { LocalSelectionTransfer, DraggedEditorIdentifier, ResourcesDropHandler, DraggedEditorGroupIdentifier, DragAndDropObserver, containsDragType } from 'vs/workbench/browser/dnd'; import { addDisposableListener, EventType, EventHelper, isAncestor, toggleClass, addClass, removeClass } from 'vs/base/browser/dom'; import { IEditorGroupsAccessor, EDITOR_TITLE_HEIGHT, IEditorGroupView, getActiveTextEditorOptions } from 'vs/workbench/browser/parts/editor/editor'; import { EDITOR_DRAG_AND_DROP_BACKGROUND, Themable } from 'vs/workbench/common/theme'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { IEditorIdentifier, EditorInput, EditorOptions } from 'vs/workbench/common/editor'; -import { isMacintosh } from 'vs/base/common/platform'; +import { isMacintosh, isWeb } from 'vs/base/common/platform'; import { GroupDirection, MergeGroupMode } from 'vs/workbench/services/editor/common/editorGroupsService'; import { toDisposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { RunOnceScheduler } from 'vs/base/common/async'; import { find } from 'vs/base/common/arrays'; +import { DataTransfers } from 'vs/base/browser/dnd'; interface IDropOperation { splitDirection?: GroupDirection; @@ -99,6 +100,10 @@ class DropOverlay extends Themable { this._register(new DragAndDropObserver(this.container, { onDragEnter: e => undefined, onDragOver: e => { + if (isWeb && containsDragType(e, DataTransfers.FILES)) { + return; // dropping files into editor is unsupported on web + } + const isDraggingGroup = this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype); const isDraggingEditor = this.editorTransfer.hasData(DraggedEditorIdentifier.prototype); diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 0ca8166512..f6f62a016b 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/editorgroupview'; import { EditorGroup, IEditorOpenOptions, EditorCloseEvent, ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup'; -import { EditorInput, EditorOptions, GroupIdentifier, ConfirmResult, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditor, EditorGroupEditorsCountContext } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditor, EditorGroupEditorsCountContext, toResource, SideBySideEditor, SaveReason } from 'vs/workbench/common/editor'; import { Event, Emitter, Relay } from 'vs/base/common/event'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { addClass, addClasses, Dimension, trackFocus, toggleClass, removeClass, addDisposableListener, EventType, EventHelper, findParentWithClass, clearNode, isAncestor } from 'vs/base/browser/dom'; @@ -21,7 +21,7 @@ import { IMoveEditorOptions, ICopyEditorOptions, ICloseEditorsFilter, IGroupChan import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleControl'; import { EditorControl } from 'vs/workbench/browser/parts/editor/editorControl'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; -import { EditorProgressService } from 'vs/workbench/services/progress/browser/editorProgressService'; +import { EditorProgressIndicator } from 'vs/workbench/services/progress/browser/progressIndicator'; import { localize } from 'vs/nls'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { dispose, MutableDisposable } from 'vs/base/common/lifecycle'; @@ -32,7 +32,7 @@ import { 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, IEditorPartOptionsChangeEvent, getActiveTextEditorOptions, IEditorOpeningEvent } from 'vs/workbench/browser/parts/editor/editor'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ActionRunner, IAction, Action } from 'vs/base/common/actions'; @@ -50,7 +50,8 @@ import { guessMimeTypes } from 'vs/base/common/mime'; import { extname } from 'vs/base/common/resources'; import { Schemas } from 'vs/base/common/network'; import { EditorActivation, EditorOpenContext } from 'vs/platform/editor/common/editor'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService, IFileDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; +import { ILogService } from 'vs/platform/log/common/log'; export class EditorGroupView extends Themable implements IEditorGroupView { @@ -128,10 +129,12 @@ export class EditorGroupView extends Themable implements IEditorGroupView { @INotificationService private readonly notificationService: INotificationService, @IDialogService private readonly dialogService: IDialogService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService, @IKeybindingService private readonly keybindingService: IKeybindingService, @IMenuService private readonly menuService: IMenuService, - @IContextMenuService private readonly contextMenuService: IContextMenuService + @IContextMenuService private readonly contextMenuService: IContextMenuService, + @IFileDialogService private readonly fileDialogService: IFileDialogService, + @ILogService private readonly logService: ILogService ) { super(themeService); @@ -173,7 +176,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { const scopedContextKeyService = this._register(this.contextKeyService.createScoped(this.element)); this.scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection( [IContextKeyService, scopedContextKeyService], - [IEditorProgressService, new EditorProgressService(this.progressBar)] + [IEditorProgressService, this._register(new EditorProgressIndicator(this.progressBar, this))] )); // Context keys @@ -255,7 +258,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { if (this.isEmpty) { EventHelper.stop(e); - this.openEditor(this.untitledEditorService.createOrGet(), EditorOptions.create({ pinned: true })); + this.openEditor(this.untitledTextEditorService.createOrGet(), EditorOptions.create({ pinned: true })); } })); @@ -517,11 +520,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView { editorsToClose.push(editor.master, editor.details); } - // Close the editor when it is no longer open in any group including diff editors + // Dispose the editor when it is no longer open in any group including diff editors editorsToClose.forEach(editorToClose => { - const resource = editorToClose ? editorToClose.getResource() : undefined; // prefer resource to not close right-hand side editors of a diff editor - if (!this.accessor.groups.some(groupView => groupView.group.contains(resource || editorToClose))) { - editorToClose.close(); + if (!this.accessor.groups.some(groupView => groupView.group.contains(editorToClose, true /* include side by side editor master & details */))) { + editorToClose.dispose(); } }); @@ -760,8 +762,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return this.editors; } - getEditor(index: number): EditorInput | undefined { - return this._group.getEditor(index); + getEditorByIndex(index: number): EditorInput | undefined { + return this._group.getEditorByIndex(index); } getIndexOfEditor(editor: EditorInput): number { @@ -922,64 +924,74 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private async doHandleOpenEditorError(error: Error, editor: EditorInput, options?: EditorOptions): Promise { - // Report error only if this was not us restoring previous error state or - // we are told to ignore errors that occur from opening an editor - if (this.isRestored && !isPromiseCanceledError(error) && (!options || !options.ignoreError)) { + // Report error only if we are not told to ignore errors that occur from opening an editor + if (!isPromiseCanceledError(error) && (!options || !options.ignoreError)) { - // Extract possible error actions from the error - let errorActions: ReadonlyArray | undefined = undefined; - if (isErrorWithActions(error)) { - errorActions = (error as IErrorWithActions).actions; - } + // Since it is more likely that errors fail to open when restoring them e.g. + // because files got deleted or moved meanwhile, we do not show any notifications + // if we are still restoring editors. + if (this.isRestored) { - // If the context is USER, we try to show a modal dialog instead of a background notification - if (options?.context === EditorOpenContext.USER) { - const buttons: string[] = []; - if (Array.isArray(errorActions) && errorActions.length > 0) { - errorActions.forEach(action => buttons.push(action.label)); - } else { - buttons.push(localize('ok', 'OK')); + // Extract possible error actions from the error + let errorActions: ReadonlyArray | undefined = undefined; + if (isErrorWithActions(error)) { + errorActions = (error as IErrorWithActions).actions; } - let cancelId: number | undefined = undefined; - if (buttons.length === 1) { - buttons.push(localize('cancel', "Cancel")); - cancelId = 1; + // If the context is USER, we try to show a modal dialog instead of a background notification + if (options?.context === EditorOpenContext.USER) { + const buttons: string[] = []; + if (Array.isArray(errorActions) && errorActions.length > 0) { + errorActions.forEach(action => buttons.push(action.label)); + } else { + buttons.push(localize('ok', 'OK')); + } + + let cancelId: number | undefined = undefined; + if (buttons.length === 1) { + buttons.push(localize('cancel', "Cancel")); + cancelId = 1; + } + + const result = await this.dialogService.show( + Severity.Error, + localize('editorOpenErrorDialog', "Unable to open '{0}'", editor.getName()), + buttons, + { + detail: toErrorMessage(error), + cancelId + } + ); + + // Make sure to run any error action if present + if (result.choice !== cancelId && Array.isArray(errorActions)) { + const errorAction = errorActions[result.choice]; + if (errorAction) { + errorAction.run(); + } + } } - const result = await this.dialogService.show( - Severity.Error, - localize('editorOpenErrorDialog', "Unable to open '{0}'", editor.getName()), - buttons, - { - detail: toErrorMessage(error), - cancelId + // Otherwise, show a background notification. + else { + const actions: INotificationActions = { primary: [] }; + if (Array.isArray(errorActions)) { + actions.primary = errorActions; } - ); - // Make sure to run any error action if present - if (result.choice !== cancelId && Array.isArray(errorActions)) { - const errorAction = errorActions[result.choice]; - if (errorAction) { - errorAction.run(); - } + const handle = this.notificationService.notify({ + severity: Severity.Error, + message: localize('editorOpenError', "Unable to open '{0}': {1}.", editor.getName(), toErrorMessage(error)), + actions + }); + + Event.once(handle.onDidClose)(() => actions.primary && dispose(actions.primary)); } } - // Otherwise, show a background notification. + // Restoring: just log errors to console else { - const actions: INotificationActions = { primary: [] }; - if (Array.isArray(errorActions)) { - actions.primary = errorActions; - } - - const handle = this.notificationService.notify({ - severity: Severity.Error, - message: localize('editorOpenError', "Unable to open '{0}': {1}.", editor.getName(), toErrorMessage(error)), - actions - }); - - Event.once(handle.onDidClose)(() => actions.primary && dispose(actions.primary)); + this.logService.error(error); } } @@ -1110,7 +1122,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } // Check for dirty and veto - const veto = await this.handleDirty([editor]); + const veto = await this.handleDirtyClosing([editor]); if (veto) { return; } @@ -1221,7 +1233,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this._group.closeEditor(editor); } - private async handleDirty(editors: EditorInput[]): Promise { + private async handleDirtyClosing(editors: EditorInput[]): Promise { if (!editors.length) { return false; // no veto } @@ -1230,13 +1242,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // To prevent multiple confirmation dialogs from showing up one after the other // we check if a pending confirmation is currently showing and if so, join that - let handleDirtyPromise = this.mapEditorToPendingConfirmation.get(editor); - if (!handleDirtyPromise) { - handleDirtyPromise = this.doHandleDirty(editor); - this.mapEditorToPendingConfirmation.set(editor, handleDirtyPromise); + let handleDirtyClosingPromise = this.mapEditorToPendingConfirmation.get(editor); + if (!handleDirtyClosingPromise) { + handleDirtyClosingPromise = this.doHandleDirtyClosing(editor); + this.mapEditorToPendingConfirmation.set(editor, handleDirtyClosingPromise); } - const veto = await handleDirtyPromise; + const veto = await handleDirtyClosingPromise; // Make sure to remove from our map of cached pending confirmations this.mapEditorToPendingConfirmation.delete(editor); @@ -1247,22 +1259,47 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } // Otherwise continue with the remainders - return this.handleDirty(editors); + return this.handleDirtyClosing(editors); } - private async doHandleDirty(editor: EditorInput): Promise { - if ( - !editor.isDirty() || // editor must be dirty - this.accessor.groups.some(groupView => groupView !== this && groupView.group.contains(editor, true /* support side by side */)) || // editor is opened in other group - editor instanceof SideBySideEditorInput && this.isOpened(editor.master) // side by side editor master is still opened - ) { + private async doHandleDirtyClosing(editor: EditorInput): Promise { + if (!editor.isDirty()) { + return false; // editor must be dirty + } + + if (editor instanceof SideBySideEditorInput && this.isOpened(editor.master)) { + return false; // master-side of editor is still opened somewhere else + } + + // Note: we explicitly decide to ask for confirm if closing a normal editor even + // if it is opened in a side-by-side editor in the group. This decision is made + // because it may be less obvious that one side of a side by side editor is dirty + // and can still be changed. + + if (this.accessor.groups.some(groupView => { + if (groupView === this) { + return false; // skip this group to avoid false assumptions about the editor being opened still + } + + const otherGroup = groupView.group; + if (otherGroup.contains(editor)) { + return true; // exact editor still opened + } + + if (editor instanceof SideBySideEditorInput && otherGroup.contains(editor.master)) { + return true; // master side of side by side editor still opened + } + return false; + })) { + return false; // editor is still editable somewhere else } // Switch to editor that we want to handle and confirm to save/revert await this.openEditor(editor); - const res = await editor.confirmSave(); + const editorResource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); + const res = await this.fileDialogService.showSaveConfirm(editorResource ? [editorResource] : [editor.getName()]); // It could be that the editor saved meanwhile, so we check again // to see if anything needs to happen before closing for good. @@ -1275,7 +1312,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Otherwise, handle accordingly switch (res) { case ConfirmResult.SAVE: - const result = await editor.save(); + const result = await editor.save(this._group.id, { reason: SaveReason.EXPLICIT }); return !result; case ConfirmResult.DONT_SAVE: @@ -1312,7 +1349,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { const editors = this.getEditorsToClose(args); // Check for dirty and veto - const veto = await this.handleDirty(editors.slice(0)); + const veto = await this.handleDirtyClosing(editors.slice(0)); if (veto) { return; } @@ -1391,7 +1428,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Check for dirty and veto const editors = this._group.getEditors(true); - const veto = await this.handleDirty(editors.slice(0)); + const veto = await this.handleDirtyClosing(editors.slice(0)); if (veto) { return; } diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 920f4ba7e5..986000f6bf 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -89,26 +89,26 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro //#region Events - private readonly _onDidLayout: Emitter = this._register(new Emitter()); - readonly onDidLayout: Event = this._onDidLayout.event; + private readonly _onDidLayout = this._register(new Emitter()); + readonly onDidLayout = this._onDidLayout.event; - private readonly _onDidActiveGroupChange: Emitter = this._register(new Emitter()); - readonly onDidActiveGroupChange: Event = this._onDidActiveGroupChange.event; + private readonly _onDidActiveGroupChange = this._register(new Emitter()); + readonly onDidActiveGroupChange = this._onDidActiveGroupChange.event; - private readonly _onDidGroupIndexChange: Emitter = this._register(new Emitter()); - readonly onDidGroupIndexChange: Event = this._onDidGroupIndexChange.event; + private readonly _onDidGroupIndexChange = this._register(new Emitter()); + readonly onDidGroupIndexChange = this._onDidGroupIndexChange.event; - private readonly _onDidActivateGroup: Emitter = this._register(new Emitter()); - readonly onDidActivateGroup: Event = this._onDidActivateGroup.event; + private readonly _onDidActivateGroup = this._register(new Emitter()); + readonly onDidActivateGroup = this._onDidActivateGroup.event; - private readonly _onDidAddGroup: Emitter = this._register(new Emitter()); - readonly onDidAddGroup: Event = this._onDidAddGroup.event; + private readonly _onDidAddGroup = this._register(new Emitter()); + readonly onDidAddGroup = this._onDidAddGroup.event; - private readonly _onDidRemoveGroup: Emitter = this._register(new Emitter()); - readonly onDidRemoveGroup: Event = this._onDidRemoveGroup.event; + private readonly _onDidRemoveGroup = this._register(new Emitter()); + readonly onDidRemoveGroup = this._onDidRemoveGroup.event; - private readonly _onDidMoveGroup: Emitter = this._register(new Emitter()); - readonly onDidMoveGroup: Event = this._onDidMoveGroup.event; + private readonly _onDidMoveGroup = this._register(new Emitter()); + readonly onDidMoveGroup = this._onDidMoveGroup.event; private onDidSetGridWidget = this._register(new Emitter<{ width: number; height: number; } | undefined>()); private _onDidSizeConstraintsChange = this._register(new Relay<{ width: number; height: number; } | undefined>()); @@ -139,7 +139,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro @IThemeService themeService: IThemeService, @IConfigurationService private readonly configurationService: IConfigurationService, @IStorageService storageService: IStorageService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService ) { super(Parts.EDITOR_PART, { hasTitle: false }, themeService, storageService, layoutService); diff --git a/src/vs/workbench/browser/parts/editor/editorPicker.ts b/src/vs/workbench/browser/parts/editor/editorPicker.ts index b50c12c65f..8afb0f7343 100644 --- a/src/vs/workbench/browser/parts/editor/editorPicker.ts +++ b/src/vs/workbench/browser/parts/editor/editorPicker.ts @@ -18,7 +18,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { toResource, SideBySideEditor, IEditorInput } from 'vs/workbench/common/editor'; import { compareItemsByScore, scoreItem, ScorerCache, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { withNullAsUndefined } from 'vs/base/common/types'; export class EditorPickerEntry extends QuickOpenEntryGroup { @@ -38,8 +37,8 @@ export class EditorPickerEntry extends QuickOpenEntryGroup { }; } - getLabel() { - return withNullAsUndefined(this.editor.getName()); + getLabel(): string { + return this.editor.getName(); } getIcon(): string { diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 6eae0cad11..0e4130d1e1 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -6,16 +6,16 @@ import 'vs/css!./media/editorstatus'; import * as nls from 'vs/nls'; import { runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; -import { format } from 'vs/base/common/strings'; +import { format, compare } from 'vs/base/common/strings'; import { extname, basename, isEqual } from 'vs/base/common/resources'; import { areFunctions, withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { Action } from 'vs/base/common/actions'; import { Language } from 'vs/base/common/platform'; -import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput, IEditor as IBaseEditor, IEditorInput, SideBySideEditor, IModeSupport } from 'vs/workbench/common/editor'; import { Disposable, MutableDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IEditorAction } from 'vs/editor/common/editorCommon'; import { EndOfLineSequence } from 'vs/editor/common/model'; import { IModelLanguageChangedEvent, IModelOptionsChangedEvent } from 'vs/editor/common/model/textModelEvents'; @@ -50,6 +50,10 @@ import { Event } from 'vs/base/common/event'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar'; +import { IMarker, IMarkerService, MarkerSeverity, IMarkerData } from 'vs/platform/markers/common/markers'; +import { find } from 'vs/base/common/arrays'; + +// {{SQL CARBON EDIT}} import { setMode } from 'sql/workbench/browser/parts/editor/editorStatusModeSelect'; // {{SQL CARBON EDIT}} class SideBySideEditorEncodingSupport implements IEncodingSupport { @@ -74,8 +78,8 @@ class SideBySideEditorModeSupport implements IModeSupport { function toEditorWithEncodingSupport(input: IEditorInput): IEncodingSupport | null { - // Untitled Editor - if (input instanceof UntitledEditorInput) { + // Untitled Text Editor + if (input instanceof UntitledTextEditorInput) { return input; } @@ -103,8 +107,8 @@ function toEditorWithEncodingSupport(input: IEditorInput): IEncodingSupport | nu function toEditorWithModeSupport(input: IEditorInput): IModeSupport | null { - // Untitled Editor - if (input instanceof UntitledEditorInput) { + // Untitled Text Editor + if (input instanceof UntitledTextEditorInput) { return input; } @@ -283,6 +287,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { private readonly eolElement = this._register(new MutableDisposable()); private readonly modeElement = this._register(new MutableDisposable()); private readonly metadataElement = this._register(new MutableDisposable()); + private readonly currentProblemStatus: ShowCurrentMarkerInStatusbarContribution = this._register(this.instantiationService.createInstance(ShowCurrentMarkerInStatusbarContribution)); private readonly state = new State(); private readonly activeEditorListeners = this._register(new DisposableStore()); @@ -294,13 +299,14 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { constructor( @IEditorService private readonly editorService: IEditorService, @IQuickInputService private readonly quickInputService: IQuickInputService, - @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService, @IModeService private readonly modeService: IModeService, @ITextFileService private readonly textFileService: ITextFileService, @IConfigurationService private readonly configurationService: IConfigurationService, @INotificationService private readonly notificationService: INotificationService, @IAccessibilityService private readonly accessibilityService: IAccessibilityService, - @IStatusbarService private readonly statusbarService: IStatusbarService + @IStatusbarService private readonly statusbarService: IStatusbarService, + @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(); @@ -310,7 +316,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { private registerListeners(): void { this._register(this.editorService.onDidActiveEditorChange(() => this.updateStatusBar())); - this._register(this.untitledEditorService.onDidChangeEncoding(r => this.onResourceEncodingChange(r))); + this._register(this.untitledTextEditorService.onDidChangeEncoding(r => this.onResourceEncodingChange(r))); this._register(this.textFileService.models.onModelEncodingChanged(e => this.onResourceEncodingChange((e.resource)))); this._register(TabFocus.onDidChangeTabFocus(e => this.onTabFocusModeChange())); } @@ -578,6 +584,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { this.onEncodingChange(activeControl, activeCodeEditor); this.onIndentationChange(activeCodeEditor); this.onMetadataChange(activeControl); + this.currentProblemStatus.update(activeCodeEditor); // Dispose old active editor listeners this.activeEditorListeners.clear(); @@ -595,6 +602,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { // Hook Listener for Selection changes this.activeEditorListeners.add(activeCodeEditor.onDidChangeCursorPosition((event: ICursorPositionChangedEvent) => { this.onSelectionChange(activeCodeEditor); + this.currentProblemStatus.update(activeCodeEditor); })); // Hook Listener for mode changes @@ -605,6 +613,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { // Hook Listener for content changes this.activeEditorListeners.add(activeCodeEditor.onDidChangeModelContent((e) => { this.onEOLChange(activeCodeEditor); + this.currentProblemStatus.update(activeCodeEditor); const selections = activeCodeEditor.getSelections(); if (selections) { @@ -660,7 +669,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { const textModel = editorWidget.getModel(); if (textModel) { const modeId = textModel.getLanguageIdentifier().language; - info = { mode: this.modeService.getLanguageName(modeId) || undefined }; + info = { mode: withNullAsUndefined(this.modeService.getLanguageName(modeId)) }; } } @@ -825,6 +834,130 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { } } +class ShowCurrentMarkerInStatusbarContribution extends Disposable { + + private readonly statusBarEntryAccessor: MutableDisposable; + private editor: ICodeEditor | undefined = undefined; + private markers: IMarker[] = []; + private currentMarker: IMarker | null = null; + + constructor( + @IStatusbarService private readonly statusbarService: IStatusbarService, + @IMarkerService private readonly markerService: IMarkerService, + @IConfigurationService private readonly configurationService: IConfigurationService, + ) { + super(); + this.statusBarEntryAccessor = this._register(new MutableDisposable()); + this._register(markerService.onMarkerChanged(changedResources => this.onMarkerChanged(changedResources))); + this._register(Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('problems.showCurrentInStatus'))(() => this.updateStatus())); + } + + update(editor: ICodeEditor | undefined): void { + this.editor = editor; + this.updateStatus(); + } + + private updateStatus(): void { + const previousMarker = this.currentMarker; + this.currentMarker = this.getMarker(); + if (this.hasToUpdateStatus(previousMarker, this.currentMarker)) { + if (this.currentMarker) { + const line = this.currentMarker.message.split(/\r\n|\r|\n/g)[0]; + const text = `${this.getType(this.currentMarker)} ${line}`; + if (!this.statusBarEntryAccessor.value) { + this.statusBarEntryAccessor.value = this.statusbarService.addEntry({ text: '' }, 'statusbar.currentProblem', nls.localize('currentProblem', "Current Problem"), StatusbarAlignment.LEFT); + } + this.statusBarEntryAccessor.value.update({ text }); + } else { + this.statusBarEntryAccessor.clear(); + } + } + } + + private hasToUpdateStatus(previousMarker: IMarker | null, currentMarker: IMarker | null): boolean { + if (!currentMarker) { + return true; + } + if (!previousMarker) { + return true; + } + return IMarkerData.makeKey(previousMarker) !== IMarkerData.makeKey(currentMarker); + } + + private getType(marker: IMarker): string { + switch (marker.severity) { + case MarkerSeverity.Error: return '$(error)'; + case MarkerSeverity.Warning: return '$(warning)'; + case MarkerSeverity.Info: return '$(info)'; + } + return ''; + } + + private getMarker(): IMarker | null { + if (!this.configurationService.getValue('problems.showCurrentInStatus')) { + return null; + } + if (!this.editor) { + return null; + } + const model = this.editor.getModel(); + if (!model) { + return null; + } + const position = this.editor.getPosition(); + if (!position) { + return null; + } + return find(this.markers, marker => Range.containsPosition(marker, position)) || null; + } + + private onMarkerChanged(changedResources: ReadonlyArray): void { + if (!this.editor) { + return; + } + const model = this.editor.getModel(); + if (!model) { + return; + } + if (model && !changedResources.some(r => isEqual(model.uri, r))) { + return; + } + this.updateMarkers(); + } + + private updateMarkers(): void { + if (!this.editor) { + return; + } + const model = this.editor.getModel(); + if (!model) { + return; + } + if (model) { + this.markers = this.markerService.read({ + resource: model.uri, + severities: MarkerSeverity.Error | MarkerSeverity.Warning | MarkerSeverity.Info + }); + this.markers.sort(compareMarker); + } else { + this.markers = []; + } + this.updateStatus(); + } +} + +function compareMarker(a: IMarker, b: IMarker): number { + let res = compare(a.resource.toString(), b.resource.toString()); + if (res === 0) { + res = MarkerSeverity.compare(a.severity, b.severity); + } + if (res === 0) { + res = Range.compareRangesUsingStarts(a, b); + } + return res; +} + + function isWritableCodeEditor(codeEditor: ICodeEditor | undefined): boolean { if (!codeEditor) { return false; @@ -833,7 +966,7 @@ function isWritableCodeEditor(codeEditor: ICodeEditor | undefined): boolean { } function isWritableBaseEditor(e: IBaseEditor): boolean { - return e && isWritableCodeEditor(getCodeEditor(e.getControl()) || undefined); + return e && isWritableCodeEditor(withNullAsUndefined(getCodeEditor(e.getControl()))); } export class ShowLanguageExtensionsAction extends Action { @@ -870,7 +1003,7 @@ export class ChangeModeAction extends Action { @IQuickInputService private readonly quickInputService: IQuickInputService, @IPreferencesService private readonly preferencesService: IPreferencesService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService + @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService ) { super(actionId, actionLabel); } @@ -885,7 +1018,7 @@ export class ChangeModeAction extends Action { const resource = this.editorService.activeEditor ? toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }) : null; let hasLanguageSupport = !!resource; - if (resource?.scheme === Schemas.untitled && !this.untitledEditorService.hasAssociatedFilePath(resource)) { + if (resource?.scheme === Schemas.untitled && !this.untitledTextEditorService.hasAssociatedFilePath(resource)) { hasLanguageSupport = false; // no configuration for untitled resources (e.g. "Untitled-1") } @@ -894,7 +1027,7 @@ export class ChangeModeAction extends Action { let modeId: string | undefined; if (textModel) { modeId = textModel.getLanguageIdentifier().language; - currentModeId = this.modeService.getLanguageName(modeId) || undefined; + currentModeId = withNullAsUndefined(this.modeService.getLanguageName(modeId)); } // All languages are valid picks @@ -1153,7 +1286,7 @@ export class ChangeEncodingAction extends Action { } let action: IQuickPickItem; - if (encodingSupport instanceof UntitledEditorInput) { + if (encodingSupport instanceof UntitledTextEditorInput) { action = saveWithEncodingPick; } else if (!isWritableBaseEditor(activeControl)) { action = reopenWithEncodingPick; diff --git a/src/vs/workbench/browser/parts/editor/media/close-all-dark.svg b/src/vs/workbench/browser/parts/editor/media/close-all-dark.svg deleted file mode 100644 index d69cc9c835..0000000000 --- a/src/vs/workbench/browser/parts/editor/media/close-all-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/parts/editor/media/close-all-light.svg b/src/vs/workbench/browser/parts/editor/media/close-all-light.svg deleted file mode 100644 index 9fcf77fe72..0000000000 --- a/src/vs/workbench/browser/parts/editor/media/close-all-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/parts/editor/media/close-dark-alt.svg b/src/vs/workbench/browser/parts/editor/media/close-dark-alt.svg deleted file mode 100644 index 44ece771f4..0000000000 --- a/src/vs/workbench/browser/parts/editor/media/close-dark-alt.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/close-dirty-dark-alt.svg b/src/vs/workbench/browser/parts/editor/media/close-dirty-dark-alt.svg deleted file mode 100644 index 51946be5bb..0000000000 --- a/src/vs/workbench/browser/parts/editor/media/close-dirty-dark-alt.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/close-dirty-light-alt.svg b/src/vs/workbench/browser/parts/editor/media/close-dirty-light-alt.svg deleted file mode 100644 index fb91225b96..0000000000 --- a/src/vs/workbench/browser/parts/editor/media/close-dirty-light-alt.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/close-light-alt.svg b/src/vs/workbench/browser/parts/editor/media/close-light-alt.svg deleted file mode 100644 index 742fcae4ae..0000000000 --- a/src/vs/workbench/browser/parts/editor/media/close-light-alt.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/editorgroupview.css b/src/vs/workbench/browser/parts/editor/media/editorgroupview.css index 59677ad2f2..ce023f628a 100644 --- a/src/vs/workbench/browser/parts/editor/media/editorgroupview.css +++ b/src/vs/workbench/browser/parts/editor/media/editorgroupview.css @@ -48,6 +48,20 @@ overflow: hidden; } +.monaco-workbench.windows.chromium .part.editor > .content .editor-group-container > .title, +.monaco-workbench.linux.chromium .part.editor > .content .editor-group-container > .title { + /* + * Explicitly put the part onto its own layer to help Chrome to + * render the content with LCD-anti-aliasing. By partioning the + * workbench into multiple layers, we can ensure that a bad + * behaving part is not making another part fallback to greyscale + * rendering. + * + * macOS: does not render LCD-anti-aliased. + */ + transform: translate3d(0px, 0px, 0px); +} + .monaco-workbench .part.editor > .content .editor-group-container > .title:not(.tabs) { display: flex; /* when tabs are not shown, use flex layout */ flex-wrap: nowrap; diff --git a/src/vs/workbench/browser/parts/editor/media/editorstatus.css b/src/vs/workbench/browser/parts/editor/media/editorstatus.css index e792c81da1..05eef2cf1b 100644 --- a/src/vs/workbench/browser/parts/editor/media/editorstatus.css +++ b/src/vs/workbench/browser/parts/editor/media/editorstatus.css @@ -48,4 +48,5 @@ padding-right: 12px; margin-right: 5px; max-width: fit-content; + max-width: -moz-fit-content; } diff --git a/src/vs/workbench/browser/parts/editor/media/next-diff-dark.svg b/src/vs/workbench/browser/parts/editor/media/next-diff-dark.svg deleted file mode 100644 index 455532ddb7..0000000000 --- a/src/vs/workbench/browser/parts/editor/media/next-diff-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/next-diff-light.svg b/src/vs/workbench/browser/parts/editor/media/next-diff-light.svg deleted file mode 100644 index a443086f35..0000000000 --- a/src/vs/workbench/browser/parts/editor/media/next-diff-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css index f95c8de3c9..457578a682 100644 --- a/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css @@ -76,6 +76,15 @@ padding-right: 4px; /* does not have trailing separator*/ } +.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item .codicon[class*='codicon-symbol-'] { + padding: 0 1px; +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item .codicon:last-child, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child .codicon:last-child { + display: none; /* hides chevrons when no tabs visible and when last items */ +} + /* Title Actions */ .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions { display: flex; diff --git a/src/vs/workbench/browser/parts/editor/media/paragraph-dark.svg b/src/vs/workbench/browser/parts/editor/media/paragraph-dark.svg deleted file mode 100644 index 24708ab31f..0000000000 --- a/src/vs/workbench/browser/parts/editor/media/paragraph-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/paragraph-disabled-dark.svg b/src/vs/workbench/browser/parts/editor/media/paragraph-disabled-dark.svg deleted file mode 100644 index 3c174668fd..0000000000 --- a/src/vs/workbench/browser/parts/editor/media/paragraph-disabled-dark.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/parts/editor/media/paragraph-disabled-light.svg b/src/vs/workbench/browser/parts/editor/media/paragraph-disabled-light.svg deleted file mode 100644 index 5edb9e63eb..0000000000 --- a/src/vs/workbench/browser/parts/editor/media/paragraph-disabled-light.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/parts/editor/media/paragraph-light.svg b/src/vs/workbench/browser/parts/editor/media/paragraph-light.svg deleted file mode 100644 index 734fe61920..0000000000 --- a/src/vs/workbench/browser/parts/editor/media/paragraph-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/previous-diff-dark.svg b/src/vs/workbench/browser/parts/editor/media/previous-diff-dark.svg deleted file mode 100644 index 5ca3526019..0000000000 --- a/src/vs/workbench/browser/parts/editor/media/previous-diff-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/previous-diff-light.svg b/src/vs/workbench/browser/parts/editor/media/previous-diff-light.svg deleted file mode 100644 index 87e179a7f3..0000000000 --- a/src/vs/workbench/browser/parts/editor/media/previous-diff-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/split-editor-horizontal-dark.svg b/src/vs/workbench/browser/parts/editor/media/split-editor-horizontal-dark.svg deleted file mode 100644 index 8c22a7c5bf..0000000000 --- a/src/vs/workbench/browser/parts/editor/media/split-editor-horizontal-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/split-editor-horizontal-hc.svg b/src/vs/workbench/browser/parts/editor/media/split-editor-horizontal-hc.svg deleted file mode 100644 index 82c19d0c8f..0000000000 --- a/src/vs/workbench/browser/parts/editor/media/split-editor-horizontal-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/split-editor-horizontal-light.svg b/src/vs/workbench/browser/parts/editor/media/split-editor-horizontal-light.svg deleted file mode 100644 index 400952cdda..0000000000 --- a/src/vs/workbench/browser/parts/editor/media/split-editor-horizontal-light.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/browser/parts/editor/media/split-editor-vertical-dark.svg b/src/vs/workbench/browser/parts/editor/media/split-editor-vertical-dark.svg deleted file mode 100644 index 419c21be4f..0000000000 --- a/src/vs/workbench/browser/parts/editor/media/split-editor-vertical-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/split-editor-vertical-hc.svg b/src/vs/workbench/browser/parts/editor/media/split-editor-vertical-hc.svg deleted file mode 100644 index 7565fd3c16..0000000000 --- a/src/vs/workbench/browser/parts/editor/media/split-editor-vertical-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/split-editor-vertical-light.svg b/src/vs/workbench/browser/parts/editor/media/split-editor-vertical-light.svg deleted file mode 100644 index 405291c14d..0000000000 --- a/src/vs/workbench/browser/parts/editor/media/split-editor-vertical-light.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index 75424d5f88..875c19f5dc 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -23,6 +23,7 @@ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container { display: flex; height: 35px; + scrollbar-width: none; /* Firefox: hide scrollbar */ } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.scroll { @@ -30,7 +31,7 @@ } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container::-webkit-scrollbar { - display: none; + display: none; /* Chrome + Safari: hide scrollbar */ } /* Tab */ @@ -53,6 +54,8 @@ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit { width: 120px; min-width: fit-content; + min-width: -moz-fit-content; + flex-shrink: 0; } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink { @@ -60,6 +63,11 @@ flex-basis: 0; /* all tabs are even */ flex-grow: 1; /* all tabs grow even */ max-width: fit-content; + max-width: -moz-fit-content; +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-left .action-label { + margin-right: 4px !important; } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.close-button-left::after, @@ -76,7 +84,7 @@ } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dragged { - will-change: transform; /* forces tab to be drawn on a separate layer (fixes https://github.com/Microsoft/vscode/issues/18733) */ + transform: translate3d(0px, 0px, 0px); /* forces tab to be drawn on a separate layer (fixes https://github.com/Microsoft/vscode/issues/18733) */ } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dragged-over div { @@ -198,6 +206,11 @@ opacity: 1; } +.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab > .tab-close .action-label.codicon { + color: inherit; + font-size: 16px; +} + /* change close icon to dirty state icon */ .monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.dirty > .tab-close .action-label:not(:hover)::before, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty > .tab-close .action-label:not(:hover)::before { @@ -232,6 +245,7 @@ padding-right: 5px; /* we need less room when sizing is shrink */ } +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty-border-top > .tab-close, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty-border-top > .tab-close { display: none; /* hide dirty state when highlightModifiedTabs is enabled and when running without close button */ } diff --git a/src/vs/workbench/browser/parts/editor/media/titlecontrol.css b/src/vs/workbench/browser/parts/editor/media/titlecontrol.css index aa3bca6d25..4bfd74fd74 100644 --- a/src/vs/workbench/browser/parts/editor/media/titlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/titlecontrol.css @@ -30,17 +30,18 @@ .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label, .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label { - display: block; + display: flex; height: 35px; - line-height: 35px; min-width: 28px; + align-items: center; + justify-content: center; background-size: 16px; background-position: center center; background-repeat: no-repeat; } .hc-black .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label, -.hc-black .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label { +.hc-black .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label:not(.codicon) { line-height: initial; } @@ -49,6 +50,15 @@ display: none; } +.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label.codicon { + color: inherit; +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label.disabled, +.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label.disabled { + opacity: 0.4; +} + /* Drag Cursor */ .monaco-workbench .part.editor > .content .editor-group-container > .title { cursor: grab; diff --git a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts index 9737270cbb..7711ae4df2 100644 --- a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts @@ -100,13 +100,21 @@ export class NoTabsTitleControl extends TitleControl { private onTitleClick(e: MouseEvent | GestureEvent): void { - // Close editor on middle mouse click - if (e instanceof MouseEvent && e.button === 1 /* Middle Button */) { - EventHelper.stop(e, true /* for https://github.com/Microsoft/vscode/issues/56715 */); + if (e instanceof MouseEvent) { + // Close editor on middle mouse click + if (e.button === 1 /* Middle Button */) { + EventHelper.stop(e, true /* for https://github.com/Microsoft/vscode/issues/56715 */); - if (this.group.activeEditor) { - this.group.closeEditor(this.group.activeEditor); + if (this.group.activeEditor) { + this.group.closeEditor(this.group.activeEditor); + } } + } else { + // @rebornix + // gesture tap should open the quick open + // editorGroupView will focus on the editor again when there are mouse/pointer/touch down events + // we need to wait a bit as `GesureEvent.Tap` is generated from `touchstart` and then `touchend` evnets, which are not an atom event. + setTimeout(() => this.quickOpenService.show(), 50); } } @@ -248,7 +256,7 @@ export class NoTabsTitleControl extends TitleControl { // Editor Label const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); - const name = editor.getName() || ''; + const name = editor.getName(); const { labelFormat } = this.accessor.partOptions; let description: string; @@ -265,7 +273,7 @@ export class NoTabsTitleControl extends TitleControl { title = ''; // dont repeat what is already shown } - editorLabel.setResource({ name, description, resource: resource || undefined }, { title: typeof title === 'string' ? title : undefined, italic: !isEditorPinned, extraClasses: ['no-tabs', 'title-label'] }); + editorLabel.setResource({ name, description, resource }, { title: typeof title === 'string' ? title : undefined, italic: !isEditorPinned, extraClasses: ['no-tabs', 'title-label'] }); if (isGroupActive) { editorLabel.element.style.color = this.getColor(TAB_ACTIVE_FOREGROUND); } else { diff --git a/src/vs/workbench/browser/parts/editor/resourceViewer.ts b/src/vs/workbench/browser/parts/editor/resourceViewer.ts index fc65d4ac65..1fa96e6f9a 100644 --- a/src/vs/workbench/browser/parts/editor/resourceViewer.ts +++ b/src/vs/workbench/browser/parts/editor/resourceViewer.ts @@ -6,7 +6,6 @@ import * as DOM from 'vs/base/browser/dom'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/resourceviewer'; import * as nls from 'vs/nls'; @@ -132,13 +131,11 @@ class FileSeemsBinaryFileView { label.textContent = nls.localize('nativeBinaryError', "The file is not displayed in the editor because it is either binary or uses an unsupported text encoding."); container.appendChild(label); - if (descriptor.resource.scheme !== Schemas.data) { - const link = DOM.append(label, DOM.$('a.embedded-link')); - link.setAttribute('role', 'button'); - link.textContent = nls.localize('openAsText', "Do you want to open it anyway?"); + const link = DOM.append(label, DOM.$('a.embedded-link')); + link.setAttribute('role', 'button'); + link.textContent = nls.localize('openAsText', "Do you want to open it anyway?"); - disposables.add(DOM.addDisposableListener(link, DOM.EventType.CLICK, () => delegate.openInternalClb(descriptor.resource))); - } + disposables.add(DOM.addDisposableListener(link, DOM.EventType.CLICK, () => delegate.openInternalClb(descriptor.resource))); scrollbar.scanDomNode(); diff --git a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts index c4442dc1e6..cc76f02d4a 100644 --- a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts +++ b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts @@ -56,7 +56,7 @@ export class SideBySideEditor extends BaseEditor { private onDidCreateEditors = this._register(new Emitter<{ width: number; height: number; } | undefined>()); private _onDidSizeConstraintsChange = this._register(new Relay<{ width: number; height: number; } | undefined>()); - readonly onDidSizeConstraintsChange: Event<{ width: number; height: number; } | undefined> = Event.any(this.onDidCreateEditors.event, this._onDidSizeConstraintsChange.event); + readonly onDidSizeConstraintsChange = Event.any(this.onDidCreateEditors.event, this._onDidSizeConstraintsChange.event); constructor( @ITelemetryService telemetryService: ITelemetryService, diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index c04eb4006c..ed953d3175 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -31,8 +31,8 @@ import { ResourcesDropHandler, fillResourceDataTransfers, DraggedEditorIdentifie import { Color } from 'vs/base/common/color'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { MergeGroupMode, IMergeGroupOptions } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { MergeGroupMode, IMergeGroupOptions, GroupsArrangement } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { addClass, addDisposableListener, hasClass, EventType, EventHelper, removeClass, Dimension, scheduleAtNextAnimationFrame, findParentWithClass, clearNode } from 'vs/base/browser/dom'; import { localize } from 'vs/nls'; import { IEditorGroupsAccessor, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; @@ -79,7 +79,7 @@ export class TabsTitleControl extends TitleControl { group: IEditorGroupView, @IContextMenuService contextMenuService: IContextMenuService, @IInstantiationService instantiationService: IInstantiationService, - @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService, @IContextKeyService contextKeyService: IContextKeyService, @IKeybindingService keybindingService: IKeybindingService, @ITelemetryService telemetryService: ITelemetryService, @@ -111,6 +111,7 @@ export class TabsTitleControl extends TitleControl { this.tabsContainer.setAttribute('role', 'tablist'); this.tabsContainer.draggable = true; addClass(this.tabsContainer, 'tabs-container'); + this._register(Gesture.addTarget(this.tabsContainer)); // Tabs Scrollbar this.tabsScrollbar = this._register(this.createTabsScrollbar(this.tabsContainer)); @@ -178,13 +179,27 @@ export class TabsTitleControl extends TitleControl { })); // New file when double clicking on tabs container (but not tabs) - this._register(addDisposableListener(tabsContainer, EventType.DBLCLICK, e => { - if (e.target === tabsContainer) { + [TouchEventType.Tap, EventType.DBLCLICK].forEach(eventType => { + this._register(addDisposableListener(tabsContainer, eventType, (e: MouseEvent | GestureEvent) => { + if (eventType === EventType.DBLCLICK) { + if (e.target !== tabsContainer) { + return; // ignore if target is not tabs container + } + } else { + if ((e).tapCount !== 2) { + return; // ignore single taps + } + + if ((e).initialTarget !== tabsContainer) { + return; // ignore if target is not tabs container + } + } + EventHelper.stop(e); - this.group.openEditor(this.untitledEditorService.createOrGet(), { pinned: true /* untitled is always pinned */, index: this.group.count /* always at the end */ }); - } - })); + this.group.openEditor(this.untitledTextEditorService.createOrGet(), { pinned: true /* untitled is always pinned */, index: this.group.count /* always at the end */ }); + })); + }); // Prevent auto-scrolling (https://github.com/Microsoft/vscode/issues/16690) this._register(addDisposableListener(tabsContainer, EventType.MOUSE_DOWN, (e: MouseEvent) => { @@ -316,7 +331,7 @@ export class TabsTitleControl extends TitleControl { (tabsContainer.lastChild as HTMLElement).remove(); // Remove associated tab label and widget - this.tabDisposables.pop()!.dispose(); + dispose(this.tabDisposables.pop()); } // A removal of a label requires to recompute all labels @@ -360,7 +375,7 @@ export class TabsTitleControl extends TitleControl { } pinEditor(editor: IEditorInput): void { - this.withTab(editor, (tabContainer, tabLabelWidget, tabLabel) => this.redrawLabel(editor, tabContainer, tabLabelWidget, tabLabel)); + this.withTab(editor, (editor, index, tabContainer, tabLabelWidget, tabLabel) => this.redrawLabel(editor, tabContainer, tabLabelWidget, tabLabel)); } setActive(isGroupActive: boolean): void { @@ -396,7 +411,7 @@ export class TabsTitleControl extends TitleControl { } updateEditorDirty(editor: IEditorInput): void { - this.withTab(editor, (tabContainer, tabLabelWidget) => this.redrawEditorActiveAndDirty(this.accessor.activeGroup === this.group, editor, tabContainer, tabLabelWidget)); + this.withTab(editor, (editor, index, tabContainer, tabLabelWidget) => this.redrawEditorActiveAndDirty(this.accessor.activeGroup === this.group, editor, tabContainer, tabLabelWidget)); } updateOptions(oldOptions: IEditorPartOptions, newOptions: IEditorPartOptions): void { @@ -423,13 +438,23 @@ export class TabsTitleControl extends TitleControl { this.redraw(); } - private withTab(editor: IEditorInput, fn: (tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel) => void): void { - const editorIndex = this.group.getIndexOfEditor(editor); + private forEachTab(fn: (editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel) => void): void { + this.group.editors.forEach((editor, index) => { + this.doWithTab(index, editor, fn); + }); + } + private withTab(editor: IEditorInput, fn: (editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel) => void): void { + this.doWithTab(this.group.getIndexOfEditor(editor), editor, fn); + } + + private doWithTab(index: number, editor: IEditorInput, fn: (editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel) => void): void { const tabsContainer = assertIsDefined(this.tabsContainer); - const tabContainer = tabsContainer.children[editorIndex] as HTMLElement; - if (tabContainer) { - fn(tabContainer, this.tabResourceLabels.get(editorIndex), this.tabLabels[editorIndex]); + const tabContainer = tabsContainer.children[index] as HTMLElement; + const tabResourceLabel = this.tabResourceLabels.get(index); + const tabLabel = this.tabLabels[index]; + if (tabContainer && tabResourceLabel && tabLabel) { + fn(editor, index, tabContainer, tabResourceLabel, tabLabel); } } @@ -496,7 +521,7 @@ export class TabsTitleControl extends TitleControl { } // Open tabs editor - const input = this.group.getEditor(index); + const input = this.group.getEditorByIndex(index); if (input) { this.group.openEditor(input); } @@ -507,7 +532,7 @@ export class TabsTitleControl extends TitleControl { const showContextMenu = (e: Event) => { EventHelper.stop(e); - const input = this.group.getEditor(index); + const input = this.group.getEditorByIndex(index); if (input) { this.onContextMenu(input, e, tab); } @@ -557,7 +582,7 @@ export class TabsTitleControl extends TitleControl { // Run action on Enter/Space if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) { handled = true; - const input = this.group.getEditor(index); + const input = this.group.getEditorByIndex(index); if (input) { this.group.openEditor(input); } @@ -576,7 +601,7 @@ export class TabsTitleControl extends TitleControl { targetIndex = this.group.count - 1; } - const target = this.group.getEditor(targetIndex); + const target = this.group.getEditorByIndex(targetIndex); if (target) { handled = true; this.group.openEditor(target, { preserveFocus: true }); @@ -594,18 +619,29 @@ export class TabsTitleControl extends TitleControl { }); })); - // Pin on double click - disposables.add(addDisposableListener(tab, EventType.DBLCLICK, (e: MouseEvent) => { - EventHelper.stop(e); + // Double click: either pin or toggle maximized + [TouchEventType.Tap, EventType.DBLCLICK].forEach(eventType => { + disposables.add(addDisposableListener(tab, eventType, (e: MouseEvent | GestureEvent) => { + if (eventType === EventType.DBLCLICK) { + EventHelper.stop(e); + } else if ((e).tapCount !== 2) { + return; // ignore single taps + } - this.group.pinEditor(this.group.getEditor(index) || undefined); - })); + const editor = this.group.getEditorByIndex(index); + if (editor && this.group.isPinned(editor)) { + this.accessor.arrangeGroups(GroupsArrangement.TOGGLE, this.group); + } else { + this.group.pinEditor(editor); + } + })); + }); // Context menu disposables.add(addDisposableListener(tab, EventType.CONTEXT_MENU, (e: Event) => { EventHelper.stop(e, true); - const input = this.group.getEditor(index); + const input = this.group.getEditorByIndex(index); if (input) { this.onContextMenu(input, e, tab); } @@ -613,7 +649,7 @@ export class TabsTitleControl extends TitleControl { // Drag support disposables.add(addDisposableListener(tab, EventType.DRAG_START, (e: DragEvent) => { - const editor = this.group.getEditor(index); + const editor = this.group.getEditorByIndex(index); if (!editor) { return; } @@ -659,7 +695,7 @@ export class TabsTitleControl extends TitleControl { const data = this.editorTransfer.getData(DraggedEditorIdentifier.prototype); if (Array.isArray(data)) { const localDraggedEditor = data[0].identifier; - if (localDraggedEditor.editor === this.group.getEditor(index) && localDraggedEditor.groupId === this.group.id) { + if (localDraggedEditor.editor === this.group.getEditorByIndex(index) && localDraggedEditor.groupId === this.group.id) { if (e.dataTransfer) { e.dataTransfer.dropEffect = 'none'; } @@ -729,7 +765,7 @@ export class TabsTitleControl extends TitleControl { private updateDropFeedback(element: HTMLElement, isDND: boolean, index?: number): void { const isTab = (typeof index === 'number'); - const editor = typeof index === 'number' ? this.group.getEditor(index) : undefined; + const editor = typeof index === 'number' ? this.group.getEditorByIndex(index) : undefined; const isActiveTab = isTab && !!editor && this.group.isActive(editor); // Background @@ -754,9 +790,9 @@ export class TabsTitleControl extends TitleControl { if (isTab) { const tabContainer = this.tabsContainer.children[index]; if (tabContainer instanceof HTMLElement) { - let editor = this.group.getEditor(index); - if (editor) { - this.setEditorTabColor(editor, tabContainer, isActiveTab); + let editor = this.group.getEditors(index); + if (editor.length > 0) { + this.setEditorTabColor(editor[0], tabContainer, isActiveTab); } } } @@ -878,16 +914,6 @@ export class TabsTitleControl extends TitleControl { this.layout(this.dimension); } - private forEachTab(fn: (editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel) => void): void { - this.group.editors.forEach((editor, index) => { - const tabsContainer = assertIsDefined(this.tabsContainer); - const tabContainer = tabsContainer.children[index] as HTMLElement; - if (tabContainer) { - fn(editor, index, tabContainer, this.tabResourceLabels.get(index), this.tabLabels[index]); - } - }); - } - private redrawTab(editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel): void { // Label @@ -931,7 +957,7 @@ export class TabsTitleControl extends TitleControl { tabContainer.title = title; // Label - tabLabelWidget.setResource({ name, description, resource: toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }) || undefined }, { title, extraClasses: ['tab-label'], italic: !this.group.isPinned(editor) }); + tabLabelWidget.setResource({ name, description, resource: toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }) }, { title, extraClasses: ['tab-label'], italic: !this.group.isPinned(editor) }); // {{SQL CARBON EDIT}} -- Display the editor's tab color const isTabActive = this.group.isActive(editor); @@ -1276,7 +1302,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { // High Contrast Border Color for Editor Actions const contrastBorderColor = theme.getColor(contrastBorder); - if (contrastBorder) { + if (contrastBorderColor) { collector.addRule(` .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions { outline: 1px solid ${contrastBorderColor} diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index 4a9a80598a..2312cab6ff 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -10,7 +10,6 @@ import { IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditorOptions, IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions'; import { BaseTextEditor, IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor'; import { TextEditorOptions, EditorInput, EditorOptions, TEXT_DIFF_EDITOR_ID, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, ITextDiffEditor, IEditorMemento } from 'vs/workbench/common/editor'; -import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { DiffNavigator } from 'vs/editor/browser/widget/diffNavigator'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget'; @@ -20,7 +19,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ITextFileService, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; +import { TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; import { ScrollType, IDiffEditorViewState, IDiffEditorModel } from 'vs/editor/common/editorCommon'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -30,7 +29,6 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { EditorMemento } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { IHostService } from 'vs/workbench/services/host/browser/host'; import { EditorActivation, IEditorOptions } from 'vs/platform/editor/common/editor'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -54,18 +52,16 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { @IEditorService editorService: IEditorService, @IThemeService themeService: IThemeService, @IEditorGroupsService editorGroupService: IEditorGroupsService, - @ITextFileService textFileService: ITextFileService, - @IHostService hostService: IHostService, - @IClipboardService private _clipboardService: IClipboardService, + @IClipboardService private clipboardService: IClipboardService ) { - super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, hostService); + super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorService, editorGroupService); } protected getEditorMemento(editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento { return new EditorMemento(this.getId(), key, Object.create(null), limit, editorGroupService); // do not persist in storage as diff editors are never persisted } - getTitle(): string | undefined { + getTitle(): string { if (this.input) { return this.input.getName(); } @@ -82,7 +78,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { if (this.reverseColor) { // {{SQL CARBON EDIT}} (configuration as IDiffEditorOptions).reverse = true; } - return this.instantiationService.createInstance(DiffEditorWidget, parent, configuration, this._clipboardService); + return this.instantiationService.createInstance(DiffEditorWidget as any, parent, configuration, this.clipboardService); // {{SQL CARBON EDIT}} strict-null-check...i guess? } async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { @@ -132,8 +128,15 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { }); this.diffNavigatorDisposables.add(this.diffNavigator); - // Readonly flag - diffEditor.updateOptions({ readOnly: resolvedDiffEditorModel.isReadonly() }); + // Since the resolved model provides information about being readonly + // or not, we apply it here to the editor even though the editor input + // was already asked for being readonly or not. The rationale is that + // a resolved model might have more specific information about being + // readonly or not that the input did not have. + diffEditor.updateOptions({ + readOnly: resolvedDiffEditorModel.modifiedModel?.isReadonly(), + originalEditable: !resolvedDiffEditorModel.originalModel?.isReadonly() + }); } catch (error) { // In case we tried to open a file and the response indicates that this is not a text file, fallback to binary diff. @@ -145,14 +148,6 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { } } - setOptions(options: EditorOptions | undefined): void { - const textOptions = options; - if (textOptions && isFunction(textOptions.apply)) { - const diffEditor = assertIsDefined(this.getControl()); - textOptions.apply(diffEditor, ScrollType.Smooth); - } - } - private restoreTextDiffEditorViewState(editor: EditorInput, control: IDiffEditor): boolean { if (editor instanceof DiffEditorInput) { const resource = this.toDiffEditorViewStateResource(editor); @@ -219,7 +214,8 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { protected getConfigurationOverrides(): ICodeEditorOptions { const options: IDiffEditorOptions = super.getConfigurationOverrides(); - options.readOnly = this.isReadOnly(); + options.readOnly = this.input instanceof DiffEditorInput && this.input.modifiedInput.isReadonly(); + options.originalEditable = this.input instanceof DiffEditorInput && !this.input.originalInput.isReadonly(); options.lineDecorationsWidth = '2ch'; return options; @@ -227,8 +223,9 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { protected getAriaLabel(): string { let ariaLabel: string; - const inputName = this.input && this.input.getName(); - if (this.isReadOnly()) { + + const inputName = this.input?.getName(); + if (this.input?.isReadonly()) { ariaLabel = inputName ? nls.localize('readonlyEditorWithInputAriaLabel', "{0}. Readonly text compare editor.", inputName) : nls.localize('readonlyEditorAriaLabel', "Readonly text compare editor."); } else { ariaLabel = inputName ? nls.localize('editableEditorWithInputAriaLabel', "{0}. Text file compare editor.", inputName) : nls.localize('editableEditorAriaLabel', "Text file compare editor."); @@ -237,17 +234,6 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { return ariaLabel; } - private isReadOnly(): boolean { - const input = this.input; - if (input instanceof DiffEditorInput) { - const modifiedInput = input.modifiedInput; - - return modifiedInput instanceof ResourceEditorInput; - } - - return false; - } - private isFileBinaryError(error: Error[]): boolean; private isFileBinaryError(error: Error): boolean; private isFileBinaryError(error: Error | Error[]): boolean { diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts index 240e97a981..0c9678e2a9 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -6,24 +6,22 @@ import { localize } from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import { distinct, deepClone, assign } from 'vs/base/common/objects'; -import { isObject, assertIsDefined } from 'vs/base/common/types'; +import { isObject, assertIsDefined, withNullAsUndefined, isFunction } from 'vs/base/common/types'; import { Dimension } from 'vs/base/browser/dom'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { EditorInput, EditorOptions, IEditorMemento, ITextEditor } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, IEditorMemento, ITextEditor, TextEditorOptions } from 'vs/workbench/common/editor'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { IEditorViewState, IEditor } from 'vs/editor/common/editorCommon'; +import { IEditorViewState, IEditor, ScrollType } from 'vs/editor/common/editorCommon'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ITextFileService, SaveReason, AutoSaveMode } from 'vs/workbench/services/textfile/common/textfiles'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { isDiffEditor, isCodeEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { isCodeEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IHostService } from 'vs/workbench/services/host/browser/host'; const TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'textEditorViewState'; @@ -38,7 +36,7 @@ export interface IEditorConfiguration { */ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { private editorControl: IEditor | undefined; - private _editorContainer: HTMLElement | undefined; + private editorContainer: HTMLElement | undefined; private hasPendingConfigurationChange: boolean | undefined; private lastAppliedEditorOptions?: IEditorOptions; private editorMemento: IEditorMemento; @@ -50,10 +48,8 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { @IStorageService storageService: IStorageService, @ITextResourceConfigurationService private readonly _configurationService: ITextResourceConfigurationService, @IThemeService protected themeService: IThemeService, - @ITextFileService private readonly _textFileService: ITextFileService, @IEditorService protected editorService: IEditorService, - @IEditorGroupsService protected editorGroupService: IEditorGroupsService, - @IHostService private readonly hostService: IHostService + @IEditorGroupsService protected editorGroupService: IEditorGroupsService ) { super(id, telemetryService, themeService, storageService); @@ -74,10 +70,6 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { return this._configurationService; } - protected get textFileService(): ITextFileService { - return this._textFileService; - } - protected handleConfigurationChangeEvent(configuration?: IEditorConfiguration): void { if (this.isVisible()) { this.updateEditorConfiguration(configuration); @@ -119,63 +111,25 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { } protected getConfigurationOverrides(): IEditorOptions { - const overrides = {}; - assign(overrides, { + return { overviewRulerLanes: 3, lineNumbersMinChars: 3, - fixedOverflowWidgets: true - }); - - return overrides; + fixedOverflowWidgets: true, + readOnly: this.input?.isReadonly() + }; } protected createEditor(parent: HTMLElement): void { // Editor for Text - this._editorContainer = parent; + this.editorContainer = parent; this.editorControl = this._register(this.createEditorControl(parent, this.computeConfiguration(this.configurationService.getValue(this.getResource())))); // Model & Language changes const codeEditor = getCodeEditor(this.editorControl); if (codeEditor) { - this._register(codeEditor.onDidChangeModelLanguage(e => this.updateEditorConfiguration())); - this._register(codeEditor.onDidChangeModel(e => this.updateEditorConfiguration())); - } - - // Application & Editor focus change to respect auto save settings - if (isCodeEditor(this.editorControl)) { - this._register(this.editorControl.onDidBlurEditorWidget(() => this.onEditorFocusLost())); - } else if (isDiffEditor(this.editorControl)) { - this._register(this.editorControl.getOriginalEditor().onDidBlurEditorWidget(() => this.onEditorFocusLost())); - this._register(this.editorControl.getModifiedEditor().onDidBlurEditorWidget(() => this.onEditorFocusLost())); - } - - this._register(this.editorService.onDidActiveEditorChange(() => this.onEditorFocusLost())); - this._register(this.hostService.onDidChangeFocus(focused => this.onWindowFocusChange(focused))); - } - - private onEditorFocusLost(): void { - this.maybeTriggerSaveAll(SaveReason.FOCUS_CHANGE); - } - - private onWindowFocusChange(focused: boolean): void { - if (!focused) { - this.maybeTriggerSaveAll(SaveReason.WINDOW_CHANGE); - } - } - - private maybeTriggerSaveAll(reason: SaveReason): void { - const mode = this.textFileService.getAutoSaveMode(); - - // Determine if we need to save all. In case of a window focus change we also save if auto save mode - // is configured to be ON_FOCUS_CHANGE (editor focus change) - if ( - (reason === SaveReason.WINDOW_CHANGE && (mode === AutoSaveMode.ON_FOCUS_CHANGE || mode === AutoSaveMode.ON_WINDOW_CHANGE)) || - (reason === SaveReason.FOCUS_CHANGE && mode === AutoSaveMode.ON_FOCUS_CHANGE) - ) { - if (this.textFileService.isDirty()) { - this.textFileService.saveAll(undefined, { reason }); - } + this._register(codeEditor.onDidChangeModelLanguage(() => this.updateEditorConfiguration())); + this._register(codeEditor.onDidChangeModel(() => this.updateEditorConfiguration())); } } @@ -198,10 +152,18 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { // editor input specific options (e.g. an ARIA label depending on the input showing) this.updateEditorConfiguration(); - const editorContainer = assertIsDefined(this._editorContainer); + const editorContainer = assertIsDefined(this.editorContainer); editorContainer.setAttribute('aria-label', this.computeAriaLabel()); } + setOptions(options: EditorOptions | undefined): void { + const textOptions = options as TextEditorOptions; + if (textOptions && isFunction(textOptions.apply)) { + const textEditor = assertIsDefined(this.getControl()); + textOptions.apply(textEditor, ScrollType.Smooth); + } + } + protected setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { // Pass on to Editor @@ -246,6 +208,15 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { this.editorMemento.saveEditorState(this.group, resource, editorViewState); } + getViewState(): IEditorViewState | undefined { + const resource = this.input?.getResource(); + if (resource) { + return withNullAsUndefined(this.retrieveTextEditorViewState(resource)); + } + + return undefined; + } + protected retrieveTextEditorViewState(resource: URI): IEditorViewState | null { const control = this.getControl(); if (!isCodeEditor(control)) { @@ -292,6 +263,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { configuration = this.configurationService.getValue(resource); } } + if (!this.editorControl || !configuration) { return; } diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index 19f392d354..fc973f686f 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -6,24 +6,21 @@ import * as nls from 'vs/nls'; import { assertIsDefined, isFunction } from 'vs/base/common/types'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { TextEditorOptions, EditorInput, EditorOptions } from 'vs/workbench/common/editor'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; -import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { Event } from 'vs/base/common/event'; import { ScrollType, IEditor } from 'vs/editor/common/editorCommon'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IHostService } from 'vs/workbench/services/host/browser/host'; /** * An editor implementation that is capable of showing the contents of resource inputs. Uses @@ -39,11 +36,9 @@ export class AbstractTextResourceEditor extends BaseTextEditor { @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, @IThemeService themeService: IThemeService, @IEditorGroupsService editorGroupService: IEditorGroupsService, - @ITextFileService textFileService: ITextFileService, - @IEditorService editorService: IEditorService, - @IHostService hostService: IHostService + @IEditorService editorService: IEditorService ) { - super(id, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, hostService); + super(id, telemetryService, instantiationService, storageService, configurationService, themeService, editorService, editorGroupService); } getTitle(): string | undefined { @@ -89,10 +84,17 @@ export class AbstractTextResourceEditor extends BaseTextEditor { if (!optionsGotApplied) { this.restoreTextResourceEditorViewState(input, textEditor); } + + // Since the resolved model provides information about being readonly + // or not, we apply it here to the editor even though the editor input + // was already asked for being readonly or not. The rationale is that + // a resolved model might have more specific information about being + // readonly or not that the input did not have. + textEditor.updateOptions({ readOnly: resolvedModel.isReadonly() }); } private restoreTextResourceEditorViewState(editor: EditorInput, control: IEditor) { - if (editor instanceof UntitledEditorInput || editor instanceof ResourceEditorInput) { + if (editor instanceof UntitledTextEditorInput || editor instanceof ResourceEditorInput) { const viewState = this.loadTextEditorViewState(editor.getResource()); if (viewState) { control.restoreViewState(viewState); @@ -100,29 +102,11 @@ export class AbstractTextResourceEditor extends BaseTextEditor { } } - setOptions(options: EditorOptions | undefined): void { - const textOptions = options; - if (textOptions && isFunction(textOptions.apply)) { - const textEditor = assertIsDefined(this.getControl()); - textOptions.apply(textEditor, ScrollType.Smooth); - } - } - - protected getConfigurationOverrides(): IEditorOptions { - const options = super.getConfigurationOverrides(); - - options.readOnly = !(this.input instanceof UntitledEditorInput); // all resource editors are readonly except for the untitled one; - - return options; - } - protected getAriaLabel(): string { - const input = this.input; - const isReadonly = !(this.input instanceof UntitledEditorInput); - let ariaLabel: string; - const inputName = input?.getName(); - if (isReadonly) { + + const inputName = this.input?.getName(); + if (this.input?.isReadonly()) { ariaLabel = inputName ? nls.localize('readonlyEditorWithInputAriaLabel', "{0}. Readonly text editor.", inputName) : nls.localize('readonlyEditorAriaLabel', "Readonly text editor."); } else { ariaLabel = inputName ? nls.localize('untitledFileEditorWithInputAriaLabel', "{0}. Untitled file text editor.", inputName) : nls.localize('untitledFileEditorAriaLabel', "Untitled file text editor."); @@ -161,7 +145,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor { protected saveState(): void { // Save View State (only for untitled) - if (this.input instanceof UntitledEditorInput) { + if (this.input instanceof UntitledTextEditorInput) { this.saveTextResourceEditorViewState(this.input); } @@ -169,7 +153,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor { } private saveTextResourceEditorViewState(input: EditorInput | undefined): void { - if (!(input instanceof UntitledEditorInput) && !(input instanceof ResourceEditorInput)) { + if (!(input instanceof UntitledTextEditorInput) && !(input instanceof ResourceEditorInput)) { return; // only enabled for untitled and resource inputs } @@ -202,11 +186,9 @@ export class TextResourceEditor extends AbstractTextResourceEditor { @IStorageService storageService: IStorageService, @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, @IThemeService themeService: IThemeService, - @ITextFileService textFileService: ITextFileService, @IEditorService editorService: IEditorService, - @IEditorGroupsService editorGroupService: IEditorGroupsService, - @IHostService hostService: IHostService + @IEditorGroupsService editorGroupService: IEditorGroupsService ) { - super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorGroupService, textFileService, editorService, hostService); + super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorGroupService, editorService); } } diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index d980297e5f..1efb60744e 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { applyDragImage } from 'vs/base/browser/dnd'; +import { applyDragImage, DataTransfers } from 'vs/base/browser/dnd'; import { addDisposableListener, Dimension, EventType } from 'vs/base/browser/dom'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { ActionsOrientation, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -41,6 +41,7 @@ import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { IFileService } from 'vs/platform/files/common/files'; import { withNullAsUndefined, withUndefinedAsNull, assertIsDefined } from 'vs/base/common/types'; import { ILabelService } from 'vs/platform/label/common/label'; +import { isFirefox } from 'vs/base/browser/browser'; export interface IToolbarActions { primary: IAction[]; @@ -266,13 +267,20 @@ export abstract class TitleControl extends Themable { } // If tabs are disabled, treat dragging as if an editor tab was dragged + let hasDataTransfer = false; if (!this.accessor.partOptions.showTabs) { const resource = this.group.activeEditor ? toResource(this.group.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }) : null; if (resource) { this.instantiationService.invokeFunction(fillResourceDataTransfers, [resource], e); + hasDataTransfer = true; } } + // Firefox: requires to set a text data transfer to get going + if (!hasDataTransfer && isFirefox) { + e.dataTransfer?.setData(DataTransfers.TEXT, String(this.group.label)); + } + // Drag Image if (this.group.activeEditor) { let label = this.group.activeEditor.getName(); @@ -280,7 +288,7 @@ export abstract class TitleControl extends Themable { label = localize('draggedEditorGroup', "{0} (+{1})", label, this.group.count - 1); } - applyDragImage(e, withUndefinedAsNull(label), 'monaco-editor-group-drag-image'); + applyDragImage(e, label, 'monaco-editor-group-drag-image'); } })); diff --git a/src/vs/workbench/browser/parts/notifications/media/notificationsActions.css b/src/vs/workbench/browser/parts/notifications/media/notificationsActions.css index 2760483447..5fcd3fe362 100644 --- a/src/vs/workbench/browser/parts/notifications/media/notificationsActions.css +++ b/src/vs/workbench/browser/parts/notifications/media/notificationsActions.css @@ -11,6 +11,7 @@ height: 22px; margin-right: 4px; margin-left: 4px; + color: inherit; background-position: center; background-repeat: no-repeat; } diff --git a/src/vs/workbench/browser/parts/notifications/media/notificationsList.css b/src/vs/workbench/browser/parts/notifications/media/notificationsList.css index ca8807406f..d25fc80a93 100644 --- a/src/vs/workbench/browser/parts/notifications/media/notificationsList.css +++ b/src/vs/workbench/browser/parts/notifications/media/notificationsList.css @@ -51,6 +51,7 @@ text-overflow: ellipsis; flex: 1; /* let the message always grow */ user-select: text; + -webkit-user-select: text; } .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-message a:focus { @@ -71,7 +72,8 @@ } .monaco-workbench .notifications-list-container .notification-list-item:hover .notification-list-item-toolbar-container, -.monaco-workbench .notifications-list-container .monaco-list-row.focused .notification-list-item .notification-list-item-toolbar-container { +.monaco-workbench .notifications-list-container .monaco-list-row.focused .notification-list-item .notification-list-item-toolbar-container, +.monaco-workbench .notifications-list-container .notification-list-item.expanded .notification-list-item-toolbar-container { display: block; } @@ -105,7 +107,8 @@ } .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-buttons-container .monaco-button { - max-width: fit-content; + width: fit-content; + width: -moz-fit-content; padding: 5px 10px; margin: 4px 5px; /* allows button focus outline to be visible */ font-size: 12px; diff --git a/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css b/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css index f6d6a712a4..44540d592f 100644 --- a/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css +++ b/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css @@ -27,10 +27,9 @@ .monaco-workbench > .notifications-toasts .notification-toast-container > .notification-toast { margin: 5px; /* enables separation and drop shadows around toasts */ - transform: translateY(100%); /* move the notification 50px to the bottom (to prevent bleed through) */ + transform: translate3d(0px, 100%, 0px); /* move the notification 50px to the bottom (to prevent bleed through) */ opacity: 0; /* fade the toast in */ transition: transform 300ms ease-out, opacity 300ms ease-out; - will-change: transform, opacity; /* force a separate layer for the toast to speed things up */ } .monaco-workbench > .notifications-toasts .notification-toast-container > .notification-toast.notification-fade-in { @@ -42,4 +41,4 @@ opacity: 1; transform: none; transition: none; -} \ No newline at end of file +} diff --git a/src/vs/workbench/browser/parts/notifications/notificationsList.ts b/src/vs/workbench/browser/parts/notifications/notificationsList.ts index c3c2b09344..cec8ad6cb6 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsList.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsList.ts @@ -73,7 +73,7 @@ export class NotificationsList extends Themable { const renderer = this.instantiationService.createInstance(NotificationRenderer, actionRunner); // List - const list = this.list = this._register(this.instantiationService.createInstance( + const list = this.list = this._register(this.instantiationService.createInstance>( WorkbenchList, 'NotificationsList', this.listContainer, @@ -82,7 +82,10 @@ export class NotificationsList extends Themable { { ...this.options, setRowLineHeight: false, - horizontalScrolling: false + horizontalScrolling: false, + overrideStyles: { + listBackground: NOTIFICATIONS_BACKGROUND + } } )); @@ -218,13 +221,13 @@ export class NotificationsList extends Themable { protected updateStyles(): void { if (this.listContainer) { const foreground = this.getColor(NOTIFICATIONS_FOREGROUND); - this.listContainer.style.color = foreground ? foreground.toString() : null; + this.listContainer.style.color = foreground ? foreground : null; const background = this.getColor(NOTIFICATIONS_BACKGROUND); - this.listContainer.style.background = background ? background.toString() : ''; + this.listContainer.style.background = background ? background : ''; const outlineColor = this.getColor(contrastBorder); - this.listContainer.style.outlineColor = outlineColor ? outlineColor.toString() : ''; + this.listContainer.style.outlineColor = outlineColor ? outlineColor : ''; } } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts index f43e42ddfb..9e05721438 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts @@ -11,7 +11,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { NotificationsList } from 'vs/workbench/browser/parts/notifications/notificationsList'; import { Event } from 'vs/base/common/event'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; -import { Themable, NOTIFICATIONS_TOAST_BORDER } from 'vs/workbench/common/theme'; +import { Themable, NOTIFICATIONS_TOAST_BORDER, NOTIFICATIONS_BACKGROUND } from 'vs/workbench/common/theme'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -421,6 +421,9 @@ export class NotificationsToasts extends Themable { protected updateStyles(): void { this.mapNotificationToToast.forEach(t => { + const backgroundColor = this.getColor(NOTIFICATIONS_BACKGROUND); + t.toast.style.background = backgroundColor ? backgroundColor : ''; + const widgetShadowColor = this.getColor(widgetShadow); t.toast.style.boxShadow = widgetShadowColor ? `0 0px 8px ${widgetShadowColor}` : ''; diff --git a/src/vs/workbench/browser/parts/panel/media/ellipsis-dark.svg b/src/vs/workbench/browser/parts/panel/media/ellipsis-dark.svg deleted file mode 100644 index 2c52e359f6..0000000000 --- a/src/vs/workbench/browser/parts/panel/media/ellipsis-dark.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/parts/panel/media/ellipsis-hc.svg b/src/vs/workbench/browser/parts/panel/media/ellipsis-hc.svg deleted file mode 100644 index 3d7068f6b4..0000000000 --- a/src/vs/workbench/browser/parts/panel/media/ellipsis-hc.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/parts/panel/media/ellipsis-light.svg b/src/vs/workbench/browser/parts/panel/media/ellipsis-light.svg deleted file mode 100644 index 883d2722ce..0000000000 --- a/src/vs/workbench/browser/parts/panel/media/ellipsis-light.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/parts/panel/media/panelpart.css b/src/vs/workbench/browser/parts/panel/media/panelpart.css index 111a3a4d58..fab8443ce6 100644 --- a/src/vs/workbench/browser/parts/panel/media/panelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/panelpart.css @@ -12,6 +12,20 @@ z-index: initial; } +.monaco-workbench.windows.chromium .part.panel, +.monaco-workbench.linux.chromium .part.panel { + /* + * Explicitly put the part onto its own layer to help Chrome to + * render the content with LCD-anti-aliasing. By partioning the + * workbench into multiple layers, we can ensure that a bad + * behaving part is not making another part fallback to greyscale + * rendering. + * + * macOS: does not render LCD-anti-aliased. + */ + transform: translate3d(0px, 0px, 0px); +} + .monaco-workbench .part.panel .title { height: 35px; display: flex; @@ -43,24 +57,14 @@ /** Panel Switcher */ -.monaco-workbench .part.panel > .title > .panel-switcher-container.composite-bar > .monaco-action-bar .action-label.toggle-more { - background-image: url('ellipsis-light.svg'); - display: block; - height: 28px; - min-width: 28px; +.monaco-workbench .part.panel > .title > .panel-switcher-container.composite-bar > .monaco-action-bar .action-label.codicon-more { + display: flex; + align-items: center; + justify-content: center; margin-left: 0px; margin-right: 0px; - background-size: 16px; - background-repeat: no-repeat; - background-position: center center; -} + color: inherit !important; -.vs-dark .monaco-workbench .part.panel > .title > .panel-switcher-container.composite-bar > .monaco-action-bar .action-label.toggle-more { - background-image: url('ellipsis-dark.svg'); -} - -.hc-black .monaco-workbench .part.panel > .title > .panel-switcher-container.composite-bar > .monaco-action-bar .action-label.toggle-more { - background-image: url('ellipsis-hc.svg'); } .monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar { @@ -110,7 +114,7 @@ text-align: center; display: inline-block; box-sizing: border-box; - } +} /** Actions */ @@ -121,7 +125,8 @@ .monaco-workbench .panel .monaco-action-bar .action-item .monaco-select-box { cursor: pointer; min-width: 110px; - margin-right: 10px; + min-height: 18px; + padding: 2px 23px 2px 8px; } /* Rotate icons when panel is on right */ diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index ecc291fd57..7200d3b4b5 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -243,14 +243,14 @@ export class NextPanelViewAction extends SwitchPanelViewAction { } const actionRegistry = Registry.as(WorkbenchExtensions.WorkbenchActions); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(TogglePanelAction, TogglePanelAction.ID, TogglePanelAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_J }), 'View: Toggle Panel', nls.localize('view', "View")); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusPanelAction, FocusPanelAction.ID, FocusPanelAction.LABEL), 'View: Focus into Panel', nls.localize('view', "View")); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleMaximizedPanelAction, ToggleMaximizedPanelAction.ID, ToggleMaximizedPanelAction.LABEL), 'View: Toggle Maximized Panel', nls.localize('view', "View")); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ClosePanelAction, ClosePanelAction.ID, ClosePanelAction.LABEL), 'View: Close Panel', nls.localize('view', "View")); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(TogglePanelPositionAction, TogglePanelPositionAction.ID, TogglePanelPositionAction.LABEL), 'View: Toggle Panel Position', nls.localize('view', "View")); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleMaximizedPanelAction, ToggleMaximizedPanelAction.ID, undefined), 'View: Toggle Panel Position', nls.localize('view', "View")); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(PreviousPanelViewAction, PreviousPanelViewAction.ID, PreviousPanelViewAction.LABEL), 'View: Previous Panel View', nls.localize('view', "View")); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(NextPanelViewAction, NextPanelViewAction.ID, NextPanelViewAction.LABEL), 'View: Next Panel View', nls.localize('view', "View")); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(TogglePanelAction, TogglePanelAction.ID, TogglePanelAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_J }), 'View: Toggle Panel', nls.localize('view', "View")); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FocusPanelAction, FocusPanelAction.ID, FocusPanelAction.LABEL), 'View: Focus into Panel', nls.localize('view', "View")); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleMaximizedPanelAction, ToggleMaximizedPanelAction.ID, ToggleMaximizedPanelAction.LABEL), 'View: Toggle Maximized Panel', nls.localize('view', "View")); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ClosePanelAction, ClosePanelAction.ID, ClosePanelAction.LABEL), 'View: Close Panel', nls.localize('view', "View")); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(TogglePanelPositionAction, TogglePanelPositionAction.ID, TogglePanelPositionAction.LABEL), 'View: Toggle Panel Position', nls.localize('view', "View")); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleMaximizedPanelAction, ToggleMaximizedPanelAction.ID, undefined), 'View: Toggle Panel Position', nls.localize('view', "View")); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(PreviousPanelViewAction, PreviousPanelViewAction.ID, PreviousPanelViewAction.LABEL), 'View: Previous Panel View', nls.localize('view', "View")); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(NextPanelViewAction, NextPanelViewAction.ID, NextPanelViewAction.LABEL), 'View: Next Panel View', nls.localize('view', "View")); MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '2_workbench_layout', diff --git a/src/vs/workbench/browser/parts/quickinput/media/quickInput.css b/src/vs/workbench/browser/parts/quickinput/media/quickInput.css index ee494b1d8b..bc07da5c68 100644 --- a/src/vs/workbench/browser/parts/quickinput/media/quickInput.css +++ b/src/vs/workbench/browser/parts/quickinput/media/quickInput.css @@ -182,6 +182,10 @@ align-items: center; } +.quick-input-list .quick-input-list-rows > .quick-input-list-row .codicon { + vertical-align: sub; +} + .quick-input-list .quick-input-list-rows .monaco-highlighted-label span { opacity: 1; } @@ -216,8 +220,7 @@ margin: 0; width: 19px; height: 100%; - background-position: center; - background-repeat: no-repeat; + vertical-align: middle; } .quick-input-list .quick-input-list-entry-action-bar { diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.ts index 61fe89a09f..b32cdd2b99 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.ts @@ -62,13 +62,20 @@ const backButton = { interface QuickInputUI { container: HTMLElement; leftActionBar: ActionBar; + titleBar: HTMLElement; title: HTMLElement; rightActionBar: ActionBar; checkAll: HTMLInputElement; + filterContainer: HTMLElement; inputBox: QuickInputBox; + visibleCountContainer: HTMLElement; visibleCount: CountBadge; + countContainer: HTMLElement; count: CountBadge; + okContainer: HTMLElement; + ok: Button; message: HTMLElement; + customButtonContainer: HTMLElement; customButton: Button; progressBar: ProgressBar; list: QuickInputList; @@ -100,12 +107,12 @@ type Visibilities = { class QuickInput extends Disposable implements IQuickInput { - private _title: string; - private _steps: number; - private _totalSteps: number; + private _title: string | undefined; + private _steps: number | undefined; + private _totalSteps: number | undefined; protected visible = false; private _enabled = true; - private _contextKey: string; + private _contextKey: string | undefined; private _busy = false; private _ignoreFocusOut = false; private _buttons: IQuickInputButton[] = []; @@ -115,7 +122,7 @@ class QuickInput extends Disposable implements IQuickInput { protected readonly visibleDisposables = this._register(new DisposableStore()); - private busyDelay: TimeoutTimer | null; + private busyDelay: TimeoutTimer | undefined; constructor( protected ui: QuickInputUI @@ -127,7 +134,7 @@ class QuickInput extends Disposable implements IQuickInput { return this._title; } - set title(title: string) { + set title(title: string | undefined) { this._title = title; this.update(); } @@ -136,7 +143,7 @@ class QuickInput extends Disposable implements IQuickInput { return this._steps; } - set step(step: number) { + set step(step: number | undefined) { this._steps = step; this.update(); } @@ -145,7 +152,7 @@ class QuickInput extends Disposable implements IQuickInput { return this._totalSteps; } - set totalSteps(totalSteps: number) { + set totalSteps(totalSteps: number | undefined) { this._totalSteps = totalSteps; this.update(); } @@ -163,7 +170,7 @@ class QuickInput extends Disposable implements IQuickInput { return this._contextKey; } - set contextKey(contextKey: string) { + set contextKey(contextKey: string | undefined) { this._contextKey = contextKey; this.update(); } @@ -248,7 +255,7 @@ class QuickInput extends Disposable implements IQuickInput { if (!this.busy && this.busyDelay) { this.ui.progressBar.stop(); this.busyDelay.cancel(); - this.busyDelay = null; + this.busyDelay = undefined; } if (this.buttonsUpdated) { this.buttonsUpdated = false; @@ -326,7 +333,7 @@ class QuickPick extends QuickInput implements IQuickPi private static readonly INPUT_BOX_ARIA_LABEL = localize('quickInputBox.ariaLabel', "Type to narrow down results."); private _value = ''; - private _placeholder: string; + private _placeholder: string | undefined; private readonly onDidChangeValueEmitter = this._register(new Emitter()); private readonly onDidAcceptEmitter = this._register(new Emitter()); private readonly onDidCustomEmitter = this._register(new Emitter()); @@ -336,6 +343,7 @@ class QuickPick extends QuickInput implements IQuickPi private _matchOnDescription = false; private _matchOnDetail = false; private _matchOnLabel = true; + private _sortByLabel = true; private _autoFocusOnList = true; private _activeItems: T[] = []; private activeItemsUpdated = false; @@ -346,15 +354,15 @@ class QuickPick extends QuickInput implements IQuickPi private selectedItemsToConfirm: T[] | null = []; private readonly onDidChangeSelectionEmitter = this._register(new Emitter()); private readonly onDidTriggerItemButtonEmitter = this._register(new Emitter>()); - private _valueSelection: Readonly<[number, number]>; + private _valueSelection: Readonly<[number, number]> | undefined; private valueSelectionUpdated = true; - private _validationMessage: string; - private _ok: boolean; - private _customButton: boolean; - private _customButtonLabel: string; - private _customButtonHover: string; + private _validationMessage: string | undefined; + private _ok = false; + private _customButton = false; + private _customButtonLabel: string | undefined; + private _customButtonHover: string | undefined; - quickNavigate: IQuickNavigateConfiguration; + quickNavigate: IQuickNavigateConfiguration | undefined; get value() { @@ -370,7 +378,7 @@ class QuickPick extends QuickInput implements IQuickPi return this._placeholder; } - set placeholder(placeholder: string) { + set placeholder(placeholder: string | undefined) { this._placeholder = placeholder; this.update(); } @@ -427,6 +435,16 @@ class QuickPick extends QuickInput implements IQuickPi this.update(); } + get sortByLabel() { + return this._sortByLabel; + } + + set sortByLabel(sortByLabel: boolean) { + this._sortByLabel = sortByLabel; + this.update(); + } + + get autoFocusOnList() { return this._autoFocusOnList; } @@ -472,7 +490,7 @@ class QuickPick extends QuickInput implements IQuickPi return this._validationMessage; } - set validationMessage(validationMessage: string) { + set validationMessage(validationMessage: string | undefined) { this._validationMessage = validationMessage; this.update(); } @@ -490,7 +508,7 @@ class QuickPick extends QuickInput implements IQuickPi return this._customButtonLabel; } - set customLabel(label: string) { + set customLabel(label: string | undefined) { this._customButtonLabel = label; this.update(); } @@ -499,7 +517,7 @@ class QuickPick extends QuickInput implements IQuickPi return this._customButtonHover; } - set customHover(hover: string) { + set customHover(hover: string | undefined) { this._customButtonHover = hover; this.update(); } @@ -654,7 +672,7 @@ class QuickPick extends QuickInput implements IQuickPi // Select element when keys are pressed that signal it const quickNavKeys = this.quickNavigate.keybindings; - const wasTriggerKeyPressed = keyCode === KeyCode.Enter || quickNavKeys.some(k => { + const wasTriggerKeyPressed = quickNavKeys.some(k => { const [firstPart, chordPart] = k.getParts(); if (chordPart) { return false; @@ -692,10 +710,11 @@ class QuickPick extends QuickInput implements IQuickPi } protected update() { - super.update(); if (!this.visible) { return; } + this.ui.setVisibilities(this.canSelectMany ? { title: !!this.title || !!this.step, checkAll: true, inputBox: true, visibleCount: true, count: true, ok: true, list: true, message: !!this.validationMessage, customButton: this.customButton } : { title: !!this.title || !!this.step, inputBox: true, visibleCount: true, list: true, message: !!this.validationMessage, customButton: this.customButton, ok: this.ok }); + super.update(); if (this.ui.inputBox.value !== this.value) { this.ui.inputBox.value = this.value; } @@ -749,14 +768,14 @@ class QuickPick extends QuickInput implements IQuickPi this.ui.message.textContent = null; this.showMessageDecoration(Severity.Ignore); } - this.ui.customButton.label = this.customLabel; - this.ui.customButton.element.title = this.customHover; + this.ui.customButton.label = this.customLabel || ''; + this.ui.customButton.element.title = this.customHover || ''; this.ui.list.matchOnDescription = this.matchOnDescription; this.ui.list.matchOnDetail = this.matchOnDetail; this.ui.list.matchOnLabel = this.matchOnLabel; + this.ui.list.sortByLabel = this.sortByLabel; this.ui.setComboboxAccessibility(true); this.ui.inputBox.setAttribute('aria-label', QuickPick.INPUT_BOX_ARIA_LABEL); - this.ui.setVisibilities(this.canSelectMany ? { title: !!this.title || !!this.step, checkAll: true, inputBox: true, visibleCount: true, count: true, ok: true, list: true, message: !!this.validationMessage } : { title: !!this.title || !!this.step, inputBox: true, visibleCount: true, list: true, message: !!this.validationMessage, customButton: this.customButton, ok: this.ok }); } } @@ -765,13 +784,13 @@ class InputBox extends QuickInput implements IInputBox { private static readonly noPromptMessage = localize('inputModeEntry', "Press 'Enter' to confirm your input or 'Escape' to cancel"); private _value = ''; - private _valueSelection: Readonly<[number, number]>; + private _valueSelection: Readonly<[number, number]> | undefined; private valueSelectionUpdated = true; - private _placeholder: string; + private _placeholder: string | undefined; private _password = false; - private _prompt: string; + private _prompt: string | undefined; private noValidationMessage = InputBox.noPromptMessage; - private _validationMessage: string; + private _validationMessage: string | undefined; private readonly onDidValueChangeEmitter = this._register(new Emitter()); private readonly onDidAcceptEmitter = this._register(new Emitter()); @@ -794,7 +813,7 @@ class InputBox extends QuickInput implements IInputBox { return this._placeholder; } - set placeholder(placeholder: string) { + set placeholder(placeholder: string | undefined) { this._placeholder = placeholder; this.update(); } @@ -812,7 +831,7 @@ class InputBox extends QuickInput implements IInputBox { return this._prompt; } - set prompt(prompt: string) { + set prompt(prompt: string | undefined) { this._prompt = prompt; this.noValidationMessage = prompt ? localize('inputModeEntryDescription', "{0} (Press 'Enter' to confirm or 'Escape' to cancel)", prompt) @@ -824,7 +843,7 @@ class InputBox extends QuickInput implements IInputBox { return this._validationMessage; } - set validationMessage(validationMessage: string) { + set validationMessage(validationMessage: string | undefined) { this._validationMessage = validationMessage; this.update(); } @@ -850,10 +869,11 @@ class InputBox extends QuickInput implements IInputBox { } protected update() { - super.update(); if (!this.visible) { return; } + this.ui.setVisibilities({ title: !!this.title || !!this.step, inputBox: true, message: true }); + super.update(); if (this.ui.inputBox.value !== this.value) { this.ui.inputBox.value = this.value; } @@ -875,7 +895,6 @@ class InputBox extends QuickInput implements IInputBox { this.ui.message.textContent = this.validationMessage; this.showMessageDecoration(Severity.Error); } - this.ui.setVisibilities({ title: !!this.title || !!this.step, inputBox: true, message: true }); } } @@ -887,14 +906,8 @@ export class QuickInputService extends Component implements IQuickInputService { private static readonly MAX_WIDTH = 600; // Max total width of quick open widget private idPrefix = 'quickInput_'; // Constant since there is still only one. - private titleBar: HTMLElement; - private filterContainer: HTMLElement; - private visibleCountContainer: HTMLElement; - private countContainer: HTMLElement; - private okContainer: HTMLElement; - private ok: Button; - private customButtonContainer: HTMLElement; - private ui: QuickInputUI; + private ui: QuickInputUI | undefined; + private dimension?: dom.Dimension; private comboboxAccessibility = false; private enabled = true; private inQuickOpenWidgets: Record = {}; @@ -925,6 +938,7 @@ export class QuickInputService extends Component implements IQuickInputService { this._register(this.quickOpenService.onShow(() => this.inQuickOpen('quickOpen', true))); this._register(this.quickOpenService.onHide(() => this.inQuickOpen('quickOpen', false))); this._register(this.layoutService.onLayout(dimension => this.layout(dimension))); + this.layout(this.layoutService.dimension); this.registerKeyModsListeners(); } @@ -1003,9 +1017,9 @@ export class QuickInputService extends Component implements IQuickInputService { })); } - private create() { + private getUI() { if (this.ui) { - return; + return this.ui; } const workbench = this.layoutService.getWorkbenchElement(); @@ -1013,14 +1027,14 @@ export class QuickInputService extends Component implements IQuickInputService { container.tabIndex = -1; container.style.display = 'none'; - this.titleBar = dom.append(container, $('.quick-input-titlebar')); + const titleBar = dom.append(container, $('.quick-input-titlebar')); - const leftActionBar = this._register(new ActionBar(this.titleBar)); + const leftActionBar = this._register(new ActionBar(titleBar)); leftActionBar.domNode.classList.add('quick-input-left-action-bar'); - const title = dom.append(this.titleBar, $('.quick-input-title')); + const title = dom.append(titleBar, $('.quick-input-title')); - const rightActionBar = this._register(new ActionBar(this.titleBar)); + const rightActionBar = this._register(new ActionBar(titleBar)); rightActionBar.domNode.classList.add('quick-input-right-action-bar'); const headerContainer = dom.append(container, $('.quick-input-header')); @@ -1038,31 +1052,31 @@ export class QuickInputService extends Component implements IQuickInputService { })); const extraContainer = dom.append(headerContainer, $('.quick-input-and-message')); - this.filterContainer = dom.append(extraContainer, $('.quick-input-filter')); + const filterContainer = dom.append(extraContainer, $('.quick-input-filter')); - const inputBox = this._register(new QuickInputBox(this.filterContainer)); + const inputBox = this._register(new QuickInputBox(filterContainer)); inputBox.setAttribute('aria-describedby', `${this.idPrefix}message`); - this.visibleCountContainer = dom.append(this.filterContainer, $('.quick-input-visible-count')); - this.visibleCountContainer.setAttribute('aria-live', 'polite'); - this.visibleCountContainer.setAttribute('aria-atomic', 'true'); - const visibleCount = new CountBadge(this.visibleCountContainer, { countFormat: localize({ key: 'quickInput.visibleCount', comment: ['This tells the user how many items are shown in a list of items to select from. The items can be anything. Currently not visible, but read by screen readers.'] }, "{0} Results") }); + const visibleCountContainer = dom.append(filterContainer, $('.quick-input-visible-count')); + visibleCountContainer.setAttribute('aria-live', 'polite'); + visibleCountContainer.setAttribute('aria-atomic', 'true'); + const visibleCount = new CountBadge(visibleCountContainer, { countFormat: localize({ key: 'quickInput.visibleCount', comment: ['This tells the user how many items are shown in a list of items to select from. The items can be anything. Currently not visible, but read by screen readers.'] }, "{0} Results") }); - this.countContainer = dom.append(this.filterContainer, $('.quick-input-count')); - this.countContainer.setAttribute('aria-live', 'polite'); - const count = new CountBadge(this.countContainer, { countFormat: localize({ key: 'quickInput.countSelected', comment: ['This tells the user how many items are selected in a list of items to select from. The items can be anything.'] }, "{0} Selected") }); + const countContainer = dom.append(filterContainer, $('.quick-input-count')); + countContainer.setAttribute('aria-live', 'polite'); + const count = new CountBadge(countContainer, { countFormat: localize({ key: 'quickInput.countSelected', comment: ['This tells the user how many items are selected in a list of items to select from. The items can be anything.'] }, "{0} Selected") }); this._register(attachBadgeStyler(count, this.themeService)); - this.okContainer = dom.append(headerContainer, $('.quick-input-action')); - this.ok = new Button(this.okContainer); - attachButtonStyler(this.ok, this.themeService); - this.ok.label = localize('ok', "OK"); - this._register(this.ok.onDidClick(e => { + const okContainer = dom.append(headerContainer, $('.quick-input-action')); + const ok = new Button(okContainer); + attachButtonStyler(ok, this.themeService); + ok.label = localize('ok', "OK"); + this._register(ok.onDidClick(e => { this.onDidAcceptEmitter.fire(); })); - this.customButtonContainer = dom.append(headerContainer, $('.quick-input-action')); - const customButton = new Button(this.customButtonContainer); + const customButtonContainer = dom.append(headerContainer, $('.quick-input-action')); + const customButton = new Button(customButtonContainer); attachButtonStyler(customButton, this.themeService); customButton.label = localize('custom', "Custom"); this._register(customButton.onDidClick(e => { @@ -1096,14 +1110,14 @@ export class QuickInputService extends Component implements IQuickInputService { })); this._register(list.onDidChangeFocus(() => { if (this.comboboxAccessibility) { - this.ui.inputBox.setAttribute('aria-activedescendant', this.ui.list.getActiveDescendant() || ''); + this.getUI().inputBox.setAttribute('aria-activedescendant', this.getUI().list.getActiveDescendant() || ''); } })); const focusTracker = dom.trackFocus(container); this._register(focusTracker); this._register(focusTracker.onDidBlur(() => { - if (!this.ui.ignoreFocusOut && !this.environmentService.args['sticky-quickopen'] && this.configurationService.getValue(CLOSE_ON_FOCUS_LOST_CONFIG)) { + if (!this.getUI().ignoreFocusOut && !this.environmentService.args['sticky-quickopen'] && this.configurationService.getValue(CLOSE_ON_FOCUS_LOST_CONFIG)) { this.hide(true); } })); @@ -1129,7 +1143,7 @@ export class QuickInputService extends Component implements IQuickInputService { } else { selectors.push('input[type=text]'); } - if (this.ui.list.isDisplayed()) { + if (this.getUI().list.isDisplayed()) { selectors.push('.monaco-list'); } const stops = container.querySelectorAll(selectors.join(', ')); @@ -1150,13 +1164,20 @@ export class QuickInputService extends Component implements IQuickInputService { this.ui = { container, leftActionBar, + titleBar, title, rightActionBar, checkAll, + filterContainer, inputBox, + visibleCountContainer, visibleCount, + countContainer, count, + okContainer, + ok, message, + customButtonContainer, customButton, progressBar, list, @@ -1174,6 +1195,7 @@ export class QuickInputService extends Component implements IQuickInputService { setContextKey: contextKey => this.setContextKey(contextKey), }; this.updateStyles(); + return this.ui; } pick>(picks: Promise[]> | QuickPickInput[], options: O = {}, token: CancellationToken = CancellationToken.None): Promise { @@ -1334,17 +1356,17 @@ export class QuickInputService extends Component implements IQuickInputService { backButton = backButton; createQuickPick(): IQuickPick { - this.create(); - return new QuickPick(this.ui); + const ui = this.getUI(); + return new QuickPick(ui); } createInputBox(): IInputBox { - this.create(); - return new InputBox(this.ui); + const ui = this.getUI(); + return new InputBox(ui); } private show(controller: QuickInput) { - this.create(); + const ui = this.getUI(); this.quickOpenService.close(); const oldController = this.controller; this.controller = controller; @@ -1353,25 +1375,26 @@ export class QuickInputService extends Component implements IQuickInputService { } this.setEnabled(true); - this.ui.leftActionBar.clear(); - this.ui.title.textContent = ''; - this.ui.rightActionBar.clear(); - this.ui.checkAll.checked = false; - // this.ui.inputBox.value = ''; Avoid triggering an event. - this.ui.inputBox.placeholder = ''; - this.ui.inputBox.password = false; - this.ui.inputBox.showDecoration(Severity.Ignore); - this.ui.visibleCount.setCount(0); - this.ui.count.setCount(0); - this.ui.message.textContent = ''; - this.ui.progressBar.stop(); - this.ui.list.setElements([]); - this.ui.list.matchOnDescription = false; - this.ui.list.matchOnDetail = false; - this.ui.list.matchOnLabel = true; - this.ui.ignoreFocusOut = false; + ui.leftActionBar.clear(); + ui.title.textContent = ''; + ui.rightActionBar.clear(); + ui.checkAll.checked = false; + // ui.inputBox.value = ''; Avoid triggering an event. + ui.inputBox.placeholder = ''; + ui.inputBox.password = false; + ui.inputBox.showDecoration(Severity.Ignore); + ui.visibleCount.setCount(0); + ui.count.setCount(0); + ui.message.textContent = ''; + ui.progressBar.stop(); + ui.list.setElements([]); + ui.list.matchOnDescription = false; + ui.list.matchOnDetail = false; + ui.list.matchOnLabel = true; + ui.list.sortByLabel = true; + ui.ignoreFocusOut = false; this.setComboboxAccessibility(false); - this.ui.inputBox.removeAttribute('aria-label'); + ui.inputBox.removeAttribute('aria-label'); const keybinding = this.keybindingService.lookupKeybinding(BackAction.ID); backButton.tooltip = keybinding ? localize('quickInput.backWithKeybinding', "Back ({0})", keybinding.getLabel()) : localize('quickInput.back', "Back"); @@ -1379,38 +1402,40 @@ export class QuickInputService extends Component implements IQuickInputService { this.inQuickOpen('quickInput', true); this.resetContextKeys(); - this.ui.container.style.display = ''; + ui.container.style.display = ''; this.updateLayout(); - this.ui.inputBox.setFocus(); + ui.inputBox.setFocus(); } private setVisibilities(visibilities: Visibilities) { - this.ui.title.style.display = visibilities.title ? '' : 'none'; - this.ui.checkAll.style.display = visibilities.checkAll ? '' : 'none'; - this.filterContainer.style.display = visibilities.inputBox ? '' : 'none'; - this.visibleCountContainer.style.display = visibilities.visibleCount ? '' : 'none'; - this.countContainer.style.display = visibilities.count ? '' : 'none'; - this.okContainer.style.display = visibilities.ok ? '' : 'none'; - this.customButtonContainer.style.display = visibilities.customButton ? '' : 'none'; - this.ui.message.style.display = visibilities.message ? '' : 'none'; - this.ui.list.display(!!visibilities.list); - this.ui.container.classList[visibilities.checkAll ? 'add' : 'remove']('show-checkboxes'); + const ui = this.getUI(); + ui.title.style.display = visibilities.title ? '' : 'none'; + ui.checkAll.style.display = visibilities.checkAll ? '' : 'none'; + ui.filterContainer.style.display = visibilities.inputBox ? '' : 'none'; + ui.visibleCountContainer.style.display = visibilities.visibleCount ? '' : 'none'; + ui.countContainer.style.display = visibilities.count ? '' : 'none'; + ui.okContainer.style.display = visibilities.ok ? '' : 'none'; + ui.customButtonContainer.style.display = visibilities.customButton ? '' : 'none'; + ui.message.style.display = visibilities.message ? '' : 'none'; + ui.list.display(!!visibilities.list); + ui.container.classList[visibilities.checkAll ? 'add' : 'remove']('show-checkboxes'); this.updateLayout(); // TODO } private setComboboxAccessibility(enabled: boolean) { if (enabled !== this.comboboxAccessibility) { + const ui = this.getUI(); this.comboboxAccessibility = enabled; if (this.comboboxAccessibility) { - this.ui.inputBox.setAttribute('role', 'combobox'); - this.ui.inputBox.setAttribute('aria-haspopup', 'true'); - this.ui.inputBox.setAttribute('aria-autocomplete', 'list'); - this.ui.inputBox.setAttribute('aria-activedescendant', this.ui.list.getActiveDescendant() || ''); + ui.inputBox.setAttribute('role', 'combobox'); + ui.inputBox.setAttribute('aria-haspopup', 'true'); + ui.inputBox.setAttribute('aria-autocomplete', 'list'); + ui.inputBox.setAttribute('aria-activedescendant', ui.list.getActiveDescendant() || ''); } else { - this.ui.inputBox.removeAttribute('role'); - this.ui.inputBox.removeAttribute('aria-haspopup'); - this.ui.inputBox.removeAttribute('aria-autocomplete'); - this.ui.inputBox.removeAttribute('aria-activedescendant'); + ui.inputBox.removeAttribute('role'); + ui.inputBox.removeAttribute('aria-haspopup'); + ui.inputBox.removeAttribute('aria-autocomplete'); + ui.inputBox.removeAttribute('aria-activedescendant'); } } } @@ -1424,16 +1449,16 @@ export class QuickInputService extends Component implements IQuickInputService { private setEnabled(enabled: boolean) { if (enabled !== this.enabled) { this.enabled = enabled; - for (const item of this.ui.leftActionBar.viewItems) { + for (const item of this.getUI().leftActionBar.viewItems) { (item as ActionViewItem).getAction().enabled = enabled; } - for (const item of this.ui.rightActionBar.viewItems) { + for (const item of this.getUI().rightActionBar.viewItems) { (item as ActionViewItem).getAction().enabled = enabled; } - this.ui.checkAll.disabled = !enabled; - // this.ui.inputBox.enabled = enabled; Avoid loosing focus. - this.ok.enabled = enabled; - this.ui.list.enabled = enabled; + this.getUI().checkAll.disabled = !enabled; + // this.getUI().inputBox.enabled = enabled; Avoid loosing focus. + this.getUI().ok.enabled = enabled; + this.getUI().list.enabled = enabled; } } @@ -1443,7 +1468,7 @@ export class QuickInputService extends Component implements IQuickInputService { this.controller = null; this.inQuickOpen('quickInput', false); this.resetContextKeys(); - this.ui.container.style.display = 'none'; + this.getUI().container.style.display = 'none'; if (!focusLost) { this.editorGroupService.activeGroup.focus(); } @@ -1453,19 +1478,19 @@ export class QuickInputService extends Component implements IQuickInputService { focus() { if (this.isDisplayed()) { - this.ui.inputBox.setFocus(); + this.getUI().inputBox.setFocus(); } } toggle() { if (this.isDisplayed() && this.controller instanceof QuickPick && this.controller.canSelectMany) { - this.ui.list.toggleCheckbox(); + this.getUI().list.toggleCheckbox(); } } navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration) { - if (this.isDisplayed() && this.ui.list.isDisplayed()) { - this.ui.list.focus(next ? 'Next' : 'Previous'); + if (this.isDisplayed() && this.getUI().list.isDisplayed()) { + this.getUI().list.focus(next ? 'Next' : 'Previous'); if (quickNavigate && this.controller instanceof QuickPick) { this.controller.quickNavigate = quickNavigate; } @@ -1488,6 +1513,7 @@ export class QuickInputService extends Component implements IQuickInputService { } layout(dimension: dom.Dimension): void { + this.dimension = dimension; this.updateLayout(); } @@ -1502,7 +1528,7 @@ export class QuickInputService extends Component implements IQuickInputService { style.marginLeft = '-' + (width / 2) + 'px'; this.ui.inputBox.layout(); - this.ui.list.layout(); + this.ui.list.layout(this.dimension && this.dimension.height * 0.4); } } @@ -1511,7 +1537,7 @@ export class QuickInputService extends Component implements IQuickInputService { if (this.ui) { // TODO const titleColor = { dark: 'rgba(255, 255, 255, 0.105)', light: 'rgba(0,0,0,.06)', hc: 'black' }[theme.type]; - this.titleBar.style.backgroundColor = titleColor ? titleColor.toString() : ''; + this.ui.titleBar.style.backgroundColor = titleColor ? titleColor.toString() : ''; this.ui.inputBox.style(theme); const quickInputBackground = theme.getColor(QUICK_INPUT_BACKGROUND); this.ui.container.style.backgroundColor = quickInputBackground ? quickInputBackground.toString() : ''; diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputActions.ts b/src/vs/workbench/browser/parts/quickinput/quickInputActions.ts index 92383601f6..f071259c66 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInputActions.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInputActions.ts @@ -14,4 +14,4 @@ import { inQuickOpenContext } from 'vs/workbench/browser/parts/quickopen/quickop KeybindingsRegistry.registerCommandAndKeybindingRule(QuickPickManyToggle); const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(BackAction, BackAction.ID, BackAction.LABEL, { primary: 0, win: { primary: KeyMod.Alt | KeyCode.LeftArrow }, mac: { primary: KeyMod.WinCtrl | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_MINUS } }, inQuickOpenContext, KeybindingWeight.WorkbenchContrib + 50), 'Back'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(BackAction, BackAction.ID, BackAction.LABEL, { primary: 0, win: { primary: KeyMod.Alt | KeyCode.LeftArrow }, mac: { primary: KeyMod.WinCtrl | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_MINUS } }, inQuickOpenContext, KeybindingWeight.WorkbenchContrib + 50), 'Back'); diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts index e8a679391a..3cef2131a5 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/quickInput'; import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list'; import * as dom from 'vs/base/browser/dom'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { WorkbenchList } from 'vs/platform/list/browser/listService'; +import { WorkbenchList, IWorkbenchListOptions } from 'vs/platform/list/browser/listService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IQuickPickItem, IQuickPickItemButtonEvent, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { IMatch } from 'vs/base/common/filters'; @@ -22,13 +22,13 @@ import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlighte import { memoize } from 'vs/base/common/decorators'; import { range } from 'vs/base/common/arrays'; import * as platform from 'vs/base/common/platform'; -import { listFocusBackground, pickerGroupBorder, pickerGroupForeground, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; +import { listFocusBackground, pickerGroupBorder, pickerGroupForeground, activeContrastBorder, listFocusForeground } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action } from 'vs/base/common/actions'; import { getIconClass } from 'vs/workbench/browser/parts/quickinput/quickInputUtils'; -import { IListOptions } from 'vs/base/browser/ui/list/listWidget'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; const $ = dom.$; @@ -44,9 +44,9 @@ interface IListElement { } class ListElement implements IListElement { - index: number; - item: IQuickPickItem; - saneLabel: string; + index!: number; + item!: IQuickPickItem; + saneLabel!: string; saneDescription?: string; saneDetail?: string; hidden = false; @@ -66,7 +66,7 @@ class ListElement implements IListElement { labelHighlights?: IMatch[]; descriptionHighlights?: IMatch[]; detailHighlights?: IMatch[]; - fireButtonTriggered: (event: IQuickPickItemButtonEvent) => void; + fireButtonTriggered!: (event: IQuickPickItemButtonEvent) => void; constructor(init: IListElement) { assign(this, init); @@ -216,12 +216,13 @@ export class QuickInputList { readonly id: string; private container: HTMLElement; private list: WorkbenchList; - private inputElements: Array; + private inputElements: Array = []; private elements: ListElement[] = []; private elementsToIndexes = new Map(); matchOnDescription = false; matchOnDetail = false; matchOnLabel = true; + sortByLabel = true; private readonly _onChangedAllVisibleChecked = new Emitter(); onChangedAllVisibleChecked: Event = this._onChangedAllVisibleChecked.event; private readonly _onChangedCheckedCount = new Emitter(); @@ -251,8 +252,11 @@ export class QuickInputList { openController: { shouldOpen: () => false }, // Workaround #58124 setRowLineHeight: false, multipleSelectionSupport: false, - horizontalScrolling: false - } as IListOptions); + horizontalScrolling: false, + overrideStyles: { + listBackground: SIDE_BAR_BACKGROUND + } + } as IWorkbenchListOptions); this.list.getHTMLElement().id = id; this.disposables.push(this.list); this.disposables.push(this.list.onKeyDown(e => { @@ -468,18 +472,19 @@ export class QuickInputList { this.list.domFocus(); } - layout(): void { + layout(maxHeight?: number): void { + this.list.getHTMLElement().style.maxHeight = maxHeight ? `calc(${Math.floor(maxHeight / 44) * 44}px)` : ''; this.list.layout(); } filter(query: string) { - if (!(this.matchOnLabel || this.matchOnDescription || this.matchOnDetail)) { + if (!(this.sortByLabel || this.matchOnLabel || this.matchOnDescription || this.matchOnDetail)) { return; } query = query.trim(); // Reset filtering - if (!query) { + if (!query || !(this.matchOnLabel || this.matchOnDescription || this.matchOnDetail)) { this.elements.forEach(element => { element.labelHighlights = undefined; element.descriptionHighlights = undefined; @@ -515,7 +520,7 @@ export class QuickInputList { const shownElements = this.elements.filter(element => !element.hidden); // Sort by value - if (query) { + if (this.sortByLabel && query) { const normalizedSearchValue = query.toLowerCase(); shownElements.sort((a, b) => { return compareEntries(a, b, normalizedSearchValue); @@ -590,18 +595,21 @@ function compareEntries(elementA: ListElement, elementB: ListElement, lookFor: s } registerThemingParticipant((theme, collector) => { + // Override inactive focus foreground with active focus foreground for single-pick case. + const listInactiveFocusForeground = theme.getColor(listFocusForeground); + if (listInactiveFocusForeground) { + collector.addRule(`.quick-input-list .monaco-list .monaco-list-row.focused { color: ${listInactiveFocusForeground}; }`); + } // Override inactive focus background with active focus background for single-pick case. const listInactiveFocusBackground = theme.getColor(listFocusBackground); if (listInactiveFocusBackground) { collector.addRule(`.quick-input-list .monaco-list .monaco-list-row.focused { background-color: ${listInactiveFocusBackground}; }`); collector.addRule(`.quick-input-list .monaco-list .monaco-list-row.focused:hover { background-color: ${listInactiveFocusBackground}; }`); } + // dotted instead of solid (as in listWidget.ts) to match QuickOpen const activeContrast = theme.getColor(activeContrastBorder); if (activeContrast) { - collector.addRule(`.quick-input-list .monaco-list .monaco-list-row.focused { border: 1px dotted ${activeContrast}; }`); - collector.addRule(`.quick-input-list .monaco-list .monaco-list-row { border: 1px solid transparent; }`); - collector.addRule(`.quick-input-list .monaco-list .quick-input-list-entry { padding: 0 5px; height: 18px; align-items: center; }`); - collector.addRule(`.quick-input-list .monaco-list .quick-input-list-entry-action-bar { margin-top: 0; }`); + collector.addRule(`.quick-input-list .monaco-list .monaco-list-row.focused { outline: 1px dotted ${activeContrast}; outline-offset: -1px; }`); } const pickerGroupBorderColor = theme.getColor(pickerGroupBorder); if (pickerGroupBorderColor) { diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenActions.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenActions.ts index 58bda0bffc..e0da651de7 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenActions.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickOpenActions.ts @@ -69,11 +69,11 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: QUICKOPEN_ACTION_ID, title: { value: QUICKOPEN_ACION_LABEL, original: 'Go to File...' } } }); -registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenSelectNextAction, QuickOpenSelectNextAction.ID, QuickOpenSelectNextAction.LABEL, { primary: 0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_N } }, inQuickOpenContext, KeybindingWeight.WorkbenchContrib + 50), 'Select Next in Quick Open'); -registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenSelectPreviousAction, QuickOpenSelectPreviousAction.ID, QuickOpenSelectPreviousAction.LABEL, { primary: 0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_P } }, inQuickOpenContext, KeybindingWeight.WorkbenchContrib + 50), 'Select Previous in Quick Open'); -registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenNavigateNextAction, QuickOpenNavigateNextAction.ID, QuickOpenNavigateNextAction.LABEL), 'Navigate Next in Quick Open'); -registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenNavigatePreviousAction, QuickOpenNavigatePreviousAction.ID, QuickOpenNavigatePreviousAction.LABEL), 'Navigate Previous in Quick Open'); -registry.registerWorkbenchAction(new SyncActionDescriptor(RemoveFromEditorHistoryAction, RemoveFromEditorHistoryAction.ID, RemoveFromEditorHistoryAction.LABEL), 'Remove From History'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenSelectNextAction, QuickOpenSelectNextAction.ID, QuickOpenSelectNextAction.LABEL, { primary: 0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_N } }, inQuickOpenContext, KeybindingWeight.WorkbenchContrib + 50), 'Select Next in Quick Open'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenSelectPreviousAction, QuickOpenSelectPreviousAction.ID, QuickOpenSelectPreviousAction.LABEL, { primary: 0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_P } }, inQuickOpenContext, KeybindingWeight.WorkbenchContrib + 50), 'Select Previous in Quick Open'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenNavigateNextAction, QuickOpenNavigateNextAction.ID, QuickOpenNavigateNextAction.LABEL), 'Navigate Next in Quick Open'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenNavigatePreviousAction, QuickOpenNavigatePreviousAction.ID, QuickOpenNavigatePreviousAction.LABEL), 'Navigate Previous in Quick Open'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(RemoveFromEditorHistoryAction, RemoveFromEditorHistoryAction.ID, RemoveFromEditorHistoryAction.LABEL), 'Remove From History'); const quickOpenNavigateNextInFilePickerId = 'workbench.action.quickOpenNavigateNextInFilePicker'; KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -98,4 +98,4 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ primary: globalQuickOpenKeybinding.mac.primary | KeyMod.Shift, secondary: undefined } -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts index 26fd0012cb..c8cc3ddfc7 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts @@ -16,7 +16,7 @@ import { Mode, IEntryRunContext, IAutoFocus, IQuickNavigateConfiguration, IModel import { QuickOpenEntry, QuickOpenModel, QuickOpenEntryGroup, QuickOpenItemAccessorClass } from 'vs/base/parts/quickopen/browser/quickOpenModel'; import { QuickOpenWidget, HideReason } from 'vs/base/parts/quickopen/browser/quickOpenWidget'; import { ContributableActionProvider } from 'vs/workbench/browser/actions'; -import { ITextFileService, AutoSaveMode } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { Registry } from 'vs/platform/registry/common/platform'; import { IResourceInput } from 'vs/platform/editor/common/editor'; import { IModeService } from 'vs/editor/common/services/modeService'; @@ -51,6 +51,7 @@ import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/commo import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; const HELP_PREFIX = '?'; @@ -723,7 +724,7 @@ export class EditorHistoryEntryGroup extends QuickOpenEntryGroup { export class EditorHistoryEntry extends EditorQuickOpenEntry { private input: IEditorInput | IResourceInput; private resource: URI | undefined; - private label: string | undefined; + private label: string; private description?: string; private dirty: boolean; @@ -735,7 +736,8 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry { @ITextFileService private readonly textFileService: ITextFileService, @IConfigurationService private readonly configurationService: IConfigurationService, @ILabelService labelService: ILabelService, - @IFileService fileService: IFileService + @IFileService fileService: IFileService, + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService ) { super(editorService); @@ -743,7 +745,7 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry { if (input instanceof EditorInput) { this.resource = resourceForEditorHistory(input, fileService); - this.label = types.withNullAsUndefined(input.getName()); + this.label = input.getName(); this.description = input.getDescription(); this.dirty = input.isDirty(); } else { @@ -753,7 +755,7 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry { this.description = labelService.getUriLabel(resources.dirname(this.resource), { relative: true }); this.dirty = this.resource && this.textFileService.isDirty(this.resource); - if (this.dirty && this.textFileService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) { + if (this.dirty && this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) { this.dirty = false; // no dirty decoration if auto save is on with a short timeout } } @@ -763,7 +765,7 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry { return this.dirty ? 'dirty' : ''; } - getLabel(): string | undefined { + getLabel(): string { return this.label; } diff --git a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css index 457943041c..1ed0387de3 100644 --- a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css +++ b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css @@ -3,16 +3,30 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .sidebar > .content { +.monaco-workbench.windows.chromium .part.sidebar, +.monaco-workbench.linux.chromium .part.sidebar { + /* + * Explicitly put the part onto its own layer to help Chrome to + * render the content with LCD-anti-aliasing. By partioning the + * workbench into multiple layers, we can ensure that a bad + * behaving part is not making another part fallback to greyscale + * rendering. + * + * macOS: does not render LCD-anti-aliased. + */ + transform: translate3d(0px, 0px, 0px); +} + +.monaco-workbench .part.sidebar > .content { overflow: hidden; } -.monaco-workbench.nosidebar > .sidebar { +.monaco-workbench.nosidebar > .part.sidebar { display: none !important; visibility: hidden !important; } -.monaco-workbench .sidebar > .title > .title-label h2 { +.monaco-workbench .part.sidebar > .title > .title-label h2 { text-transform: uppercase; } @@ -54,4 +68,4 @@ background-repeat: no-repeat; width: 16px; height: 16px; -} \ No newline at end of file +} diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index 6c7524f61c..af7a7106d4 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -320,7 +320,7 @@ class FocusSideBarAction extends Action { } const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(FocusSideBarAction, FocusSideBarAction.ID, FocusSideBarAction.LABEL, { +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusSideBarAction, FocusSideBarAction.ID, FocusSideBarAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_0 }), 'View: Focus into Side Bar', nls.localize('viewCategory', "View")); diff --git a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css index 035487680f..d27bc7a9f8 100644 --- a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css +++ b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css @@ -13,6 +13,20 @@ overflow: visible; } +.monaco-workbench.windows.chromium .part.statusbar, +.monaco-workbench.linux.chromium .part.statusbar { + /* + * Explicitly put the part onto its own layer to help Chrome to + * render the content with LCD-anti-aliasing. By partioning the + * workbench into multiple layers, we can ensure that a bad + * behaving part is not making another part fallback to greyscale + * rendering. + * + * macOS: does not render LCD-anti-aliased. + */ + transform: translate3d(0px, 0px, 0px); +} + .monaco-workbench .part.statusbar.status-border-top::after { content: ''; position: absolute; @@ -28,15 +42,16 @@ .monaco-workbench .part.statusbar > .left-items, .monaco-workbench .part.statusbar > .right-items { display: flex; - flex-wrap: wrap; /* individual entries should not shrink since we do not control their content */ } .monaco-workbench .part.statusbar > .right-items { - flex-direction: row-reverse; /* ensures that the most right elements wrap last when space is little */ + flex-direction: row-reverse; + flex-wrap: wrap /* ensures that the most right elements wrap last when space is little */; } .monaco-workbench .part.statusbar > .left-items { flex-grow: 1; /* left items push right items to the far right end */ + overflow: hidden; /* Hide the overflowing entries */ } .monaco-workbench .part.statusbar > .items-container > .statusbar-item { @@ -90,10 +105,11 @@ .monaco-workbench .part.statusbar > .items-container > .statusbar-item > a { cursor: pointer; - display: inline-block; + display: flex; height: 100%; padding: 0 5px 0 5px; white-space: pre; /* gives some degree of styling */ + align-items: center } .monaco-workbench .part.statusbar > .items-container > .statusbar-item > a:hover { diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 5be6b7b2e0..9e16b57eac 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -33,6 +33,7 @@ import { ToggleStatusbarVisibilityAction } from 'vs/workbench/browser/actions/la import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { values } from 'vs/base/common/map'; import { assertIsDefined } from 'vs/base/common/types'; +import { Emitter, Event } from 'vs/base/common/event'; interface IPendingStatusbarEntry { id: string; @@ -60,6 +61,9 @@ class StatusbarViewModel extends Disposable { private hidden!: Set; + private readonly _onDidChangeEntryVisibility = this._register(new Emitter<{ id: string, visible: boolean }>()); + readonly onDidChangeEntryVisibility = this._onDidChangeEntryVisibility.event; + constructor(private storageService: IStorageService) { super(); @@ -117,7 +121,7 @@ class StatusbarViewModel extends Disposable { if (changed.size > 0) { this._entries.forEach(entry => { if (changed.has(entry.id)) { - this.updateVisibility(entry.id); + this.updateVisibility(entry.id, true); changed.delete(entry.id); } @@ -130,7 +134,7 @@ class StatusbarViewModel extends Disposable { this._entries.push(entry); // intentionally not using a map here since multiple entries can have the same ID! // Update visibility directly - this.updateVisibility(entry); + this.updateVisibility(entry, false); // Sort according to priority this.sort(); @@ -159,7 +163,7 @@ class StatusbarViewModel extends Disposable { if (!this.hidden.has(id)) { this.hidden.add(id); - this.updateVisibility(id); + this.updateVisibility(id, true); this.saveState(); } @@ -169,7 +173,7 @@ class StatusbarViewModel extends Disposable { if (this.hidden.has(id)) { this.hidden.delete(id); - this.updateVisibility(id); + this.updateVisibility(id, true); this.saveState(); } @@ -183,9 +187,9 @@ class StatusbarViewModel extends Disposable { return this._entries.filter(entry => entry.alignment === alignment); } - private updateVisibility(id: string): void; - private updateVisibility(entry: IStatusbarViewModelEntry): void; - private updateVisibility(arg1: string | IStatusbarViewModelEntry): void { + private updateVisibility(id: string, trigger: boolean): void; + private updateVisibility(entry: IStatusbarViewModelEntry, trigger: boolean): void; + private updateVisibility(arg1: string | IStatusbarViewModelEntry, trigger: boolean): void { // By identifier if (typeof arg1 === 'string') { @@ -193,7 +197,7 @@ class StatusbarViewModel extends Disposable { for (const entry of this._entries) { if (entry.id === id) { - this.updateVisibility(entry); + this.updateVisibility(entry, trigger); } } } @@ -210,6 +214,10 @@ class StatusbarViewModel extends Disposable { show(entry.container); } + if (trigger) { + this._onDidChangeEntryVisibility.fire({ id: entry.id, visible: !isHidden }); + } + // Mark first/last visible entry this.markFirstLastVisibleEntry(); } @@ -341,6 +349,8 @@ export class StatusbarPart extends Part implements IStatusbarService { private leftItemsContainer: HTMLElement | undefined; private rightItemsContainer: HTMLElement | undefined; + readonly onDidChangeEntryVisibility: Event<{ id: string, visible: boolean }>; + constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, @@ -352,6 +362,7 @@ export class StatusbarPart extends Part implements IStatusbarService { super(Parts.STATUSBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); this.viewModel = this._register(new StatusbarViewModel(storageService)); + this.onDidChangeEntryVisibility = this.viewModel.onDidChangeEntryVisibility; this.registerListeners(); } @@ -422,6 +433,10 @@ export class StatusbarPart extends Part implements IStatusbarService { }; } + isEntryVisible(id: string): boolean { + return !this.viewModel.isHidden(id); + } + updateEntryVisibility(id: string, visible: boolean): void { if (visible) { this.viewModel.show(id); diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 3a2504b0d0..efc5f85d4d 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -12,12 +12,29 @@ align-items: center; justify-content: center; user-select: none; + -webkit-user-select: none; zoom: 1; /* prevent zooming */ line-height: 22px; height: 22px; display: flex; } +.monaco-workbench.windows .part.titlebar, +.monaco-workbench.linux .part.titlebar { + /* + * Explicitly put the part onto its own layer to help Chrome to + * render the content with LCD-anti-aliasing. By partioning the + * workbench into multiple layers, we can ensure that a bad + * behaving part is not making another part fallback to greyscale + * rendering. + * + * macOS: does not render LCD-anti-aliased. + */ + transform: translate3d(0px, 0px, 0px); + position: relative; + z-index: 1000; /* move the entire titlebar above the workbench, except modals/dialogs */ +} + .monaco-workbench .part.titlebar > .titlebar-drag-region { top: 0; left: 0; @@ -25,10 +42,14 @@ position: absolute; width: 100%; height: 100%; - z-index: -1; -webkit-app-region: drag; } +.monaco-workbench .part.titlebar > .menubar { + /* Move above drag region since negative z-index on that element causes AA issues */ + z-index: 1; +} + .monaco-workbench .part.titlebar > .window-title { flex: 0 1 auto; font-size: 12px; @@ -67,7 +88,7 @@ position: absolute; top: 0; width: 100%; - height: 20%; + height: 4px; } .monaco-workbench.windows.fullscreen .part.titlebar > .resizer, @@ -79,7 +100,7 @@ width: 35px; height: 100%; position: relative; - z-index: 3000; + z-index: 2; /* highest level of titlebar */ background-image: url('code-icon.svg'); background-repeat: no-repeat; background-position: center center; @@ -97,11 +118,12 @@ flex-shrink: 0; text-align: center; position: relative; - z-index: 3000; + z-index: 2; /* highest level of titlebar */ -webkit-app-region: no-drag; height: 100%; width: 138px; margin-left: auto; + transform: translate3d(0px, 0px, 0px); } .monaco-workbench.fullscreen .part.titlebar > .window-controls-container { diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 2529e54f95..70a7bb13c3 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -12,7 +12,7 @@ import { IAction, Action } from 'vs/base/common/actions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import * as DOM from 'vs/base/browser/dom'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { isMacintosh, isWeb } from 'vs/base/common/platform'; +import { isMacintosh, isWeb, isIOS } from 'vs/base/common/platform'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -42,6 +42,7 @@ import { IElectronService } from 'vs/platform/electron/node/electron'; import { optional } from 'vs/platform/instantiation/common/instantiation'; // tslint:disable-next-line: import-patterns layering TODO@sbatten import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; +import { BrowserFeatures } from 'vs/base/browser/canIUse'; export abstract class MenubarControl extends Disposable { @@ -294,6 +295,7 @@ export class CustomMenubarControl extends MenubarControl { @IThemeService private readonly themeService: IThemeService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IHostService protected readonly hostService: IHostService, + @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, @optional(IElectronService) private readonly electronService: IElectronService, @optional(IElectronEnvironmentService) private readonly electronEnvironmentService: IElectronEnvironmentService ) { @@ -445,9 +447,8 @@ export class CustomMenubarControl extends MenubarControl { return null; case StateType.Idle: - const context = `window:${this.electronEnvironmentService ? this.electronEnvironmentService.windowId : 'any'}`; return new Action('update.check', nls.localize({ key: 'checkForUpdates', comment: ['&& denotes a mnemonic'] }, "Check for &&Updates..."), undefined, true, () => - this.updateService.checkForUpdates(context)); + this.updateService.checkForUpdates(this.workbenchEnvironmentService.configuration.sessionId)); case StateType.CheckingForUpdates: return new Action('update.checking', nls.localize('checkingForUpdates', "Checking for Updates..."), undefined, false); @@ -674,7 +675,7 @@ export class CustomMenubarControl extends MenubarControl { } this._register(DOM.addDisposableListener(window, DOM.EventType.RESIZE, () => { - if (this.menubar) { + if (this.menubar && !(isIOS && BrowserFeatures.pointerEvents)) { this.menubar.blur(); } })); diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 3b01b0f837..97fac3d7f7 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -20,7 +20,7 @@ import { EditorInput, toResource, Verbosity, SideBySideEditor } from 'vs/workben import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; -import { TITLE_BAR_ACTIVE_BACKGROUND, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_BACKGROUND, TITLE_BAR_BORDER } from 'vs/workbench/common/theme'; +import { TITLE_BAR_ACTIVE_BACKGROUND, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_BACKGROUND, TITLE_BAR_BORDER, WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme'; import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { Color } from 'vs/base/common/color'; @@ -39,12 +39,12 @@ import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/m import { IMenuService, IMenu, MenuId } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; // TODO@sbatten https://github.com/microsoft/vscode/issues/81360 // tslint:disable-next-line: import-patterns layering import { IElectronService } from 'vs/platform/electron/node/electron'; -// tslint:disable-next-line: import-patterns layering -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; export class TitlebarPart extends Part implements ITitleService { @@ -104,8 +104,8 @@ export class TitlebarPart extends Part implements ITitleService { @IMenuService menuService: IMenuService, @IContextKeyService contextKeyService: IContextKeyService, @IHostService private readonly hostService: IHostService, - @optional(IElectronService) private electronService: IElectronService, - @optional(IElectronEnvironmentService) private readonly electronEnvironmentService: IElectronEnvironmentService + @IProductService private readonly productService: IProductService, + @optional(IElectronService) private electronService: IElectronService ) { super(Parts.TITLEBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); @@ -176,7 +176,7 @@ export class TitlebarPart extends Part implements ITitleService { } private onMenubarFocusChanged(focused: boolean) { - if (!isWeb && (isWindows || isLinux) && this.currentMenubarVisibility === 'compact' && this.dragRegion) { + if (!isWeb && (isWindows || isLinux) && this.currentMenubarVisibility !== 'compact' && this.dragRegion) { if (focused) { hide(this.dragRegion); } else { @@ -207,7 +207,7 @@ export class TitlebarPart extends Part implements ITitleService { // Always set the native window title to identify us properly to the OS let nativeTitle = title; if (!trim(nativeTitle)) { - nativeTitle = this.environmentService.appNameLong; + nativeTitle = this.productService.nameLong; } window.document.title = nativeTitle; @@ -229,15 +229,15 @@ export class TitlebarPart extends Part implements ITitleService { let title = this.doGetWindowTitle(); if (this.properties.isAdmin) { - title = `${title || this.environmentService.appNameLong} ${TitlebarPart.NLS_USER_IS_ADMIN}`; + title = `${title || this.productService.nameLong} ${TitlebarPart.NLS_USER_IS_ADMIN}`; } if (!this.properties.isPure) { - title = `${title || this.environmentService.appNameLong} ${TitlebarPart.NLS_UNSUPPORTED}`; + title = `${title || this.productService.nameLong} ${TitlebarPart.NLS_UNSUPPORTED}`; } if (this.environmentService.isExtensionDevelopment) { - title = `${TitlebarPart.NLS_EXTENSION_HOST} - ${title || this.environmentService.appNameLong}`; + title = `${TitlebarPart.NLS_EXTENSION_HOST} - ${title || this.productService.nameLong}`; } return title; @@ -317,8 +317,8 @@ export class TitlebarPart extends Part implements ITitleService { const folderName = folder ? folder.name : ''; const folderPath = folder ? this.labelService.getUriLabel(folder.uri) : ''; const dirty = editor?.isDirty() ? TitlebarPart.TITLE_DIRTY : ''; - const appName = this.environmentService.appNameLong; - const remoteName = this.environmentService.configuration.remoteAuthority; + const appName = this.productService.nameLong; + const remoteName = this.labelService.getHostLabel(REMOTE_HOST_SCHEME, this.environmentService.configuration.remoteAuthority); const separator = TitlebarPart.TITLE_SEPARATOR; const titleTemplate = this.configurationService.getValue('window.title'); @@ -445,13 +445,8 @@ export class TitlebarPart extends Part implements ITitleService { // Resizer this.resizer = append(this.element, $('div.resizer')); - const isMaximized = this.environmentService.configuration.maximized ? true : false; - this.onDidChangeMaximized(isMaximized); - - this._register(Event.any( - Event.map(Event.filter(this.electronService.onWindowMaximize, id => id === this.electronEnvironmentService.windowId), _ => true), - Event.map(Event.filter(this.electronService.onWindowUnmaximize, id => id === this.electronEnvironmentService.windowId), _ => false) - )(e => this.onDidChangeMaximized(e))); + this._register(this.layoutService.onMaximizeChange(maximized => this.onDidChangeMaximized(maximized))); + this.onDidChangeMaximized(this.layoutService.isWindowMaximized()); } // Since the title area is used to drag the window, we do not want to steal focus from the @@ -507,7 +502,13 @@ export class TitlebarPart extends Part implements ITitleService { removeClass(this.element, 'inactive'); } - const titleBackground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_BACKGROUND : TITLE_BAR_ACTIVE_BACKGROUND) || ''; + const titleBackground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_BACKGROUND : TITLE_BAR_ACTIVE_BACKGROUND, (color, theme) => { + // LCD Rendering Support: the title bar part is a defining its own GPU layer. + // To benefit from LCD font rendering, we must ensure that we always set an + // opaque background color. As such, we compute an opaque color given we know + // the background color is the workbench background. + return color.isOpaque() ? color : color.makeOpaque(WORKBENCH_BACKGROUND(theme)); + }) || ''; this.element.style.backgroundColor = titleBackground; if (titleBackground && Color.fromHex(titleBackground).isLighter()) { addClass(this.element, 'light'); diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index b2616e10a6..4a0c521ebd 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -26,10 +26,10 @@ import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels'; import { ActionBar, IActionViewItemProvider, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { URI } from 'vs/base/common/uri'; import { dirname, basename } from 'vs/base/common/resources'; -import { LIGHT, FileThemeIcon, FolderThemeIcon, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { LIGHT, FileThemeIcon, FolderThemeIcon, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { FileKind } from 'vs/platform/files/common/files'; import { WorkbenchAsyncDataTree, TreeResourceNavigator2 } from 'vs/platform/list/browser/listService'; -import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet'; import { localize } from 'vs/nls'; import { timeout } from 'vs/base/common/async'; import { textLinkForeground, textCodeBlockBackground, focusBorder, listFilterMatchHighlight, listFilterMatchHighlightBorder } from 'vs/platform/theme/common/colorRegistry'; @@ -41,8 +41,9 @@ import { ITreeRenderer, ITreeNode, IAsyncDataSource, ITreeContextMenuEvent } fro import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; -export class CustomTreeViewPanel extends ViewletPanel { +export class CustomTreeViewPane extends ViewletPane { private treeView: ITreeView; @@ -54,7 +55,7 @@ export class CustomTreeViewPanel extends ViewletPanel { @IConfigurationService configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService, ) { - super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewletPaneOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService); const { treeView } = (Registry.as(Extensions.ViewsRegistry).getView(options.id)); this.treeView = treeView; this._register(this.treeView.onDidChangeActions(() => this.updateActions(), this)); @@ -403,6 +404,9 @@ export class CustomTreeView extends Disposable implements ITreeView { return e.collapsibleState !== TreeItemCollapsibleState.Expanded; }, multipleSelectionSupport: this.canSelectMany, + overrideStyles: { + listBackground: SIDE_BAR_BACKGROUND + } }) as WorkbenchAsyncDataTree); aligner.tree = this.tree; const actionRunner = new MultipleSelectionActionRunner(this.notificationService, () => this.tree!.getSelection()); @@ -610,8 +614,7 @@ export class CustomTreeView extends Disposable implements ITreeView { const tree = this.tree; if (tree && this.visible) { this.refreshing = true; - await Promise.all(elements.map(element => tree.updateChildren(element, true))); - elements.map(element => tree.rerender(element)); + await Promise.all(elements.map(element => tree.updateChildren(element, true, true))); this.refreshing = false; this.updateContentAreas(); if (this.focused) { @@ -777,16 +780,27 @@ class TreeRenderer extends Disposable implements ITreeRenderer('explorer.decorations'); templateData.resourceLabel.setResource({ name: label, description, resource: resource ? resource : URI.parse('missing:_icon_resource') }, { fileKind: this.getFileKind(node), title, hideIcon: !!iconUrl, fileDecorations, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches: matches ? matches : createMatches(element.filterData) }); } else { templateData.resourceLabel.setResource({ name: label, description }, { title, hideIcon: true, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches: matches ? matches : createMatches(element.filterData) }); } - templateData.icon.style.backgroundImage = iconUrl ? DOM.asCSSUrl(iconUrl) : ''; templateData.icon.title = title ? title : ''; - DOM.toggleClass(templateData.icon, 'custom-view-tree-node-item-icon', !!iconUrl); + + if (iconUrl) { + templateData.icon.className = 'custom-view-tree-node-item-icon'; + templateData.icon.style.backgroundImage = DOM.asCSSUrl(iconUrl); + + } else { + let iconClass: string | undefined; + if (node.themeIcon && !this.isFileKindThemeIcon(node.themeIcon)) { + iconClass = ThemeIcon.asClassName(node.themeIcon); + } + templateData.icon.className = iconClass ? `custom-view-tree-node-item-icon ${iconClass}` : ''; + } + templateData.actionBar.context = { $treeViewId: this.treeViewId, $treeItemHandle: node.handle }; templateData.actionBar.push(this.menus.getResourceActions(node), { icon: true, label: false }); if (this._actionRunner) { @@ -800,6 +814,14 @@ class TreeRenderer extends Disposable implements ITreeRenderer .panel > .panel-header { - border-top: none !important; /* less clutter: do not show any border for first views in a panel */ +.monaco-pane-view .split-view-view:first-of-type > .pane > .pane-header { + border-top: none !important; /* less clutter: do not show any border for first views in a pane */ } -.monaco-panel-view .panel > .panel-header > .actions.show { +.monaco-pane-view .pane > .pane-header > .actions.show { display: initial; } -.monaco-panel-view .panel > .panel-header h3.title { +.monaco-pane-view .pane > .pane-header h3.title { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; diff --git a/src/vs/workbench/browser/parts/views/media/views.css b/src/vs/workbench/browser/parts/views/media/views.css index fd1b45ad78..9e1d51f2af 100644 --- a/src/vs/workbench/browser/parts/views/media/views.css +++ b/src/vs/workbench/browser/parts/views/media/views.css @@ -32,7 +32,7 @@ .file-icon-themable-tree.hide-arrows .monaco-tl-twistie:not(.force-twistie) { background-image: none !important; width: 0 !important; - margin-right: 0 !important; + padding-right: 0 !important; visibility: hidden; } @@ -44,8 +44,9 @@ .monaco-workbench .tree-explorer-viewlet-tree-view .message { display: flex; - padding: 4px 12px 0px 18px; - user-select: text + padding: 4px 12px 4px 18px; + user-select: text; + -webkit-user-select: text; } .monaco-workbench .tree-explorer-viewlet-tree-view .message p { @@ -94,6 +95,11 @@ padding-left: 3px; } +.customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .monaco-inputbox { + line-height: normal; + flex: 1; +} + .customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .custom-view-tree-node-item-resourceLabel { flex: 1; text-overflow: ellipsis; @@ -108,6 +114,11 @@ width: 16px; height: 22px; -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item > .custom-view-tree-node-item-icon.codicon { + margin-top: 3px; } .customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .custom-view-tree-node-item-resourceLabel .monaco-icon-label-description-container { diff --git a/src/vs/workbench/browser/parts/views/panelViewlet.ts b/src/vs/workbench/browser/parts/views/paneViewlet.ts similarity index 71% rename from src/vs/workbench/browser/parts/views/panelViewlet.ts rename to src/vs/workbench/browser/parts/views/paneViewlet.ts index 1d486b812c..1a445057df 100644 --- a/src/vs/workbench/browser/parts/views/panelViewlet.ts +++ b/src/vs/workbench/browser/parts/views/paneViewlet.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/panelviewlet'; +import 'vs/css!./media/paneviewlet'; import * as nls from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; @@ -22,7 +22,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { PanelView, IPanelViewOptions, IPanelOptions, Panel } from 'vs/base/browser/ui/splitview/panelview'; +import { PaneView, IPaneViewOptions, IPaneOptions, Pane } from 'vs/base/browser/ui/splitview/paneview'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; @@ -31,21 +31,21 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { assertIsDefined } from 'vs/base/common/types'; -export interface IPanelColors extends IColorMapping { +export interface IPaneColors extends IColorMapping { dropBackground?: ColorIdentifier; headerForeground?: ColorIdentifier; headerBackground?: ColorIdentifier; headerBorder?: ColorIdentifier; } -export interface IViewletPanelOptions extends IPanelOptions { +export interface IViewletPaneOptions extends IPaneOptions { actionRunner?: IActionRunner; id: string; title: string; showActionsAlways?: boolean; } -export abstract class ViewletPanel extends Panel implements IView { +export abstract class ViewletPane extends Pane implements IView { private static readonly AlwaysShowActionsConfig = 'workbench.view.alwaysShowHeaderActions'; @@ -65,7 +65,7 @@ export abstract class ViewletPanel extends Panel implements IView { private _isVisible: boolean = false; readonly id: string; - readonly title: string; + title: string; protected actionRunner?: IActionRunner; protected toolbar?: ToolBar; @@ -75,7 +75,7 @@ export abstract class ViewletPanel extends Panel implements IView { protected twistiesContainer?: HTMLElement; constructor( - options: IViewletPanelOptions, + options: IViewletPaneOptions, @IKeybindingService protected keybindingService: IKeybindingService, @IContextMenuService protected contextMenuService: IContextMenuService, @IConfigurationService protected readonly configurationService: IConfigurationService, @@ -152,7 +152,7 @@ export abstract class ViewletPanel extends Panel implements IView { this._register(this.toolbar); this.setActions(); - const onDidRelevantConfigurationChange = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ViewletPanel.AlwaysShowActionsConfig)); + const onDidRelevantConfigurationChange = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ViewletPane.AlwaysShowActionsConfig)); this._register(onDidRelevantConfigurationChange(this.updateActionsVisibility, this)); this.updateActionsVisibility(); } @@ -169,6 +169,7 @@ export abstract class ViewletPanel extends Panel implements IView { if (this.titleContainer) { this.titleContainer.textContent = title; } + this.title = title; this._onDidChangeTitleArea.fire(); } @@ -224,31 +225,31 @@ export abstract class ViewletPanel extends Panel implements IView { } } -export interface IViewsViewletOptions extends IPanelViewOptions { +export interface IViewsViewletOptions extends IPaneViewOptions { showHeaderInTitleWhenSingleView: boolean; } -interface IViewletPanelItem { - panel: ViewletPanel; +interface IViewletPaneItem { + pane: ViewletPane; disposable: IDisposable; } -export class PanelViewlet extends Viewlet { +export class PaneViewlet extends Viewlet { - private lastFocusedPanel: ViewletPanel | undefined; - private panelItems: IViewletPanelItem[] = []; - private panelview?: PanelView; + private lastFocusedPane: ViewletPane | undefined; + private paneItems: IViewletPaneItem[] = []; + private paneview?: PaneView; get onDidSashChange(): Event { - return assertIsDefined(this.panelview).onDidSashChange; + return assertIsDefined(this.paneview).onDidSashChange; } - protected get panels(): ViewletPanel[] { - return this.panelItems.map(i => i.panel); + protected get panes(): ViewletPane[] { + return this.paneItems.map(i => i.pane); } protected get length(): number { - return this.panelItems.length; + return this.paneItems.length; } constructor( @@ -266,15 +267,15 @@ export class PanelViewlet extends Viewlet { create(parent: HTMLElement): void { super.create(parent); - this.panelview = this._register(new PanelView(parent, this.options)); - this._register(this.panelview.onDidDrop(({ from, to }) => this.movePanel(from as ViewletPanel, to as ViewletPanel))); + this.paneview = this._register(new PaneView(parent, this.options)); + this._register(this.paneview.onDidDrop(({ from, to }) => this.movePane(from as ViewletPane, to as ViewletPane))); this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, (e: MouseEvent) => this.showContextMenu(new StandardMouseEvent(e)))); } private showContextMenu(event: StandardMouseEvent): void { - for (const panelItem of this.panelItems) { - // Do not show context menu if target is coming from inside panel views - if (isAncestor(event.target, panelItem.panel.element)) { + for (const paneItem of this.paneItems) { + // Do not show context menu if target is coming from inside pane views + if (isAncestor(event.target, paneItem.pane.element)) { return; } } @@ -293,8 +294,8 @@ export class PanelViewlet extends Viewlet { let title = Registry.as(Extensions.Viewlets).getViewlet(this.getId()).name; if (this.isSingleView()) { - const panelItemTitle = this.panelItems[0].panel.title; - title = panelItemTitle ? `${title}: ${panelItemTitle}` : title; + const paneItemTitle = this.paneItems[0].pane.title; + title = paneItemTitle ? `${title}: ${paneItemTitle}` : title; } return title; @@ -302,7 +303,7 @@ export class PanelViewlet extends Viewlet { getActions(): IAction[] { if (this.isSingleView()) { - return this.panelItems[0].panel.getActions(); + return this.paneItems[0].pane.getActions(); } return []; @@ -310,7 +311,7 @@ export class PanelViewlet extends Viewlet { getSecondaryActions(): IAction[] { if (this.isSingleView()) { - return this.panelItems[0].panel.getSecondaryActions(); + return this.paneItems[0].pane.getSecondaryActions(); } return []; @@ -318,7 +319,7 @@ export class PanelViewlet extends Viewlet { getActionViewItem(action: IAction): IActionViewItem | undefined { if (this.isSingleView()) { - return this.panelItems[0].panel.getActionViewItem(action); + return this.paneItems[0].pane.getActionViewItem(action); } return super.getActionViewItem(action); @@ -327,12 +328,12 @@ export class PanelViewlet extends Viewlet { focus(): void { super.focus(); - if (this.lastFocusedPanel) { - this.lastFocusedPanel.focus(); - } else if (this.panelItems.length > 0) { - for (const { panel } of this.panelItems) { - if (panel.isExpanded()) { - panel.focus(); + if (this.lastFocusedPane) { + this.lastFocusedPane.focus(); + } else if (this.paneItems.length > 0) { + for (const { pane: pane } of this.paneItems) { + if (pane.isExpanded()) { + pane.focus(); return; } } @@ -340,23 +341,23 @@ export class PanelViewlet extends Viewlet { } layout(dimension: Dimension): void { - if (this.panelview) { - this.panelview.layout(dimension.height, dimension.width); + if (this.paneview) { + this.paneview.layout(dimension.height, dimension.width); } } getOptimalWidth(): number { - const sizes = this.panelItems - .map(panelItem => panelItem.panel.getOptimalWidth() || 0); + const sizes = this.paneItems + .map(paneItem => paneItem.pane.getOptimalWidth() || 0); return Math.max(...sizes); } - addPanels(panels: { panel: ViewletPanel, size: number, index?: number; }[]): void { + addPanes(panes: { pane: ViewletPane, size: number, index?: number; }[]): void { const wasSingleView = this.isSingleView(); - for (const { panel, size, index } of panels) { - this.addPanel(panel, size, index); + for (const { pane: pane, size, index } of panes) { + this.addPane(pane, size, index); } this.updateViewHeaders(); @@ -365,36 +366,36 @@ export class PanelViewlet extends Viewlet { } } - private addPanel(panel: ViewletPanel, size: number, index = this.panelItems.length - 1): void { - const onDidFocus = panel.onDidFocus(() => this.lastFocusedPanel = panel); - const onDidChangeTitleArea = panel.onDidChangeTitleArea(() => { + private addPane(pane: ViewletPane, size: number, index = this.paneItems.length - 1): void { + const onDidFocus = pane.onDidFocus(() => this.lastFocusedPane = pane); + const onDidChangeTitleArea = pane.onDidChangeTitleArea(() => { if (this.isSingleView()) { this.updateTitleArea(); } }); - const onDidChange = panel.onDidChange(() => { - if (panel === this.lastFocusedPanel && !panel.isExpanded()) { - this.lastFocusedPanel = undefined; + const onDidChange = pane.onDidChange(() => { + if (pane === this.lastFocusedPane && !pane.isExpanded()) { + this.lastFocusedPane = undefined; } }); - const panelStyler = attachStyler(this.themeService, { + const paneStyler = attachStyler(this.themeService, { headerForeground: SIDE_BAR_SECTION_HEADER_FOREGROUND, headerBackground: SIDE_BAR_SECTION_HEADER_BACKGROUND, headerBorder: SIDE_BAR_SECTION_HEADER_BORDER, dropBackground: SIDE_BAR_DRAG_AND_DROP_BACKGROUND - }, panel); - const disposable = combinedDisposable(onDidFocus, onDidChangeTitleArea, panelStyler, onDidChange); - const panelItem: IViewletPanelItem = { panel, disposable }; + }, pane); + const disposable = combinedDisposable(onDidFocus, onDidChangeTitleArea, paneStyler, onDidChange); + const paneItem: IViewletPaneItem = { pane: pane, disposable }; - this.panelItems.splice(index, 0, panelItem); - assertIsDefined(this.panelview).addPanel(panel, size, index); + this.paneItems.splice(index, 0, paneItem); + assertIsDefined(this.paneview).addPane(pane, size, index); } - removePanels(panels: ViewletPanel[]): void { + removePanes(panes: ViewletPane[]): void { const wasSingleView = this.isSingleView(); - panels.forEach(panel => this.removePanel(panel)); + panes.forEach(pane => this.removePane(pane)); this.updateViewHeaders(); if (wasSingleView !== this.isSingleView()) { @@ -402,67 +403,67 @@ export class PanelViewlet extends Viewlet { } } - private removePanel(panel: ViewletPanel): void { - const index = firstIndex(this.panelItems, i => i.panel === panel); + private removePane(pane: ViewletPane): void { + const index = firstIndex(this.paneItems, i => i.pane === pane); if (index === -1) { return; } - if (this.lastFocusedPanel === panel) { - this.lastFocusedPanel = undefined; + if (this.lastFocusedPane === pane) { + this.lastFocusedPane = undefined; } - assertIsDefined(this.panelview).removePanel(panel); - const [panelItem] = this.panelItems.splice(index, 1); - panelItem.disposable.dispose(); + assertIsDefined(this.paneview).removePane(pane); + const [paneItem] = this.paneItems.splice(index, 1); + paneItem.disposable.dispose(); } - movePanel(from: ViewletPanel, to: ViewletPanel): void { - const fromIndex = firstIndex(this.panelItems, item => item.panel === from); - const toIndex = firstIndex(this.panelItems, item => item.panel === to); + movePane(from: ViewletPane, to: ViewletPane): void { + const fromIndex = firstIndex(this.paneItems, item => item.pane === from); + const toIndex = firstIndex(this.paneItems, item => item.pane === to); - if (fromIndex < 0 || fromIndex >= this.panelItems.length) { + if (fromIndex < 0 || fromIndex >= this.paneItems.length) { return; } - if (toIndex < 0 || toIndex >= this.panelItems.length) { + if (toIndex < 0 || toIndex >= this.paneItems.length) { return; } - const [panelItem] = this.panelItems.splice(fromIndex, 1); - this.panelItems.splice(toIndex, 0, panelItem); + const [paneItem] = this.paneItems.splice(fromIndex, 1); + this.paneItems.splice(toIndex, 0, paneItem); - assertIsDefined(this.panelview).movePanel(from, to); + assertIsDefined(this.paneview).movePane(from, to); } - resizePanel(panel: ViewletPanel, size: number): void { - assertIsDefined(this.panelview).resizePanel(panel, size); + resizePane(pane: ViewletPane, size: number): void { + assertIsDefined(this.paneview).resizePane(pane, size); } - getPanelSize(panel: ViewletPanel): number { - return assertIsDefined(this.panelview).getPanelSize(panel); + getPaneSize(pane: ViewletPane): number { + return assertIsDefined(this.paneview).getPaneSize(pane); } protected updateViewHeaders(): void { if (this.isSingleView()) { - this.panelItems[0].panel.setExpanded(true); - this.panelItems[0].panel.headerVisible = false; + this.paneItems[0].pane.setExpanded(true); + this.paneItems[0].pane.headerVisible = false; } else { - this.panelItems.forEach(i => i.panel.headerVisible = true); + this.paneItems.forEach(i => i.pane.headerVisible = true); } } protected isSingleView(): boolean { - return this.options.showHeaderInTitleWhenSingleView && this.panelItems.length === 1; + return this.options.showHeaderInTitleWhenSingleView && this.paneItems.length === 1; } dispose(): void { super.dispose(); - this.panelItems.forEach(i => i.disposable.dispose()); - if (this.panelview) { - this.panelview.dispose(); + this.paneItems.forEach(i => i.disposable.dispose()); + if (this.paneview) { + this.paneview.dispose(); } } } diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index 00ac603e41..ddb3220e6b 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -11,7 +11,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IContextKeyService, IContextKeyChangeEvent, IReadableSet, IContextKey, RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Event, Emitter } from 'vs/base/common/event'; -import { sortedDiff, firstIndex, move, isNonEmptyArray } from 'vs/base/common/arrays'; +import { firstIndex, move, isNonEmptyArray } from 'vs/base/common/arrays'; import { isUndefinedOrNull, isUndefined } from 'vs/base/common/types'; import { MenuId, MenuRegistry, ICommandAction } from 'vs/platform/actions/common/actions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -223,7 +223,11 @@ export interface IAddedViewDescriptorRef extends IViewDescriptorRef { export class ContributableViewsModel extends Disposable { - readonly viewDescriptors: IViewDescriptor[] = []; + private _viewDescriptors: IViewDescriptor[] = []; + get viewDescriptors(): ReadonlyArray { + return this._viewDescriptors; + } + get visibleViewDescriptors(): IViewDescriptor[] { return this.viewDescriptors.filter(v => this.isViewDescriptorVisible(v)); } @@ -240,6 +244,9 @@ export class ContributableViewsModel extends Disposable { private _onDidChangeViewState = this._register(new Emitter()); protected readonly onDidChangeViewState: Event = this._onDidChangeViewState.event; + private _onDidChangeActiveViews = this._register(new Emitter>()); + readonly onDidChangeActiveViews: Event> = this._onDidChangeActiveViews.event; + constructor( container: ViewContainer, viewsService: IViewsService, @@ -335,7 +342,7 @@ export class ContributableViewsModel extends Disposable { const fromViewDescriptor = this.viewDescriptors[fromIndex]; const toViewDescriptor = this.viewDescriptors[toIndex]; - move(this.viewDescriptors, fromIndex, toIndex); + move(this._viewDescriptors, fromIndex, toIndex); for (let index = 0; index < this.viewDescriptors.length; index++) { const state = this.viewStates.get(this.viewDescriptors[index].id)!; @@ -403,14 +410,6 @@ export class ContributableViewsModel extends Disposable { } private onDidChangeViewDescriptors(viewDescriptors: IViewDescriptor[]): void { - const ids = new Set(); - - for (const viewDescriptor of this.viewDescriptors) { - ids.add(viewDescriptor.id); - } - - viewDescriptors = viewDescriptors.sort(this.compareViewDescriptors.bind(this)); - for (const viewDescriptor of viewDescriptors) { const viewState = this.viewStates.get(viewDescriptor.id); if (viewState) { @@ -430,37 +429,28 @@ export class ContributableViewsModel extends Disposable { } } - const splices = sortedDiff( - this.viewDescriptors, - viewDescriptors, - (a, b) => a.id === b.id ? 0 : a.id < b.id ? -1 : 1 - ).reverse(); + viewDescriptors = viewDescriptors.sort(this.compareViewDescriptors.bind(this)); const toRemove: { index: number, viewDescriptor: IViewDescriptor; }[] = []; - const toAdd: { index: number, viewDescriptor: IViewDescriptor, size?: number, collapsed: boolean; }[] = []; - - for (const splice of splices) { - const startViewDescriptor = this.viewDescriptors[splice.start]; - let startIndex = startViewDescriptor ? this.find(startViewDescriptor.id).visibleIndex : this.viewDescriptors.length; - - for (let i = 0; i < splice.deleteCount; i++) { - const viewDescriptor = this.viewDescriptors[splice.start + i]; - - if (this.isViewDescriptorVisible(viewDescriptor)) { - toRemove.push({ index: startIndex++, viewDescriptor }); - } - } - - for (const viewDescriptor of splice.toInsert) { - const state = this.viewStates.get(viewDescriptor.id)!; - - if (this.isViewDescriptorVisible(viewDescriptor)) { - toAdd.push({ index: startIndex++, viewDescriptor, size: state.size, collapsed: !!state.collapsed }); - } + for (let index = 0; index < this._viewDescriptors.length; index++) { + const previousViewDescriptor = this._viewDescriptors[index]; + if (this.isViewDescriptorVisible(previousViewDescriptor) && viewDescriptors.every(viewDescriptor => viewDescriptor.id !== previousViewDescriptor.id)) { + const { visibleIndex } = this.find(previousViewDescriptor.id); + toRemove.push({ index: visibleIndex, viewDescriptor: previousViewDescriptor }); } } - this.viewDescriptors.splice(0, this.viewDescriptors.length, ...viewDescriptors); + const previous = this._viewDescriptors; + this._viewDescriptors = viewDescriptors.slice(0); + + const toAdd: { index: number, viewDescriptor: IViewDescriptor, size?: number, collapsed: boolean; }[] = []; + for (let i = 0; i < this._viewDescriptors.length; i++) { + const viewDescriptor = this._viewDescriptors[i]; + if (this.isViewDescriptorVisible(viewDescriptor) && previous.every(previousViewDescriptor => previousViewDescriptor.id !== viewDescriptor.id)) { + const { visibleIndex, state } = this.find(viewDescriptor.id); + toAdd.push({ index: visibleIndex, viewDescriptor, size: state.size, collapsed: !!state.collapsed }); + } + } if (toRemove.length) { this._onDidRemove.fire(toRemove); @@ -469,6 +459,8 @@ export class ContributableViewsModel extends Disposable { if (toAdd.length) { this._onDidAdd.fire(toAdd); } + + this._onDidChangeActiveViews.fire(this.viewDescriptors); } } diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index 71f94b18b5..f477ee5796 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -18,8 +18,8 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { PanelViewlet, ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; -import { DefaultPanelDndController } from 'vs/base/browser/ui/splitview/panelview'; +import { PaneViewlet, ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet'; +import { DefaultPaneDndController } from 'vs/base/browser/ui/splitview/paneview'; import { WorkbenchTree, IListService } from 'vs/platform/list/browser/listService'; import { IWorkbenchThemeService, IFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ITreeConfiguration, ITreeOptions } from 'vs/base/parts/tree/browser/tree'; @@ -31,11 +31,11 @@ import { IAddedViewDescriptorRef, IViewDescriptorRef, PersistentContributableVie import { Registry } from 'vs/platform/registry/common/platform'; import { MementoObject } from 'vs/workbench/common/memento'; -export interface IViewletViewOptions extends IViewletPanelOptions { +export interface IViewletViewOptions extends IViewletPaneOptions { viewletState: MementoObject; } -export abstract class ViewContainerViewlet extends PanelViewlet implements IViewsViewlet { +export abstract class ViewContainerViewlet extends PaneViewlet implements IViewsViewlet { private readonly viewletState: MementoObject; private didLayout = false; @@ -61,7 +61,7 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView @IExtensionService protected extensionService: IExtensionService, @IWorkspaceContextService protected contextService: IWorkspaceContextService ) { - super(id, { showHeaderInTitleWhenSingleView, dnd: new DefaultPanelDndController() }, configurationService, layoutService, contextMenuService, telemetryService, themeService, storageService); + super(id, { showHeaderInTitleWhenSingleView, dnd: new DefaultPaneDndController() }, configurationService, layoutService, contextMenuService, telemetryService, themeService, storageService); const container = Registry.as(ViewContainerExtensions.ViewContainersRegistry).get(id); if (!container) { @@ -92,7 +92,7 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView // Update headers after and title contributed views after available, since we read from cache in the beginning to know if the viewlet has single view or not. Ref #29609 this.extensionService.whenInstalledExtensionsRegistered().then(() => { this.areExtensionsReady = true; - if (this.panels.length) { + if (this.panes.length) { this.updateTitleArea(); this.updateViewHeaders(); } @@ -112,7 +112,7 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView })); result.push(...viewToggleActions); - const parentActions = super.getContextMenuActions(); + const parentActions = this.getViewletContextMenuActions(); if (viewToggleActions.length && parentActions.length) { result.push(new Separator()); } @@ -121,9 +121,13 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView return result; } + protected getViewletContextMenuActions() { + return super.getContextMenuActions(); + } + setVisible(visible: boolean): void { super.setVisible(visible); - this.panels.filter(view => view.isVisible() !== visible) + this.panes.filter(view => view.isVisible() !== visible) .map((view) => view.setVisible(visible)); } @@ -143,13 +147,13 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView return view; } - movePanel(from: ViewletPanel, to: ViewletPanel): void { - const fromIndex = firstIndex(this.panels, panel => panel === from); - const toIndex = firstIndex(this.panels, panel => panel === to); + movePane(from: ViewletPane, to: ViewletPane): void { + const fromIndex = firstIndex(this.panes, pane => pane === from); + const toIndex = firstIndex(this.panes, pane => pane === to); const fromViewDescriptor = this.viewsModel.visibleViewDescriptors[fromIndex]; const toViewDescriptor = this.viewsModel.visibleViewDescriptors[toIndex]; - super.movePanel(from, to); + super.movePane(from, to); this.viewsModel.move(fromViewDescriptor.id, toViewDescriptor.id); } @@ -166,7 +170,7 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView getOptimalWidth(): number { const additionalMargin = 16; - const optimalWidth = Math.max(...this.panels.map(view => view.getOptimalWidth() || 0)); + const optimalWidth = Math.max(...this.panes.map(view => view.getOptimalWidth() || 0)); return optimalWidth + additionalMargin; } @@ -184,18 +188,18 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView return true; } - protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewletPanel { - return (this.instantiationService as any).createInstance(viewDescriptor.ctorDescriptor.ctor, ...(viewDescriptor.ctorDescriptor.arguments || []), options) as ViewletPanel; + protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewletPane { + return (this.instantiationService as any).createInstance(viewDescriptor.ctorDescriptor.ctor, ...(viewDescriptor.ctorDescriptor.arguments || []), options) as ViewletPane; } - protected getView(id: string): ViewletPanel | undefined { - return this.panels.filter(view => view.id === id)[0]; + protected getView(id: string): ViewletPane | undefined { + return this.panes.filter(view => view.id === id)[0]; } - protected onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPanel[] { - const panelsToAdd: { panel: ViewletPanel, size: number, index: number }[] = []; + protected onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPane[] { + const panesToAdd: { pane: ViewletPane, size: number, index: number }[] = []; for (const { viewDescriptor, collapsed, index, size } of added) { - const panel = this.createView(viewDescriptor, + const pane = this.createView(viewDescriptor, { id: viewDescriptor.id, title: viewDescriptor.name, @@ -203,42 +207,42 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView expanded: !collapsed, viewletState: this.viewletState }); - panel.render(); - const contextMenuDisposable = DOM.addDisposableListener(panel.draggableElement, 'contextmenu', e => { + pane.render(); + const contextMenuDisposable = DOM.addDisposableListener(pane.draggableElement, 'contextmenu', e => { e.stopPropagation(); e.preventDefault(); this.onContextMenu(new StandardMouseEvent(e), viewDescriptor); }); - const collapseDisposable = Event.latch(Event.map(panel.onDidChange, () => !panel.isExpanded()))(collapsed => { + const collapseDisposable = Event.latch(Event.map(pane.onDidChange, () => !pane.isExpanded()))(collapsed => { this.viewsModel.setCollapsed(viewDescriptor.id, collapsed); }); this.viewDisposables.splice(index, 0, combinedDisposable(contextMenuDisposable, collapseDisposable)); - panelsToAdd.push({ panel, size: size || panel.minimumSize, index }); + panesToAdd.push({ pane, size: size || pane.minimumSize, index }); } - this.addPanels(panelsToAdd); + this.addPanes(panesToAdd); this.restoreViewSizes(); - const panels: ViewletPanel[] = []; - for (const { panel } of panelsToAdd) { - panel.setVisible(this.isVisible()); - panels.push(panel); + const panes: ViewletPane[] = []; + for (const { pane } of panesToAdd) { + pane.setVisible(this.isVisible()); + panes.push(pane); } - return panels; + return panes; } private onDidRemoveViews(removed: IViewDescriptorRef[]): void { removed = removed.sort((a, b) => b.index - a.index); - const panelsToRemove: ViewletPanel[] = []; + const panesToRemove: ViewletPane[] = []; for (const { index } of removed) { const [disposable] = this.viewDisposables.splice(index, 1); disposable.dispose(); - panelsToRemove.push(this.panels[index]); + panesToRemove.push(this.panes[index]); } - this.removePanels(panelsToRemove); - dispose(panelsToRemove); + this.removePanes(panesToRemove); + dispose(panesToRemove); } private onContextMenu(event: StandardMouseEvent, viewDescriptor: IViewDescriptor): void { @@ -264,7 +268,7 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView }); } - private toggleViewVisibility(viewId: string): void { + protected toggleViewVisibility(viewId: string): void { const visible = !this.viewsModel.isVisible(viewId); type ViewsToggleVisibilityClassification = { viewId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; @@ -277,8 +281,8 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView private saveViewSizes(): void { // Save size only when the layout has happened if (this.didLayout) { - for (const view of this.panels) { - this.viewsModel.setSize(view.id, this.getPanelSize(view)); + for (const view of this.panes) { + this.viewsModel.setSize(view.id, this.getPaneSize(view)); } } } @@ -288,15 +292,15 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView if (this.didLayout) { let initialSizes; for (let i = 0; i < this.viewsModel.visibleViewDescriptors.length; i++) { - const panel = this.panels[i]; + const pane = this.panes[i]; const viewDescriptor = this.viewsModel.visibleViewDescriptors[i]; const size = this.viewsModel.getSize(viewDescriptor.id); if (typeof size === 'number') { - this.resizePanel(panel, size); + this.resizePane(pane, size); } else { initialSizes = initialSizes ? initialSizes : this.computeInitialSizes(); - this.resizePanel(panel, initialSizes.get(panel.id) || 200); + this.resizePane(pane, initialSizes.get(pane.id) || 200); } } } @@ -314,13 +318,123 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView } protected saveState(): void { - this.panels.forEach((view) => view.saveState()); + this.panes.forEach((view) => view.saveState()); this.storageService.store(this.visibleViewsStorageId, this.length, StorageScope.WORKSPACE); super.saveState(); } } +export abstract class FilterViewContainerViewlet extends ViewContainerViewlet { + private constantViewDescriptors: Map = new Map(); + private allViews: Map> = new Map(); + private filterValue: string | undefined; + + constructor( + viewletId: string, + onDidChangeFilterValue: Event, + @IConfigurationService configurationService: IConfigurationService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ITelemetryService telemetryService: ITelemetryService, + @IStorageService storageService: IStorageService, + @IInstantiationService instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IContextMenuService contextMenuService: IContextMenuService, + @IExtensionService extensionService: IExtensionService, + @IWorkspaceContextService contextService: IWorkspaceContextService + ) { + super(viewletId, `${viewletId}.state`, false, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); + this._register(onDidChangeFilterValue(newFilterValue => { + this.filterValue = newFilterValue; + this.onFilterChanged(newFilterValue); + })); + + this._register(this.viewsModel.onDidChangeActiveViews((viewDescriptors) => { + this.updateAllViews(viewDescriptors); + })); + } + + private updateAllViews(viewDescriptors: ReadonlyArray) { + viewDescriptors.forEach(descriptor => { + let filterOnValue = this.getFilterOn(descriptor); + if (!filterOnValue) { + return; + } + if (!this.allViews.has(filterOnValue)) { + this.allViews.set(filterOnValue, new Map()); + } + this.allViews.get(filterOnValue)!.set(descriptor.id, descriptor); + if (filterOnValue !== this.filterValue) { + this.viewsModel.setVisible(descriptor.id, false); + } + }); + } + + protected addConstantViewDescriptors(constantViewDescriptors: IViewDescriptor[]) { + constantViewDescriptors.forEach(viewDescriptor => this.constantViewDescriptors.set(viewDescriptor.id, viewDescriptor)); + } + + protected abstract getFilterOn(viewDescriptor: IViewDescriptor): string | undefined; + + private onFilterChanged(newFilterValue: string) { + this.getViewsNotForTarget(newFilterValue).forEach(item => this.viewsModel.setVisible(item.id, false)); + this.getViewsForTarget(newFilterValue).forEach(item => this.viewsModel.setVisible(item.id, true)); + } + + getContextMenuActions(): IAction[] { + const result: IAction[] = []; + let viewToggleActions: IAction[] = Array.from(this.constantViewDescriptors.values()).map(viewDescriptor => ({ + id: `${viewDescriptor.id}.toggleVisibility`, + label: viewDescriptor.name, + checked: this.viewsModel.isVisible(viewDescriptor.id), + enabled: viewDescriptor.canToggleVisibility, + run: () => this.toggleViewVisibility(viewDescriptor.id) + })); + + result.push(...viewToggleActions); + const parentActions = this.getViewletContextMenuActions(); + if (viewToggleActions.length && parentActions.length) { + result.push(new Separator()); + } + + result.push(...parentActions); + return result; + } + + private getViewsForTarget(target: string): IViewDescriptor[] { + return this.allViews.has(target) ? Array.from(this.allViews.get(target)!.values()) : []; + } + + private getViewsNotForTarget(target: string): IViewDescriptor[] { + const iterable = this.allViews.keys(); + let key = iterable.next(); + let views: IViewDescriptor[] = []; + while (!key.done) { + if (key.value !== target) { + views = views.concat(this.getViewsForTarget(key.value)); + } + key = iterable.next(); + } + return views; + } + + onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPane[] { + const panes: ViewletPane[] = super.onDidAddViews(added); + for (let i = 0; i < added.length; i++) { + if (this.constantViewDescriptors.has(added[i].viewDescriptor.id)) { + panes[i].setExpanded(false); + } + } + // Check that allViews is ready + if (this.allViews.size === 0) { + this.updateAllViews(this.viewsModel.viewDescriptors); + } + return panes; + } + + abstract getTitle(): string; +} + export class FileIconThemableWorkbenchTree extends WorkbenchTree { constructor( diff --git a/src/vs/workbench/browser/quickopen.ts b/src/vs/workbench/browser/quickopen.ts index fc88a4a70f..d4240c3e7a 100644 --- a/src/vs/workbench/browser/quickopen.ts +++ b/src/vs/workbench/browser/quickopen.ts @@ -15,7 +15,7 @@ import { QuickOpenEntry, QuickOpenEntryGroup } from 'vs/base/parts/quickopen/bro import { EditorOptions, EditorInput, IEditorInput } from 'vs/workbench/common/editor'; import { IResourceInput, IEditorOptions } from 'vs/platform/editor/common/editor'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; -import { IConstructorSignature0, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IConstructorSignature0, IInstantiationService, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -136,9 +136,13 @@ export class QuickOpenHandlerDescriptor { private id: string; private ctor: IConstructorSignature0; - constructor(ctor: IConstructorSignature0, id: string, prefix: string, contextKey: string | undefined, description: string, instantProgress?: boolean); - constructor(ctor: IConstructorSignature0, id: string, prefix: string, contextKey: string | undefined, helpEntries: QuickOpenHandlerHelpEntry[], instantProgress?: boolean); - constructor(ctor: IConstructorSignature0, id: string, prefix: string, contextKey: string | undefined, param: string | QuickOpenHandlerHelpEntry[], instantProgress: boolean = false) { + public static create(ctor: { new(...services: Services): QuickOpenHandler }, id: string, prefix: string, contextKey: string | undefined, description: string, instantProgress?: boolean): QuickOpenHandlerDescriptor; + public static create(ctor: { new(...services: Services): QuickOpenHandler }, id: string, prefix: string, contextKey: string | undefined, helpEntries: QuickOpenHandlerHelpEntry[], instantProgress?: boolean): QuickOpenHandlerDescriptor; + public static create(ctor: { new(...services: Services): QuickOpenHandler }, id: string, prefix: string, contextKey: string | undefined, param: string | QuickOpenHandlerHelpEntry[], instantProgress: boolean = false): QuickOpenHandlerDescriptor { + return new QuickOpenHandlerDescriptor(ctor as IConstructorSignature0, id, prefix, contextKey, param, instantProgress); + } + + private constructor(ctor: IConstructorSignature0, id: string, prefix: string, contextKey: string | undefined, param: string | QuickOpenHandlerHelpEntry[], instantProgress: boolean = false) { this.ctor = ctor; this.id = id; this.prefix = prefix; diff --git a/src/vs/workbench/browser/style.ts b/src/vs/workbench/browser/style.ts index b19321d391..fd858c9071 100644 --- a/src/vs/workbench/browser/style.ts +++ b/src/vs/workbench/browser/style.ts @@ -8,8 +8,9 @@ import 'vs/css!./media/style'; import { registerThemingParticipant, ITheme, ICssStyleCollector, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; import { iconForeground, foreground, selectionBackground, focusBorder, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listHighlightForeground, inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry'; import { WORKBENCH_BACKGROUND, TITLE_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme'; -import { isWeb } from 'vs/base/common/platform'; +import { isWeb, isIOS } from 'vs/base/common/platform'; import { createMetaElement } from 'vs/base/browser/dom'; +import { isSafari, isStandalone } from 'vs/base/browser/browser'; registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { @@ -34,8 +35,16 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { // Input placeholder const placeholderForeground = theme.getColor(inputPlaceholderForeground); if (placeholderForeground) { - collector.addRule(`.monaco-workbench input::-webkit-input-placeholder { color: ${placeholderForeground}; }`); - collector.addRule(`.monaco-workbench textarea::-webkit-input-placeholder { color: ${placeholderForeground}; }`); + collector.addRule(` + .monaco-workbench input::placeholder { color: ${placeholderForeground}; } + .monaco-workbench input::-webkit-input-placeholder { color: ${placeholderForeground}; } + .monaco-workbench input::-moz-placeholder { color: ${placeholderForeground}; } + `); + collector.addRule(` + .monaco-workbench textarea::placeholder { color: ${placeholderForeground}; } + .monaco-workbench textarea::-webkit-input-placeholder { color: ${placeholderForeground}; } + .monaco-workbench textarea::-moz-placeholder { color: ${placeholderForeground}; } + `); } // List highlight @@ -160,4 +169,24 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { metaElement.content = titleBackground.toString(); } } + + // We disable user select on the root element, however on Safari this seems + // to prevent any text selection in the monaco editor. As a workaround we + // allow to select text in monaco editor instances. + if (isSafari) { + collector.addRule(` + body.web { + touch-action: none; + } + .monaco-workbench .monaco-editor .view-lines { + user-select: text; + -webkit-user-select: text; + } + `); + } + + // Update body background color to ensure the home indicator area looks similar to the workbench + if (isIOS && isStandalone) { + collector.addRule(`body { background-color: ${workbenchBackground}; }`); + } }); diff --git a/src/vs/workbench/browser/viewlet.ts b/src/vs/workbench/browser/viewlet.ts index a8d1cd5971..bd04fcd729 100644 --- a/src/vs/workbench/browser/viewlet.ts +++ b/src/vs/workbench/browser/viewlet.ts @@ -10,7 +10,7 @@ import { Action, IAction } from 'vs/base/common/actions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { Composite, CompositeDescriptor, CompositeRegistry } from 'vs/workbench/browser/composite'; -import { IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation'; +import { IConstructorSignature0, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { ToggleSidebarVisibilityAction, ToggleSidebarPositionAction } from 'vs/workbench/browser/actions/layoutActions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; @@ -56,20 +56,27 @@ export abstract class Viewlet extends Composite implements IViewlet { */ export class ViewletDescriptor extends CompositeDescriptor { - constructor( + public static create( + ctor: { new(...services: Services): Viewlet }, + id: string, + name: string, + cssClass?: string, + order?: number, + iconUrl?: URI + ): ViewletDescriptor { + return new ViewletDescriptor(ctor as IConstructorSignature0, id, name, cssClass, order, iconUrl); + } + + private constructor( ctor: IConstructorSignature0, id: string, name: string, cssClass?: string, order?: number, - private _iconUrl?: URI + readonly iconUrl?: URI ) { super(ctor, id, name, cssClass, order, id); } - - get iconUrl(): URI | undefined { - return this._iconUrl; - } } export const Extensions = { diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 290440d64b..fb98813a75 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -11,7 +11,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { Workbench } from 'vs/workbench/browser/workbench'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { REMOTE_FILE_SYSTEM_CHANNEL_NAME, RemoteExtensionsFileSystemProvider } from 'vs/platform/remote/common/remoteAgentFileSystemChannel'; +import { REMOTE_FILE_SYSTEM_CHANNEL_NAME, RemoteFileSystemProvider } from 'vs/platform/remote/common/remoteAgentFileSystemChannel'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IProductService } from 'vs/platform/product/common/productService'; import product from 'vs/platform/product/common/product'; @@ -26,6 +26,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { onUnexpectedError } from 'vs/base/common/errors'; import * as browser from 'vs/base/browser/browser'; +import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; @@ -90,7 +91,10 @@ class BrowserMain extends Disposable { private registerListeners(workbench: Workbench, storageService: BrowserStorageService): void { // Layout - this._register(addDisposableListener(window, EventType.RESIZE, () => workbench.layout())); + const viewport = platform.isIOS && (window).visualViewport ? (window).visualViewport /** Visual viewport */ : window /** Layout viewport */; + this._register(addDisposableListener(viewport, EventType.RESIZE, () => { + workbench.layout(); + })); // Prevent the back/forward gestures in macOS this._register(addDisposableListener(this.domElement, EventType.WHEEL, (e) => { @@ -100,6 +104,9 @@ class BrowserMain extends Disposable { // Prevent native context menus in web this._register(addDisposableListener(this.domElement, EventType.CONTEXT_MENU, (e) => EventHelper.stop(e, true))); + // Prevent default navigation on drop + this._register(addDisposableListener(this.domElement, EventType.DROP, (e) => EventHelper.stop(e, true))); + // Workbench Lifecycle this._register(workbench.onBeforeShutdown(event => { if (storageService.hasPendingUpdate) { @@ -126,7 +133,7 @@ class BrowserMain extends Disposable { } private restoreBaseTheme(): void { - addClass(this.domElement, window.localStorage.getItem('baseTheme') || getThemeTypeSelector(DARK)); + addClass(this.domElement, window.localStorage.getItem('vscode.baseTheme') || getThemeTypeSelector(DARK)); } private saveBaseTheme(): void { @@ -134,7 +141,7 @@ class BrowserMain extends Disposable { const baseThemes = [DARK, LIGHT, HIGH_CONTRAST].map(baseTheme => getThemeTypeSelector(baseTheme)); for (const baseTheme of baseThemes) { if (classes.indexOf(baseTheme) >= 0) { - window.localStorage.setItem('baseTheme', baseTheme); + window.localStorage.setItem('vscode.baseTheme', baseTheme); break; } } @@ -162,10 +169,7 @@ class BrowserMain extends Disposable { // Product const productService = { _serviceBrand: undefined, - ...{ - ...product, // dev or built time config - ...{ urlProtocol: '' } // web related overrides from us - } + ...product }; serviceCollection.set(IProductService, productService); @@ -214,17 +218,21 @@ class BrowserMain extends Disposable { private registerFileSystemProviders(environmentService: IWorkbenchEnvironmentService, fileService: IFileService, remoteAgentService: IRemoteAgentService, logService: BufferLogService, logsPath: URI): void { // Logger - const indexedDBLogProvider = new IndexedDBLogProvider(logsPath.scheme); (async () => { - try { - await indexedDBLogProvider.database; - - fileService.registerProvider(logsPath.scheme, indexedDBLogProvider); - } catch (error) { - logService.info('Error while creating indexedDB log provider. Falling back to in-memory log provider.'); - logService.error(error); - + if (browser.isEdge) { fileService.registerProvider(logsPath.scheme, new InMemoryLogProvider(logsPath.scheme)); + } else { + try { + const indexedDBLogProvider = new IndexedDBLogProvider(logsPath.scheme); + await indexedDBLogProvider.database; + + fileService.registerProvider(logsPath.scheme, indexedDBLogProvider); + } catch (error) { + logService.info('Error while creating indexedDB log provider. Falling back to in-memory log provider.'); + logService.error(error); + + fileService.registerProvider(logsPath.scheme, new InMemoryLogProvider(logsPath.scheme)); + } } const consoleLogService = new ConsoleLogService(logService.getLevel()); @@ -237,7 +245,7 @@ class BrowserMain extends Disposable { // Remote file system const channel = connection.getChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME); - const remoteFileSystemProvider = this._register(new RemoteExtensionsFileSystemProvider(channel, remoteAgentService.getEnvironment())); + const remoteFileSystemProvider = this._register(new RemoteFileSystemProvider(channel, remoteAgentService.getEnvironment())); fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider); if (!this.configuration.userDataProvider) { diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index fadcfa0725..1ce62d9c64 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -7,6 +7,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import * as nls from 'vs/nls'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common/platform'; +import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; // Configuration (function registerConfiguration(): void { @@ -14,10 +15,7 @@ import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common // Workbench registry.registerConfiguration({ - 'id': 'workbench', - 'order': 7, - 'title': nls.localize('workbenchConfigurationTitle', "Workbench"), - 'type': 'object', + ...workbenchConfigurationNodeBase, 'properties': { 'workbench.editor.showTabs': { 'type': 'boolean', @@ -87,7 +85,7 @@ import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common }, 'workbench.editor.enablePreviewFromQuickOpen': { 'type': 'boolean', - 'description': nls.localize('enablePreviewFromQuickOpen', "Controls whether opened editors from Quick Open show as preview. Preview editors are reused until they are pinned (e.g. via double click or editing)."), + 'description': nls.localize('enablePreviewFromQuickOpen', "Controls whether editors opened from Quick Open show as preview. Preview editors are reused until they are pinned (e.g. via double click or editing)."), 'default': true }, 'workbench.editor.closeOnFileDelete': { @@ -297,7 +295,7 @@ import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common nls.localize('window.menuBarVisibility.visible', "Menu is always visible even in full screen mode."), nls.localize('window.menuBarVisibility.toggle', "Menu is hidden but can be displayed via Alt key."), nls.localize('window.menuBarVisibility.hidden', "Menu is always hidden."), - nls.localize('window.menuBarVisibility.compact', "Menu is displayed as a compact button in the sidebar.") + nls.localize('window.menuBarVisibility.compact', "Menu is displayed as a compact button in the sidebar. This value is ignored when 'window.titleBarStyle' is 'native'.") ], 'default': isWeb ? 'compact' : 'default', 'scope': ConfigurationScope.APPLICATION, diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 524f6b6ea1..ef1be41590 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -9,7 +9,7 @@ import { localize } from 'vs/nls'; import { Event, Emitter, setGlobalLeakWarningThreshold } from 'vs/base/common/event'; import { addClasses, addClass, removeClasses } from 'vs/base/browser/dom'; import { runWhenIdle } from 'vs/base/common/async'; -import { getZoomLevel } from 'vs/base/browser/browser'; +import { getZoomLevel, isFirefox, isSafari, isChrome } from 'vs/base/browser/browser'; import { mark } from 'vs/base/common/performance'; import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -272,7 +272,7 @@ export class Workbench extends Layout { private restoreFontInfo(storageService: IStorageService, configurationService: IConfigurationService): void { // Restore (native: use storage service, web: use browser specific local storage) - const storedFontInfoRaw = isNative ? storageService.get('editorFontInfo', StorageScope.GLOBAL) : window.localStorage.getItem('editorFontInfo'); + const storedFontInfoRaw = isNative ? storageService.get('editorFontInfo', StorageScope.GLOBAL) : window.localStorage.getItem('vscode.editorFontInfo'); if (storedFontInfoRaw) { try { const storedFontInfo = JSON.parse(storedFontInfoRaw); @@ -299,7 +299,7 @@ export class Workbench extends Layout { if (isNative) { storageService.store('editorFontInfo', serializedFontInfoRaw, StorageScope.GLOBAL); } else { - window.localStorage.setItem('editorFontInfo', serializedFontInfoRaw); + window.localStorage.setItem('vscode.editorFontInfo', serializedFontInfoRaw); } } } @@ -312,6 +312,7 @@ export class Workbench extends Layout { 'monaco-workbench', platformClass, isWeb ? 'web' : undefined, + isChrome ? 'chromium' : isFirefox ? 'firefox' : isSafari ? 'safari' : undefined, ...this.getLayoutClasses() ]); diff --git a/src/vs/workbench/common/activity.ts b/src/vs/workbench/common/activity.ts index ffce2de6a1..666bed860f 100644 --- a/src/vs/workbench/common/activity.ts +++ b/src/vs/workbench/common/activity.ts @@ -3,11 +3,14 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { URI } from 'vs/base/common/uri'; + export interface IActivity { id: string; name: string; keybindingId?: string; cssClass?: string; + iconUrl?: URI; } -export const GLOBAL_ACTIVITY_ID = 'workbench.action.globalActivity'; \ No newline at end of file +export const GLOBAL_ACTIVITY_ID = 'workbench.action.globalActivity'; diff --git a/src/vs/workbench/common/composite.ts b/src/vs/workbench/common/composite.ts index 12300b3977..a900178bda 100644 --- a/src/vs/workbench/common/composite.ts +++ b/src/vs/workbench/common/composite.ts @@ -4,9 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import { IAction, IActionViewItem } from 'vs/base/common/actions'; +import { Event } from 'vs/base/common/event'; export interface IComposite { + /** + * An event when the composite gained focus. + */ + readonly onDidFocus: Event; + + /** + * An event when the composite lost focus. + */ + readonly onDidBlur: Event; + /** * Returns the unique identifier of this composite. */ diff --git a/src/typings/require-monaco.d.ts b/src/vs/workbench/common/configuration.ts similarity index 51% rename from src/typings/require-monaco.d.ts rename to src/vs/workbench/common/configuration.ts index 81890ff24f..ca52580d80 100644 --- a/src/typings/require-monaco.d.ts +++ b/src/vs/workbench/common/configuration.ts @@ -3,10 +3,12 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -interface NodeRequire { - toUrl(path: string): string; - (dependencies: string[], callback: (...args: any[]) => any, errorback?: (err: any) => void): any; - config(data: any): any; -} +import { localize } from 'vs/nls'; +import { IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry'; -declare var require: NodeRequire; \ No newline at end of file +export const workbenchConfigurationNodeBase = Object.freeze({ + 'id': 'workbench', + 'order': 7, + 'title': localize('workbenchConfigurationTitle', "Workbench"), + 'type': 'object', +}); diff --git a/src/vs/workbench/common/contributions.ts b/src/vs/workbench/common/contributions.ts index db1197674f..33fd164612 100644 --- a/src/vs/workbench/common/contributions.ts +++ b/src/vs/workbench/common/contributions.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IInstantiationService, IConstructorSignature0, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, IConstructorSignature0, ServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { runWhenIdle, IdleDeadline } from 'vs/base/common/async'; @@ -19,7 +19,7 @@ export namespace Extensions { export const Workbench = 'workbench.contributions.kind'; } -export type IWorkbenchContributionSignature = IConstructorSignature0; +type IWorkbenchContributionSignature = new (...services: Service) => IWorkbenchContribution; export interface IWorkbenchContributionsRegistry { @@ -29,7 +29,7 @@ export interface IWorkbenchContributionsRegistry { * * @param phase the lifecycle phase when to instantiate the contribution. */ - registerWorkbenchContribution(contribution: IWorkbenchContributionSignature, phase: LifecyclePhase): void; + registerWorkbenchContribution(contribution: IWorkbenchContributionSignature, phase: LifecyclePhase): void; /** * Starts the registry by providing the required services. @@ -43,8 +43,7 @@ class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry private readonly toBeInstantiated: Map[]> = new Map[]>(); - registerWorkbenchContribution(ctor: IWorkbenchContributionSignature, phase: LifecyclePhase = LifecyclePhase.Starting): void { - + registerWorkbenchContribution(ctor: { new(...services: Services): IWorkbenchContribution }, phase: LifecyclePhase = LifecyclePhase.Starting): void { // Instantiate directly if we are already matching the provided phase if (this.instantiationService && this.lifecycleService && this.lifecycleService.phase >= phase) { this.instantiationService.createInstance(ctor); diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 67c927afc5..7d1a8e5d33 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -10,19 +10,24 @@ import { URI } from 'vs/base/common/uri'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { IEditor as ICodeEditor, IEditorViewState, ScrollType, IDiffEditor } from 'vs/editor/common/editorCommon'; import { IEditorModel, IEditorOptions, ITextEditorOptions, IBaseResourceInput, IResourceInput, EditorActivation, EditorOpenContext } from 'vs/platform/editor/common/editor'; -import { IInstantiationService, IConstructorSignature0, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, IConstructorSignature0, ServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; import { ITextModel } from 'vs/editor/common/model'; -import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ICompositeControl } from 'vs/workbench/common/composite'; import { ActionRunner, IAction } from 'vs/base/common/actions'; import { IFileService } from 'vs/platform/files/common/files'; import { IPathData } from 'vs/platform/windows/common/windows'; import { coalesce, firstOrDefault } from 'vs/base/common/arrays'; +import { ITextFileSaveOptions, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { isEqual } from 'vs/base/common/resources'; +import { IPanel } from 'vs/workbench/common/panel'; +export const DirtyWorkingCopiesContext = new RawContextKey('dirtyWorkingCopies', false); export const ActiveEditorContext = new RawContextKey('activeEditor', null); -export const ActiveEditorIsSaveableContext = new RawContextKey('activeEditorIsSaveable', false); +export const ActiveEditorIsReadonlyContext = new RawContextKey('activeEditorIsReadonly', false); export const EditorsVisibleContext = new RawContextKey('editorIsOpen', false); export const EditorPinnedContext = new RawContextKey('editorPinned', false); export const EditorGroupActiveEditorDirtyContext = new RawContextKey('groupActiveEditorDirty', false); @@ -50,7 +55,7 @@ export const TEXT_DIFF_EDITOR_ID = 'workbench.editors.textDiffEditor'; */ export const BINARY_DIFF_EDITOR_ID = 'workbench.editors.binaryResourceDiffEditor'; -export interface IEditor { +export interface IEditor extends IPanel { /** * The assigned input of this editor. @@ -92,21 +97,11 @@ export interface IEditor { */ readonly onDidSizeConstraintsChange: Event<{ width: number; height: number; } | undefined>; - /** - * Returns the unique identifier of this editor. - */ - getId(): string; - /** * Returns the underlying control of this editor. */ getControl(): IEditorControl | undefined; - /** - * Asks the underlying control to focus. - */ - focus(): void; - /** * Finds out if this editor is visible or not. */ @@ -119,6 +114,17 @@ export interface ITextEditor extends IEditor { * Returns the underlying text editor widget of this editor. */ getControl(): ICodeEditor | undefined; + + /** + * Returns the current view state of the text editor if any. + */ + getViewState(): IEditorViewState | undefined; +} + +export function isTextEditor(thing: IEditor | undefined): thing is ITextEditor { + const candidate = thing as ITextEditor | undefined; + + return typeof candidate?.getViewState === 'function'; } export interface ITextDiffEditor extends IEditor { @@ -175,7 +181,7 @@ export interface IEditorInputFactoryRegistry { * @param editorInputId the identifier of the editor input * @param factory the editor input factory for serialization/deserialization */ - registerEditorInputFactory(editorInputId: string, ctor: IConstructorSignature0): void; + registerEditorInputFactory(editorInputId: string, ctor: { new(...Services: Services): IEditorInputFactory }): void; /** * Returns the editor input factory for the given editor input. @@ -205,11 +211,11 @@ export interface IEditorInputFactory { deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput | undefined; } -export interface IUntitledResourceInput extends IBaseResourceInput { +export interface IUntitledTextResourceInput extends IBaseResourceInput { /** * Optional resource. If the resource is not provided a new untitled file is created (e.g. Untitled-1). - * Otherwise the untitled editor will have an associated path and use that when saving. + * Otherwise the untitled text editor will have an associated path and use that when saving. */ resource?: URI; @@ -261,15 +267,67 @@ export const enum Verbosity { LONG } -export interface IRevertOptions { +export const enum SaveReason { /** - * Forces to load the contents of the editor again even if the editor is not dirty. + * Explicit user gesture. + */ + EXPLICIT = 1, + + /** + * Auto save after a timeout. + */ + AUTO = 2, + + /** + * Auto save after editor focus change. + */ + FOCUS_CHANGE = 3, + + /** + * Auto save after window change. + */ + WINDOW_CHANGE = 4 +} + +export interface ISaveOptions { + + /** + * An indicator how the save operation was triggered. + */ + reason?: SaveReason; + + /** + * Forces to load the contents of the working copy + * again even if the working copy is not dirty. */ force?: boolean; /** - * A soft revert will clear dirty state of an editor but will not attempt to load it. + * Instructs the save operation to skip any save participants. + */ + skipSaveParticipants?: boolean; + + /** + * A hint as to which file systems should be available for saving. + */ + availableFileSystems?: string[]; +} + +export interface IRevertOptions { + + /** + * Forces to load the contents of the working copy + * again even if the working copy is not dirty. + */ + force?: boolean; + + /** + * A soft revert will clear dirty state of a working copy + * but will not attempt to load it from its persisted state. + * + * This option may be used in scenarios where an editor is + * closed and where we do not require to load the contents. */ soft?: boolean; } @@ -279,7 +337,7 @@ export interface IEditorInput extends IDisposable { /** * Triggered when this input is disposed. */ - onDispose: Event; + readonly onDispose: Event; /** * Returns the associated resource of this input. @@ -294,7 +352,7 @@ export interface IEditorInput extends IDisposable { /** * Returns the display name of this input. */ - getName(): string | undefined; + getName(): string; /** * Returns the display description of this input. @@ -311,11 +369,40 @@ export interface IEditorInput extends IDisposable { */ resolve(): Promise; + /** + * Returns if this input is readonly or not. + */ + isReadonly(): boolean; + + /** + * Returns if the input is an untitled editor or not. + */ + isUntitled(): boolean; + /** * Returns if this input is dirty or not. */ isDirty(): boolean; + /** + * Saves the editor. The provided groupId helps + * implementors to e.g. preserve view state of the editor + * and re-open it in the correct group after saving. + */ + save(groupId: GroupIdentifier, options?: ISaveOptions): Promise; + + /** + * Saves the editor to a different location. The provided groupId + * helps implementors to e.g. preserve view state of the editor + * and re-open it in the correct group after saving. + */ + saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise; + + /** + * Handles when the input is replaced, such as by renaming its backing resource. + */ + handleMove?(groupId: GroupIdentifier, uri: URI, options?: ITextEditorOptions): IEditorInput | undefined; + /** * Reverts this input. */ @@ -325,6 +412,11 @@ export interface IEditorInput extends IDisposable { * Returns if the other object matches this input. */ matches(other: unknown): boolean; + + /** + * Returns if this editor is disposed. + */ + isDisposed(): boolean; } /** @@ -333,50 +425,32 @@ export interface IEditorInput extends IDisposable { */ export abstract class EditorInput extends Disposable implements IEditorInput { - protected readonly _onDidChangeDirty: Emitter = this._register(new Emitter()); - readonly onDidChangeDirty: Event = this._onDidChangeDirty.event; + protected readonly _onDidChangeDirty = this._register(new Emitter()); + readonly onDidChangeDirty = this._onDidChangeDirty.event; - protected readonly _onDidChangeLabel: Emitter = this._register(new Emitter()); - readonly onDidChangeLabel: Event = this._onDidChangeLabel.event; + protected readonly _onDidChangeLabel = this._register(new Emitter()); + readonly onDidChangeLabel = this._onDidChangeLabel.event; - private readonly _onDispose: Emitter = this._register(new Emitter()); - readonly onDispose: Event = this._onDispose.event; + private readonly _onDispose = this._register(new Emitter()); + readonly onDispose = this._onDispose.event; private disposed: boolean = false; - /** - * Returns the unique type identifier of this input. - */ abstract getTypeId(): string; - /** - * Returns the associated resource of this input if any. - */ getResource(): URI | undefined { return undefined; } - /** - * Returns the name of this input that can be shown to the user. Examples include showing the name of the input - * above the editor area when the input is shown. - */ - getName(): string | undefined { - return undefined; + getName(): string { + return `Editor ${this.getTypeId()}`; } - /** - * Returns the description of this input that can be shown to the user. Examples include showing the description of - * the input above the editor area to the side of the name of the input. - */ getDescription(verbosity?: Verbosity): string | undefined { return undefined; } - /** - * Returns the title of this input that can be shown to the user. Examples include showing the title of - * the input above the editor area as hover over the input label. - */ - getTitle(verbosity?: Verbosity): string | undefined { + getTitle(verbosity?: Verbosity): string { return this.getName(); } @@ -389,10 +463,10 @@ export abstract class EditorInput extends Disposable implements IEditorInput { } /** - * Returns a descriptor suitable for telemetry events. - * - * Subclasses should extend if they can contribute. - */ + * Returns a descriptor suitable for telemetry events. + * + * Subclasses should extend if they can contribute. + */ getTelemetryDescriptor(): { [key: string]: unknown } { /* __GDPR__FRAGMENT__ "EditorTelemetryDescriptor" : { @@ -408,41 +482,30 @@ export abstract class EditorInput extends Disposable implements IEditorInput { */ abstract resolve(): Promise; - /** - * An editor that is dirty will be asked to be saved once it closes. - */ + isReadonly(): boolean { + return true; + } + + isUntitled(): boolean { + return false; + } + isDirty(): boolean { return false; } - /** - * Subclasses should bring up a proper dialog for the user if the editor is dirty and return the result. - */ - confirmSave(): Promise { - return Promise.resolve(ConfirmResult.DONT_SAVE); - } - - /** - * Saves the editor if it is dirty. Subclasses return a promise with a boolean indicating the success of the operation. - */ - save(): Promise { + save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + return Promise.resolve(true); + } + + saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { return Promise.resolve(true); } - /** - * Reverts the editor if it is dirty. Subclasses return a promise with a boolean indicating the success of the operation. - */ revert(options?: IRevertOptions): Promise { return Promise.resolve(true); } - /** - * Called when this input is no longer opened in any editor. Subclasses can free resources as needed. - */ - close(): void { - this.dispose(); - } - /** * Subclasses can set this to false if it does not make sense to split the editor input. */ @@ -450,24 +513,14 @@ export abstract class EditorInput extends Disposable implements IEditorInput { return true; } - /** - * Returns true if this input is identical to the otherInput. - */ matches(otherInput: unknown): boolean { return this === otherInput; } - /** - * Returns whether this input was disposed or not. - */ isDisposed(): boolean { return this.disposed; } - /** - * Called when an editor input is no longer needed. Allows to free up any resources taken by - * resolving the editor input. - */ dispose(): void { this.disposed = true; this._onDispose.fire(); @@ -476,10 +529,61 @@ export abstract class EditorInput extends Disposable implements IEditorInput { } } -export const enum ConfirmResult { - SAVE, - DONT_SAVE, - CANCEL +export abstract class TextEditorInput extends EditorInput { + + constructor( + protected readonly resource: URI, + @IEditorService protected readonly editorService: IEditorService, + @IEditorGroupsService protected readonly editorGroupService: IEditorGroupsService, + @ITextFileService protected readonly textFileService: ITextFileService + ) { + super(); + } + + getResource(): URI { + return this.resource; + } + + async save(groupId: GroupIdentifier, options?: ITextFileSaveOptions): Promise { + if (this.isReadonly()) { + return false; // return early if editor is readonly + } + + return this.textFileService.save(this.resource, options); + } + + saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { + return this.doSaveAs(group, () => this.textFileService.saveAs(this.resource, undefined, options)); + } + + protected async doSaveAs(group: GroupIdentifier, saveRunnable: () => Promise, replaceAllEditors?: boolean): Promise { + + // Preserve view state by opening the editor first. In addition + // this allows the user to review the contents of the editor. + let viewState: IEditorViewState | undefined = undefined; + const editor = await this.editorService.openEditor(this, undefined, group); + if (isTextEditor(editor)) { + viewState = editor.getViewState(); + } + + // Save as + const target = await saveRunnable(); + if (!target) { + return false; // save cancelled + } + + // Replace editor preserving viewstate (either across all groups or + // only selected group) if the target is different from the current resource + if (!isEqual(target, this.resource)) { + const replacement = this.editorService.createInput({ resource: target }); + const targetGroups = replaceAllEditors ? this.editorGroupService.groups.map(group => group.id) : [group]; + for (const group of targetGroups) { + await this.editorService.replaceEditors([{ editor: this, replacement, options: { pinned: true, viewState } }], group); + } + } + + return true; + } } export const enum EncodingMode { @@ -569,20 +673,28 @@ export class SideBySideEditorInput extends EditorInput { return this._details; } + isReadonly(): boolean { + return this.master.isReadonly(); + } + + isUntitled(): boolean { + return this.master.isUntitled(); + } + isDirty(): boolean { return this.master.isDirty(); } - confirmSave(): Promise { - return this.master.confirmSave(); + save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + return this.master.save(groupId, options); } - save(): Promise { - return this.master.save(); + saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + return this.master.saveAs(groupId, options); } - revert(): Promise { - return this.master.revert(); + revert(options?: IRevertOptions): Promise { + return this.master.revert(options); } getTelemetryDescriptor(): { [key: string]: unknown } { @@ -657,8 +769,8 @@ export interface ITextEditorModel extends IEditorModel { */ export class EditorModel extends Disposable implements IEditorModel { - private readonly _onDispose: Emitter = this._register(new Emitter()); - readonly onDispose: Event = this._onDispose.event; + private readonly _onDispose = this._register(new Emitter()); + readonly onDispose = this._onDispose.event; /** * Causes this model to load returning a promise when loading is completed. @@ -1149,7 +1261,7 @@ export const Extensions = { Registry.add(Extensions.EditorInputFactories, new EditorInputFactoryRegistry()); -export async function pathsToEditors(paths: IPathData[] | undefined, fileService: IFileService): Promise<(IResourceInput | IUntitledResourceInput)[]> { +export async function pathsToEditors(paths: IPathData[] | undefined, fileService: IFileService): Promise<(IResourceInput | IUntitledTextResourceInput)[]> { if (!paths || !paths.length) { return []; } @@ -1170,7 +1282,7 @@ export async function pathsToEditors(paths: IPathData[] | undefined, fileService }; } - let input: IResourceInput | IUntitledResourceInput; + let input: IResourceInput | IUntitledTextResourceInput; if (!exists) { input = { resource, options, forceUntitled: true }; } else { diff --git a/src/vs/workbench/common/editor/binaryEditorModel.ts b/src/vs/workbench/common/editor/binaryEditorModel.ts index 2f55727b35..603b0e6c04 100644 --- a/src/vs/workbench/common/editor/binaryEditorModel.ts +++ b/src/vs/workbench/common/editor/binaryEditorModel.ts @@ -6,8 +6,6 @@ import { EditorModel } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; -import { Schemas } from 'vs/base/common/network'; -import { DataUri, basename } from 'vs/base/common/resources'; import { MIME_BINARY } from 'vs/base/common/mime'; /** @@ -19,8 +17,8 @@ export class BinaryEditorModel extends EditorModel { private readonly mime: string; constructor( - private readonly resource: URI, - private readonly name: string | undefined, + public readonly resource: URI, + private readonly name: string, @IFileService private readonly fileService: IFileService ) { super(); @@ -28,32 +26,13 @@ export class BinaryEditorModel extends EditorModel { this.resource = resource; this.name = name; this.mime = MIME_BINARY; - - if (resource.scheme === Schemas.data) { - const metadata = DataUri.parseMetaData(resource); - if (metadata.has(DataUri.META_DATA_SIZE)) { - this.size = Number(metadata.get(DataUri.META_DATA_SIZE)); - } - - const metadataMime = metadata.get(DataUri.META_DATA_MIME); - if (metadataMime) { - this.mime = metadataMime; - } - } } /** * The name of the binary resource. */ getName(): string { - return this.name || basename(this.resource); - } - - /** - * The resource of the binary resource. - */ - getResource(): URI { - return this.resource; + return this.name; } /** diff --git a/src/vs/workbench/common/editor/dataUriEditorInput.ts b/src/vs/workbench/common/editor/dataUriEditorInput.ts deleted file mode 100644 index 35195c3074..0000000000 --- a/src/vs/workbench/common/editor/dataUriEditorInput.ts +++ /dev/null @@ -1,73 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { EditorInput } from 'vs/workbench/common/editor'; -import { URI } from 'vs/base/common/uri'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; -import { DataUri } from 'vs/base/common/resources'; - -/** - * An editor input to present data URIs in a binary editor. Data URIs have the form of: - * data:[mime type];[meta data ;...];base64,[base64 encoded value] - */ -export class DataUriEditorInput extends EditorInput { - - static readonly ID: string = 'workbench.editors.dataUriEditorInput'; - - constructor( - private readonly name: string | undefined, - private readonly description: string | undefined, - private readonly resource: URI, - @IInstantiationService private readonly instantiationService: IInstantiationService - ) { - super(); - - if (!this.name || !this.description) { - const metadata = DataUri.parseMetaData(this.resource); - - if (!this.name) { - this.name = metadata.get(DataUri.META_DATA_LABEL); - } - - if (!this.description) { - this.description = metadata.get(DataUri.META_DATA_DESCRIPTION); - } - } - } - - getResource(): URI { - return this.resource; - } - - getTypeId(): string { - return DataUriEditorInput.ID; - } - - getName(): string | undefined { - return this.name; - } - - getDescription(): string | undefined { - return this.description; - } - - resolve(): Promise { - return this.instantiationService.createInstance(BinaryEditorModel, this.resource, this.getName()).load(); - } - - matches(otherInput: unknown): boolean { - if (super.matches(otherInput) === true) { - return true; - } - - // Compare by resource - if (otherInput instanceof DataUriEditorInput) { - return otherInput.resource.toString() === this.resource.toString(); - } - - return false; - } -} diff --git a/src/vs/workbench/common/editor/diffEditorInput.ts b/src/vs/workbench/common/editor/diffEditorInput.ts index 29d71bfd3f..f3600a257d 100644 --- a/src/vs/workbench/common/editor/diffEditorInput.ts +++ b/src/vs/workbench/common/editor/diffEditorInput.ts @@ -18,7 +18,13 @@ export class DiffEditorInput extends SideBySideEditorInput { private cachedModel: DiffEditorModel | null = null; - constructor(name: string, description: string | undefined, original: EditorInput, modified: EditorInput, private readonly forceOpenAsBinary?: boolean) { + constructor( + name: string, + description: string | undefined, + original: EditorInput, + modified: EditorInput, + private readonly forceOpenAsBinary?: boolean + ) { super(name, description, original, modified); } diff --git a/src/vs/workbench/common/editor/editorGroup.ts b/src/vs/workbench/common/editor/editorGroup.ts index 07815ffaaa..4690a6c364 100644 --- a/src/vs/workbench/common/editor/editorGroup.ts +++ b/src/vs/workbench/common/editor/editorGroup.ts @@ -4,19 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { Event, Emitter } from 'vs/base/common/event'; -import { Extensions, IEditorInputFactoryRegistry, EditorInput, toResource, IEditorIdentifier, IEditorCloseEvent, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorInput, SideBySideEditor } from 'vs/workbench/common/editor'; -import { URI } from 'vs/base/common/uri'; +import { Extensions, IEditorInputFactoryRegistry, EditorInput, IEditorIdentifier, IEditorCloseEvent, GroupIdentifier, CloseDirection, IEditorInput, SideBySideEditorInput } from 'vs/workbench/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ResourceMap } from 'vs/base/common/map'; import { coalesce, firstIndex } from 'vs/base/common/arrays'; // {{SQL CARBON EDIT}} -import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; import { doHandleUpgrade } from 'sql/workbench/common/languageAssociation'; const EditorOpenPositioning = { @@ -66,31 +64,31 @@ export class EditorGroup extends Disposable { //#region events private readonly _onDidEditorActivate = this._register(new Emitter()); - readonly onDidEditorActivate: Event = this._onDidEditorActivate.event; + readonly onDidEditorActivate = this._onDidEditorActivate.event; private readonly _onDidEditorOpen = this._register(new Emitter()); - readonly onDidEditorOpen: Event = this._onDidEditorOpen.event; + readonly onDidEditorOpen = this._onDidEditorOpen.event; private readonly _onDidEditorClose = this._register(new Emitter()); - readonly onDidEditorClose: Event = this._onDidEditorClose.event; + readonly onDidEditorClose = this._onDidEditorClose.event; private readonly _onDidEditorDispose = this._register(new Emitter()); - readonly onDidEditorDispose: Event = this._onDidEditorDispose.event; + readonly onDidEditorDispose = this._onDidEditorDispose.event; private readonly _onDidEditorBecomeDirty = this._register(new Emitter()); - readonly onDidEditorBecomeDirty: Event = this._onDidEditorBecomeDirty.event; + readonly onDidEditorBecomeDirty = this._onDidEditorBecomeDirty.event; private readonly _onDidEditorLabelChange = this._register(new Emitter()); - readonly onDidEditorLabelChange: Event = this._onDidEditorLabelChange.event; + readonly onDidEditorLabelChange = this._onDidEditorLabelChange.event; private readonly _onDidEditorMove = this._register(new Emitter()); - readonly onDidEditorMove: Event = this._onDidEditorMove.event; + readonly onDidEditorMove = this._onDidEditorMove.event; private readonly _onDidEditorPin = this._register(new Emitter()); - readonly onDidEditorPin: Event = this._onDidEditorPin.event; + readonly onDidEditorPin = this._onDidEditorPin.event; private readonly _onDidEditorUnpin = this._register(new Emitter()); - readonly onDidEditorUnpin: Event = this._onDidEditorUnpin.event; + readonly onDidEditorUnpin = this._onDidEditorUnpin.event; //#endregion @@ -99,7 +97,6 @@ export class EditorGroup extends Disposable { private editors: EditorInput[] = []; private mru: EditorInput[] = []; - private mapResourceToEditorCount: ResourceMap = new ResourceMap(); private preview: EditorInput | null = null; // editor in preview state private active: EditorInput | null = null; // editor in active state @@ -141,26 +138,8 @@ export class EditorGroup extends Disposable { return mru ? this.mru.slice(0) : this.editors.slice(0); } - getEditor(index: number): EditorInput | undefined; - getEditor(resource: URI): EditorInput | undefined; - getEditor(arg1: number | URI): EditorInput | undefined { - if (typeof arg1 === 'number') { - return this.editors[arg1]; - } - - const resource: URI = arg1; - if (!this.contains(resource)) { - return undefined; // fast check for resource opened or not - } - - for (const editor of this.editors) { - const editorResource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); - if (editorResource?.toString() === resource.toString()) { - return editor; - } - } - - return undefined; + getEditorByIndex(index: number): EditorInput | undefined { + return this.editors[index]; } get activeEditor(): EditorInput | null { @@ -515,7 +494,6 @@ export class EditorGroup extends Disposable { // Add if (!del && editor) { this.mru.push(editor); // make it LRU editor - this.updateResourceMap(editor, false /* add */); // add new to resource map } // Remove / Replace @@ -525,41 +503,11 @@ export class EditorGroup extends Disposable { // Remove if (del && !editor) { this.mru.splice(indexInMRU, 1); // remove from MRU - this.updateResourceMap(editorToDeleteOrReplace, true /* delete */); // remove from resource map } // Replace else if (del && editor) { this.mru.splice(indexInMRU, 1, editor); // replace MRU at location - this.updateResourceMap(editor, false /* add */); // add new to resource map - this.updateResourceMap(editorToDeleteOrReplace, true /* delete */); // remove replaced from resource map - } - } - } - - private updateResourceMap(editor: EditorInput, remove: boolean): void { - const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); - if (resource) { - - // It is possible to have the same resource opened twice (once as normal input and once as diff input) - // So we need to do ref counting on the resource to provide the correct picture - const counter = this.mapResourceToEditorCount.get(resource) || 0; - - // Add - let newCounter: number; - if (!remove) { - newCounter = counter + 1; - } - - // Delete - else { - newCounter = counter - 1; - } - - if (newCounter > 0) { - this.mapResourceToEditorCount.set(resource, newCounter); - } else { - this.mapResourceToEditorCount.delete(resource); } } } @@ -578,28 +526,20 @@ export class EditorGroup extends Disposable { return -1; } - contains(editorOrResource: EditorInput | URI): boolean; - contains(editor: EditorInput, supportSideBySide?: boolean): boolean; - contains(editorOrResource: EditorInput | URI, supportSideBySide?: boolean): boolean { - if (editorOrResource instanceof EditorInput) { - const index = this.indexOf(editorOrResource); - if (index >= 0) { + contains(candidate: EditorInput, searchInSideBySideEditors?: boolean): boolean { + for (const editor of this.editors) { + if (this.matches(editor, candidate)) { return true; } - if (supportSideBySide && editorOrResource instanceof SideBySideEditorInput) { - const index = this.indexOf(editorOrResource.master); - if (index >= 0) { + if (searchInSideBySideEditors && editor instanceof SideBySideEditorInput) { + if (this.matches(editor.master, candidate) || this.matches(editor.details, candidate)) { return true; } } - - return false; } - const counter = this.mapResourceToEditorCount.get(editorOrResource); - - return typeof counter === 'number' && counter > 0; + return false; } private setMostRecentlyUsed(editor: EditorInput): void { @@ -625,7 +565,6 @@ export class EditorGroup extends Disposable { const group = this.instantiationService.createInstance(EditorGroup, undefined); group.editors = this.editors.slice(0); group.mru = this.mru.slice(0); - group.mapResourceToEditorCount = this.mapResourceToEditorCount.clone(); group.preview = this.preview; group.active = this.active; group.editorOpenPositioning = this.editorOpenPositioning; @@ -647,7 +586,7 @@ export class EditorGroup extends Disposable { if (factory) { // {{SQL CARBON EDIT}} // don't serialize unmodified unitited files - if (e instanceof UntitledEditorInput && !e.isDirty() + if (e instanceof UntitledTextEditorInput && !e.isDirty() && !this.configurationService.getValue('sql.promptToSaveGeneratedFiles')) { return; } @@ -692,7 +631,6 @@ export class EditorGroup extends Disposable { const editor = this.instantiationService.invokeFunction(doHandleUpgrade, factory.deserialize(this.instantiationService, e.value)); // {{SQL CARBON EDIT}} handle upgrade path to new serialization if (editor) { this.registerEditorListeners(editor); - this.updateResourceMap(editor, false /* add */); } return editor; @@ -725,7 +663,7 @@ export class EditorGroup extends Disposable { // remove from MRU list otherwise later if we try to close them it leaves a sticky active editor with no data this.mru.splice(index, 1); this.active = this.isActive(editor) ? this.editors[0] : this.active; - editor.close(); + editor.dispose(); } else { n++; diff --git a/src/vs/workbench/common/editor/resourceEditorModel.ts b/src/vs/workbench/common/editor/resourceEditorModel.ts index 32bec24ef6..e1caa2daf7 100644 --- a/src/vs/workbench/common/editor/resourceEditorModel.ts +++ b/src/vs/workbench/common/editor/resourceEditorModel.ts @@ -21,10 +21,6 @@ export class ResourceEditorModel extends BaseTextEditorModel { super(modelService, modeService, resource); } - isReadonly(): boolean { - return true; - } - dispose(): void { // TODO@Joao: force this class to dispose the underlying model diff --git a/src/vs/workbench/common/editor/textEditorModel.ts b/src/vs/workbench/common/editor/textEditorModel.ts index e07dcda857..f5479fee23 100644 --- a/src/vs/workbench/common/editor/textEditorModel.ts +++ b/src/vs/workbench/common/editor/textEditorModel.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ITextModel, ITextBufferFactory, ITextSnapshot } from 'vs/editor/common/model'; +import { ITextModel, ITextBufferFactory, ITextSnapshot, ModelConstants } from 'vs/editor/common/model'; import { EditorModel, IModeSupport } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; import { ITextEditorModel, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; @@ -16,8 +16,10 @@ import { withUndefinedAsNull } from 'vs/base/common/types'; /** * The base text editor model leverages the code editor model. This class is only intended to be subclassed and not instantiated. */ -export abstract class BaseTextEditorModel extends EditorModel implements ITextEditorModel, IModeSupport { +export class BaseTextEditorModel extends EditorModel implements ITextEditorModel, IModeSupport { + protected textEditorModelHandle: URI | null = null; + private createdEditorModel: boolean | undefined; private readonly modelDisposeListener = this._register(new MutableDisposable()); @@ -59,7 +61,9 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd return this.textEditorModelHandle ? this.modelService.getModel(this.textEditorModelHandle) : null; } - abstract isReadonly(): boolean; + isReadonly(): boolean { + return true; + } setMode(mode: string): void { if (!this.isResolved()) { @@ -106,12 +110,12 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd // text buffer factory const textBufferFactory = value as ITextBufferFactory; if (typeof textBufferFactory.getFirstLineText === 'function') { - return textBufferFactory.getFirstLineText(100); + return textBufferFactory.getFirstLineText(ModelConstants.FIRST_LINE_DETECTION_LENGTH_LIMIT); } // text model const textSnapshot = value as ITextModel; - return textSnapshot.getLineContent(1).substr(0, 100); + return textSnapshot.getLineContent(1).substr(0, ModelConstants.FIRST_LINE_DETECTION_LENGTH_LIMIT); } /** diff --git a/src/vs/workbench/common/editor/untitledEditorInput.ts b/src/vs/workbench/common/editor/untitledTextEditorInput.ts similarity index 56% rename from src/vs/workbench/common/editor/untitledEditorInput.ts rename to src/vs/workbench/common/editor/untitledTextEditorInput.ts index a9da560f87..2e00a3d554 100644 --- a/src/vs/workbench/common/editor/untitledEditorInput.ts +++ b/src/vs/workbench/common/editor/untitledTextEditorInput.ts @@ -5,44 +5,58 @@ import { URI } from 'vs/base/common/uri'; import { suggestFilename } from 'vs/base/common/mime'; -import { memoize } from 'vs/base/common/decorators'; +import { createMemoizer } from 'vs/base/common/decorators'; import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; -import { basenameOrAuthority, dirname } from 'vs/base/common/resources'; -import { EditorInput, IEncodingSupport, EncodingMode, ConfirmResult, Verbosity, IModeSupport } from 'vs/workbench/common/editor'; -import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel'; +import { basenameOrAuthority, dirname, toLocalResource } from 'vs/base/common/resources'; +import { IEncodingSupport, EncodingMode, Verbosity, IModeSupport, TextEditorInput, GroupIdentifier, IRevertOptions } from 'vs/workbench/common/editor'; +import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { Event, Emitter } from 'vs/base/common/event'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { Emitter } from 'vs/base/common/event'; +import { ITextFileService, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; import { ILabelService } from 'vs/platform/label/common/label'; import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; /** * An editor input to be used for untitled text buffers. */ -export class UntitledEditorInput extends EditorInput implements IEncodingSupport, IModeSupport { +export class UntitledTextEditorInput extends TextEditorInput implements IEncodingSupport, IModeSupport { static readonly ID: string = 'workbench.editors.untitledEditorInput'; - private cachedModel: UntitledEditorModel | null = null; - private modelResolve: Promise | null = null; + private static readonly MEMOIZER = createMemoizer(); - private readonly _onDidModelChangeContent: Emitter = this._register(new Emitter()); - readonly onDidModelChangeContent: Event = this._onDidModelChangeContent.event; + private cachedModel: UntitledTextEditorModel | null = null; + private modelResolve: Promise | null = null; - private readonly _onDidModelChangeEncoding: Emitter = this._register(new Emitter()); - readonly onDidModelChangeEncoding: Event = this._onDidModelChangeEncoding.event; + private readonly _onDidModelChangeContent = this._register(new Emitter()); + readonly onDidModelChangeContent = this._onDidModelChangeContent.event; + + private readonly _onDidModelChangeEncoding = this._register(new Emitter()); + readonly onDidModelChangeEncoding = this._onDidModelChangeEncoding.event; constructor( - private readonly resource: URI, + resource: URI, private readonly _hasAssociatedFilePath: boolean, private preferredMode: string | undefined, private readonly initialValue: string | undefined, private preferredEncoding: string | undefined, @IInstantiationService private readonly instantiationService: IInstantiationService, - @ITextFileService private readonly textFileService: ITextFileService, - @ILabelService private readonly labelService: ILabelService + @ITextFileService textFileService: ITextFileService, + @ILabelService private readonly labelService: ILabelService, + @IEditorService editorService: IEditorService, + @IEditorGroupsService editorGroupService: IEditorGroupsService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService ) { - super(); + super(resource, editorService, editorGroupService, textFileService); + + this.registerListeners(); + } + + private registerListeners(): void { + this._register(this.labelService.onDidChangeFormatters(() => UntitledTextEditorInput.MEMOIZER.clear())); } get hasAssociatedFilePath(): boolean { @@ -50,28 +64,24 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport } getTypeId(): string { - return UntitledEditorInput.ID; - } - - getResource(): URI { - return this.resource; + return UntitledTextEditorInput.ID; } getName(): string { return this.hasAssociatedFilePath ? basenameOrAuthority(this.resource) : this.resource.path; } - @memoize + @UntitledTextEditorInput.MEMOIZER private get shortDescription(): string { return this.labelService.getUriBasenameLabel(dirname(this.resource)); } - @memoize + @UntitledTextEditorInput.MEMOIZER private get mediumDescription(): string { return this.labelService.getUriLabel(dirname(this.resource), { relative: true }); } - @memoize + @UntitledTextEditorInput.MEMOIZER private get longDescription(): string { return this.labelService.getUriLabel(dirname(this.resource)); } @@ -92,22 +102,22 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport } } - @memoize + @UntitledTextEditorInput.MEMOIZER private get shortTitle(): string { return this.getName(); } - @memoize + @UntitledTextEditorInput.MEMOIZER private get mediumTitle(): string { return this.labelService.getUriLabel(this.resource, { relative: true }); } - @memoize + @UntitledTextEditorInput.MEMOIZER private get longTitle(): string { return this.labelService.getUriLabel(this.resource); } - getTitle(verbosity: Verbosity): string | undefined { + getTitle(verbosity: Verbosity): string { if (!this.hasAssociatedFilePath) { return this.getName(); } @@ -120,8 +130,14 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport case Verbosity.LONG: return this.longTitle; } + } - return undefined; + isReadonly(): boolean { + return false; + } + + isUntitled(): boolean { + return true; } isDirty(): boolean { @@ -146,22 +162,37 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport return false; } - confirmSave(): Promise { - return this.textFileService.confirmSave([this.resource]); + save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { + return this.doSaveAs(group, async () => { + + // With associated file path, save to the path that is + // associated. Make sure to convert the result using + // remote authority properly. + if (this.hasAssociatedFilePath) { + if (await this.textFileService.save(this.resource, options)) { + return toLocalResource(this.resource, this.environmentService.configuration.remoteAuthority); + } + + return undefined; // {{SQL CARBON EDIT}} strict-null-checks + } + + // Without associated file path, do a normal "Save As" + return this.textFileService.saveAs(this.resource, undefined, options); + }, true /* replace editor across all groups */); } - save(): Promise { - return this.textFileService.save(this.resource); + saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { + return this.doSaveAs(group, () => this.textFileService.saveAs(this.resource, undefined, options), true /* replace editor across all groups */); } - revert(): Promise { + async revert(options?: IRevertOptions): Promise { if (this.cachedModel) { this.cachedModel.revert(); } - this.dispose(); // a reverted untitled editor is no longer valid, so we dispose it + this.dispose(); // a reverted untitled text editor is no longer valid, so we dispose it - return Promise.resolve(true); + return true; } suggestFileName(): string { @@ -209,7 +240,7 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport return this.preferredMode; } - resolve(): Promise { + resolve(): Promise { // Join a model resolve if we have had one before if (this.modelResolve) { @@ -223,8 +254,8 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport return this.modelResolve; } - private createModel(): UntitledEditorModel { - const model = this._register(this.instantiationService.createInstance(UntitledEditorModel, this.preferredMode, this.resource, this.hasAssociatedFilePath, this.initialValue, this.preferredEncoding)); + private createModel(): UntitledTextEditorModel { + const model = this._register(this.instantiationService.createInstance(UntitledTextEditorModel, this.preferredMode, this.resource, this.hasAssociatedFilePath, this.initialValue, this.preferredEncoding)); // re-emit some events from the model this._register(model.onDidChangeContent(() => this._onDidModelChangeContent.fire())); @@ -240,7 +271,7 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport } // Otherwise compare by properties - if (otherInput instanceof UntitledEditorInput) { + if (otherInput instanceof UntitledTextEditorInput) { return otherInput.resource.toString() === this.resource.toString(); } @@ -253,4 +284,4 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport super.dispose(); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/common/editor/untitledEditorModel.ts b/src/vs/workbench/common/editor/untitledTextEditorModel.ts similarity index 78% rename from src/vs/workbench/common/editor/untitledEditorModel.ts rename to src/vs/workbench/common/editor/untitledTextEditorModel.ts index 886b32a3eb..e1990c1c41 100644 --- a/src/vs/workbench/common/editor/untitledEditorModel.ts +++ b/src/vs/workbench/common/editor/untitledTextEditorModel.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IEncodingSupport } from 'vs/workbench/common/editor'; +import { IEncodingSupport, ISaveOptions } from 'vs/workbench/common/editor'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { URI } from 'vs/base/common/uri'; import { CONTENT_CHANGE_EVENT_BUFFER_DELAY } from 'vs/platform/files/common/files'; @@ -16,8 +16,10 @@ import { ITextResourceConfigurationService } from 'vs/editor/common/services/res import { ITextBufferFactory } from 'vs/editor/common/model'; import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; +import { IWorkingCopyService, IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -export class UntitledEditorModel extends BaseTextEditorModel implements IEncodingSupport { +export class UntitledTextEditorModel extends BaseTextEditorModel implements IEncodingSupport, IWorkingCopy { static DEFAULT_CONTENT_CHANGE_BUFFER_DELAY = CONTENT_CHANGE_EVENT_BUFFER_DELAY; @@ -30,33 +32,34 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin private readonly _onDidChangeEncoding: Emitter = this._register(new Emitter()); readonly onDidChangeEncoding: Event = this._onDidChangeEncoding.event; - private dirty: boolean = false; - private versionId: number = 0; - private readonly contentChangeEventScheduler: RunOnceScheduler; + readonly capabilities = 0; + + private dirty = false; + private versionId = 0; + private readonly contentChangeEventScheduler = this._register(new RunOnceScheduler(() => this._onDidChangeContent.fire(), UntitledTextEditorModel.DEFAULT_CONTENT_CHANGE_BUFFER_DELAY)); private configuredEncoding?: string; constructor( private readonly preferredMode: string | undefined, - private readonly resource: URI, - private _hasAssociatedFilePath: boolean, + public readonly resource: URI, + public readonly hasAssociatedFilePath: boolean, private readonly initialValue: string | undefined, private preferredEncoding: string | undefined, @IModeService modeService: IModeService, @IModelService modelService: IModelService, @IBackupFileService private readonly backupFileService: IBackupFileService, - @ITextResourceConfigurationService private readonly configurationService: ITextResourceConfigurationService + @ITextResourceConfigurationService private readonly configurationService: ITextResourceConfigurationService, + @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, + @ITextFileService private readonly textFileService: ITextFileService ) { super(modelService, modeService); - this.contentChangeEventScheduler = this._register(new RunOnceScheduler(() => this._onDidChangeContent.fire(), UntitledEditorModel.DEFAULT_CONTENT_CHANGE_BUFFER_DELAY)); + // Make known to working copy service + this._register(this.workingCopyService.registerWorkingCopy(this)); this.registerListeners(); } - get hasAssociatedFilePath(): boolean { - return this._hasAssociatedFilePath; - } - private registerListeners(): void { // Config Changes @@ -116,15 +119,17 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin this._onDidChangeDirty.fire(); } - getResource(): URI { - return this.resource; + save(options?: ISaveOptions): Promise { + return this.textFileService.save(this.resource, options); } - revert(): void { + async revert(): Promise { this.setDirty(false); // Handle content change event buffered this.contentChangeEventScheduler.schedule(); + + return true; } backup(): Promise { @@ -139,7 +144,7 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin return this.backupFileService.hasBackupSync(this.resource, this.versionId); } - async load(): Promise { + async load(): Promise { // Check for backups first let backup: IResolvedBackup | undefined = undefined; @@ -149,7 +154,7 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin } // untitled associated to file path are dirty right away as well as untitled with content - this.setDirty(this._hasAssociatedFilePath || !!backup || !!this.initialValue); + this.setDirty(this.hasAssociatedFilePath || !!backup || !!this.initialValue); let untitledContents: ITextBufferFactory; if (backup) { @@ -180,7 +185,7 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin // Listen to mode changes this._register(textEditorModel.onDidChangeLanguage(() => this.onConfigurationChange())); // mode change can have impact on config - return this as UntitledEditorModel & IResolvedTextEditorModel; + return this as UntitledTextEditorModel & IResolvedTextEditorModel; } private onModelContentChanged(): void { @@ -190,9 +195,9 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin this.versionId++; - // mark the untitled editor as non-dirty once its content becomes empty and we do + // mark the untitled text editor as non-dirty once its content becomes empty and we do // not have an associated path set. we never want dirty indicator in that case. - if (!this._hasAssociatedFilePath && this.textEditorModel.getLineCount() === 1 && this.textEditorModel.getLineContent(1) === '') { + if (!this.hasAssociatedFilePath && this.textEditorModel.getLineCount() === 1 && this.textEditorModel.getLineContent(1) === '') { this.setDirty(false); } diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index 3e0871eb4a..7ea68a9a89 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -352,6 +352,12 @@ export const ACTIVITY_BAR_ACTIVE_BORDER = registerColor('activityBar.activeBorde hc: null }, nls.localize('activityBarActiveBorder', "Activity bar border color for the active item. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); +export const ACTIVITY_BAR_ACTIVE_FOCUS_BORDER = registerColor('activityBar.activeFocusBorder', { + dark: null, + light: null, + hc: null +}, nls.localize('activityBarActiveFocusBorder', "Activity bar focus border color for the active item. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); + export const ACTIVITY_BAR_ACTIVE_BACKGROUND = registerColor('activityBar.activeBackground', { dark: null, light: null, @@ -575,19 +581,31 @@ export const NOTIFICATIONS_ERROR_ICON_FOREGROUND = registerColor('notificationsE dark: editorErrorForeground, light: editorErrorForeground, hc: editorErrorForeground -}, nls.localize('notificationsErrorIconForeground', "The color used for the notification error icon.")); +}, nls.localize('notificationsErrorIconForeground', "The color used for the icon of error notifications. Notifications slide in from the bottom right of the window.")); export const NOTIFICATIONS_WARNING_ICON_FOREGROUND = registerColor('notificationsWarningIcon.foreground', { dark: editorWarningForeground, light: editorWarningForeground, hc: editorWarningForeground -}, nls.localize('notificationsWarningIconForeground', "The color used for the notification warning icon.")); +}, nls.localize('notificationsWarningIconForeground', "The color used for the icon of warning notifications. Notifications slide in from the bottom right of the window.")); export const NOTIFICATIONS_INFO_ICON_FOREGROUND = registerColor('notificationsInfoIcon.foreground', { dark: editorInfoForeground, light: editorInfoForeground, hc: editorInfoForeground -}, nls.localize('notificationsInfoIconForeground', "The color used for the notification info icon.")); +}, nls.localize('notificationsInfoIconForeground', "The color used for the icon of info notifications. Notifications slide in from the bottom right of the window.")); + +export const WINDOW_ACTIVE_BORDER = registerColor('window.activeBorder', { + dark: null, + light: null, + hc: contrastBorder +}, nls.localize('windowActiveBorder', "The color used for the border of the window when it is active. Only supported in the desktop client when using the custom title bar.")); + +export const WINDOW_INACTIVE_BORDER = registerColor('window.inactiveBorder', { + dark: null, + light: null, + hc: contrastBorder +}, nls.localize('windowInactiveBorder', "The color used for the border of the window when it is inactive. Only supported in the desktop client when using the custom title bar.")); /** * Base class for all themable workbench components. diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 0a743adf6f..2eb24d53d2 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -424,3 +424,10 @@ export interface ITreeViewDataProvider { getChildren(element?: ITreeItem): Promise; } + +export interface IEditableData { + validationMessage: (value: string) => string | null; + placeholder?: string | null; + startingValue?: string | null; + onFinish: (value: string, success: boolean) => void; +} diff --git a/src/vs/workbench/contrib/backup/common/backupModelTracker.ts b/src/vs/workbench/contrib/backup/common/backupModelTracker.ts index e2883d5a5e..59b2e5ec07 100644 --- a/src/vs/workbench/contrib/backup/common/backupModelTracker.ts +++ b/src/vs/workbench/contrib/backup/common/backupModelTracker.ts @@ -7,10 +7,10 @@ import { URI as Uri } from 'vs/base/common/uri'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { Disposable } from 'vs/base/common/lifecycle'; import { ITextFileService, TextFileModelChangeEvent, StateChange } from 'vs/workbench/services/textfile/common/textfiles'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IFilesConfiguration, AutoSaveConfiguration, CONTENT_CHANGE_EVENT_BUFFER_DELAY } from 'vs/platform/files/common/files'; +import { CONTENT_CHANGE_EVENT_BUFFER_DELAY } from 'vs/platform/files/common/files'; +import { IFilesConfigurationService, IAutoSaveConfiguration } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; const AUTO_SAVE_AFTER_DELAY_DISABLED_TIME = CONTENT_CHANGE_EVENT_BUFFER_DELAY + 500; @@ -21,8 +21,8 @@ export class BackupModelTracker extends Disposable implements IWorkbenchContribu constructor( @IBackupFileService private readonly backupFileService: IBackupFileService, @ITextFileService private readonly textFileService: ITextFileService, - @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService, + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService ) { super(); @@ -32,26 +32,20 @@ export class BackupModelTracker extends Disposable implements IWorkbenchContribu private registerListeners() { // Listen for text file model changes - this._register(this.textFileService.models.onModelContentChanged((e) => this.onTextFileModelChanged(e))); - this._register(this.textFileService.models.onModelSaved((e) => this.discardBackup(e.resource))); - this._register(this.textFileService.models.onModelDisposed((e) => this.discardBackup(e))); + this._register(this.textFileService.models.onModelContentChanged(e => this.onTextFileModelChanged(e))); + this._register(this.textFileService.models.onModelSaved(e => this.discardBackup(e.resource))); + this._register(this.textFileService.models.onModelDisposed(e => this.discardBackup(e))); // Listen for untitled model changes - this._register(this.untitledEditorService.onDidChangeContent((e) => this.onUntitledModelChanged(e))); - this._register(this.untitledEditorService.onDidDisposeModel((e) => this.discardBackup(e))); + this._register(this.untitledTextEditorService.onDidChangeContent(e => this.onUntitledModelChanged(e))); + this._register(this.untitledTextEditorService.onDidDisposeModel(e => this.discardBackup(e))); - // Listen to config changes - this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationChange(this.configurationService.getValue()))); + // Listen to auto save config changes + this._register(this.filesConfigurationService.onAutoSaveConfigurationChange(c => this.onAutoSaveConfigurationChange(c))); } - private onConfigurationChange(configuration: IFilesConfiguration): void { - if (!configuration || !configuration.files) { - this.configuredAutoSaveAfterDelay = false; - - return; - } - - this.configuredAutoSaveAfterDelay = (configuration.files.autoSave === AutoSaveConfiguration.AFTER_DELAY && configuration.files.autoSaveDelay <= AUTO_SAVE_AFTER_DELAY_DISABLED_TIME); + private onAutoSaveConfigurationChange(configuration: IAutoSaveConfiguration): void { + this.configuredAutoSaveAfterDelay = typeof configuration.autoSaveDelay === 'number' && configuration.autoSaveDelay < AUTO_SAVE_AFTER_DELAY_DISABLED_TIME; } private onTextFileModelChanged(event: TextFileModelChangeEvent): void { @@ -71,8 +65,8 @@ export class BackupModelTracker extends Disposable implements IWorkbenchContribu } private onUntitledModelChanged(resource: Uri): void { - if (this.untitledEditorService.isDirty(resource)) { - this.untitledEditorService.loadOrCreate({ resource }).then(model => model.backup()); + if (this.untitledTextEditorService.isDirty(resource)) { + this.untitledTextEditorService.loadOrCreate({ resource }).then(model => model.backup()); } else { this.discardBackup(resource); } diff --git a/src/vs/workbench/contrib/backup/common/backupRestorer.ts b/src/vs/workbench/contrib/backup/common/backupRestorer.ts index 40158dc9e3..091ec7265b 100644 --- a/src/vs/workbench/contrib/backup/common/backupRestorer.ts +++ b/src/vs/workbench/contrib/backup/common/backupRestorer.ts @@ -10,7 +10,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IResourceInput } from 'vs/platform/editor/common/editor'; import { Schemas } from 'vs/base/common/network'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { IUntitledResourceInput } from 'vs/workbench/common/editor'; +import { IUntitledTextResourceInput } from 'vs/workbench/common/editor'; import { toLocalResource } from 'vs/base/common/resources'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -76,7 +76,7 @@ export class BackupRestorer implements IWorkbenchContribution { await this.editorService.openEditors(inputs); } - private resolveInput(resource: URI, index: number, hasOpenedEditors: boolean): IResourceInput | IUntitledResourceInput { + private resolveInput(resource: URI, index: number, hasOpenedEditors: boolean): IResourceInput | IUntitledTextResourceInput { const options = { pinned: true, preserveFocus: true, inactive: index > 0 || hasOpenedEditors }; // this is a (weak) strategy to find out if the untitled input had diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts index ac84a41c81..392c14b303 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { CallHierarchyProviderRegistry, CallHierarchyDirection } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy'; +import { CallHierarchyProviderRegistry, CallHierarchyDirection, CallHierarchyModel } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { CallHierarchyTreePeekWidget } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek'; @@ -17,9 +17,12 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { PeekContext } from 'vs/editor/contrib/referenceSearch/peekViewWidget'; -import { CallHierarchyRoot } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchyTree'; +import { PeekContext } from 'vs/editor/contrib/peekView/peekView'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { Range } from 'vs/editor/common/core/range'; +import { IPosition } from 'vs/editor/common/core/position'; +import { MenuId } from 'vs/platform/actions/common/actions'; const _ctxHasCompletionItemProvider = new RawContextKey('editorHasCallHierarchyProvider', false); const _ctxCallHierarchyVisible = new RawContextKey('callHierarchyVisible', false); @@ -39,10 +42,13 @@ class CallHierarchyController implements IEditorContribution { private readonly _dispoables = new DisposableStore(); private readonly _sessionDisposables = new DisposableStore(); + private _widget?: CallHierarchyTreePeekWidget; + constructor( private readonly _editor: ICodeEditor, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IStorageService private readonly _storageService: IStorageService, + @ICodeEditorService private readonly _editorService: ICodeEditorService, @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { this._ctxIsVisible = _ctxCallHierarchyVisible.bindTo(this._contextKeyService); @@ -59,48 +65,83 @@ class CallHierarchyController implements IEditorContribution { this._dispoables.dispose(); } - async startCallHierarchy(): Promise { + async startCallHierarchyFromEditor(): Promise { this._sessionDisposables.clear(); if (!this._editor.hasModel()) { return; } - const model = this._editor.getModel(); + const document = this._editor.getModel(); const position = this._editor.getPosition(); - const [provider] = CallHierarchyProviderRegistry.ordered(model); - if (!provider) { + if (!CallHierarchyProviderRegistry.has(document)) { return; } + const cts = new CancellationTokenSource(); + const model = CallHierarchyModel.create(document, position, cts.token); const direction = this._storageService.getNumber(CallHierarchyController._StorageDirection, StorageScope.GLOBAL, CallHierarchyDirection.CallsFrom); - Event.any(this._editor.onDidChangeModel, this._editor.onDidChangeModelLanguage)(this.endCallHierarchy, this, this._sessionDisposables); - const widget = this._instantiationService.createInstance( - CallHierarchyTreePeekWidget, - this._editor, - position, - provider, - direction + this._showCallHierarchyWidget(position, direction, model, cts); + } + + async startCallHierarchyFromCallHierarchy(): Promise { + if (!this._widget) { + return; + } + const model = this._widget.getModel(); + const call = this._widget.getFocused(); + if (!call || !model) { + return; + } + const newEditor = await this._editorService.openCodeEditor({ resource: call.item.uri }, this._editor); + if (!newEditor) { + return; + } + const newModel = model.fork(call.item); + this._sessionDisposables.clear(); + + CallHierarchyController.get(newEditor)._showCallHierarchyWidget( + Range.lift(newModel.root.selectionRange).getStartPosition(), + this._widget.direction, + Promise.resolve(newModel), + new CancellationTokenSource() ); + } - widget.showLoading(); + private _showCallHierarchyWidget(position: IPosition, direction: number, model: Promise, cts: CancellationTokenSource) { + + Event.any(this._editor.onDidChangeModel, this._editor.onDidChangeModelLanguage)(this.endCallHierarchy, this, this._sessionDisposables); + this._widget = this._instantiationService.createInstance(CallHierarchyTreePeekWidget, this._editor, position, direction); + this._widget.showLoading(); this._ctxIsVisible.set(true); - - const cancel = new CancellationTokenSource(); - - this._sessionDisposables.add(widget.onDidClose(() => { + this._sessionDisposables.add(this._widget.onDidClose(() => { this.endCallHierarchy(); - this._storageService.store(CallHierarchyController._StorageDirection, widget.direction, StorageScope.GLOBAL); + this._storageService.store(CallHierarchyController._StorageDirection, this._widget!.direction, StorageScope.GLOBAL); })); - this._sessionDisposables.add({ dispose() { cancel.cancel(); } }); - this._sessionDisposables.add(widget); + this._sessionDisposables.add({ dispose() { cts.dispose(true); } }); + this._sessionDisposables.add(this._widget); - const root = CallHierarchyRoot.fromEditor(this._editor); - if (root) { - widget.showItem(root); - } else { - widget.showMessage(localize('no.item', "No results")); + model.then(model => { + if (cts.token.isCancellationRequested) { + return; // nothing + } + if (model) { + this._sessionDisposables.add(model); + this._widget!.showModel(model); + } + else { + this._widget!.showMessage(localize('no.item', "No results")); + } + }).catch(e => { + this._widget!.showMessage(localize('error', "Failed to show call hierarchy")); + console.error(e); + }); + } + + toggleCallHierarchyDirection(): void { + if (this._widget) { + this._widget.toggleDirection(); } } @@ -120,9 +161,10 @@ registerEditorAction(class extends EditorAction { id: 'editor.showCallHierarchy', label: localize('title', "Peek Call Hierarchy"), alias: 'Peek Call Hierarchy', - menuOpts: { + contextMenuOpts: { + menuId: MenuId.EditorContextPeek, group: 'navigation', - order: 1.48 + order: 1000 }, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -130,14 +172,55 @@ registerEditorAction(class extends EditorAction { primary: KeyMod.Shift + KeyMod.Alt + KeyCode.KEY_H }, precondition: ContextKeyExpr.and( + _ctxCallHierarchyVisible.negate(), _ctxHasCompletionItemProvider, PeekContext.notInPeekEditor ) }); } - async run(_accessor: ServicesAccessor, editor: ICodeEditor, args: any): Promise { - return CallHierarchyController.get(editor).startCallHierarchy(); + async run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise { + return CallHierarchyController.get(editor).startCallHierarchyFromEditor(); + } +}); + +registerEditorAction(class extends EditorAction { + + constructor() { + super({ + id: 'editor.toggleCallHierarchy', + label: localize('title.toggle', "Toggle Call Hierarchy"), + alias: 'Toggle Call Hierarchy', + kbOpts: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.Shift + KeyMod.Alt + KeyCode.KEY_H + }, + precondition: _ctxCallHierarchyVisible + }); + } + + async run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise { + return CallHierarchyController.get(editor).toggleCallHierarchyDirection(); + } +}); + +registerEditorAction(class extends EditorAction { + + constructor() { + super({ + id: 'editor.refocusCallHierarchy', + label: localize('title.refocus', "Refocus Call Hierarchy"), + alias: 'Refocus Call Hierarchy', + kbOpts: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.Shift + KeyCode.Enter + }, + precondition: _ctxCallHierarchyVisible + }); + } + + async run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise { + return CallHierarchyController.get(editor).startCallHierarchyFromCallHierarchy(); } }); diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.ts index 8d109470f6..c784fbe82c 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.ts @@ -9,10 +9,14 @@ import { ITextModel } from 'vs/editor/common/model'; import { CancellationToken } from 'vs/base/common/cancellation'; import { LanguageFeatureRegistry } from 'vs/editor/common/modes/languageFeatureRegistry'; import { URI } from 'vs/base/common/uri'; -import { IPosition } from 'vs/editor/common/core/position'; -import { registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions'; +import { IPosition, Position } from 'vs/editor/common/core/position'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { onUnexpectedExternalError } from 'vs/base/common/errors'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { assertType } from 'vs/base/common/types'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; export const enum CallHierarchyDirection { CallsTo = 1, @@ -20,6 +24,8 @@ export const enum CallHierarchyDirection { } export interface CallHierarchyItem { + _sessionId: string; + _itemId: string; kind: SymbolKind; name: string; detail?: string; @@ -38,47 +44,167 @@ export interface OutgoingCall { to: CallHierarchyItem; } +export interface CallHierarchySession { + root: CallHierarchyItem; + dispose(): void; +} + export interface CallHierarchyProvider { - provideIncomingCalls(document: ITextModel, postion: IPosition, token: CancellationToken): ProviderResult; + prepareCallHierarchy(document: ITextModel, position: IPosition, token: CancellationToken): ProviderResult; - provideOutgoingCalls(document: ITextModel, postion: IPosition, token: CancellationToken): ProviderResult; + provideIncomingCalls(item: CallHierarchyItem, token: CancellationToken): ProviderResult; + + provideOutgoingCalls(item: CallHierarchyItem, token: CancellationToken): ProviderResult; } export const CallHierarchyProviderRegistry = new LanguageFeatureRegistry(); -export async function provideIncomingCalls(model: ITextModel, position: IPosition, token: CancellationToken): Promise { - const [provider] = CallHierarchyProviderRegistry.ordered(model); - if (!provider) { - return []; +class RefCountedDisposabled { + + constructor( + private readonly _disposable: IDisposable, + private _counter = 1 + ) { } + + acquire() { + this._counter++; + return this; } - try { - const result = await provider.provideIncomingCalls(model, position, token); - if (isNonEmptyArray(result)) { - return result; + + release() { + if (--this._counter === 0) { + this._disposable.dispose(); } - } catch (e) { - onUnexpectedExternalError(e); + return this; } - return []; } -export async function provideOutgoingCalls(model: ITextModel, position: IPosition, token: CancellationToken): Promise { - const [provider] = CallHierarchyProviderRegistry.ordered(model); - if (!provider) { +export class CallHierarchyModel { + + static async create(model: ITextModel, position: IPosition, token: CancellationToken): Promise { + const [provider] = CallHierarchyProviderRegistry.ordered(model); + if (!provider) { + return undefined; + } + const session = await provider.prepareCallHierarchy(model, position, token); + if (!session) { + return undefined; + } + return new CallHierarchyModel(session.root._sessionId, provider, session.root, new RefCountedDisposabled(session)); + } + + private constructor( + readonly id: string, + readonly provider: CallHierarchyProvider, + readonly root: CallHierarchyItem, + readonly ref: RefCountedDisposabled, + ) { } + + dispose(): void { + this.ref.release(); + } + + fork(item: CallHierarchyItem): CallHierarchyModel { + const that = this; + return new class extends CallHierarchyModel { + constructor() { + super(that.id, that.provider, item, that.ref.acquire()); + } + }; + } + + async resolveIncomingCalls(item: CallHierarchyItem, token: CancellationToken): Promise { + try { + const result = await this.provider.provideIncomingCalls(item, token); + if (isNonEmptyArray(result)) { + return result; + } + } catch (e) { + onUnexpectedExternalError(e); + } return []; } - try { - const result = await provider.provideOutgoingCalls(model, position, token); - if (isNonEmptyArray(result)) { - return result; + + async resolveOutgoingCalls(item: CallHierarchyItem, token: CancellationToken): Promise { + try { + const result = await this.provider.provideOutgoingCalls(item, token); + if (isNonEmptyArray(result)) { + return result; + } + } catch (e) { + onUnexpectedExternalError(e); } - } catch (e) { - onUnexpectedExternalError(e); + return []; } - return []; } -registerDefaultLanguageCommand('_executeCallHierarchyIncomingCalls', async (model, position) => provideIncomingCalls(model, position, CancellationToken.None)); -registerDefaultLanguageCommand('_executeCallHierarchyOutgoingCalls', async (model, position) => provideOutgoingCalls(model, position, CancellationToken.None)); +// --- API command support + +const _models = new Map(); + +CommandsRegistry.registerCommand('_executePrepareCallHierarchy', async (accessor, ...args) => { + const [resource, position] = args; + assertType(URI.isUri(resource)); + assertType(Position.isIPosition(position)); + + const modelService = accessor.get(IModelService); + let textModel = modelService.getModel(resource); + let textModelReference: IDisposable | undefined; + if (!textModel) { + const textModelService = accessor.get(ITextModelService); + const result = await textModelService.createModelReference(resource); + textModel = result.object.textEditorModel; + textModelReference = result; + } + + try { + const model = await CallHierarchyModel.create(textModel, position, CancellationToken.None); + if (!model) { + return []; + } + // + _models.set(model.id, model); + _models.forEach((value, key, map) => { + if (map.size > 10) { + value.dispose(); + _models.delete(key); + } + }); + return [model.root]; + + } finally { + dispose(textModelReference); + } +}); + +function isCallHierarchyItemDto(obj: any): obj is CallHierarchyItem { + return true; +} + +CommandsRegistry.registerCommand('_executeProvideIncomingCalls', async (_accessor, ...args) => { + const [item] = args; + assertType(isCallHierarchyItemDto(item)); + + // find model + const model = _models.get(item._sessionId); + if (!model) { + return undefined; + } + + return model.resolveIncomingCalls(item, CancellationToken.None); +}); + +CommandsRegistry.registerCommand('_executeProvideOutgoingCalls', async (_accessor, ...args) => { + const [item] = args; + assertType(isCallHierarchyItemDto(item)); + + // find model + const model = _models.get(item._sessionId); + if (!model) { + return undefined; + } + + return model.resolveOutgoingCalls(item, CancellationToken.None); +}); diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts index 0c0eba65d9..fbc8cc33fa 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/callHierarchy'; -import { PeekViewWidget, IPeekViewService } from 'vs/editor/contrib/referenceSearch/peekViewWidget'; +import * as peekView from 'vs/editor/contrib/peekView/peekView'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { CallHierarchyProvider, CallHierarchyDirection } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy'; -import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; +import { CallHierarchyDirection, CallHierarchyModel } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy'; +import { WorkbenchAsyncDataTree, IWorkbenchAsyncDataTreeOptions } from 'vs/platform/list/browser/listService'; import { FuzzyScore } from 'vs/base/common/filters'; import * as callHTree from 'vs/workbench/contrib/callHierarchy/browser/callHierarchyTree'; -import { IAsyncDataTreeOptions, IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree'; +import { IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree'; import { localize } from 'vs/nls'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { IRange, Range } from 'vs/editor/common/core/range'; @@ -25,13 +25,12 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationOptions, OverviewRulerLane } from 'vs/editor/common/model'; import { registerThemingParticipant, themeColorFromId, IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; -import * as referencesWidget from 'vs/editor/contrib/referenceSearch/referencesWidget'; import { IPosition } from 'vs/editor/common/core/position'; import { Action } from 'vs/base/common/actions'; import { IActionBarOptions, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { Color } from 'vs/base/common/color'; -import { TreeMouseEventTarget } from 'vs/base/browser/ui/tree/tree'; +import { TreeMouseEventTarget, ITreeNode } from 'vs/base/browser/ui/tree/tree'; import { URI } from 'vs/base/common/uri'; const enum State { @@ -42,19 +41,14 @@ const enum State { class ChangeHierarchyDirectionAction extends Action { - constructor(direction: CallHierarchyDirection, updateDirection: (direction: CallHierarchyDirection) => void) { + constructor(getDirection: () => CallHierarchyDirection, toggleDirection: () => void) { super('', undefined, '', true, () => { - if (direction === CallHierarchyDirection.CallsTo) { - direction = CallHierarchyDirection.CallsFrom; - } else { - direction = CallHierarchyDirection.CallsTo; - } - updateDirection(direction); + toggleDirection(); update(); return Promise.resolve(); }); const update = () => { - if (direction === CallHierarchyDirection.CallsFrom) { + if (getDirection() === CallHierarchyDirection.CallsFrom) { this.label = localize('toggle.from', "Showing Calls"); this.class = 'calls-from'; } else { @@ -88,25 +82,26 @@ class LayoutInfo { ) { } } -export class CallHierarchyTreePeekWidget extends PeekViewWidget { +export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget { private _changeDirectionAction?: ChangeHierarchyDirectionAction; private _parent!: HTMLElement; private _message!: HTMLElement; private _splitView!: SplitView; - private _tree!: WorkbenchAsyncDataTree; + private _tree!: WorkbenchAsyncDataTree; private _treeViewStates = new Map(); private _editor!: EmbeddedCodeEditorWidget; private _dim!: Dimension; private _layoutInfo!: LayoutInfo; + private readonly _previewDisposable = new DisposableStore(); + constructor( editor: ICodeEditor, private readonly _where: IPosition, - private readonly _provider: CallHierarchyProvider, private _direction: CallHierarchyDirection, @IThemeService themeService: IThemeService, - @IPeekViewService private readonly _peekViewService: IPeekViewService, + @peekView.IPeekViewService private readonly _peekViewService: peekView.IPeekViewService, @IEditorService private readonly _editorService: IEditorService, @ITextModelService private readonly _textModelService: ITextModelService, @IStorageService private readonly _storageService: IStorageService, @@ -117,6 +112,7 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget { this._peekViewService.addExclusiveWidget(editor, this); this._applyTheme(themeService.getTheme()); this._disposables.add(themeService.onThemeChange(this._applyTheme, this)); + this._disposables.add(this._previewDisposable); } dispose(): void { @@ -132,13 +128,13 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget { } private _applyTheme(theme: ITheme) { - const borderColor = theme.getColor(referencesWidget.peekViewBorder) || Color.transparent; + const borderColor = theme.getColor(peekView.peekViewBorder) || Color.transparent; this.style({ arrowColor: borderColor, frameColor: borderColor, - headerBackgroundColor: theme.getColor(referencesWidget.peekViewTitleBackground) || Color.transparent, - primaryHeadingColor: theme.getColor(referencesWidget.peekViewTitleForeground), - secondaryHeadingColor: theme.getColor(referencesWidget.peekViewTitleInfoForeground) + headerBackgroundColor: theme.getColor(peekView.peekViewTitleBackground) || Color.transparent, + primaryHeadingColor: theme.getColor(peekView.peekViewTitleForeground), + secondaryHeadingColor: theme.getColor(peekView.peekViewTitleInfoForeground) }); } @@ -198,18 +194,22 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget { const treeContainer = document.createElement('div'); addClass(treeContainer, 'tree'); container.appendChild(treeContainer); - const options: IAsyncDataTreeOptions = { + const options: IWorkbenchAsyncDataTreeOptions = { + sorter: new callHTree.Sorter(), identityProvider: new callHTree.IdentityProvider(() => this._direction), ariaLabel: localize('tree.aria', "Call Hierarchy"), expandOnlyOnTwistieClick: true, + overrideStyles: { + listBackground: peekView.peekViewResultsBackground + } }; - this._tree = this._instantiationService.createInstance( + this._tree = this._instantiationService.createInstance>( WorkbenchAsyncDataTree, 'CallHierarchyPeek', treeContainer, new callHTree.VirtualDelegate(), [this._instantiationService.createInstance(callHTree.CallRenderer)], - this._instantiationService.createInstance(callHTree.DataSource, this._provider, () => this._direction), + this._instantiationService.createInstance(callHTree.DataSource, () => this._direction), options ); @@ -244,63 +244,8 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget { } })); - // session state - const localDispose = new DisposableStore(); - this._disposables.add(localDispose); - // update editor - this._disposables.add(this._tree.onDidChangeFocus(async e => { - const [element] = e.elements; - if (!element) { - return; - } - - localDispose.clear(); - - // update: editor and editor highlights - const options: IModelDecorationOptions = { - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - className: 'call-decoration', - overviewRuler: { - color: themeColorFromId(referencesWidget.peekViewEditorMatchHighlight), - position: OverviewRulerLane.Center - }, - }; - - let previewUri: URI; - if (this._direction === CallHierarchyDirection.CallsFrom) { - // outgoing calls: show caller and highlight focused calls - previewUri = element.parent ? element.parent.item.uri : this._tree.getInput()!.model.uri; - } else { - // incoming calls: show caller and highlight focused calls - previewUri = element.item.uri; - } - - const value = await this._textModelService.createModelReference(previewUri); - this._editor.setModel(value.object.textEditorModel); - - // set decorations for caller ranges (if in the same file) - let decorations: IModelDeltaDecoration[] = []; - let fullRange: IRange | undefined; - for (const loc of element.locations) { - if (loc.uri.toString() === previewUri.toString()) { - decorations.push({ range: loc.range, options }); - fullRange = !fullRange ? loc.range : Range.plusRange(loc.range, fullRange); - } - } - if (fullRange) { - this._editor.revealRangeInCenter(fullRange, ScrollType.Immediate); - const ids = this._editor.deltaDecorations([], decorations); - localDispose.add(toDisposable(() => this._editor.deltaDecorations(ids, []))); - } - localDispose.add(value); - - // update: title - const title = this._direction === CallHierarchyDirection.CallsFrom - ? localize('callFrom', "Calls from '{0}'", this._tree.getInput()!.word) - : localize('callsTo', "Callers of '{0}'", this._tree.getInput()!.word); - this.setTitle(title); - })); + this._disposables.add(this._tree.onDidChangeFocus(this._updatePreview, this)); this._disposables.add(this._editor.onMouseDown(e => { const { event, target } = e; @@ -346,6 +291,60 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget { })); } + private async _updatePreview() { + const [element] = this._tree.getFocus(); + if (!element) { + return; + } + + this._previewDisposable.clear(); + + // update: editor and editor highlights + const options: IModelDecorationOptions = { + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + className: 'call-decoration', + overviewRuler: { + color: themeColorFromId(peekView.peekViewEditorMatchHighlight), + position: OverviewRulerLane.Center + }, + }; + + let previewUri: URI; + if (this._direction === CallHierarchyDirection.CallsFrom) { + // outgoing calls: show caller and highlight focused calls + previewUri = element.parent ? element.parent.item.uri : element.model.root.uri; + + } else { + // incoming calls: show caller and highlight focused calls + previewUri = element.item.uri; + } + + const value = await this._textModelService.createModelReference(previewUri); + this._editor.setModel(value.object.textEditorModel); + + // set decorations for caller ranges (if in the same file) + let decorations: IModelDeltaDecoration[] = []; + let fullRange: IRange | undefined; + for (const loc of element.locations) { + if (loc.uri.toString() === previewUri.toString()) { + decorations.push({ range: loc.range, options }); + fullRange = !fullRange ? loc.range : Range.plusRange(loc.range, fullRange); + } + } + if (fullRange) { + this._editor.revealRangeInCenter(fullRange, ScrollType.Immediate); + const ids = this._editor.deltaDecorations([], decorations); + this._previewDisposable.add(toDisposable(() => this._editor.deltaDecorations(ids, []))); + } + this._previewDisposable.add(value); + + // update: title + const title = this._direction === CallHierarchyDirection.CallsFrom + ? localize('callFrom', "Calls from '{0}'", element.model.root.name) + : localize('callsTo', "Callers of '{0}'", element.model.root.name); + this.setTitle(title); + } + showLoading(): void { this._parent.dataset['state'] = State.Loading; this.setTitle(localize('title.loading', "Loading...")); @@ -361,41 +360,56 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget { this._message.focus(); } - async showItem(item: callHTree.CallHierarchyRoot): Promise { + async showModel(model: CallHierarchyModel): Promise { this._show(); const viewState = this._treeViewStates.get(this._direction); - await this._tree.setInput(item, viewState); - if (this._tree.getNode(item).children.length === 0) { + await this._tree.setInput(model, viewState); + + const root = >this._tree.getNode(model).children[0]; // {{SQL CARBON EDIT}} strict-null-checks + await this._tree.expand(root.element); + + if (root.children.length === 0) { // this.showMessage(this._direction === CallHierarchyDirection.CallsFrom - ? localize('empt.callsFrom', "No calls from '{0}'", item.word) - : localize('empt.callsTo', "No callers of '{0}'", item.word)); + ? localize('empt.callsFrom', "No calls from '{0}'", model.root.name) + : localize('empt.callsTo', "No callers of '{0}'", model.root.name)); } else { this._parent.dataset['state'] = State.Data; - this._tree.domFocus(); if (!viewState) { - this._tree.focusFirst(); + this._tree.setFocus([root.children[0].element]); } + this._tree.domFocus(); + this._updatePreview(); } if (!this._changeDirectionAction) { - const changeDirection = (newDirection: CallHierarchyDirection) => { - if (this._direction !== newDirection) { - this._treeViewStates.set(this._direction, this._tree.getViewState()); - this._direction = newDirection; - this._tree.setFocus([]); - this.showItem(this._tree.getInput()!); - } - }; - this._changeDirectionAction = new ChangeHierarchyDirectionAction(this._direction, changeDirection); + this._changeDirectionAction = new ChangeHierarchyDirectionAction(() => this._direction, () => this.toggleDirection()); this._disposables.add(this._changeDirectionAction); this._actionbarWidget!.push(this._changeDirectionAction, { icon: true, label: false }); } } + getModel(): CallHierarchyModel | undefined { + return this._tree.getInput(); + } + + getFocused(): callHTree.Call | undefined { + return this._tree.getFocus()[0]; + } + + async toggleDirection(): Promise { + const model = this._tree.getInput(); + if (model) { + const newDirection = this._direction === CallHierarchyDirection.CallsTo ? CallHierarchyDirection.CallsFrom : CallHierarchyDirection.CallsTo; + this._treeViewStates.set(this._direction, this._tree.getViewState()); + this._direction = newDirection; + await this.showModel(model); + } + } + private _show() { if (!this._isShowing) { this.editor.revealLineInCenterIfOutsideViewport(this._where.lineNumber, ScrollType.Smooth); @@ -421,31 +435,31 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget { } registerThemingParticipant((theme, collector) => { - const referenceHighlightColor = theme.getColor(referencesWidget.peekViewEditorMatchHighlight); + const referenceHighlightColor = theme.getColor(peekView.peekViewEditorMatchHighlight); if (referenceHighlightColor) { collector.addRule(`.monaco-editor .call-hierarchy .call-decoration { background-color: ${referenceHighlightColor}; }`); } - const referenceHighlightBorder = theme.getColor(referencesWidget.peekViewEditorMatchHighlightBorder); + const referenceHighlightBorder = theme.getColor(peekView.peekViewEditorMatchHighlightBorder); if (referenceHighlightBorder) { collector.addRule(`.monaco-editor .call-hierarchy .call-decoration { border: 2px solid ${referenceHighlightBorder}; box-sizing: border-box; }`); } - const resultsBackground = theme.getColor(referencesWidget.peekViewResultsBackground); + const resultsBackground = theme.getColor(peekView.peekViewResultsBackground); if (resultsBackground) { collector.addRule(`.monaco-editor .call-hierarchy .tree { background-color: ${resultsBackground}; }`); } - const resultsMatchForeground = theme.getColor(referencesWidget.peekViewResultsFileForeground); + const resultsMatchForeground = theme.getColor(peekView.peekViewResultsFileForeground); if (resultsMatchForeground) { collector.addRule(`.monaco-editor .call-hierarchy .tree { color: ${resultsMatchForeground}; }`); } - const resultsSelectedBackground = theme.getColor(referencesWidget.peekViewResultsSelectionBackground); + const resultsSelectedBackground = theme.getColor(peekView.peekViewResultsSelectionBackground); if (resultsSelectedBackground) { collector.addRule(`.monaco-editor .call-hierarchy .tree .monaco-list:focus .monaco-list-rows > .monaco-list-row.selected:not(.highlighted) { background-color: ${resultsSelectedBackground}; }`); } - const resultsSelectedForeground = theme.getColor(referencesWidget.peekViewResultsSelectionForeground); + const resultsSelectedForeground = theme.getColor(peekView.peekViewResultsSelectionForeground); if (resultsSelectedForeground) { collector.addRule(`.monaco-editor .call-hierarchy .tree .monaco-list:focus .monaco-list-rows > .monaco-list-row.selected:not(.highlighted) { color: ${resultsSelectedForeground} !important; }`); } - const editorBackground = theme.getColor(referencesWidget.peekViewEditorBackground); + const editorBackground = theme.getColor(peekView.peekViewEditorBackground); if (editorBackground) { collector.addRule( `.monaco-editor .call-hierarchy .editor .monaco-editor .monaco-editor-background,` + @@ -454,7 +468,7 @@ registerThemingParticipant((theme, collector) => { `}` ); } - const editorGutterBackground = theme.getColor(referencesWidget.peekViewEditorGutterBackground); + const editorGutterBackground = theme.getColor(peekView.peekViewEditorGutterBackground); if (editorGutterBackground) { collector.addRule( `.monaco-editor .call-hierarchy .editor .monaco-editor .margin {` + diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts index c4cc0deab8..e64b2453f4 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts @@ -3,104 +3,79 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IAsyncDataSource, ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree'; -import { CallHierarchyItem, CallHierarchyProvider, CallHierarchyDirection, provideOutgoingCalls, provideIncomingCalls } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy'; +import { IAsyncDataSource, ITreeRenderer, ITreeNode, ITreeSorter } from 'vs/base/browser/ui/tree/tree'; +import { CallHierarchyItem, CallHierarchyDirection, CallHierarchyModel, } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { SymbolKinds, Location } from 'vs/editor/common/modes'; -import { hash } from 'vs/base/common/hash'; -import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import * as dom from 'vs/base/browser/dom'; +import { compare } from 'vs/base/common/strings'; import { Range } from 'vs/editor/common/core/range'; -import { ITextModel } from 'vs/editor/common/model'; -import { IPosition } from 'vs/editor/common/core/position'; -import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; export class Call { constructor( readonly item: CallHierarchyItem, readonly locations: Location[], + readonly model: CallHierarchyModel, readonly parent: Call | undefined ) { } -} -export class CallHierarchyRoot { - - static fromEditor(editor: IActiveCodeEditor): CallHierarchyRoot | undefined { - const model = editor.getModel(); - const position = editor.getPosition(); - const wordInfo = model.getWordAtPosition(position); - return wordInfo - ? new CallHierarchyRoot(model, position, wordInfo.word) - : undefined; + static compare(a: Call, b: Call): number { + let res = compare(a.item.uri.toString(), b.item.uri.toString()); + if (res === 0) { + res = Range.compareRangesUsingStarts(a.item.range, b.item.range); + } + return res; } - - constructor( - readonly model: ITextModel, - readonly position: IPosition, - readonly word: string - ) { } } -export class DataSource implements IAsyncDataSource { +export class DataSource implements IAsyncDataSource { constructor( - public provider: CallHierarchyProvider, public getDirection: () => CallHierarchyDirection, - @ITextModelService private readonly _modelService: ITextModelService, ) { } hasChildren(): boolean { return true; } - async getChildren(element: CallHierarchyRoot | Call): Promise { + async getChildren(element: CallHierarchyModel | Call): Promise { + if (element instanceof CallHierarchyModel) { + return [new Call(element.root, [], element, undefined)]; + } - const results: Call[] = []; + const { model, item } = element; + + if (this.getDirection() === CallHierarchyDirection.CallsFrom) { + return (await model.resolveOutgoingCalls(item, CancellationToken.None)).map(call => { + return new Call( + call.to, + call.fromRanges.map(range => ({ range, uri: item.uri })), + model, + element + ); + }); - if (element instanceof CallHierarchyRoot) { - if (this.getDirection() === CallHierarchyDirection.CallsFrom) { - await this._getOutgoingCalls(element.model, element.position, results); - } else { - await this._getIncomingCalls(element.model, element.position, results); - } } else { - const reference = await this._modelService.createModelReference(element.item.uri); - const position = Range.lift(element.item.selectionRange).getStartPosition(); - if (this.getDirection() === CallHierarchyDirection.CallsFrom) { - await this._getOutgoingCalls(reference.object.textEditorModel, position, results, element); - } else { - await this._getIncomingCalls(reference.object.textEditorModel, position, results, element); - } - reference.dispose(); - } - - return results; - } - - private async _getOutgoingCalls(model: ITextModel, position: IPosition, bucket: Call[], parent?: Call): Promise { - const outgoingCalls = await provideOutgoingCalls(model, position, CancellationToken.None); - for (const call of outgoingCalls) { - bucket.push(new Call( - call.to, - call.fromRanges.map(range => ({ range, uri: model.uri })), - parent - )); + return (await model.resolveIncomingCalls(item, CancellationToken.None)).map(call => { + return new Call( + call.from, + call.fromRanges.map(range => ({ range, uri: call.from.uri })), + model, + element + ); + }); } } +} - private async _getIncomingCalls(model: ITextModel, position: IPosition, bucket: Call[], parent?: Call): Promise { - const incomingCalls = await provideIncomingCalls(model, position, CancellationToken.None); - for (const call of incomingCalls) { - bucket.push(new Call( - call.from, - call.fromRanges.map(range => ({ range, uri: call.from.uri })), - parent - )); - } +export class Sorter implements ITreeSorter { + + compare(element: Call, otherElement: Call): number { + return Call.compare(element, otherElement); } - } export class IdentityProvider implements IIdentityProvider { @@ -110,13 +85,18 @@ export class IdentityProvider implements IIdentityProvider { ) { } getId(element: Call): { toString(): string; } { - return this.getDirection() + hash(element.item.uri.toString(), hash(JSON.stringify(element.item.range))).toString() + (element.parent ? this.getId(element.parent) : ''); + let res = this.getDirection() + JSON.stringify(element.item.uri) + JSON.stringify(element.item.range); + if (element.parent) { + res += this.getId(element.parent); + } + return res; } } class CallRenderingTemplate { constructor( - readonly iconLabel: IconLabel + readonly icon: HTMLDivElement, + readonly label: IconLabel ) { } } @@ -127,24 +107,23 @@ export class CallRenderer implements ITreeRenderer, _index: number, template: CallRenderingTemplate): void { const { element, filterData } = node; - - template.iconLabel.setLabel( + template.icon.className = SymbolKinds.toCssClassName(element.item.kind, true); + template.label.setLabel( element.item.name, element.item.detail, - { - labelEscapeNewLines: true, - matches: createMatches(filterData), - extraClasses: [SymbolKinds.toCssClassName(element.item.kind, true)] - } + { labelEscapeNewLines: true, matches: createMatches(filterData) } ); } disposeTemplate(template: CallRenderingTemplate): void { - template.iconLabel.dispose(); + template.label.dispose(); } } diff --git a/src/vs/workbench/contrib/callHierarchy/browser/media/callHierarchy.css b/src/vs/workbench/contrib/callHierarchy/browser/media/callHierarchy.css index 2a1b344742..0f1345c235 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/media/callHierarchy.css +++ b/src/vs/workbench/contrib/callHierarchy/browser/media/callHierarchy.css @@ -38,3 +38,14 @@ .monaco-workbench .call-hierarchy .tree { height: 100%; } + +.monaco-workbench .call-hierarchy .tree .callhierarchy-element { + display: flex; + flex: 1; + flex-flow: row nowrap; + align-items: center; +} + +.monaco-workbench .call-hierarchy .tree .callhierarchy-element .monaco-icon-label { + padding-left: 4px; +} diff --git a/src/vs/workbench/contrib/cli/node/cli.contribution.ts b/src/vs/workbench/contrib/cli/node/cli.contribution.ts index 9431092973..ad3ea9ed4c 100644 --- a/src/vs/workbench/contrib/cli/node/cli.contribution.ts +++ b/src/vs/workbench/contrib/cli/node/cli.contribution.ts @@ -108,7 +108,7 @@ class InstallAction extends Action { promisify(cp.exec)(command, {}) .then(undefined, _ => Promise.reject(new Error(nls.localize('cantCreateBinFolder', "Unable to create '/usr/local/bin'.")))) - .then(resolve, reject); + .then(() => resolve(), reject); break; case 1 /* Cancel */: reject(new Error(nls.localize('aborted', "Aborted"))); @@ -175,7 +175,7 @@ class UninstallAction extends Action { promisify(cp.exec)(command, {}) .then(undefined, _ => Promise.reject(new Error(nls.localize('cantUninstall', "Unable to uninstall the shell command '{0}'.", this.target)))) - .then(resolve, reject); + .then(() => resolve(), reject); break; case 1 /* Cancel */: reject(new Error(nls.localize('aborted', "Aborted"))); @@ -189,6 +189,6 @@ if (platform.isMacintosh) { const category = nls.localize('shellCommand', "Shell Command"); const workbenchActionsRegistry = Registry.as(ActionExtensions.WorkbenchActions); - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(InstallAction, InstallAction.ID, InstallAction.LABEL), `Shell Command: Install \'${product.applicationName}\' command in PATH`, category); - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(UninstallAction, UninstallAction.ID, UninstallAction.LABEL), `Shell Command: Uninstall \'${product.applicationName}\' command from PATH`, category); + workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.create(InstallAction, InstallAction.ID, InstallAction.LABEL), `Shell Command: Install \'${product.applicationName}\' command in PATH`, category); + workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.create(UninstallAction, UninstallAction.ID, UninstallAction.LABEL), `Shell Command: Uninstall \'${product.applicationName}\' command from PATH`, category); } diff --git a/src/vs/workbench/contrib/codeActions/common/codeActions.contribution.ts b/src/vs/workbench/contrib/codeActions/common/codeActions.contribution.ts new file mode 100644 index 0000000000..3ab3f93e96 --- /dev/null +++ b/src/vs/workbench/contrib/codeActions/common/codeActions.contribution.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { CodeActionWorkbenchContribution, editorConfiguration } from 'vs/workbench/contrib/codeActions/common/configuration'; +import { CodeActionsExtensionPoint, codeActionsExtensionPointDescriptor } from 'vs/workbench/contrib/codeActions/common/extensionPoint'; +import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; + +const codeActionsExtensionPoint = ExtensionsRegistry.registerExtensionPoint(codeActionsExtensionPointDescriptor); + +Registry.as(Extensions.Configuration) + .registerConfiguration(editorConfiguration); + +class WorkbenchContribution { + constructor( + @IKeybindingService keybindingsService: IKeybindingService, + ) { + // tslint:disable-next-line: no-unused-expression + new CodeActionWorkbenchContribution(codeActionsExtensionPoint, keybindingsService); + } +} + +Registry.as(WorkbenchExtensions.Workbench) + .registerWorkbenchContribution(WorkbenchContribution, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/codeActions/common/configuration.ts b/src/vs/workbench/contrib/codeActions/common/configuration.ts new file mode 100644 index 0000000000..bf44f6606d --- /dev/null +++ b/src/vs/workbench/contrib/codeActions/common/configuration.ts @@ -0,0 +1,155 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { flatten } from 'vs/base/common/arrays'; +import { Emitter } from 'vs/base/common/event'; +import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { values } from 'vs/base/common/map'; +import { codeActionCommandId, refactorCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/codeAction'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; +import * as nls from 'vs/nls'; +import { Extensions, IConfigurationNode, IConfigurationRegistry, ConfigurationScope, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { CodeActionsExtensionPoint, ContributedCodeAction } from 'vs/workbench/contrib/codeActions/common/extensionPoint'; +import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { editorConfigurationBaseNode } from 'vs/editor/common/config/commonEditorConfig'; + +const codeActionsOnSaveDefaultProperties = Object.freeze({ + 'source.fixAll': { + type: 'boolean', + description: nls.localize('codeActionsOnSave.fixAll', "Controls whether auto fix action should be run on file save.") + } +}); + +const codeActionsOnSaveSchema: IConfigurationPropertySchema = { + type: 'object', + properties: codeActionsOnSaveDefaultProperties, + 'additionalProperties': { + type: 'boolean' + }, + default: {}, + description: nls.localize('codeActionsOnSave', "Code action kinds to be run on save."), + scope: ConfigurationScope.RESOURCE +}; + +export const editorConfiguration = Object.freeze({ + ...editorConfigurationBaseNode, + properties: { + 'editor.codeActionsOnSave': codeActionsOnSaveSchema, + 'editor.codeActionsOnSaveTimeout': { + type: 'number', + default: 750, + description: nls.localize('codeActionsOnSaveTimeout', "Timeout in milliseconds after which the code actions that are run on save are cancelled."), + scope: ConfigurationScope.RESOURCE + }, + } +}); + +export class CodeActionWorkbenchContribution extends Disposable implements IWorkbenchContribution { + + private _contributedCodeActions: CodeActionsExtensionPoint[] = []; + + private readonly _onDidChangeContributions = this._register(new Emitter()); + + constructor( + codeActionsExtensionPoint: IExtensionPoint, + keybindingService: IKeybindingService, + ) { + super(); + + codeActionsExtensionPoint.setHandler(extensionPoints => { + this._contributedCodeActions = flatten(extensionPoints.map(x => x.value)); + this.updateConfigurationSchema(this._contributedCodeActions); + this._onDidChangeContributions.fire(); + }); + + keybindingService.registerSchemaContribution({ + getSchemaAdditions: () => this.getSchemaAdditions(), + onDidChange: this._onDidChangeContributions.event, + }); + } + + private updateConfigurationSchema(codeActionContributions: readonly CodeActionsExtensionPoint[]) { + const newProperties: IJSONSchemaMap = { ...codeActionsOnSaveDefaultProperties }; + for (const [sourceAction, props] of this.getSourceActions(codeActionContributions)) { + newProperties[sourceAction] = { + type: 'boolean', + description: nls.localize('codeActionsOnSave.generic', "Controls whether '{0}' actions should be run on file save.", props.title) + }; + } + codeActionsOnSaveSchema.properties = newProperties; + Registry.as(Extensions.Configuration) + .notifyConfigurationSchemaUpdated(editorConfiguration); + } + + private getSourceActions(contributions: readonly CodeActionsExtensionPoint[]) { + const defaultKinds = Object.keys(codeActionsOnSaveDefaultProperties).map(value => new CodeActionKind(value)); + const sourceActions = new Map(); + for (const contribution of contributions) { + for (const action of contribution.actions) { + const kind = new CodeActionKind(action.kind); + if (CodeActionKind.Source.contains(kind) + // Exclude any we already included by default + && !defaultKinds.some(defaultKind => defaultKind.contains(kind)) + ) { + sourceActions.set(kind.value, action); + } + } + } + return sourceActions; + } + + private getSchemaAdditions(): IJSONSchema[] { + const conditionalSchema = (command: string, actions: readonly ContributedCodeAction[]): IJSONSchema => { + return { + if: { + properties: { + 'command': { const: command } + } + }, + then: { + required: ['args'], + properties: { + 'args': { + required: ['kind'], + properties: { + 'kind': { + anyOf: [ + { + enum: actions.map(action => action.kind), + enumDescriptions: actions.map(action => action.description ?? action.title), + }, + { type: 'string' }, + ] + } + } + } + } + } + }; + }; + + const getActions = (ofKind: CodeActionKind): ContributedCodeAction[] => { + const allActions = flatten(this._contributedCodeActions.map(desc => desc.actions.slice())); + + const out = new Map(); + for (const action of allActions) { + if (!out.has(action.kind) && ofKind.contains(new CodeActionKind(action.kind))) { + out.set(action.kind, action); + } + } + return values(out); + }; + + return [ + conditionalSchema(codeActionCommandId, getActions(CodeActionKind.Empty)), + conditionalSchema(refactorCommandId, getActions(CodeActionKind.Refactor)), + conditionalSchema(sourceActionCommandId, getActions(CodeActionKind.Source)), + ]; + } +} diff --git a/src/vs/workbench/contrib/codeActions/common/extensionPoint.ts b/src/vs/workbench/contrib/codeActions/common/extensionPoint.ts new file mode 100644 index 0000000000..54461eff3d --- /dev/null +++ b/src/vs/workbench/contrib/codeActions/common/extensionPoint.ts @@ -0,0 +1,67 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; +import { languagesExtPoint } from 'vs/workbench/services/mode/common/workbenchModeService'; + +export enum CodeActionExtensionPointFields { + languages = 'languages', + actions = 'actions', + kind = 'kind', + title = 'title', + description = 'description' +} + +export interface ContributedCodeAction { + readonly [CodeActionExtensionPointFields.kind]: string; + readonly [CodeActionExtensionPointFields.title]: string; + readonly [CodeActionExtensionPointFields.description]?: string; +} + +export interface CodeActionsExtensionPoint { + readonly [CodeActionExtensionPointFields.languages]: readonly string[]; + readonly [CodeActionExtensionPointFields.actions]: readonly ContributedCodeAction[]; +} + +const codeActionsExtensionPointSchema = Object.freeze({ + type: 'array', + markdownDescription: nls.localize('contributes.codeActions', "Configure which editor to use for a resource."), + items: { + type: 'object', + required: [CodeActionExtensionPointFields.languages, CodeActionExtensionPointFields.actions], + properties: { + [CodeActionExtensionPointFields.languages]: { + type: 'array', + description: nls.localize('contributes.codeActions.languages', "Language modes that the code actions are enabled for."), + items: { type: 'string' } + }, + [CodeActionExtensionPointFields.actions]: { + type: 'object', + required: [CodeActionExtensionPointFields.kind, CodeActionExtensionPointFields.title], + properties: { + [CodeActionExtensionPointFields.kind]: { + type: 'string', + markdownDescription: nls.localize('contributes.codeActions.kind', "`CodeActionKind` of the contributed code action."), + }, + [CodeActionExtensionPointFields.title]: { + type: 'string', + description: nls.localize('contributes.codeActions.title', "Label for the code action used in the UI."), + }, + [CodeActionExtensionPointFields.description]: { + type: 'string', + description: nls.localize('contributes.codeActions.description', "Description of what the code action does."), + }, + } + } + } + } +}); + +export const codeActionsExtensionPointDescriptor = { + extensionPoint: 'codeActions', + deps: [languagesExtPoint], + jsonSchema: codeActionsExtensionPointSchema +}; diff --git a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.css b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.css index 00a2e5295b..df132418c6 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.css +++ b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.css @@ -6,4 +6,5 @@ .monaco-editor .accessibilityHelpWidget { padding: 10px; vertical-align: middle; -} \ No newline at end of file + overflow: auto; +} diff --git a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts index fabfdf24a0..53971e97f6 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts @@ -263,11 +263,13 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { private _layout(): void { let editorLayout = this._editor.getLayoutInfo(); - let top = Math.round((editorLayout.height - AccessibilityHelpWidget.HEIGHT) / 2); - this._domNode.setTop(top); + const width = Math.min(editorLayout.width - 40, AccessibilityHelpWidget.WIDTH); + const height = Math.min(editorLayout.height - 40, AccessibilityHelpWidget.HEIGHT); - let left = Math.round((editorLayout.width - AccessibilityHelpWidget.WIDTH) / 2); - this._domNode.setLeft(left); + this._domNode.setTop(Math.round((editorLayout.height - height) / 2)); + this._domNode.setLeft(Math.round((editorLayout.width - width) / 2)); + this._domNode.setWidth(width); + this._domNode.setHeight(height); } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts index c614e74e09..f009f85495 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts @@ -15,7 +15,7 @@ import { IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBo import { SimpleButton } from 'vs/editor/contrib/find/findWidget'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { editorWidgetBackground, inputActiveOptionBorder, inputActiveOptionBackground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; +import { editorWidgetBackground, inputActiveOptionBorder, inputActiveOptionBackground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry'; import { ITheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ContextScopedFindInput } from 'vs/platform/browser/contextScopedHistoryWidget'; @@ -280,6 +280,11 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.monaco-workbench .simple-find-part { background-color: ${findWidgetBGColor} !important; }`); } + const widgetForeground = theme.getColor(editorWidgetForeground); + if (widgetForeground) { + collector.addRule(`.monaco-workbench .simple-find-part { color: ${widgetForeground}; }`); + } + const widgetShadowColor = theme.getColor(widgetShadow); if (widgetShadowColor) { collector.addRule(`.monaco-workbench .simple-find-part { box-shadow: 0 2px 8px ${widgetShadowColor}; }`); diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectKeybindings.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectKeybindings.ts index bc943535f7..3be91346a8 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectKeybindings.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectKeybindings.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IUntitledResourceInput } from 'vs/workbench/common/editor'; +import { IUntitledTextResourceInput } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; @@ -29,7 +29,7 @@ class InspectKeyMap extends EditorAction { const keybindingService = accessor.get(IKeybindingService); const editorService = accessor.get(IEditorService); - editorService.openEditor({ contents: keybindingService._dumpDebugInfo(), options: { pinned: true } } as IUntitledResourceInput); + editorService.openEditor({ contents: keybindingService._dumpDebugInfo(), options: { pinned: true } } as IUntitledTextResourceInput); } } @@ -49,9 +49,9 @@ class InspectKeyMapJSON extends Action { } public run(): Promise { - return this._editorService.openEditor({ contents: this._keybindingService._dumpDebugInfoJSON(), options: { pinned: true } } as IUntitledResourceInput); + return this._editorService.openEditor({ contents: this._keybindingService._dumpDebugInfoJSON(), options: { pinned: true } } as IUntitledTextResourceInput); } } const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(InspectKeyMapJSON, InspectKeyMapJSON.ID, InspectKeyMapJSON.LABEL), 'Developer: Inspect Key Mappings (JSON)', nls.localize('developer', "Developer")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(InspectKeyMapJSON, InspectKeyMapJSON.ID, InspectKeyMapJSON.LABEL), 'Developer: Inspect Key Mappings (JSON)', nls.localize('developer', "Developer")); diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.css b/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.css index 53865e7f95..e2f08ab669 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.css +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.css @@ -6,6 +6,7 @@ .tm-inspect-widget { z-index: 50; user-select: text; + -webkit-user-select: text; padding: 10px; } diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.ts index 8dc4cbc91e..38141efdcd 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.ts @@ -174,7 +174,7 @@ class InspectTMScopesWidget extends Disposable implements IContentWidget { private readonly _notificationService: INotificationService; private readonly _model: ITextModel; private readonly _domNode: HTMLElement; - private readonly _grammar: Promise; + private readonly _grammar: Promise; constructor( editor: IActiveCodeEditor, @@ -212,7 +212,12 @@ class InspectTMScopesWidget extends Disposable implements IContentWidget { dom.clearNode(this._domNode); this._domNode.appendChild(document.createTextNode(nls.localize('inspectTMScopesWidget.loading', "Loading..."))); this._grammar.then( - (grammar) => this._compute(grammar, position), + (grammar) => { + if (!grammar) { + throw new Error(`Could not find grammar for language!`); + } + this._compute(grammar, position); + }, (err) => { this._notificationService.warn(err); setTimeout(() => { diff --git a/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts b/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts index 9469bb577b..7559db5610 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { ParseError, parse } from 'vs/base/common/json'; +import { ParseError, parse, getNodeType } from 'vs/base/common/json'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import * as types from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; @@ -12,12 +12,12 @@ import { LanguageIdentifier } from 'vs/editor/common/modes'; import { CharacterPair, CommentRule, FoldingRules, IAutoClosingPair, IAutoClosingPairConditional, IndentationRule, LanguageConfiguration } from 'vs/editor/common/modes/languageConfiguration'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { IFileService } from 'vs/platform/files/common/files'; import { Extensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ITextMateService } from 'vs/workbench/services/textMate/common/textMateService'; import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; +import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; interface IRegExp { pattern: string; @@ -43,7 +43,16 @@ interface ILanguageConfiguration { } function isStringArr(something: string[] | null): something is string[] { - return Array.isArray(something) && something.every(value => typeof value === 'string'); + if (!Array.isArray(something)) { + return false; + } + for (let i = 0, len = something.length; i < len; i++) { + if (typeof something[i] !== 'string') { + return false; + } + } + return true; + } function isCharacterPair(something: CharacterPair | null): boolean { @@ -60,7 +69,7 @@ export class LanguageConfigurationFileHandler { constructor( @ITextMateService textMateService: ITextMateService, @IModeService private readonly _modeService: IModeService, - @IFileService private readonly _fileService: IFileService, + @IExtensionResourceLoaderService private readonly _extensionResourceLoaderService: IExtensionResourceLoaderService, @IExtensionService private readonly _extensionService: IExtensionService ) { this._done = []; @@ -89,12 +98,16 @@ export class LanguageConfigurationFileHandler { } private _handleConfigFile(languageIdentifier: LanguageIdentifier, configFileLocation: URI): void { - this._fileService.readFile(configFileLocation).then((contents) => { + this._extensionResourceLoaderService.readExtensionResource(configFileLocation).then((contents) => { const errors: ParseError[] = []; - const configuration = parse(contents.value.toString(), errors); + let configuration = parse(contents, errors); if (errors.length) { console.error(nls.localize('parseErrors', "Errors parsing {0}: {1}", configFileLocation.toString(), errors.map(e => (`[${e.offset}, ${e.length}] ${getParseErrorMessage(e.error)}`)).join('\n'))); } + if (getNodeType(configuration) !== 'object') { + console.error(nls.localize('formatError', "{0}: Invalid format, JSON object expected.", configFileLocation.toString())); + configuration = {}; + } this._handleConfig(languageIdentifier, configuration); }, (err) => { console.error(err); diff --git a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.css b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.css index b0680b67ae..1eb0923eb9 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.css +++ b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.css @@ -10,8 +10,6 @@ .suggest-input-container .monaco-editor-background, .suggest-input-container .monaco-editor, .suggest-input-container .mtk1 { - /* allow the embedded monaco to be styled from the outer context */ - background-color: transparent; color: inherit; } @@ -25,4 +23,3 @@ margin-top: 2px; margin-left: 1px; } - diff --git a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts index f90f89d6ae..37cdf82fb7 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts @@ -27,12 +27,13 @@ import { SuggestController } from 'vs/editor/contrib/suggest/suggestController'; import { IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ColorIdentifier, editorSelectionBackground, inputBackground, inputBorder, inputForeground, inputPlaceholderForeground, selectionBackground } from 'vs/platform/theme/common/colorRegistry'; -import { IStyleOverrides, IThemable, attachStyler } from 'vs/platform/theme/common/styler'; +import { IStyleOverrides, attachStyler } from 'vs/platform/theme/common/styler'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { MenuPreventer } from 'vs/workbench/contrib/codeEditor/browser/menuPreventer'; import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; +import { IThemable } from 'vs/base/common/styler'; interface SuggestResultsProvider { /** @@ -81,7 +82,7 @@ export interface ISuggestEnabledInputStyleOverrides extends IStyleOverrides { } type ISuggestEnabledInputStyles = { - [P in keyof ISuggestEnabledInputStyleOverrides]: Color; + [P in keyof ISuggestEnabledInputStyleOverrides]: Color | undefined; }; export function attachSuggestEnabledInputBoxStyler(widget: IThemable, themeService: IThemeService, style?: ISuggestEnabledInputStyleOverrides): IDisposable { @@ -223,7 +224,8 @@ export class SuggestEnabledInput extends Widget implements IThemable { public style(colors: ISuggestEnabledInputStyles): void { - this.stylingContainer.style.backgroundColor = colors.inputBackground ? colors.inputBackground.toString() : ''; + this.placeholderText.style.backgroundColor = + this.stylingContainer.style.backgroundColor = colors.inputBackground ? colors.inputBackground.toString() : ''; this.stylingContainer.style.color = colors.inputForeground ? colors.inputForeground.toString() : null; this.placeholderText.style.color = colors.inputPlaceholderForeground ? colors.inputPlaceholderForeground.toString() : null; @@ -247,9 +249,13 @@ export class SuggestEnabledInput extends Widget implements IThemable { } } + public onHide(): void { + this.inputWidget.onHide(); + } + public layout(dimension: Dimension): void { this.inputWidget.layout(dimension); - this.placeholderText.style.width = `${dimension.width}px`; + this.placeholderText.style.width = `${dimension.width - 2}px`; } private selectAll(): void { @@ -281,6 +287,12 @@ registerThemingParticipant((theme, collector) => { if (inputForegroundColor) { collector.addRule(`.suggest-input-container .monaco-editor .view-line span.inline-selected-text { color: ${inputForegroundColor}; }`); } + + const backgroundColor = theme.getColor(inputBackground); + if (backgroundColor) { + collector.addRule(`.suggest-input-container .monaco-editor-background { background-color: ${backgroundColor}; } `); + collector.addRule(`.suggest-input-container .monaco-editor { background-color: ${backgroundColor}; } `); + } }); diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts index d38d36ddde..3038f45628 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts @@ -30,7 +30,7 @@ export class ToggleMinimapAction extends Action { } const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleMinimapAction, ToggleMinimapAction.ID, ToggleMinimapAction.LABEL), 'View: Toggle Minimap', nls.localize('view', "View")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleMinimapAction, ToggleMinimapAction.ID, ToggleMinimapAction.LABEL), 'View: Toggle Minimap', nls.localize('view', "View")); /* {{SQL CARBON EDIT}} - Disable unused menu item MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts index 5d8813b054..b8675d897f 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts @@ -66,7 +66,7 @@ Registry.as(WorkbenchExtensions.Workbench).regi const registry = Registry.as(Extensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleMultiCursorModifierAction, ToggleMultiCursorModifierAction.ID, ToggleMultiCursorModifierAction.LABEL), 'Toggle Multi-Cursor Modifier'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleMultiCursorModifierAction, ToggleMultiCursorModifierAction.ID, ToggleMultiCursorModifierAction.LABEL), 'Toggle Multi-Cursor Modifier'); MenuRegistry.appendMenuItem(MenuId.MenubarSelectionMenu, { group: '3_multi', command: { @@ -88,4 +88,4 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSelectionMenu, { }, when: multiCursorModifier.isEqualTo('altKey'), order: 1 -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleRenderControlCharacter.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleRenderControlCharacter.ts index 161fba0dc5..5702c01c33 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleRenderControlCharacter.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleRenderControlCharacter.ts @@ -31,7 +31,7 @@ export class ToggleRenderControlCharacterAction extends Action { } const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleRenderControlCharacterAction, ToggleRenderControlCharacterAction.ID, ToggleRenderControlCharacterAction.LABEL), 'View: Toggle Control Characters', nls.localize('view', "View")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleRenderControlCharacterAction, ToggleRenderControlCharacterAction.ID, ToggleRenderControlCharacterAction.LABEL), 'View: Toggle Control Characters', nls.localize('view', "View")); /* {{SQL CARBON EDIT}} - Disable unused menu item MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleRenderWhitespace.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleRenderWhitespace.ts index dffc2f7c1c..39601690be 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleRenderWhitespace.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleRenderWhitespace.ts @@ -39,7 +39,7 @@ export class ToggleRenderWhitespaceAction extends Action { } const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleRenderWhitespaceAction, ToggleRenderWhitespaceAction.ID, ToggleRenderWhitespaceAction.LABEL), 'View: Toggle Render Whitespace', nls.localize('view', "View")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleRenderWhitespaceAction, ToggleRenderWhitespaceAction.ID, ToggleRenderWhitespaceAction.LABEL), 'View: Toggle Render Whitespace', nls.localize('view', "View")); /* {{SQL CARBON EDIT}} - Disable unused menu item MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts index 04652e39e0..8387e3921b 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts @@ -211,6 +211,10 @@ class ToggleWordWrapController extends Disposable implements IEditorContribution // in the settings editor... return; } + if (this.editor.isSimpleWidget) { + // in a simple widget... + return; + } // Ensure correct word wrap settings const newModel = this.editor.getModel(); if (!newModel) { @@ -275,7 +279,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_WORD_WRAP_ID, title: nls.localize('unwrapMinified', "Disable wrapping for this file"), - iconLocation: { + icon: { dark: WORD_WRAP_DARK_ICON, light: WORD_WRAP_LIGHT_ICON } @@ -292,7 +296,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_WORD_WRAP_ID, title: nls.localize('wrapMinified', "Enable wrapping for this file"), - iconLocation: { + icon: { dark: WORD_WRAP_DARK_ICON, light: WORD_WRAP_LIGHT_ICON } diff --git a/src/vs/workbench/contrib/codeEditor/browser/workbenchReferenceSearch.ts b/src/vs/workbench/contrib/codeEditor/browser/workbenchReferenceSearch.ts index ea8f40e964..82a043f67f 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/workbenchReferenceSearch.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/workbenchReferenceSearch.ts @@ -6,7 +6,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { ReferencesController } from 'vs/editor/contrib/referenceSearch/referencesController'; +import { ReferencesController } from 'vs/editor/contrib/gotoSymbol/peek/referencesController'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; diff --git a/src/vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution.ts b/src/vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution.ts index 6000fabe0e..0e26f72cc1 100644 --- a/src/vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution.ts +++ b/src/vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution.ts @@ -3,5 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import './inputClipboardActions'; import './sleepResumeRepaintMinimap'; import './selectionClipboard'; diff --git a/src/vs/workbench/contrib/codeEditor/electron-browser/inputClipboardActions.ts b/src/vs/workbench/contrib/codeEditor/electron-browser/inputClipboardActions.ts new file mode 100644 index 0000000000..822cd5ab5f --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/electron-browser/inputClipboardActions.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import * as platform from 'vs/base/common/platform'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; + +if (platform.isMacintosh) { + + // On the mac, cmd+x, cmd+c and cmd+v do not result in cut / copy / paste + // We therefore add a basic keybinding rule that invokes document.execCommand + // This is to cover s... + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'execCut', + primary: KeyMod.CtrlCmd | KeyCode.KEY_X, + handler: bindExecuteCommand('cut'), + weight: 0, + when: undefined, + }); + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'execCopy', + primary: KeyMod.CtrlCmd | KeyCode.KEY_C, + handler: bindExecuteCommand('copy'), + weight: 0, + when: undefined, + }); + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'execPaste', + primary: KeyMod.CtrlCmd | KeyCode.KEY_V, + handler: bindExecuteCommand('paste'), + weight: 0, + when: undefined, + }); + + function bindExecuteCommand(command: 'cut' | 'copy' | 'paste') { + return () => { + document.execCommand(command); + }; + } +} diff --git a/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts b/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts index 51e88400c7..3b56e368b1 100644 --- a/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts +++ b/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts @@ -6,7 +6,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { Disposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; -import { ICodeEditor, IEditorMouseEvent } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; import { ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; @@ -15,6 +15,10 @@ import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EndOfLinePreference } from 'vs/editor/common/model'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class SelectionClipboard extends Disposable implements IEditorContribution { private static readonly SELECTION_LENGTH_LIMIT = 65536; @@ -31,15 +35,6 @@ export class SelectionClipboard extends Disposable implements IEditorContributio } })); - this._register(editor.onMouseUp((e: IEditorMouseEvent) => { - if (!isEnabled) { - if (e.event.middleButton) { - // try to stop the upcoming paste - e.event.preventDefault(); - } - } - })); - let setSelectionToClipboard = this._register(new RunOnceScheduler(() => { if (!editor.hasModel()) { return; @@ -92,4 +87,25 @@ export class SelectionClipboard extends Disposable implements IEditorContributio } } +class SelectionClipboardPastePreventer implements IWorkbenchContribution { + constructor( + @IConfigurationService configurationService: IConfigurationService + ) { + if (platform.isLinux) { + document.addEventListener('mouseup', (e) => { + if (e.button === 1) { + // middle button + const config = configurationService.getValue<{ selectionClipboard: boolean; }>('editor'); + if (!config.selectionClipboard) { + // selection clipboard is disabled + // try to stop the upcoming paste + e.preventDefault(); + } + } + }); + } + } +} + registerEditorContribution(SelectionClipboardContributionID, SelectionClipboard); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(SelectionClipboardPastePreventer, LifecyclePhase.Ready); diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index b942a6a1b8..368c41ae2a 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -57,7 +57,7 @@ export class CommentNode extends Disposable { protected toolbar: ToolBar | undefined; private _commentFormActions: CommentFormActions | null = null; - private readonly _onDidDelete = new Emitter(); + private readonly _onDidClick = new Emitter(); public get domNode(): HTMLElement { return this._domNode; @@ -89,7 +89,7 @@ export class CommentNode extends Disposable { this._contextKeyService = contextKeyService.createScoped(this._domNode); this._commentContextValue = this._contextKeyService.createKey('comment', comment.contextValue); - this._domNode.tabIndex = 0; + this._domNode.tabIndex = -1; const avatar = dom.append(this._domNode, dom.$('div.avatar-container')); if (comment.userIconPath) { const img = dom.append(avatar, dom.$('img.avatar')); @@ -111,10 +111,12 @@ export class CommentNode extends Disposable { this._domNode.setAttribute('aria-label', `${comment.userName}, ${comment.body.value}`); this._domNode.setAttribute('role', 'treeitem'); this._clearTimeout = null; + + this._register(dom.addDisposableListener(this._domNode, dom.EventType.CLICK, () => this.isEditing || this._onDidClick.fire(this))); } - public get onDidDelete(): Event { - return this._onDidDelete.event; + public get onDidClick(): Event { + return this._onDidClick.event; } private createHeader(commentDetailsContainer: HTMLElement): void { @@ -430,19 +432,31 @@ export class CommentNode extends Disposable { this._commentFormActions.setActions(menu); } + setFocus(focused: boolean, visible: boolean = false) { + if (focused) { + this._domNode.focus(); + this._actionsToolbarContainer.classList.remove('hidden'); + this._actionsToolbarContainer.classList.add('tabfocused'); + this._domNode.tabIndex = 0; + } else { + if (this._actionsToolbarContainer.classList.contains('tabfocused') && !this._actionsToolbarContainer.classList.contains('mouseover')) { + this._actionsToolbarContainer.classList.add('hidden'); + this._domNode.tabIndex = -1; + } + this._actionsToolbarContainer.classList.remove('tabfocused'); + } + } + private registerActionBarListeners(actionsContainer: HTMLElement): void { this._register(dom.addDisposableListener(this._domNode, 'mouseenter', () => { actionsContainer.classList.remove('hidden'); + actionsContainer.classList.add('mouseover'); })); - - this._register(dom.addDisposableListener(this._domNode, 'focus', () => { - actionsContainer.classList.remove('hidden'); - })); - this._register(dom.addDisposableListener(this._domNode, 'mouseleave', () => { - if (!this._domNode.contains(document.activeElement)) { + if (actionsContainer.classList.contains('mouseover') && !actionsContainer.classList.contains('tabfocused')) { actionsContainer.classList.add('hidden'); } + actionsContainer.classList.remove('mouseover'); })); } diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index c7cb23d753..16ae10cfa7 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -23,11 +23,11 @@ import * as modes from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; -import { peekViewBorder } from 'vs/editor/contrib/referenceSearch/referencesWidget'; +import { peekViewBorder } from 'vs/editor/contrib/peekView/peekView'; import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget'; import * as nls from 'vs/nls'; import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IMenu, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { IMenu, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -46,6 +46,8 @@ import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/comme import { SimpleCommentEditor } from './simpleCommentEditor'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; export const COMMENTEDITOR_DECORATION_KEY = 'commenteditordecoration'; const COLLAPSE_ACTION_CLASS = 'expand-review-action'; @@ -84,6 +86,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget private _commentEditorIsEmpty!: IContextKey; private _commentFormActions!: CommentFormActions; private _scopedInstatiationService: IInstantiationService; + private _focusedComment: number | undefined = undefined; public get owner(): string { return this._owner; @@ -257,7 +260,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } private setActionBarActions(menu: IMenu): void { - const groups = menu.getActions({ shouldForwardArgs: true }).reduce((r, [, actions]) => [...r, ...actions], []); + const groups = menu.getActions({ shouldForwardArgs: true }).reduce((r, [, actions]) => [...r, ...actions], <(MenuItemAction | SubmenuItemAction)[]>[]); this._actionbarWidget.clear(); this._actionbarWidget.push([...groups, this._collapseAction], { label: false, icon: true }); } @@ -268,6 +271,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } public collapse(): Promise { + this._commentThread.collapsibleState = modes.CommentThreadCollapsibleState.Collapsed; if (this._commentThread.comments && this._commentThread.comments.length === 0) { this.deleteCommentThread(); return Promise.resolve(); @@ -286,11 +290,13 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget toggleExpand(lineNumber: number) { if (this._isExpanded) { + this._commentThread.collapsibleState = modes.CommentThreadCollapsibleState.Collapsed; this.hide(); if (!this._commentThread.comments || !this._commentThread.comments.length) { this.deleteCommentThread(); } } else { + this._commentThread.collapsibleState = modes.CommentThreadCollapsibleState.Expanded; this.show({ lineNumber: lineNumber, column: 1 }, 2); } } @@ -379,6 +385,8 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } else { this._commentThreadContextValue.reset(); } + + this.setFocusedComment(this._focusedComment); } protected _onWidth(widthInPixel: number): void { @@ -401,6 +409,21 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._commentsElement = dom.append(this._bodyElement, dom.$('div.comments-container')); this._commentsElement.setAttribute('role', 'presentation'); + this._commentsElement.tabIndex = 0; + + this._disposables.add(dom.addDisposableListener(this._commentsElement, dom.EventType.KEY_DOWN, (e) => { + let event = new StandardKeyboardEvent(e as KeyboardEvent); + if (event.equals(KeyCode.UpArrow) || event.equals(KeyCode.DownArrow)) { + const moveFocusWithinBounds = (change: number): number => { + if (this._focusedComment === undefined && change >= 0) { return 0; } + if (this._focusedComment === undefined && change < 0) { return this._commentElements.length - 1; } + let newIndex = this._focusedComment! + change; + return Math.min(Math.max(0, newIndex), this._commentElements.length - 1); + }; + + this.setFocusedComment(event.equals(KeyCode.UpArrow) ? moveFocusWithinBounds(-1) : moveFocusWithinBounds(1)); + } + })); this._commentElements = []; if (this._commentThread.comments) { @@ -565,6 +588,19 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget })); } + private setFocusedComment(value: number | undefined) { + if (this._focusedComment !== undefined) { + this._commentElements[this._focusedComment]?.setFocus(false); + } + + if (this._commentElements.length === 0 || value === undefined) { + this._focusedComment = undefined; + } else { + this._focusedComment = Math.min(value, this._commentElements.length - 1); + this._commentElements[this._focusedComment].setFocus(true); + } + } + private getActiveComment(): CommentNode | ReviewZoneWidget { return this._commentElements.filter(node => node.isEditing)[0] || this; } @@ -613,25 +649,9 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._markdownRenderer); this._disposables.add(newCommentNode); - this._disposables.add(newCommentNode.onDidDelete(deletedNode => { - const deletedNodeId = deletedNode.comment.uniqueIdInThread; - const deletedElementIndex = arrays.firstIndex(this._commentElements, commentNode => commentNode.comment.uniqueIdInThread === deletedNodeId); - if (deletedElementIndex > -1) { - this._commentElements.splice(deletedElementIndex, 1); - } - - const deletedCommentIndex = arrays.firstIndex(this._commentThread.comments!, comment => comment.uniqueIdInThread === deletedNodeId); - if (deletedCommentIndex > -1) { - this._commentThread.comments!.splice(deletedCommentIndex, 1); - } - - this._commentsElement.removeChild(deletedNode.domNode); - deletedNode.dispose(); - - if (this._commentThread.comments!.length === 0) { - this.dispose(); - } - })); + this._disposables.add(newCommentNode.onDidClick(clickedNode => + this.setFocusedComment(arrays.firstIndex(this._commentElements, commentNode => commentNode.comment.uniqueIdInThread === clickedNode.comment.uniqueIdInThread)) + )); return newCommentNode; } diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index 164f062e28..cfcc940800 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -21,7 +21,7 @@ import { IEditorContribution, IModelChangedEvent } from 'vs/editor/common/editor import { IModelDecorationOptions } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import * as modes from 'vs/editor/common/modes'; -import { peekViewResultsBackground, peekViewResultsSelectionBackground, peekViewTitleBackground } from 'vs/editor/contrib/referenceSearch/referencesWidget'; +import { peekViewResultsBackground, peekViewResultsSelectionBackground, peekViewTitleBackground } from 'vs/editor/contrib/peekView/peekView'; import * as nls from 'vs/nls'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index e718cb1b9f..ddebf8db93 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -8,7 +8,6 @@ import * as nls from 'vs/nls'; import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; import { CommentNode, CommentsModel, ResourceWithCommentThreads } from 'vs/workbench/contrib/comments/common/commentModel'; @@ -21,6 +20,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { WorkbenchAsyncDataTree, IListService } from 'vs/platform/list/browser/listService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { PANEL_BACKGROUND } from 'vs/workbench/common/theme'; export const COMMENTS_PANEL_ID = 'workbench.panel.comments'; export const COMMENTS_PANEL_TITLE = 'Comments'; @@ -127,12 +127,7 @@ export class CommentNodeRenderer implements IListRenderer inline: true, actionHandler: { callback: (content) => { - try { - const uri = URI.parse(content); - this.openerService.open(uri).catch(onUnexpectedError); - } catch (err) { - // ignore - } + this.openerService.open(content).catch(onUnexpectedError); }, disposeables: disposables } @@ -206,6 +201,9 @@ export class CommentsList extends WorkbenchAsyncDataTree { }, collapseByDefault: () => { return false; + }, + overrideStyles: { + listBackground: PANEL_BACKGROUND } }, contextKeyService, diff --git a/src/vs/workbench/contrib/comments/browser/media/review.css b/src/vs/workbench/contrib/comments/browser/media/review.css index 41c1ad8039..5652125d0b 100644 --- a/src/vs/workbench/contrib/comments/browser/media/review.css +++ b/src/vs/workbench/contrib/comments/browser/media/review.css @@ -78,6 +78,7 @@ .monaco-editor .review-widget .body .review-comment .review-comment-contents { padding-left: 20px; user-select: text; + -webkit-user-select: text; width: 100%; overflow: hidden; } @@ -134,6 +135,7 @@ width: 16px; height: 12px; -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; display: inline-block; margin-top: 3px; margin-right: 4px; @@ -274,10 +276,6 @@ text-align: left; width: 100%; box-sizing: border-box; - -webkit-box-sizing: border-box; - -o-box-sizing: border-box; - -moz-box-sizing: border-box; - -ms-box-sizing: border-box; padding: 0.4em; font-size: 12px; line-height: 17px; @@ -362,10 +360,6 @@ } .monaco-editor .review-widget .head { - -webkit-box-sizing: border-box; - -o-box-sizing: border-box; - -moz-box-sizing: border-box; - -ms-box-sizing: border-box; box-sizing: border-box; display: flex; height: 100%; diff --git a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts index d621320c75..d1a6643e64 100644 --- a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts +++ b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { EditorAction, EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; +import { EditorAction, EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -46,8 +46,9 @@ export class SimpleCommentEditor extends CodeEditorWidget { @INotificationService notificationService: INotificationService, @IAccessibilityService accessibilityService: IAccessibilityService ) { - const codeEditorWidgetOptions = { - contributions: [ + const codeEditorWidgetOptions: ICodeEditorWidgetOptions = { + isSimpleWidget: true, + contributions: [ { id: MenuPreventer.ID, ctor: MenuPreventer }, { id: ContextMenuController.ID, ctor: ContextMenuController }, { id: SuggestController.ID, ctor: SuggestController }, diff --git a/src/vs/workbench/contrib/customEditor/browser/commands.ts b/src/vs/workbench/contrib/customEditor/browser/commands.ts index a6c40d7a3b..b6333d20cf 100644 --- a/src/vs/workbench/contrib/customEditor/browser/commands.ts +++ b/src/vs/workbench/contrib/customEditor/browser/commands.ts @@ -4,15 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import { firstOrDefault } from 'vs/base/common/arrays'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { URI } from 'vs/base/common/uri'; +import { Command } from 'vs/editor/browser/editorExtensions'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import * as nls from 'vs/nls'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IListService } from 'vs/platform/list/browser/listService'; import { IEditorCommandsContext } from 'vs/workbench/common/editor'; -import { ICustomEditorService, CONTEXT_HAS_CUSTOM_EDITORS } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CONTEXT_HAS_CUSTOM_EDITORS, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { getMultiSelectedResources } from 'vs/workbench/contrib/files/browser/files'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -98,3 +102,72 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { }); // #endregion + + +(new class UndoCustomEditorCommand extends Command { + public static readonly ID = 'editor.action.customEditor.undo'; + + constructor() { + super({ + id: UndoCustomEditorCommand.ID, + precondition: ContextKeyExpr.and( + CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, + ContextKeyExpr.not(InputFocusedContextKey)), + kbOpts: { + primary: KeyMod.CtrlCmd | KeyCode.KEY_Z, + weight: KeybindingWeight.EditorContrib + } + }); + } + + public runCommand(accessor: ServicesAccessor): void { + const customEditorService = accessor.get(ICustomEditorService); + + const activeCustomEditor = customEditorService.activeCustomEditor; + if (!activeCustomEditor) { + return; + } + + const model = customEditorService.models.get(activeCustomEditor.resource, activeCustomEditor.viewType); + if (!model) { + return; + } + + model.undo(); + } +}).register(); + +(new class RedoWebviewEditorCommand extends Command { + public static readonly ID = 'editor.action.customEditor.redo'; + + constructor() { + super({ + id: RedoWebviewEditorCommand.ID, + precondition: ContextKeyExpr.and( + CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, + ContextKeyExpr.not(InputFocusedContextKey)), + kbOpts: { + primary: KeyMod.CtrlCmd | KeyCode.KEY_Y, + secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z], + mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z }, + weight: KeybindingWeight.EditorContrib + } + }); + } + + public runCommand(accessor: ServicesAccessor): void { + const customEditorService = accessor.get(ICustomEditorService); + + const activeCustomEditor = customEditorService.activeCustomEditor; + if (!activeCustomEditor) { + return; + } + + const model = customEditorService.models.get(activeCustomEditor.resource, activeCustomEditor.viewType); + if (!model) { + return; + } + + model.redo(); + } +}).register(); diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 0caa17305b..336b873263 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -4,38 +4,44 @@ *--------------------------------------------------------------------------------------------*/ import { memoize } from 'vs/base/common/decorators'; -import { Emitter } from 'vs/base/common/event'; import { Lazy } from 'vs/base/common/lazy'; -import { UnownedDisposable } from 'vs/base/common/lifecycle'; -import { Schemas } from 'vs/base/common/network'; import { basename } from 'vs/base/common/path'; -import { DataUri, isEqual } from 'vs/base/common/resources'; +import { isEqual } from 'vs/base/common/resources'; +import { assertIsDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; -import { WebviewContentState } from 'vs/editor/common/modes'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { generateUuid } from 'vs/base/common/uuid'; +import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IEditorModel, ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; -import { ConfirmResult, IEditorInput, Verbosity } from 'vs/workbench/common/editor'; +import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; +import { GroupIdentifier, IEditorInput, IRevertOptions, ISaveOptions, Verbosity } from 'vs/workbench/common/editor'; +import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview'; import { IWebviewWorkbenchService, LazilyResolvedWebviewEditorInput } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; -import { promptSave } from 'vs/workbench/services/textfile/browser/textFileService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { public static typeId = 'workbench.editors.webviewEditor'; private readonly _editorResource: URI; - private _state = WebviewContentState.Readonly; + private _model?: ICustomEditorModel; constructor( resource: URI, viewType: string, id: string, - webview: Lazy>, + webview: Lazy, + @ILifecycleService lifecycleService: ILifecycleService, @IWebviewWorkbenchService webviewWorkbenchService: IWebviewWorkbenchService, - @IDialogService private readonly dialogService: IDialogService, + @IInstantiationService private readonly instantiationService: IInstantiationService, @ILabelService private readonly labelService: ILabelService, + @ICustomEditorService private readonly customEditorService: ICustomEditorService, + @IEditorService private readonly editorService: IEditorService, + @IFileDialogService private readonly fileDialogService: IFileDialogService, ) { - super(id, viewType, '', webview, webviewWorkbenchService); + super(id, viewType, '', webview, webviewWorkbenchService, lifecycleService); this._editorResource = resource; } @@ -47,27 +53,17 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { return this._editorResource; } + public supportsSplitEditor() { + return true; + } + @memoize getName(): string { - if (this.getResource().scheme === Schemas.data) { - const metadata = DataUri.parseMetaData(this.getResource()); - const label = metadata.get(DataUri.META_DATA_LABEL); - if (typeof label === 'string') { - return label; - } - } return basename(this.labelService.getUriLabel(this.getResource())); } @memoize getDescription(): string | undefined { - if (this.getResource().scheme === Schemas.data) { - const metadata = DataUri.parseMetaData(this.getResource()); - const description = metadata.get(DataUri.META_DATA_DESCRIPTION); - if (typeof description === 'string') { - return description; - } - } return super.getDescription(); } @@ -84,17 +80,11 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { @memoize private get mediumTitle(): string { - if (this.getResource().scheme === Schemas.data) { - return this.getName(); - } return this.labelService.getUriLabel(this.getResource(), { relative: true }); } @memoize private get longTitle(): string { - if (this.getResource().scheme === Schemas.data) { - return this.getName(); - } return this.labelService.getUriLabel(this.getResource()); } @@ -110,34 +100,71 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { } } - public setState(newState: WebviewContentState): void { - this._state = newState; + public isReadonly(): boolean { + return false; + } + + public isDirty(): boolean { + return this._model ? this._model.isDirty() : false; + } + + public save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + return this._model ? this._model.save(options) : Promise.resolve(false); + } + + public async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + if (!this._model) { + return false; + } + + // Preserve view state by opening the editor first. In addition + // this allows the user to review the contents of the editor. + // let viewState: IEditorViewState | undefined = undefined; + // const editor = await this.editorService.openEditor(this, undefined, group); + // if (isTextEditor(editor)) { + // viewState = editor.getViewState(); + // } + + let dialogPath = this._editorResource; + // if (this._editorResource.scheme === Schemas.untitled) { + // dialogPath = this.suggestFileName(resource); + // } + + const target = await this.promptForPath(this._editorResource, dialogPath, options?.availableFileSystems); + if (!target) { + return false; // save cancelled + } + + await this._model.saveAs(this._editorResource, target, options); + + return true; + } + + public revert(options?: IRevertOptions): Promise { + return this._model ? this._model.revert(options) : Promise.resolve(false); + } + + public async resolve(): Promise { + this._model = await this.customEditorService.models.loadOrCreate(this.getResource(), this.viewType); + this._register(this._model.onDidChangeDirty(() => this._onDidChangeDirty.fire())); this._onDidChangeDirty.fire(); + return await super.resolve(); } - public isDirty() { - return this._state === WebviewContentState.Dirty; + protected async promptForPath(resource: URI, defaultUri: URI, availableFileSystems?: readonly string[]): Promise { + + // Help user to find a name for the file by opening it first + await this.editorService.openEditor({ resource, options: { revealIfOpened: true, preserveFocus: true } }); + + return this.fileDialogService.pickFileToSave({});//this.getSaveDialogOptions(defaultUri, availableFileSystems)); } - public async confirmSave(): Promise { - if (!this.isDirty()) { - return ConfirmResult.DONT_SAVE; - } - return promptSave(this.dialogService, [this.getResource()]); + public handleMove(groupId: GroupIdentifier, uri: URI, options?: ITextEditorOptions): IEditorInput | undefined { + const webview = assertIsDefined(this.takeOwnershipOfWebview()); + return this.instantiationService.createInstance(CustomFileEditorInput, + uri, + this.viewType, + generateUuid(), + new Lazy(() => webview)); } - - public async save(): Promise { - if (!this.isDirty) { - return true; - } - const waitingOn: Promise[] = []; - this._onWillSave.fire({ - waitUntil: (thenable: Promise): void => { waitingOn.push(thenable); }, - }); - const result = await Promise.all(waitingOn); - return result.every(x => x); - } - - private readonly _onWillSave = this._register(new Emitter<{ waitUntil: (thenable: Thenable) => void }>()); - public readonly onWillSave = this._onWillSave.event; } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts index 9e8189fc3d..fa3d7219e6 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts @@ -3,7 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { UnownedDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -12,7 +11,7 @@ import { WebviewEditorInputFactory } from 'vs/workbench/contrib/webview/browser/ import { IWebviewWorkbenchService } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; import { Lazy } from 'vs/base/common/lazy'; -export class CustomEditoInputFactory extends WebviewEditorInputFactory { +export class CustomEditorInputFactory extends WebviewEditorInputFactory { public static readonly ID = CustomFileEditorInput.typeId; @@ -48,7 +47,7 @@ export class CustomEditoInputFactory extends WebviewEditorInputFactory { location: data.extensionLocation, id: data.extensionId } : undefined, data.group); - return new UnownedDisposable(webviewInput.webview); + return webviewInput.webview; }); const customInput = this._instantiationService.createInstance(CustomFileEditorInput, URI.from((data as any).editorResource), data.viewType, id, webview); diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 7f80c34a25..a7d38c795d 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -3,15 +3,16 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { coalesce, distinct, mergeSort, find } from 'vs/base/common/arrays'; +import { coalesce, distinct, find, mergeSort } from 'vs/base/common/arrays'; import * as glob from 'vs/base/common/glob'; -import { UnownedDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { Schemas } from 'vs/base/common/network'; -import { basename, DataUri, isEqual } from 'vs/base/common/resources'; +import { Lazy } from 'vs/base/common/lazy'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { basename, isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; @@ -21,15 +22,14 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { EditorInput, EditorOptions, IEditor, IEditorInput } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { webviewEditorsExtensionPoint } from 'vs/workbench/contrib/customEditor/browser/extensionPoint'; -import { CustomEditorPriority, CustomEditorInfo, CustomEditorSelector, ICustomEditorService, CONTEXT_HAS_CUSTOM_EDITORS } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CONTEXT_HAS_CUSTOM_EDITORS, CustomEditorInfo, CustomEditorPriority, CustomEditorSelector, ICustomEditor, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { CustomEditorModelManager } from 'vs/workbench/contrib/customEditor/common/customEditorModelManager'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; -import { IWebviewService } from 'vs/workbench/contrib/webview/browser/webview'; +import { IWebviewService, webviewHasOwnEditFunctionsContext } from 'vs/workbench/contrib/webview/browser/webview'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { CustomFileEditorInput } from './customEditorInput'; -import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { Lazy } from 'vs/base/common/lazy'; - const defaultEditorId = 'default'; const defaultEditorInfo: CustomEditorInfo = { @@ -41,7 +41,7 @@ const defaultEditorInfo: CustomEditorInfo = { priority: CustomEditorPriority.default, }; -export class CustomEditorStore { +export class CustomEditorInfoStore { private readonly contributedEditors = new Map(); public clear() { @@ -71,11 +71,17 @@ export class CustomEditorStore { export class CustomEditorService extends Disposable implements ICustomEditorService { _serviceBrand: any; - private readonly editors = new CustomEditorStore(); + private readonly _editorInfoStore = new CustomEditorInfoStore(); + + private readonly _models: CustomEditorModelManager; + private readonly _hasCustomEditor: IContextKey; + private readonly _focusedCustomEditorIsEditable: IContextKey; + private readonly _webviewHasOwnEditFunctions: IContextKey; constructor( @IContextKeyService contextKeyService: IContextKeyService, + @IWorkingCopyService workingCopyService: IWorkingCopyService, @IConfigurationService private readonly configurationService: IConfigurationService, @IEditorService private readonly editorService: IEditorService, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -84,12 +90,14 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ ) { super(); + this._models = new CustomEditorModelManager(workingCopyService); + webviewEditorsExtensionPoint.setHandler(extensions => { - this.editors.clear(); + this._editorInfoStore.clear(); for (const extension of extensions) { for (const webviewEditorContribution of extension.value) { - this.editors.add({ + this._editorInfoStore.add({ id: webviewEditorContribution.viewType, displayName: webviewEditorContribution.displayName, selector: webviewEditorContribution.selector || [], @@ -97,24 +105,37 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ }); } } - this.updateContext(); + this.updateContexts(); }); this._hasCustomEditor = CONTEXT_HAS_CUSTOM_EDITORS.bindTo(contextKeyService); + this._focusedCustomEditorIsEditable = CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE.bindTo(contextKeyService); + this._webviewHasOwnEditFunctions = webviewHasOwnEditFunctionsContext.bindTo(contextKeyService); - this._register(this.editorService.onDidActiveEditorChange(() => this.updateContext())); - this.updateContext(); + this._register(this.editorService.onDidActiveEditorChange(() => this.updateContexts())); + this.updateContexts(); + } + + public get models() { return this._models; } + + public get activeCustomEditor(): ICustomEditor | undefined { + const activeInput = this.editorService.activeControl?.input; + if (!(activeInput instanceof CustomFileEditorInput)) { + return undefined; + } + const resource = activeInput.getResource(); + return { resource, viewType: activeInput.viewType }; } public getContributedCustomEditors(resource: URI): readonly CustomEditorInfo[] { - return this.editors.getContributedEditors(resource); + return this._editorInfoStore.getContributedEditors(resource); } public getUserConfiguredCustomEditors(resource: URI): readonly CustomEditorInfo[] { const rawAssociations = this.configurationService.getValue(customEditorsAssociationsKey) || []; return coalesce(rawAssociations .filter(association => matches(association, resource)) - .map(association => this.editors.get(association.viewType))); + .map(association => this._editorInfoStore.get(association.viewType))); } public async promptOpenWith( @@ -164,7 +185,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ return this.openEditorForResource(resource, fileInput, { ...options, ignoreOverrides: true }, group); } - if (!this.editors.get(viewType)) { + if (!this._editorInfoStore.get(viewType)) { return this.promptOpenWith(resource, options, group); } @@ -180,7 +201,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ ): CustomFileEditorInput { const id = generateUuid(); const webview = new Lazy(() => { - return new UnownedDisposable(this.webviewService.createWebviewEditorOverlay(id, { customClasses: options ? options.customClasses : undefined }, {})); + return this.webviewService.createWebviewEditorOverlay(id, { customClasses: options?.customClasses }, {}); }); const input = this.instantiationService.createInstance(CustomFileEditorInput, resource, viewType, id, webview); if (group) { @@ -215,22 +236,23 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ return this.editorService.openEditor(input, options, group); } - private updateContext() { + private updateContexts() { const activeControl = this.editorService.activeControl; - if (!activeControl) { - this._hasCustomEditor.reset(); - return; - } - const resource = activeControl.input.getResource(); + const resource = activeControl?.input.getResource(); if (!resource) { this._hasCustomEditor.reset(); + this._focusedCustomEditorIsEditable.reset(); + this._webviewHasOwnEditFunctions.reset(); return; } + const possibleEditors = [ ...this.getContributedCustomEditors(resource), ...this.getUserConfiguredCustomEditors(resource), ]; this._hasCustomEditor.set(possibleEditors.length > 0); + this._focusedCustomEditorIsEditable.set(activeControl?.input instanceof CustomFileEditorInput); + this._webviewHasOwnEditFunctions.set(true); } } @@ -301,6 +323,11 @@ export class CustomEditorContribution implements IWorkbenchContribution { }; } + // If we have all optional editors, then open VS Code's standard editor + if (contributedEditors.every(editor => editor.priority === CustomEditorPriority.option)) { + return undefined; // {{SQL CARBON EDIT}} strict-null-check + } + // Open VS Code's standard editor but prompt user to see if they wish to use a custom one instead return { override: (async () => { @@ -337,7 +364,7 @@ export class CustomEditorContribution implements IWorkbenchContribution { const editors = mergeSort( distinct([ ...this.customEditorService.getUserConfiguredCustomEditors(resource), - ...this.customEditorService.getContributedCustomEditors(resource), + ...this.customEditorService.getContributedCustomEditors(resource).filter(x => x.priority !== CustomEditorPriority.option), ], editor => editor.id), (a, b) => { return priorityToRank(a.priority) - priorityToRank(b.priority); @@ -379,18 +406,6 @@ function priorityToRank(priority: CustomEditorPriority): number { } function matches(selector: CustomEditorSelector, resource: URI): boolean { - if (resource.scheme === Schemas.data) { - if (!selector.mime) { - return false; - } - const metadata = DataUri.parseMetaData(resource); - const mime = metadata.get(DataUri.META_DATA_MIME); - if (!mime) { - return false; - } - return glob.match(selector.mime, mime.toLowerCase()); - } - if (selector.filenamePattern) { if (glob.match(selector.filenamePattern.toLowerCase(), basename(resource).toLowerCase())) { return true; diff --git a/src/vs/workbench/contrib/customEditor/browser/extensionPoint.ts b/src/vs/workbench/contrib/customEditor/browser/extensionPoint.ts index 446876b942..6f23a3bede 100644 --- a/src/vs/workbench/contrib/customEditor/browser/extensionPoint.ts +++ b/src/vs/workbench/contrib/customEditor/browser/extensionPoint.ts @@ -53,10 +53,6 @@ const webviewEditorsContribution: IJSONSchema = { type: 'string', description: nls.localize('contributes.selector.filenamePattern', 'Glob that the custom editor is enabled for.'), }, - mime: { - type: 'string', - description: nls.localize('contributes.selector.mime', 'Glob that matches the mime type of a data uri resource.'), - } } } }, diff --git a/src/vs/workbench/contrib/customEditor/browser/webviewEditor.contribution.ts b/src/vs/workbench/contrib/customEditor/browser/webviewEditor.contribution.ts index d6fd28dc76..be9c891c72 100644 --- a/src/vs/workbench/contrib/customEditor/browser/webviewEditor.contribution.ts +++ b/src/vs/workbench/contrib/customEditor/browser/webviewEditor.contribution.ts @@ -10,9 +10,10 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor'; +import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; -import { CustomEditoInputFactory } from 'vs/workbench/contrib/customEditor/browser/customEditorInputFactory'; +import { CustomEditorInputFactory } from 'vs/workbench/contrib/customEditor/browser/customEditorInputFactory'; import { ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEditor'; import './commands'; @@ -25,7 +26,7 @@ Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(CustomEditorContribution, LifecyclePhase.Starting); Registry.as(EditorExtensions.Editors).registerEditor( - new EditorDescriptor( + EditorDescriptor.create( WebviewEditor, WebviewEditor.ID, 'Webview Editor', @@ -34,15 +35,12 @@ Registry.as(EditorExtensions.Editors).registerEditor( ]); Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory( - CustomEditoInputFactory.ID, - CustomEditoInputFactory); + CustomEditorInputFactory.ID, + CustomEditorInputFactory); Registry.as(ConfigurationExtensions.Configuration) .registerConfiguration({ - 'id': 'workbench', - 'order': 7, - 'title': nls.localize('workbenchConfigurationTitle', "Workbench"), - 'type': 'object', + ...workbenchConfigurationNodeBase, 'properties': { [customEditorsAssociationsKey]: { type: 'array', @@ -60,7 +58,7 @@ Registry.as(ConfigurationExtensions.Configuration) }, 'filenamePattern': { type: 'string', - description: nls.localize('editor.editorAssociations.filenamePattern', "Glob pattern the the editor should be used for."), + description: nls.localize('editor.editorAssociations.filenamePattern', "Glob pattern the editor should be used for."), } } } diff --git a/src/vs/workbench/contrib/customEditor/common/customEditor.ts b/src/vs/workbench/contrib/customEditor/common/customEditor.ts index 402d01560e..39a9dfcd62 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditor.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditor.ts @@ -3,20 +3,32 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { EditorInput, IEditor } from 'vs/workbench/common/editor'; +import { EditorInput, IEditor, ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; export const ICustomEditorService = createDecorator('customEditorService'); export const CONTEXT_HAS_CUSTOM_EDITORS = new RawContextKey('hasCustomEditors', false); +export const CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE = new RawContextKey('focusedCustomEditorIsEditable', false); + +export interface ICustomEditor { + readonly resource: URI; + readonly viewType: string; +} export interface ICustomEditorService { _serviceBrand: any; + readonly models: ICustomEditorModelManager; + + readonly activeCustomEditor: ICustomEditor | undefined; + getContributedCustomEditors(resource: URI): readonly CustomEditorInfo[]; getUserConfiguredCustomEditors(resource: URI): readonly CustomEditorInfo[]; @@ -26,6 +38,45 @@ export interface ICustomEditorService { promptOpenWith(resource: URI, options?: ITextEditorOptions, group?: IEditorGroup): Promise; } +export type CustomEditorEdit = { source?: any, data: any }; + +export interface ICustomEditorModelManager { + get(resource: URI, viewType: string): ICustomEditorModel | undefined; + + loadOrCreate(resource: URI, viewType: string): Promise; + + disposeModel(model: ICustomEditorModel): void; +} + +export interface CustomEditorSaveEvent { + readonly resource: URI; + readonly waitUntil: (until: Promise) => void; +} + +export interface CustomEditorSaveAsEvent { + readonly resource: URI; + readonly targetResource: URI; + readonly waitUntil: (until: Promise) => void; +} + +export interface ICustomEditorModel extends IWorkingCopy { + readonly onUndo: Event; + readonly onApplyEdit: Event; + readonly onWillSave: Event; + readonly onWillSaveAs: Event; + + readonly currentEdits: readonly CustomEditorEdit[]; + + undo(): void; + redo(): void; + revert(options?: IRevertOptions): Promise; + + save(options?: ISaveOptions): Promise; + saveAs(resource: URI, targetResource: URI, currentOptions?: ISaveOptions): Promise; + + makeEdit(edit: CustomEditorEdit): void; +} + export const enum CustomEditorPriority { default = 'default', builtin = 'builtin', @@ -34,7 +85,6 @@ export const enum CustomEditorPriority { export interface CustomEditorSelector { readonly filenamePattern?: string; - readonly mime?: string; } export interface CustomEditorInfo { diff --git a/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts b/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts new file mode 100644 index 0000000000..d4c2f2402c --- /dev/null +++ b/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts @@ -0,0 +1,157 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { ICustomEditorModel, CustomEditorEdit, CustomEditorSaveAsEvent, CustomEditorSaveEvent } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; + +export class CustomEditorModel extends Disposable implements ICustomEditorModel { + + private _currentEditIndex: number = -1; + private _savePoint: number = -1; + private _edits: Array = []; + + constructor( + private readonly _resource: URI, + ) { + super(); + } + + //#region IWorkingCopy + + public get resource() { + return this._resource; + } + + public get capabilities(): WorkingCopyCapabilities { + return 0; + } + + public isDirty(): boolean { + return this._edits.length > 0 && this._savePoint !== this._currentEditIndex; + } + + protected readonly _onDidChangeDirty: Emitter = this._register(new Emitter()); + readonly onDidChangeDirty: Event = this._onDidChangeDirty.event; + + //#endregion + + protected readonly _onUndo = this._register(new Emitter()); + readonly onUndo = this._onUndo.event; + + protected readonly _onApplyEdit = this._register(new Emitter()); + readonly onApplyEdit = this._onApplyEdit.event; + + protected readonly _onWillSave = this._register(new Emitter()); + readonly onWillSave = this._onWillSave.event; + + protected readonly _onWillSaveAs = this._register(new Emitter()); + readonly onWillSaveAs = this._onWillSaveAs.event; + + get currentEdits(): readonly CustomEditorEdit[] { + return this._edits.slice(0, Math.max(0, this._currentEditIndex + 1)); + } + + public makeEdit(edit: CustomEditorEdit): void { + this._edits.splice(this._currentEditIndex + 1, this._edits.length - this._currentEditIndex, edit.data); + this._currentEditIndex = this._edits.length - 1; + this.updateDirty(); + this._onApplyEdit.fire([edit]); + } + + private updateDirty() { + this._onDidChangeDirty.fire(); + } + + public async save(_options?: ISaveOptions): Promise { + const untils: Promise[] = []; + const handler: CustomEditorSaveEvent = { + resource: this._resource, + waitUntil: (until: Promise) => untils.push(until) + }; + + try { + this._onWillSave.fire(handler); + await Promise.all(untils); + } catch { + return false; + } + + this._savePoint = this._currentEditIndex; + this.updateDirty(); + + return true; + } + + public async saveAs(resource: URI, targetResource: URI, _options?: ISaveOptions): Promise { + const untils: Promise[] = []; + const handler: CustomEditorSaveAsEvent = { + resource, + targetResource, + waitUntil: (until: Promise) => untils.push(until) + }; + + try { + this._onWillSaveAs.fire(handler); + await Promise.all(untils); + } catch { + return false; + } + + this._savePoint = this._currentEditIndex; + this.updateDirty(); + + return true; + } + + public async revert(_options?: IRevertOptions) { + if (this._currentEditIndex === this._savePoint) { + return true; + } + + if (this._currentEditIndex >= this._savePoint) { + const editsToUndo = this._edits.slice(this._savePoint, this._currentEditIndex); + this._onUndo.fire(editsToUndo.reverse()); + } else if (this._currentEditIndex < this._savePoint) { + const editsToRedo = this._edits.slice(this._currentEditIndex, this._savePoint); + this._onApplyEdit.fire(editsToRedo); + } + + this._currentEditIndex = this._savePoint; + this._edits.splice(this._currentEditIndex + 1, this._edits.length - this._currentEditIndex); + this.updateDirty(); + return true; + } + + public undo() { + if (this._currentEditIndex < 0) { + // nothing to undo + return; + } + + const undoneEdit = this._edits[this._currentEditIndex]; + --this._currentEditIndex; + this._onUndo.fire([{ data: undoneEdit }]); + + this.updateDirty(); + } + + public redo() { + if (this._currentEditIndex >= this._edits.length - 1) { + // nothing to redo + return; + } + + ++this._currentEditIndex; + const redoneEdit = this._edits[this._currentEditIndex]; + + this._onApplyEdit.fire([{ data: redoneEdit }]); + + this.updateDirty(); + } +} diff --git a/src/vs/workbench/contrib/customEditor/common/customEditorModelManager.ts b/src/vs/workbench/contrib/customEditor/common/customEditorModelManager.ts new file mode 100644 index 0000000000..eb9630f8c3 --- /dev/null +++ b/src/vs/workbench/contrib/customEditor/common/customEditorModelManager.ts @@ -0,0 +1,54 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { ICustomEditorModel, ICustomEditorModelManager } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { CustomEditorModel } from 'vs/workbench/contrib/customEditor/common/customEditorModel'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; + +export class CustomEditorModelManager implements ICustomEditorModelManager { + private readonly _models = new Map(); + + constructor( + @IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService, + ) { } + + + public get(resource: URI, viewType: string): ICustomEditorModel | undefined { + return this._models.get(this.key(resource, viewType))?.model; + } + + public async loadOrCreate(resource: URI, viewType: string): Promise { + const existing = this.get(resource, viewType); + if (existing) { + return existing; + } + + const model = new CustomEditorModel(resource); + const disposables = new DisposableStore(); + this._workingCopyService.registerWorkingCopy(model); + this._models.set(this.key(resource, viewType), { model, disposables }); + return model; + } + + public disposeModel(model: ICustomEditorModel): void { + let foundKey: string | undefined; + this._models.forEach((value, key) => { + if (model === value.model) { + value.disposables.dispose(); + foundKey = key; + } + }); + if (typeof foundKey === 'string') { + this._models.delete(foundKey); + } + return; + } + + private key(resource: URI, viewType: string): string { + return `${resource.toString()}@@@${viewType}`; + } +} diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index e8c2dc8005..b83c3d1167 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -17,6 +17,7 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; +import { ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel'; export const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; export const twistiePixels = 20; @@ -58,7 +59,7 @@ export function renderExpressionValue(expressionOrValue: IExpressionContainer | // remove stale classes container.className = 'value'; // when resolving expressions we represent errors from the server as a variable with name === null. - if (value === null || ((expressionOrValue instanceof Expression || expressionOrValue instanceof Variable) && !expressionOrValue.available)) { + if (value === null || ((expressionOrValue instanceof Expression || expressionOrValue instanceof Variable || expressionOrValue instanceof ReplEvaluationResult) && !expressionOrValue.available)) { dom.addClass(container, 'unavailable'); if (value !== Expression.DEFAULT_VALUE) { dom.addClass(container, 'error'); diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index ad2eb8d43d..55a9eccb58 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -32,6 +32,8 @@ import { distinct } from 'vs/base/common/arrays'; import { RunOnceScheduler } from 'vs/base/common/async'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { BrowserFeatures } from 'vs/base/browser/canIUse'; +import { isSafari } from 'vs/base/browser/browser'; const $ = dom.$; @@ -43,7 +45,7 @@ interface IBreakpointDecoration { } const breakpointHelperDecoration: IModelDecorationOptions = { - glyphMarginClassName: 'debug-breakpoint-hint', + glyphMarginClassName: 'codicon-debug-hint', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges }; @@ -91,7 +93,7 @@ function getBreakpointDecorationOptions(model: ITextModel, breakpoint: IBreakpoi } return { - glyphMarginClassName: className, + glyphMarginClassName: `${className}`, glyphMarginHoverMessage, stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, beforeContentClassName: breakpoint.column ? `debug-breakpoint-placeholder` : undefined, @@ -105,25 +107,29 @@ async function createCandidateDecorations(model: ITextModel, breakpointDecoratio const session = debugService.getViewModel().focusedSession; if (session && session.capabilities.supportsBreakpointLocationsRequest) { await Promise.all(lineNumbers.map(async lineNumber => { - const positions = await session.breakpointsLocations(model.uri, lineNumber); - if (positions.length > 1) { - // Do not render candidates if there is only one, since it is already covered by the line breakpoint - positions.forEach(p => { - const range = new Range(p.lineNumber, p.column, p.lineNumber, p.column + 1); - const breakpointAtPosition = breakpointDecorations.filter(bpd => bpd.range.equalsRange(range)).pop(); - if (breakpointAtPosition && breakpointAtPosition.inlineWidget) { - // Space already occupied, do not render candidate. - return; - } - result.push({ - range, - options: { - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - beforeContentClassName: `debug-breakpoint-placeholder` - }, - breakpoint: breakpointAtPosition ? breakpointAtPosition.breakpoint : undefined + try { + const positions = await session.breakpointsLocations(model.uri, lineNumber); + if (positions.length > 1) { + // Do not render candidates if there is only one, since it is already covered by the line breakpoint + positions.forEach(p => { + const range = new Range(p.lineNumber, p.column, p.lineNumber, p.column + 1); + const breakpointAtPosition = breakpointDecorations.filter(bpd => bpd.range.equalsRange(range)).pop(); + if (breakpointAtPosition && breakpointAtPosition.inlineWidget) { + // Space already occupied, do not render candidate. + return; + } + result.push({ + range, + options: { + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + beforeContentClassName: `debug-breakpoint-placeholder` + }, + breakpoint: breakpointAtPosition ? breakpointAtPosition.breakpoint : undefined + }); }); - }); + } + } catch (e) { + // If there is an error when fetching breakpoint locations just do not render them } })); } @@ -131,7 +137,6 @@ async function createCandidateDecorations(model: ITextModel, breakpointDecoratio return result; } - class BreakpointEditorContribution implements IBreakpointEditorContribution { private breakpointHintDecoration: string[] = []; @@ -227,34 +232,48 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { } })); - this.toDispose.push(this.editor.onMouseMove((e: IEditorMouseEvent) => { - let showBreakpointHintAtLineNumber = -1; - const model = this.editor.getModel(); - if (model && e.target.position && (e.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN || e.target.type === MouseTargetType.GUTTER_LINE_NUMBERS) && this.debugService.getConfigurationManager().canSetBreakpointsIn(model) && - this.marginFreeFromNonDebugDecorations(e.target.position.lineNumber)) { - const data = e.target.detail as IMarginData; - if (!data.isAfterLines) { - showBreakpointHintAtLineNumber = e.target.position.lineNumber; + if (!(BrowserFeatures.pointerEvents && isSafari)) { + /** + * We disable the hover feature for Safari on iOS as + * 1. Browser hover events are handled specially by the system (it treats first click as hover if there is `:hover` css registered). Below hover behavior will confuse users with inconsistent expeirence. + * 2. When users click on line numbers, the breakpoint hint displays immediately, however it doesn't create the breakpoint unless users click on the left gutter. On a touch screen, it's hard to click on that small area. + */ + this.toDispose.push(this.editor.onMouseMove((e: IEditorMouseEvent) => { + let showBreakpointHintAtLineNumber = -1; + const model = this.editor.getModel(); + if (model && e.target.position && (e.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN || e.target.type === MouseTargetType.GUTTER_LINE_NUMBERS) && this.debugService.getConfigurationManager().canSetBreakpointsIn(model) && + this.marginFreeFromNonDebugDecorations(e.target.position.lineNumber)) { + const data = e.target.detail as IMarginData; + if (!data.isAfterLines) { + showBreakpointHintAtLineNumber = e.target.position.lineNumber; + } } - } - this.ensureBreakpointHintDecoration(showBreakpointHintAtLineNumber); - })); - this.toDispose.push(this.editor.onMouseLeave((e: IEditorMouseEvent) => { - this.ensureBreakpointHintDecoration(-1); - })); + this.ensureBreakpointHintDecoration(showBreakpointHintAtLineNumber); + })); + this.toDispose.push(this.editor.onMouseLeave(() => { + this.ensureBreakpointHintDecoration(-1); + })); + } + this.toDispose.push(this.editor.onDidChangeModel(async () => { this.closeBreakpointWidget(); await this.setDecorations(); })); - this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(async () => { + this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => { if (!this.ignoreBreakpointsChangeEvent && !this.setDecorationsScheduler.isScheduled()) { this.setDecorationsScheduler.schedule(); } })); + this.toDispose.push(this.debugService.onDidChangeState(() => { + // We need to update breakpoint decorations when state changes since the top stack frame and breakpoint decoration might change + if (!this.setDecorationsScheduler.isScheduled()) { + this.setDecorationsScheduler.schedule(); + } + })); this.toDispose.push(this.editor.onDidChangeModelDecorations(() => this.onModelDecorationsChanged())); this.toDispose.push(this.configurationService.onDidChangeConfiguration(async (e) => { - if (e.affectsConfiguration('debug.showBreakpointsInOverviewRuler')) { + if (e.affectsConfiguration('debug.showBreakpointsInOverviewRuler') || e.affectsConfiguration('debug.showInlineBreakpointCandidates')) { await this.setDecorations(); } })); @@ -338,7 +357,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { const decorations = this.editor.getLineDecorations(line); if (decorations) { for (const { options } of decorations) { - if (options.glyphMarginClassName && options.glyphMarginClassName.indexOf('debug') === -1) { + if (options.glyphMarginClassName && options.glyphMarginClassName.indexOf('codicon-') === -1) { return false; } } @@ -406,7 +425,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { } // Set breakpoint candidate decorations - const desiredCandidateDecorations = await createCandidateDecorations(this.editor.getModel(), this.breakpointDecorations, this.debugService); + const desiredCandidateDecorations = debugSettings.showInlineBreakpointCandidates ? await createCandidateDecorations(this.editor.getModel(), this.breakpointDecorations, this.debugService) : []; const candidateDecorationIds = this.editor.deltaDecorations(this.candidateDecorations.map(c => c.decorationId), desiredCandidateDecorations); this.candidateDecorations.forEach(candidate => { candidate.inlineWidget.dispose(); @@ -416,7 +435,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { // Candidate decoration has a breakpoint attached when a breakpoint is already at that location and we did not yet set a decoration there // In practice this happens for the first breakpoint that was set on a line // We could have also rendered this first decoration as part of desiredBreakpointDecorations however at that moment we have no location information - const cssClass = candidate.breakpoint ? getBreakpointMessageAndClassName(this.debugService, candidate.breakpoint).className : 'debug-breakpoint-disabled'; + const cssClass = candidate.breakpoint ? getBreakpointMessageAndClassName(this.debugService, candidate.breakpoint).className : 'codicon-debug-breakpoint-disabled'; const contextMenuActions = () => this.getContextMenuActions(candidate.breakpoint ? [candidate.breakpoint] : [], activeCodeEditor.getModel().uri, candidate.range.startLineNumber, candidate.range.startColumn); const inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, cssClass, candidate.breakpoint, this.debugService, this.contextMenuService, contextMenuActions); @@ -537,6 +556,7 @@ class InlineBreakpointWidget implements IContentWidget, IDisposable { private create(cssClass: string | null | undefined): void { this.domNode = $('.inline-breakpoint-widget'); + this.domNode.classList.add('codicon'); if (cssClass) { this.domNode.classList.add(cssClass); } diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index ed7f67dab2..b55a4134c0 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -35,6 +35,8 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { getSimpleEditorOptions, getSimpleCodeEditorWidgetOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; import { IRange, Range } from 'vs/editor/common/core/range'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions'; const $ = dom.$; const IPrivateBreakpointWidgetService = createDecorator('privateBreakpointWidgetService'); @@ -48,6 +50,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi _serviceBrand: undefined; private selectContainer!: HTMLElement; + private inputContainer!: HTMLElement; private input!: IActiveCodeEditor; private toDispose: lifecycle.IDisposable[]; private conditionInput = ''; @@ -55,6 +58,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi private logMessageInput = ''; private breakpoint: IBreakpoint | undefined; private context: Context; + private heightInPx: number | undefined; constructor(editor: ICodeEditor, private lineNumber: number, private column: number | undefined, context: Context | undefined, @IContextViewService private readonly contextViewService: IContextViewService, @@ -64,6 +68,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi @IInstantiationService private readonly instantiationService: IInstantiationService, @IModelService private readonly modelService: IModelService, @ICodeEditorService private readonly codeEditorService: ICodeEditorService, + @IConfigurationService private readonly _configurationService: IConfigurationService ) { super(editor, { showFrame: true, showArrow: false, frameWidth: 1 }); @@ -158,7 +163,8 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi this.input.focus(); }); - this.createBreakpointInput(dom.append(container, $('.inputContainer'))); + this.inputContainer = $('.inputContainer'); + this.createBreakpointInput(dom.append(container, this.inputContainer)); this.input.getModel().setValue(this.getInputValue(this.breakpoint)); this.toDispose.push(this.input.getModel().onDidChangeContent(() => { @@ -170,7 +176,9 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi } protected _doLayout(heightInPixel: number, widthInPixel: number): void { + this.heightInPx = heightInPixel; this.input.layout({ height: heightInPixel, width: widthInPixel - 113 }); + this.centerInputVertically(); } private createBreakpointInput(container: HTMLElement): void { @@ -180,7 +188,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi const scopedInstatiationService = this.instantiationService.createChild(new ServiceCollection( [IContextKeyService, scopedContextKeyService], [IPrivateBreakpointWidgetService, this])); - const options = getSimpleEditorOptions(); + const options = this.createEditorOptions(); const codeEditorWidgetOptions = getSimpleCodeEditorWidgetOptions(); this.input = scopedInstatiationService.createInstance(CodeEditorWidget, container, options, codeEditorWidgetOptions); CONTEXT_IN_BREAKPOINT_WIDGET.bindTo(scopedContextKeyService).set(true); @@ -227,6 +235,29 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi return suggestionsPromise; } })); + + this.toDispose.push(this._configurationService.onDidChangeConfiguration((e) => { + if (e.affectsConfiguration('editor.fontSize') || e.affectsConfiguration('editor.lineHeight')) { + this.input.updateOptions(this.createEditorOptions()); + this.centerInputVertically(); + } + })); + } + + private createEditorOptions(): IEditorOptions { + const editorConfig = this._configurationService.getValue('editor'); + const options = getSimpleEditorOptions(); + options.fontSize = editorConfig.fontSize; + return options; + } + + private centerInputVertically() { + if (this.container && typeof this.heightInPx === 'number') { + const lineHeight = this.input.getOption(EditorOption.lineHeight); + const lineNum = this.input.getModel().getLineCount(); + const newTopMargin = (this.heightInPx - lineNum * lineHeight) / 2; + this.inputContainer.style.marginTop = newTopMargin + 'px'; + } } private createDecorations(): IDecorationOptions[] { diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index dd408218ee..8deb08fcaf 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -28,9 +28,11 @@ import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet'; import { ILabelService } from 'vs/platform/label/common/label'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { Gesture } from 'vs/base/browser/touch'; const $ = dom.$; @@ -38,11 +40,12 @@ function createCheckbox(): HTMLInputElement { const checkbox = $('input'); checkbox.type = 'checkbox'; checkbox.tabIndex = -1; + Gesture.ignoreTarget(checkbox); return checkbox; } -export class BreakpointsView extends ViewletPanel { +export class BreakpointsView extends ViewletPane { private static readonly MAX_VISIBLE_FILES = 9; private list!: WorkbenchList; @@ -60,7 +63,7 @@ export class BreakpointsView extends ViewletPanel { @IConfigurationService configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService, ) { - super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('breakpointsSection', "Breakpoints Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewletPaneOptions), ariaHeaderLabel: nls.localize('breakpointsSection', "Breakpoints Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); this.minimumBodySize = this.maximumBodySize = this.getExpandedBodySize(); this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange())); @@ -85,6 +88,9 @@ export class BreakpointsView extends ViewletPanel { getPosInSet: (_: IEnablement, index: number) => index, getRole: (breakpoint: IEnablement) => 'checkbox', isChecked: (breakpoint: IEnablement) => breakpoint.enabled + }, + overrideStyles: { + listBackground: SIDE_BAR_BACKGROUND } }); @@ -346,7 +352,7 @@ class BreakpointsRenderer implements IListRenderer>(WorkbenchAsyncDataTree, 'CallStackView', treeContainer, new CallStackDelegate(), [ new SessionsRenderer(this.instantiationService), new ThreadsRenderer(this.instantiationService), this.instantiationService.createInstance(StackFramesRenderer), @@ -162,10 +175,13 @@ export class CallStackView extends ViewletPanel { return nls.localize('showMoreStackFrames2', "Show More Stack Frames"); } }, - expandOnlyOnTwistieClick: true + expandOnlyOnTwistieClick: true, + overrideStyles: { + listBackground: SIDE_BAR_BACKGROUND + } }); - this.tree.setInput(this.debugService.getModel()).then(undefined, onUnexpectedError); + this.tree.setInput(this.debugService.getModel()); const callstackNavigator = new TreeResourceNavigator2(this.tree); this._register(callstackNavigator); @@ -326,15 +342,15 @@ export class CallStackView extends ViewletPanel { this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => actions, - getActionsContext: () => element && element instanceof StackFrame ? element.getId() : undefined, + getActionsContext: () => getContext(element), onHide: () => dispose(actionsDisposable) }); } - private getContextForContributedActions(element: CallStackItem | null): string | number | undefined { + private getContextForContributedActions(element: CallStackItem | null): string | number { if (element instanceof StackFrame) { if (element.source.inMemory) { - return element.source.raw.path || element.source.reference; + return element.source.raw.path || element.source.reference || ''; } return element.source.uri.toString(); @@ -346,7 +362,7 @@ export class CallStackView extends ViewletPanel { return element.getId(); } - return undefined; + return ''; } } @@ -382,6 +398,7 @@ interface IStackFrameTemplateData { fileName: HTMLElement; lineNumber: HTMLElement; label: HighlightedLabel; + actionBar: ActionBar; } class SessionsRenderer implements ITreeRenderer { @@ -478,8 +495,9 @@ class StackFramesRenderer implements ITreeRenderer, index: number, data: IStackFrameTemplateData): void { @@ -487,6 +505,8 @@ class StackFramesRenderer implements ITreeRenderer { + return stackFrame.restart(); + }); + data.actionBar.push(action, { icon: true, label: false }); + } } disposeTemplate(templateData: IStackFrameTemplateData): void { - // noop + templateData.actionBar.dispose(); } } @@ -644,7 +672,7 @@ class CallStackDataSource implements IAsyncDataSource 1) { + if (sessions.length > 1 || this.debugService.getViewModel().isMultiSessionView()) { return Promise.resolve(sessions.filter(s => !s.parentSession)); } @@ -786,11 +814,11 @@ class StopAction extends Action { private readonly session: IDebugSession, @ICommandService private readonly commandService: ICommandService ) { - super(`action.${STOP_ID}`, STOP_LABEL, 'debug-action stop'); + super(`action.${STOP_ID}`, STOP_LABEL, 'debug-action codicon-debug-stop'); } public run(): Promise { - return this.commandService.executeCommand(STOP_ID, this.session.getId(), this.session); + return this.commandService.executeCommand(STOP_ID, getContext(this.session)); } } @@ -800,11 +828,11 @@ class DisconnectAction extends Action { private readonly session: IDebugSession, @ICommandService private readonly commandService: ICommandService ) { - super(`action.${DISCONNECT_ID}`, DISCONNECT_LABEL, 'debug-action disconnect'); + super(`action.${DISCONNECT_ID}`, DISCONNECT_LABEL, 'debug-action codicon-debug-disconnect'); } public run(): Promise { - return this.commandService.executeCommand(DISCONNECT_ID, this.session.getId(), this.session); + return this.commandService.executeCommand(DISCONNECT_ID, getContext(this.session)); } } @@ -814,11 +842,11 @@ class RestartAction extends Action { private readonly session: IDebugSession, @ICommandService private readonly commandService: ICommandService ) { - super(`action.${RESTART_SESSION_ID}`, RESTART_LABEL, 'debug-action restart'); + super(`action.${RESTART_SESSION_ID}`, RESTART_LABEL, 'debug-action codicon-debug-restart'); } public run(): Promise { - return this.commandService.executeCommand(RESTART_SESSION_ID, this.session.getId(), this.session); + return this.commandService.executeCommand(RESTART_SESSION_ID, getContext(this.session)); } } @@ -828,11 +856,11 @@ class StepOverAction extends Action { private readonly thread: IThread, @ICommandService private readonly commandService: ICommandService ) { - super(`action.${STEP_OVER_ID}`, STEP_OVER_LABEL, 'debug-action step-over', thread.stopped); + super(`action.${STEP_OVER_ID}`, STEP_OVER_LABEL, 'debug-action codicon-debug-step-over', thread.stopped); } public run(): Promise { - return this.commandService.executeCommand(STEP_OVER_ID, this.thread.threadId, this.thread); + return this.commandService.executeCommand(STEP_OVER_ID, getContext(this.thread)); } } @@ -842,11 +870,11 @@ class StepIntoAction extends Action { private readonly thread: IThread, @ICommandService private readonly commandService: ICommandService ) { - super(`action.${STEP_INTO_ID}`, STEP_INTO_LABEL, 'debug-action step-into', thread.stopped); + super(`action.${STEP_INTO_ID}`, STEP_INTO_LABEL, 'debug-action codicon-debug-step-into', thread.stopped); } public run(): Promise { - return this.commandService.executeCommand(STEP_INTO_ID, this.thread.threadId, this.thread); + return this.commandService.executeCommand(STEP_INTO_ID, getContext(this.thread)); } } @@ -856,11 +884,11 @@ class StepOutAction extends Action { private readonly thread: IThread, @ICommandService private readonly commandService: ICommandService ) { - super(`action.${STEP_OUT_ID}`, STEP_OUT_LABEL, 'debug-action step-out', thread.stopped); + super(`action.${STEP_OUT_ID}`, STEP_OUT_LABEL, 'debug-action codicon-debug-step-out', thread.stopped); } public run(): Promise { - return this.commandService.executeCommand(STEP_OUT_ID, this.thread.threadId, this.thread); + return this.commandService.executeCommand(STEP_OUT_ID, getContext(this.thread)); } } @@ -870,11 +898,11 @@ class PauseAction extends Action { private readonly thread: IThread, @ICommandService private readonly commandService: ICommandService ) { - super(`action.${PAUSE_ID}`, PAUSE_LABEL, 'debug-action pause', !thread.stopped); + super(`action.${PAUSE_ID}`, PAUSE_LABEL, 'debug-action codicon-debug-pause', !thread.stopped); } public run(): Promise { - return this.commandService.executeCommand(PAUSE_ID, this.thread.threadId, this.thread); + return this.commandService.executeCommand(PAUSE_ID, getContext(this.thread)); } } @@ -884,10 +912,10 @@ class ContinueAction extends Action { private readonly thread: IThread, @ICommandService private readonly commandService: ICommandService ) { - super(`action.${CONTINUE_ID}`, CONTINUE_LABEL, 'debug-action continue', thread.stopped); + super(`action.${CONTINUE_ID}`, CONTINUE_LABEL, 'debug-action codicon-debug-continue', thread.stopped); } public run(): Promise { - return this.commandService.executeCommand(CONTINUE_ID, this.thread.threadId, this.thread); + return this.commandService.executeCommand(CONTINUE_ID, getContext(this.thread)); } } diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index ff9b3c05f3..2248736905 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -20,7 +20,7 @@ import { CallStackView } from 'vs/workbench/contrib/debug/browser/callStackView' import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { IDebugService, VIEWLET_ID, REPL_ID, CONTEXT_IN_DEBUG_MODE, INTERNAL_CONSOLE_OPTIONS_SCHEMA, - CONTEXT_DEBUG_STATE, VARIABLES_VIEW_ID, CALLSTACK_VIEW_ID, WATCH_VIEW_ID, BREAKPOINTS_VIEW_ID, VIEW_CONTAINER, LOADED_SCRIPTS_VIEW_ID, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, + CONTEXT_DEBUG_STATE, VARIABLES_VIEW_ID, CALLSTACK_VIEW_ID, WATCH_VIEW_ID, BREAKPOINTS_VIEW_ID, VIEW_CONTAINER, LOADED_SCRIPTS_VIEW_ID, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_DEBUG_UX, } from 'vs/workbench/contrib/debug/common/debug'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; @@ -47,8 +47,9 @@ import { WatchExpressionsView } from 'vs/workbench/contrib/debug/browser/watchEx import { VariablesView } from 'vs/workbench/contrib/debug/browser/variablesView'; import { ClearReplAction, Repl } from 'vs/workbench/contrib/debug/browser/repl'; import { DebugContentProvider } from 'vs/workbench/contrib/debug/common/debugContentProvider'; -import { registerAndGetAmdImageURL } from 'vs/base/common/amd'; import { DebugCallStackContribution } from 'vs/workbench/contrib/debug/browser/debugCallStackContribution'; +import { StartView } from 'vs/workbench/contrib/debug/browser/startView'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; class OpenDebugViewletAction extends ShowViewletAction { public static readonly ID = VIEWLET_ID; @@ -80,13 +81,12 @@ class OpenDebugPanelAction extends TogglePanelAction { } // register viewlet -Registry.as(ViewletExtensions.Viewlets).registerViewlet(new ViewletDescriptor( +Registry.as(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( DebugViewlet, VIEWLET_ID, - nls.localize('debug', "Debug"), - 'debug', - // {{SQL CARBON EDIT}} - 13 + nls.localize('debugAndRun', "Debug And Run"), + 'codicon-debug', + 13 // {{SQL CARBON EDIT}} )); const openViewletKb: IKeybindings = { @@ -97,7 +97,7 @@ const openPanelKb: IKeybindings = { }; // register repl panel -Registry.as(PanelExtensions.Panels).registerPanel(new PanelDescriptor( +Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( Repl, REPL_ID, nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugPanel' }, 'Debug Console'), @@ -108,18 +108,19 @@ Registry.as(PanelExtensions.Panels).registerPanel(new PanelDescri // Register default debug views const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); -viewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variables', "Variables"), ctorDescriptor: { ctor: VariablesView }, order: 10, weight: 40, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusVariablesView' } }], VIEW_CONTAINER); -viewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), ctorDescriptor: { ctor: WatchExpressionsView }, order: 20, weight: 10, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusWatchView' } }], VIEW_CONTAINER); -viewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), ctorDescriptor: { ctor: CallStackView }, order: 30, weight: 30, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusCallStackView' } }], VIEW_CONTAINER); -viewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), ctorDescriptor: { ctor: BreakpointsView }, order: 40, weight: 20, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusBreakpointsView' } }], VIEW_CONTAINER); -viewsRegistry.registerViews([{ id: LOADED_SCRIPTS_VIEW_ID, name: nls.localize('loadedScripts', "Loaded Scripts"), ctorDescriptor: { ctor: LoadedScriptsView }, order: 35, weight: 5, canToggleVisibility: true, collapsed: true, when: CONTEXT_LOADED_SCRIPTS_SUPPORTED }], VIEW_CONTAINER); +viewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variables', "Variables"), ctorDescriptor: { ctor: VariablesView }, order: 10, weight: 40, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusVariablesView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], VIEW_CONTAINER); +viewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), ctorDescriptor: { ctor: WatchExpressionsView }, order: 20, weight: 10, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusWatchView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], VIEW_CONTAINER); +viewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), ctorDescriptor: { ctor: CallStackView }, order: 30, weight: 30, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusCallStackView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], VIEW_CONTAINER); +viewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), ctorDescriptor: { ctor: BreakpointsView }, order: 40, weight: 20, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusBreakpointsView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], VIEW_CONTAINER); +viewsRegistry.registerViews([{ id: StartView.ID, name: StartView.LABEL, ctorDescriptor: { ctor: StartView }, order: 10, weight: 40, canToggleVisibility: true, when: CONTEXT_DEBUG_UX.isEqualTo('simple') }], VIEW_CONTAINER); +viewsRegistry.registerViews([{ id: LOADED_SCRIPTS_VIEW_ID, name: nls.localize('loadedScripts', "Loaded Scripts"), ctorDescriptor: { ctor: LoadedScriptsView }, order: 35, weight: 5, canToggleVisibility: true, collapsed: true, when: ContextKeyExpr.and(CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_DEBUG_UX.isEqualTo('default')) }], VIEW_CONTAINER); registerCommands(); // register action to open viewlet const registry = Registry.as(WorkbenchActionRegistryExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenDebugPanelAction, OpenDebugPanelAction.ID, OpenDebugPanelAction.LABEL, openPanelKb), 'View: Debug Console', nls.localize('view', "View")); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenDebugViewletAction, OpenDebugViewletAction.ID, OpenDebugViewletAction.LABEL, openViewletKb), 'View: Show Debug', nls.localize('view', "View")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenDebugPanelAction, OpenDebugPanelAction.ID, OpenDebugPanelAction.LABEL, openPanelKb), 'View: Debug Console', nls.localize('view', "View")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenDebugViewletAction, OpenDebugViewletAction.ID, OpenDebugViewletAction.LABEL, openViewletKb), 'View: Show Debug', nls.localize('view', "View")); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugCallStackContribution, LifecyclePhase.Restored); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugToolBar, LifecyclePhase.Restored); @@ -128,16 +129,16 @@ Registry.as(WorkbenchExtensions.Workbench).regi const debugCategory = nls.localize('debugCategory', "Debug"); -registry.registerWorkbenchAction(new SyncActionDescriptor(StartAction, StartAction.ID, StartAction.LABEL, { primary: KeyCode.F5 }, CONTEXT_IN_DEBUG_MODE.toNegated()), 'Debug: Start Debugging', debugCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(ConfigureAction, ConfigureAction.ID, ConfigureAction.LABEL), 'Debug: Open launch.json', debugCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(AddFunctionBreakpointAction, AddFunctionBreakpointAction.ID, AddFunctionBreakpointAction.LABEL), 'Debug: Add Function Breakpoint', debugCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(ReapplyBreakpointsAction, ReapplyBreakpointsAction.ID, ReapplyBreakpointsAction.LABEL), 'Debug: Reapply All Breakpoints', debugCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(RunAction, RunAction.ID, RunAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.F5, mac: { primary: KeyMod.WinCtrl | KeyCode.F5 } }), 'Debug: Start Without Debugging', debugCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(RemoveAllBreakpointsAction, RemoveAllBreakpointsAction.ID, RemoveAllBreakpointsAction.LABEL), 'Debug: Remove All Breakpoints', debugCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(EnableAllBreakpointsAction, EnableAllBreakpointsAction.ID, EnableAllBreakpointsAction.LABEL), 'Debug: Enable All Breakpoints', debugCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(DisableAllBreakpointsAction, DisableAllBreakpointsAction.ID, DisableAllBreakpointsAction.LABEL), 'Debug: Disable All Breakpoints', debugCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(SelectAndStartAction, SelectAndStartAction.ID, SelectAndStartAction.LABEL), 'Debug: Select and Start Debugging', debugCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(ClearReplAction, ClearReplAction.ID, ClearReplAction.LABEL), 'Debug: Clear Console', debugCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(StartAction, StartAction.ID, StartAction.LABEL, { primary: KeyCode.F5 }, CONTEXT_IN_DEBUG_MODE.toNegated()), 'Debug: Start Debugging', debugCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ConfigureAction, ConfigureAction.ID, ConfigureAction.LABEL), 'Debug: Open launch.json', debugCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(AddFunctionBreakpointAction, AddFunctionBreakpointAction.ID, AddFunctionBreakpointAction.LABEL), 'Debug: Add Function Breakpoint', debugCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ReapplyBreakpointsAction, ReapplyBreakpointsAction.ID, ReapplyBreakpointsAction.LABEL), 'Debug: Reapply All Breakpoints', debugCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(RunAction, RunAction.ID, RunAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.F5, mac: { primary: KeyMod.WinCtrl | KeyCode.F5 } }), 'Debug: Start Without Debugging', debugCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(RemoveAllBreakpointsAction, RemoveAllBreakpointsAction.ID, RemoveAllBreakpointsAction.LABEL), 'Debug: Remove All Breakpoints', debugCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(EnableAllBreakpointsAction, EnableAllBreakpointsAction.ID, EnableAllBreakpointsAction.LABEL), 'Debug: Enable All Breakpoints', debugCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(DisableAllBreakpointsAction, DisableAllBreakpointsAction.ID, DisableAllBreakpointsAction.LABEL), 'Debug: Disable All Breakpoints', debugCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(SelectAndStartAction, SelectAndStartAction.ID, SelectAndStartAction.LABEL), 'Debug: Select and Start Debugging', debugCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ClearReplAction, ClearReplAction.ID, ClearReplAction.LABEL), 'Debug: Clear Console', debugCategory); const registerDebugCommandPaletteItem = (id: string, title: string, when?: ContextKeyExpr, precondition?: ContextKeyExpr) => { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { @@ -167,7 +168,7 @@ registerDebugCommandPaletteItem(TOGGLE_INLINE_BREAKPOINT_ID, nls.localize('inlin // Register Quick Open (Registry.as(QuickOpenExtensions.Quickopen)).registerQuickOpenHandler( - new QuickOpenHandlerDescriptor( + QuickOpenHandlerDescriptor.create( DebugQuickOpenHandler, DebugQuickOpenHandler.ID, 'debug ', @@ -270,6 +271,11 @@ configurationRegistry.registerConfiguration({ type: 'boolean', description: nls.localize({ comment: ['This is the description for a setting'], key: 'showBreakpointsInOverviewRuler' }, "Controls whether breakpoints should be shown in the overview ruler."), default: false + }, + 'debug.showInlineBreakpointCandidates': { + type: 'boolean', + description: nls.localize({ comment: ['This is the description for a setting'], key: 'showInlineBreakpointCandidates' }, "Controls whether inline breakpoints candidate decorations should be shown in the editor while debugging."), + default: true } } }); @@ -279,7 +285,7 @@ Registry.as(WorkbenchExtensions.Workbench).regi // Debug toolbar -const registerDebugToolBarItem = (id: string, title: string, iconLightUri: URI, iconDarkUri: URI, order: number, when?: ContextKeyExpr, precondition?: ContextKeyExpr) => { +const registerDebugToolBarItem = (id: string, title: string, order: number, icon: { light?: URI, dark?: URI } | ThemeIcon, when?: ContextKeyExpr, precondition?: ContextKeyExpr) => { MenuRegistry.appendMenuItem(MenuId.DebugToolBar, { group: 'navigation', when, @@ -287,25 +293,22 @@ const registerDebugToolBarItem = (id: string, title: string, iconLightUri: URI, command: { id, title, - iconLocation: { - light: iconLightUri, - dark: iconDarkUri - }, + icon, precondition } }); }; -registerDebugToolBarItem(CONTINUE_ID, CONTINUE_LABEL, URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/continue-light.svg')), URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/continue-dark.svg')), 10, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); -registerDebugToolBarItem(PAUSE_ID, PAUSE_LABEL, URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/pause-light.svg')), URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/pause-dark.svg')), 10, CONTEXT_DEBUG_STATE.notEqualsTo('stopped')); -registerDebugToolBarItem(STOP_ID, STOP_LABEL, URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/stop-light.svg')), URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/stop-dark.svg')), 70, CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated()); -registerDebugToolBarItem(DISCONNECT_ID, DISCONNECT_LABEL, URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/disconnect-light.svg')), URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/disconnect-dark.svg')), 70, CONTEXT_FOCUSED_SESSION_IS_ATTACH); -registerDebugToolBarItem(STEP_OVER_ID, STEP_OVER_LABEL, URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/step-over-light.svg')), URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/step-over-dark.svg')), 20, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); -registerDebugToolBarItem(STEP_INTO_ID, STEP_INTO_LABEL, URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/step-into-light.svg')), URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/step-into-dark.svg')), 30, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); -registerDebugToolBarItem(STEP_OUT_ID, STEP_OUT_LABEL, URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/step-out-light.svg')), URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/step-out-dark.svg')), 40, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); -registerDebugToolBarItem(RESTART_SESSION_ID, RESTART_LABEL, URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/restart-light.svg')), URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/restart-dark.svg')), 60); -registerDebugToolBarItem(STEP_BACK_ID, nls.localize('stepBackDebug', "Step Back"), URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/step-back-light.svg')), URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/step-back-dark.svg')), 50, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); -registerDebugToolBarItem(REVERSE_CONTINUE_ID, nls.localize('reverseContinue', "Reverse"), URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/reverse-continue-light.svg')), URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/reverse-continue-dark.svg')), 60, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); +registerDebugToolBarItem(CONTINUE_ID, CONTINUE_LABEL, 10, { id: 'codicon/debug-continue' }, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); +registerDebugToolBarItem(PAUSE_ID, PAUSE_LABEL, 10, { id: 'codicon/debug-pause' }, CONTEXT_DEBUG_STATE.notEqualsTo('stopped')); +registerDebugToolBarItem(STOP_ID, STOP_LABEL, 70, { id: 'codicon/debug-stop' }, CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated()); +registerDebugToolBarItem(DISCONNECT_ID, DISCONNECT_LABEL, 70, { id: 'codicon/debug-disconnect' }, CONTEXT_FOCUSED_SESSION_IS_ATTACH); +registerDebugToolBarItem(STEP_OVER_ID, STEP_OVER_LABEL, 20, { id: 'codicon/debug-step-over' }, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); +registerDebugToolBarItem(STEP_INTO_ID, STEP_INTO_LABEL, 30, { id: 'codicon/debug-step-into' }, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); +registerDebugToolBarItem(STEP_OUT_ID, STEP_OUT_LABEL, 40, { id: 'codicon/debug-step-out' }, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); +registerDebugToolBarItem(RESTART_SESSION_ID, RESTART_LABEL, 60, { id: 'codicon/debug-restart' }); +registerDebugToolBarItem(STEP_BACK_ID, nls.localize('stepBackDebug', "Step Back"), 50, { id: 'codicon/debug-step-back' }, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); +registerDebugToolBarItem(REVERSE_CONTINUE_ID, nls.localize('reverseContinue', "Reverse"), 60, { id: 'codicon/debug-reverse-continue' }, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); // Debug callstack context menu const registerDebugCallstackItem = (id: string, title: string, order: number, when?: ContextKeyExpr, precondition?: ContextKeyExpr, group = 'navigation') => { @@ -370,7 +373,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { group: '1_debug', command: { id: RunAction.ID, - title: nls.localize({ key: 'miStartWithoutDebugging', comment: ['&& denotes a mnemonic'] }, "Start &&Without Debugging") + title: nls.localize({ key: 'miRun', comment: ['&& denotes a mnemonic'] }, "Run &&Without Debugging") }, order: 2 }); @@ -554,7 +557,7 @@ if (isMacintosh) { command: { id, title, - iconLocation: { dark: iconUri } + icon: { dark: iconUri } }, when, group: '9_debug', diff --git a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index 61d3025175..15cf1e1218 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -69,7 +69,7 @@ export class StartDebugActionViewItem implements IActionViewItem { render(container: HTMLElement): void { this.container = container; dom.addClass(container, 'start-debug-action-item'); - this.start = dom.append(container, $('.icon')); + this.start = dom.append(container, $('.codicon.codicon-debug-start')); this.start.title = this.action.label; this.start.setAttribute('role', 'button'); this.start.tabIndex = 0; diff --git a/src/vs/workbench/contrib/debug/browser/debugActions.ts b/src/vs/workbench/contrib/debug/browser/debugActions.ts index 05fb2da0e6..43b5db1b9c 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActions.ts @@ -62,7 +62,7 @@ export class ConfigureAction extends AbstractDebugAction { @INotificationService private readonly notificationService: INotificationService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService ) { - super(id, label, 'debug-action configure', debugService, keybindingService); + super(id, label, 'debug-action codicon codicon-gear', debugService, keybindingService); this._register(debugService.getConfigurationManager().onDidSelectConfiguration(() => this.updateClass())); this.updateClass(); } @@ -78,7 +78,7 @@ export class ConfigureAction extends AbstractDebugAction { private updateClass(): void { const configurationManager = this.debugService.getConfigurationManager(); const configurationCount = configurationManager.getLaunches().map(l => l.getConfigurationNames().length).reduce((sum, current) => sum + current); - this.class = configurationCount > 0 ? 'debug-action configure' : 'debug-action configure notification'; + this.class = configurationCount > 0 ? 'debug-action codicon codicon-gear' : 'debug-action codicon codicon-gear notification'; } async run(event?: any): Promise { @@ -143,7 +143,7 @@ export class StartAction extends AbstractDebugAction { export class RunAction extends StartAction { static readonly ID = 'workbench.action.debug.run'; - static LABEL = nls.localize('startWithoutDebugging', "Start Without Debugging"); + static LABEL = nls.localize('startWithoutDebugging', "Run (Start Without Debugging)"); protected isNoDebug(): boolean { return true; @@ -186,7 +186,7 @@ export class RemoveAllBreakpointsAction extends AbstractDebugAction { static readonly LABEL = nls.localize('removeAllBreakpoints', "Remove All Breakpoints"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action remove-all', debugService, keybindingService); + super(id, label, 'debug-action codicon-close-all', debugService, keybindingService); this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement())); } @@ -244,7 +244,7 @@ export class ToggleBreakpointsActivatedAction extends AbstractDebugAction { static readonly DEACTIVATE_LABEL = nls.localize('deactivateBreakpoints', "Deactivate Breakpoints"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action breakpoints-activate', debugService, keybindingService); + super(id, label, 'debug-action codicon-activate-breakpoints', debugService, keybindingService); this.updateLabel(this.debugService.getModel().areBreakpointsActivated() ? ToggleBreakpointsActivatedAction.DEACTIVATE_LABEL : ToggleBreakpointsActivatedAction.ACTIVATE_LABEL); this._register(this.debugService.getModel().onDidChangeBreakpoints(() => { @@ -287,7 +287,7 @@ export class AddFunctionBreakpointAction extends AbstractDebugAction { static readonly LABEL = nls.localize('addFunctionBreakpoint', "Add Function Breakpoint"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action add-function-breakpoint', debugService, keybindingService); + super(id, label, 'debug-action codicon-add', debugService, keybindingService); this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement())); } @@ -306,7 +306,7 @@ export class AddWatchExpressionAction extends AbstractDebugAction { static readonly LABEL = nls.localize('addWatchExpression', "Add Expression"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action add-watch-expression', debugService, keybindingService); + super(id, label, 'debug-action codicon-add', debugService, keybindingService); this._register(this.debugService.getModel().onDidChangeWatchExpressions(() => this.updateEnablement())); this._register(this.debugService.getViewModel().onDidSelectExpression(() => this.updateEnablement())); } @@ -326,7 +326,7 @@ export class RemoveAllWatchExpressionsAction extends AbstractDebugAction { static readonly LABEL = nls.localize('removeAllWatchExpressions', "Remove All Expressions"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action remove-all', debugService, keybindingService); + super(id, label, 'debug-action codicon-close-all', debugService, keybindingService); this._register(this.debugService.getModel().onDidChangeWatchExpressions(() => this.updateEnablement())); } diff --git a/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts b/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts index c9d96da1c2..0126ee998d 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts @@ -128,12 +128,12 @@ export class DebugCallStackContribution implements IWorkbenchContribution { static readonly STICKINESS = TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges; // we need a separate decoration for glyph margin, since we do not want it on each line of a multi line statement. private static TOP_STACK_FRAME_MARGIN: IModelDecorationOptions = { - glyphMarginClassName: 'debug-top-stack-frame', + glyphMarginClassName: 'codicon-debug-breakpoint-stackframe', stickiness }; private static FOCUSED_STACK_FRAME_MARGIN: IModelDecorationOptions = { - glyphMarginClassName: 'debug-focused-stack-frame', + glyphMarginClassName: 'codicon-debug-breakpoint-stackframe-focused', stickiness }; @@ -176,7 +176,68 @@ registerThemingParticipant((theme, collector) => { if (focusedStackFrame) { collector.addRule(`.monaco-editor .view-overlays .debug-focused-stack-frame-line { background: ${focusedStackFrame}; }`); } + + const debugIconBreakpointColor = theme.getColor(debugIconBreakpointForeground); + if (debugIconBreakpointColor) { + collector.addRule(` + .monaco-workbench .codicon-debug-breakpoint, + .monaco-workbench .codicon-debug-breakpoint-conditional, + .monaco-workbench .codicon-debug-breakpoint-log, + .monaco-workbench .codicon-debug-breakpoint-function, + .monaco-workbench .codicon-debug-breakpoint-data, + .monaco-workbench .codicon-debug-breakpoint-unsupported, + .monaco-workbench .codicon-debug-hint:not(*[class*='codicon-debug-breakpoint']) , + .monaco-workbench .codicon-debug-breakpoint-stackframe-dot, + .monaco-workbench .codicon-debug-breakpoint.codicon-debug-breakpoint-stackframe-focused::after { + color: ${debugIconBreakpointColor} !important; + } + `); + } + + const debugIconBreakpointDisabledColor = theme.getColor(debugIconBreakpointDisabledForeground); + if (debugIconBreakpointDisabledColor) { + collector.addRule(` + .monaco-workbench .codicon[class*='-disabled'] { + color: ${debugIconBreakpointDisabledColor} !important; + } + `); + } + + const debugIconBreakpointUnverifiedColor = theme.getColor(debugIconBreakpointUnverifiedForeground); + if (debugIconBreakpointUnverifiedColor) { + collector.addRule(` + .monaco-workbench .codicon[class*='-unverified'] { + color: ${debugIconBreakpointUnverifiedColor} !important; + } + `); + } + + const debugIconBreakpointStackframeColor = theme.getColor(debugIconBreakpointStackframeForeground); + if (debugIconBreakpointStackframeColor) { + collector.addRule(` + .monaco-workbench .codicon-debug-breakpoint-stackframe, + .monaco-workbench .codicon-debug-breakpoint-stackframe-dot::after { + color: ${debugIconBreakpointStackframeColor} !important; + } + `); + } + + const debugIconBreakpointStackframeFocusedColor = theme.getColor(debugIconBreakpointStackframeFocusedForeground); + if (debugIconBreakpointStackframeFocusedColor) { + collector.addRule(` + .monaco-workbench .codicon-debug-breakpoint-stackframe-focused { + color: ${debugIconBreakpointStackframeFocusedColor} !important; + } + `); + } + }); const topStackFrameColor = registerColor('editor.stackFrameHighlightBackground', { dark: '#ffff0033', light: '#ffff6673', hc: '#fff600' }, localize('topStackFrameLineHighlight', 'Background color for the highlight of line at the top stack frame position.')); const focusedStackFrameColor = registerColor('editor.focusedStackFrameHighlightBackground', { dark: '#7abd7a4d', light: '#cee7ce73', hc: '#cee7ce' }, localize('focusedStackFrameLineHighlight', 'Background color for the highlight of line at focused stack frame position.')); + +const debugIconBreakpointForeground = registerColor('debugIcon.breakpointForeground', { dark: '#E51400', light: '#E51400', hc: '#E51400' }, localize('debugIcon.breakpointForeground', 'Icon color for breakpoints.')); +const debugIconBreakpointDisabledForeground = registerColor('debugIcon.breakpointDisabledForeground', { dark: '#848484', light: '#848484', hc: '#848484' }, localize('debugIcon.breakpointDisabledForeground', 'Icon color for disabled breakpoints.')); +const debugIconBreakpointUnverifiedForeground = registerColor('debugIcon.breakpointUnverifiedForeground', { dark: '#848484', light: '#848484', hc: '#848484' }, localize('debugIcon.breakpointUnverifiedForeground', 'Icon color for unverified breakpoints.')); +const debugIconBreakpointStackframeForeground = registerColor('debugIcon.breakpointStackframeForeground', { dark: '#FFCC00', light: '#FFCC00', hc: '#FFCC00' }, localize('debugIcon.breakpointStackframeForeground', 'Icon color for breakpoints.')); +const debugIconBreakpointStackframeFocusedForeground = registerColor('debugIcon.breakpointStackframeFocusedForeground', { dark: '#89D185', light: '#89D185', hc: '#89D185' }, localize('debugIcon.breakpointStackframeFocusedForeground', 'Icon color for breakpoints.')); diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index f79ccb922d..e4827854f6 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -58,15 +58,24 @@ export const DISCONNECT_LABEL = nls.localize('disconnect', "Disconnect"); export const STOP_LABEL = nls.localize('stop', "Stop"); export const CONTINUE_LABEL = nls.localize('continueDebug', "Continue"); -async function getThreadAndRun(accessor: ServicesAccessor, threadId: number | any, run: (thread: IThread) => Promise): Promise { +interface CallStackContext { + sessionId: string; + threadId: string; + frameId: string; +} + +function isThreadContext(obj: any): obj is CallStackContext { + return obj && typeof obj.sessionId === 'string' && typeof obj.threadId === 'string'; +} + +async function getThreadAndRun(accessor: ServicesAccessor, sessionAndThreadId: CallStackContext | unknown, run: (thread: IThread) => Promise): Promise { const debugService = accessor.get(IDebugService); let thread: IThread | undefined; - if (typeof threadId === 'number') { - debugService.getModel().getSessions().forEach(s => { - if (!thread) { - thread = s.getThread(threadId); - } - }); + if (isThreadContext(sessionAndThreadId)) { + const session = debugService.getModel().getSession(sessionAndThreadId.sessionId); + if (session) { + thread = session.getAllThreads().filter(t => t.getId() === sessionAndThreadId.threadId).pop(); + } } else { thread = debugService.getViewModel().focusedThread; if (!thread) { @@ -81,18 +90,17 @@ async function getThreadAndRun(accessor: ServicesAccessor, threadId: number | an } } -function getFrame(debugService: IDebugService, frameId: string | undefined): IStackFrame | undefined { - if (!frameId) { - return undefined; - } +function isStackFrameContext(obj: any): obj is CallStackContext { + return obj && typeof obj.sessionId === 'string' && typeof obj.threadId === 'string' && typeof obj.frameId === 'string'; +} - const sessions = debugService.getModel().getSessions(); - for (let s of sessions) { - for (let t of s.getAllThreads()) { - for (let sf of t.getCallStack()) { - if (sf.getId() === frameId) { - return sf; - } +function getFrame(debugService: IDebugService, context: CallStackContext | unknown): IStackFrame | undefined { + if (isStackFrameContext(context)) { + const session = debugService.getModel().getSession(context.sessionId); + if (session) { + const thread = session.getAllThreads().filter(t => t.getId() === context.threadId).pop(); + if (thread) { + return thread.getCallStack().filter(sf => sf.getId() === context.frameId).pop(); } } } @@ -100,6 +108,10 @@ function getFrame(debugService: IDebugService, frameId: string | undefined): ISt return undefined; } +function isSessionContext(obj: any): obj is CallStackContext { + return obj && typeof obj.sessionId === 'string'; +} + export function registerCommands(): void { // These commands are used in call stack context menu, call stack inline actions, command pallete, debug toolbar, mac native touch bar @@ -108,10 +120,10 @@ export function registerCommands(): void { // Same for stackFrame commands and session commands. CommandsRegistry.registerCommand({ id: COPY_STACK_TRACE_ID, - handler: async (accessor: ServicesAccessor, _: string, frameId: string | undefined) => { + handler: async (accessor: ServicesAccessor, context: CallStackContext | unknown) => { const textResourcePropertiesService = accessor.get(ITextResourcePropertiesService); const clipboardService = accessor.get(IClipboardService); - let frame = getFrame(accessor.get(IDebugService), frameId); + let frame = getFrame(accessor.get(IDebugService), context); if (frame) { const eol = textResourcePropertiesService.getEOL(frame.source.uri); await clipboardService.writeText(frame.thread.getCallStack().map(sf => sf.toString()).join(eol)); @@ -121,22 +133,22 @@ export function registerCommands(): void { CommandsRegistry.registerCommand({ id: REVERSE_CONTINUE_ID, - handler: (accessor: ServicesAccessor, threadId: number | any) => { - getThreadAndRun(accessor, threadId, thread => thread.reverseContinue()); + handler: (accessor: ServicesAccessor, context: CallStackContext | unknown) => { + getThreadAndRun(accessor, context, thread => thread.reverseContinue()); } }); CommandsRegistry.registerCommand({ id: STEP_BACK_ID, - handler: (accessor: ServicesAccessor, threadId: number | any) => { - getThreadAndRun(accessor, threadId, thread => thread.stepBack()); + handler: (accessor: ServicesAccessor, context: CallStackContext | unknown) => { + getThreadAndRun(accessor, context, thread => thread.stepBack()); } }); CommandsRegistry.registerCommand({ id: TERMINATE_THREAD_ID, - handler: (accessor: ServicesAccessor, threadId: number | any) => { - getThreadAndRun(accessor, threadId, thread => thread.terminate()); + handler: (accessor: ServicesAccessor, context: CallStackContext | unknown) => { + getThreadAndRun(accessor, context, thread => thread.terminate()); } }); @@ -194,9 +206,12 @@ export function registerCommands(): void { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.F5, when: CONTEXT_IN_DEBUG_MODE, - handler: (accessor: ServicesAccessor, _: string, session: IDebugSession | undefined) => { + handler: (accessor: ServicesAccessor, context: CallStackContext | unknown) => { const debugService = accessor.get(IDebugService); - if (!session || !session.getId) { + let session: IDebugSession | undefined; + if (isSessionContext(context)) { + session = debugService.getModel().getSession(context.sessionId); + } else { session = debugService.getViewModel().focusedSession; } @@ -215,8 +230,8 @@ export function registerCommands(): void { weight: KeybindingWeight.WorkbenchContrib, primary: KeyCode.F10, when: CONTEXT_DEBUG_STATE.isEqualTo('stopped'), - handler: (accessor: ServicesAccessor, threadId: number | any) => { - getThreadAndRun(accessor, threadId, (thread: IThread) => thread.next()); + handler: (accessor: ServicesAccessor, context: CallStackContext | unknown) => { + getThreadAndRun(accessor, context, (thread: IThread) => thread.next()); } }); @@ -224,9 +239,9 @@ export function registerCommands(): void { id: STEP_INTO_ID, weight: KeybindingWeight.WorkbenchContrib + 10, // Have a stronger weight to have priority over full screen when debugging primary: KeyCode.F11, - when: CONTEXT_IN_DEBUG_MODE, - handler: (accessor: ServicesAccessor, threadId: number | any) => { - getThreadAndRun(accessor, threadId, (thread: IThread) => thread.stepIn()); + when: CONTEXT_DEBUG_STATE.isEqualTo('stopped'), + handler: (accessor: ServicesAccessor, context: CallStackContext | unknown) => { + getThreadAndRun(accessor, context, (thread: IThread) => thread.stepIn()); } }); @@ -235,8 +250,8 @@ export function registerCommands(): void { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.Shift | KeyCode.F11, when: CONTEXT_DEBUG_STATE.isEqualTo('stopped'), - handler: (accessor: ServicesAccessor, threadId: number | any) => { - getThreadAndRun(accessor, threadId, (thread: IThread) => thread.stepOut()); + handler: (accessor: ServicesAccessor, context: CallStackContext | unknown) => { + getThreadAndRun(accessor, context, (thread: IThread) => thread.stepOut()); } }); @@ -245,8 +260,8 @@ export function registerCommands(): void { weight: KeybindingWeight.WorkbenchContrib, primary: KeyCode.F6, when: CONTEXT_DEBUG_STATE.isEqualTo('running'), - handler: (accessor: ServicesAccessor, threadId: number | any) => { - getThreadAndRun(accessor, threadId, thread => thread.pause()); + handler: (accessor: ServicesAccessor, context: CallStackContext | unknown) => { + getThreadAndRun(accessor, context, thread => thread.pause()); } }); @@ -264,9 +279,15 @@ export function registerCommands(): void { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.Shift | KeyCode.F5, when: CONTEXT_IN_DEBUG_MODE, - handler: (accessor: ServicesAccessor, sessionId: string | undefined) => { + handler: (accessor: ServicesAccessor, context: CallStackContext | unknown) => { const debugService = accessor.get(IDebugService); - let session = debugService.getModel().getSession(sessionId) || debugService.getViewModel().focusedSession; + let session: IDebugSession | undefined; + if (isSessionContext(context)) { + session = debugService.getModel().getSession(context.sessionId); + } else { + session = debugService.getViewModel().focusedSession; + } + const configurationService = accessor.get(IConfigurationService); const showSubSessions = configurationService.getValue('debug').showSubSessionsInToolBar; // Stop should be sent to the root parent session @@ -280,9 +301,9 @@ export function registerCommands(): void { CommandsRegistry.registerCommand({ id: RESTART_FRAME_ID, - handler: async (accessor: ServicesAccessor, _: string, frameId: string | undefined) => { + handler: async (accessor: ServicesAccessor, context: CallStackContext | unknown) => { const debugService = accessor.get(IDebugService); - let frame = getFrame(debugService, frameId); + let frame = getFrame(debugService, context); if (frame) { await frame.restart(); } @@ -294,8 +315,8 @@ export function registerCommands(): void { weight: KeybindingWeight.WorkbenchContrib, primary: KeyCode.F5, when: CONTEXT_IN_DEBUG_MODE, - handler: (accessor: ServicesAccessor, threadId: number | any) => { - getThreadAndRun(accessor, threadId, thread => thread.continue()); + handler: (accessor: ServicesAccessor, context: CallStackContext | unknown) => { + getThreadAndRun(accessor, context, thread => thread.continue()); } }); diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index 3b0c83a31f..bc1c86b7e7 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -11,6 +11,7 @@ import * as objects from 'vs/base/common/objects'; import { URI as uri } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import * as editorCommon from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import { IEditor } from 'vs/workbench/common/editor'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; @@ -56,6 +57,7 @@ export class ConfigurationManager implements IConfigurationManager { private adapterDescriptorFactories: IDebugAdapterDescriptorFactory[]; private debugAdapterFactories = new Map(); private debugConfigurationTypeContext: IContextKey; + private readonly _onDidRegisterDebugger = new Emitter(); constructor( @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @@ -166,6 +168,10 @@ export class ConfigurationManager implements IConfigurationManager { return Promise.resolve(undefined); } + get onDidRegisterDebugger(): Event { + return this._onDidRegisterDebugger.event; + } + // debug configurations registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable { @@ -196,11 +202,15 @@ export class ConfigurationManager implements IConfigurationManager { const providers = this.configProviders.filter(p => p.type === type && p.resolveDebugConfiguration) .concat(this.configProviders.filter(p => p.type === '*' && p.resolveDebugConfiguration)); + let result: IConfig | null | undefined = config; await sequence(providers.map(provider => async () => { - config = (await provider.resolveDebugConfiguration!(folderUri, config, token)) || config; + // If any provider returned undefined or null make sure to respect that and do not pass the result to more resolver + if (result) { + result = await provider.resolveDebugConfiguration!(folderUri, result, token); + } })); - return config; + return result; } async provideDebugConfigurations(folderUri: uri | undefined, type: string, token: CancellationToken): Promise { @@ -262,6 +272,7 @@ export class ConfigurationManager implements IConfigurationManager { }); this.setCompoundSchemaValues(); + this._onDidRegisterDebugger.fire(); }); breakpointsExtPoint.setHandler((extensions, delta) => { @@ -275,7 +286,8 @@ export class ConfigurationManager implements IConfigurationManager { this.toDispose.push(this.contextService.onDidChangeWorkspaceFolders(() => { this.initLaunches(); - this.selectConfiguration(this.selectedLaunch); + const toSelect = this.selectedLaunch || (this.launches.length > 0 ? this.launches[0] : undefined); + this.selectConfiguration(toSelect); this.setCompoundSchemaValues(); })); this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => { @@ -384,6 +396,17 @@ export class ConfigurationManager implements IConfigurationManager { return this.debuggers.filter(dbg => strings.equalsIgnoreCase(dbg.type, type)).pop(); } + getDebuggerLabelsForEditor(editor: editorCommon.IEditor | undefined): string[] { + if (isCodeEditor(editor)) { + const model = editor.getModel(); + const language = model ? model.getLanguageIdentifier().language : undefined; + + return this.debuggers.filter(a => language && a.languages && a.languages.indexOf(language) >= 0).map(d => d.label); + } + + return []; + } + async guessDebugger(type?: string): Promise { if (type) { const adapter = this.getDebugger(type); @@ -411,16 +434,16 @@ export class ConfigurationManager implements IConfigurationManager { candidates.sort((first, second) => first.label.localeCompare(second.label)); const picks = candidates.map(c => ({ label: c.label, debugger: c })); - const picked = await this.quickInputService.pick<{ label: string, debugger: Debugger | undefined }>([...picks, { type: 'separator' }, { label: 'More...', debugger: undefined }], { placeHolder: nls.localize('selectDebug', "Select Environment") }); - - if (picked && picked.debugger) { - return picked.debugger; - } - if (picked) { - this.commandService.executeCommand('debug.installAdditionalDebuggers'); - } - - return undefined; + return this.quickInputService.pick<{ label: string, debugger: Debugger | undefined }>([...picks, { type: 'separator' }, { label: nls.localize('more', "More..."), debugger: undefined }], { placeHolder: nls.localize('selectDebug', "Select Environment") }) + .then(picked => { + if (picked && picked.debugger) { + return picked.debugger; + } + if (picked) { + this.commandService.executeCommand('debug.installAdditionalDebuggers'); + } + return undefined; + }); } async activateDebuggers(activationEvent: string, debugType?: string): Promise { diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index b25e36fa23..af56a6c7f2 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -111,7 +111,7 @@ export class RunToCursorAction extends EditorAction { label: RunToCursorAction.LABEL, alias: 'Debug: Run to Cursor', precondition: ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, PanelFocusContext.toNegated(), CONTEXT_DEBUG_STATE.isEqualTo('stopped'), EditorContextKeys.editorTextFocus), - menuOpts: { + contextMenuOpts: { group: 'debug', order: 2 } @@ -160,7 +160,7 @@ class SelectionToReplAction extends EditorAction { label: nls.localize('debugEvaluate', "Debug: Evaluate"), alias: 'Debug: Evaluate', precondition: ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, CONTEXT_IN_DEBUG_MODE, EditorContextKeys.editorTextFocus), - menuOpts: { + contextMenuOpts: { group: 'debug', order: 0 } @@ -190,7 +190,7 @@ class SelectionToWatchExpressionsAction extends EditorAction { label: nls.localize('debugAddToWatch', "Debug: Add to Watch"), alias: 'Debug: Add to Watch', precondition: ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, CONTEXT_IN_DEBUG_MODE, EditorContextKeys.editorTextFocus), - menuOpts: { + contextMenuOpts: { group: 'debug', order: 1 } diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index 00fe86c75f..d2c27cd57d 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -12,7 +12,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { StandardTokenType } from 'vs/editor/common/modes'; import { DEFAULT_WORD_REGEXP } from 'vs/editor/common/model/wordHelper'; -import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, IEditorMouseEvent, MouseTargetType, IPartialEditorMouseEvent } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { IDecorationOptions } from 'vs/editor/common/editorCommon'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; @@ -80,7 +80,7 @@ class DebugEditorContribution implements IDebugEditorContribution { this.toDispose.push(this.editor.onMouseDown((e: IEditorMouseEvent) => this.onEditorMouseDown(e))); this.toDispose.push(this.editor.onMouseUp(() => this.mouseDown = false)); this.toDispose.push(this.editor.onMouseMove((e: IEditorMouseEvent) => this.onEditorMouseMove(e))); - this.toDispose.push(this.editor.onMouseLeave((e: IEditorMouseEvent) => { + this.toDispose.push(this.editor.onMouseLeave((e: IPartialEditorMouseEvent) => { this.provideNonDebugHoverScheduler.cancel(); const hoverDomNode = this.hoverWidget.getDomNode(); if (!hoverDomNode) { diff --git a/src/vs/workbench/contrib/debug/browser/debugHover.ts b/src/vs/workbench/contrib/debug/browser/debugHover.ts index 8fe8000748..44bb809d01 100644 --- a/src/vs/workbench/contrib/debug/browser/debugHover.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHover.ts @@ -20,7 +20,7 @@ import { renderExpressionValue, replaceWhitespace } from 'vs/workbench/contrib/d import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { attachStylerCallback } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { editorHoverBackground, editorHoverBorder } from 'vs/platform/theme/common/colorRegistry'; +import { editorHoverBackground, editorHoverBorder, editorHoverForeground } from 'vs/platform/theme/common/colorRegistry'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { getExactExpressionStartAndEnd } from 'vs/workbench/contrib/debug/common/debugUtils'; import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; @@ -73,12 +73,15 @@ export class DebugHoverWidget implements IContentWidget { this.treeContainer.setAttribute('role', 'tree'); const dataSource = new DebugHoverDataSource(); - this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'DebugHover', this.treeContainer, new DebugHoverDelegate(), [this.instantiationService.createInstance(VariablesRenderer)], + this.tree = this.instantiationService.createInstance>(WorkbenchAsyncDataTree, 'DebugHover', this.treeContainer, new DebugHoverDelegate(), [this.instantiationService.createInstance(VariablesRenderer)], dataSource, { ariaLabel: nls.localize('treeAriaLabel', "Debug Hover"), accessibilityProvider: new DebugHoverAccessibilityProvider(), mouseSupport: false, - horizontalScrolling: true + horizontalScrolling: true, + overrideStyles: { + listBackground: editorHoverBackground + } }); this.valueContainer = $('.value'); @@ -90,7 +93,7 @@ export class DebugHoverWidget implements IContentWidget { this.editor.applyFontInfo(this.domNode); - this.toDispose.push(attachStylerCallback(this.themeService, { editorHoverBackground, editorHoverBorder }, colors => { + this.toDispose.push(attachStylerCallback(this.themeService, { editorHoverBackground, editorHoverBorder, editorHoverForeground }, colors => { if (colors.editorHoverBackground) { this.domNode.style.backgroundColor = colors.editorHoverBackground.toString(); } else { @@ -101,6 +104,11 @@ export class DebugHoverWidget implements IContentWidget { } else { this.domNode.style.border = ''; } + if (colors.editorHoverForeground) { + this.domNode.style.color = colors.editorHoverForeground.toString(); + } else { + this.domNode.style.color = null; + } })); this.toDispose.push(this.tree.onDidChangeContentHeight(() => this.layoutTreeAndContainer())); diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index b193f88f52..e94a2550ff 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -40,13 +40,14 @@ import { IAction } from 'vs/base/common/actions'; import { deepClone, equals } from 'vs/base/common/objects'; import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { IDebugService, State, IDebugSession, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_MODE, IThread, IDebugConfiguration, VIEWLET_ID, REPL_ID, IConfig, ILaunch, IViewModel, IConfigurationManager, IDebugModel, IEnablement, IBreakpoint, IBreakpointData, ICompound, IGlobalConfig, IStackFrame, AdapterEndEvent, getStateLabel, IDebugSessionOptions } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, State, IDebugSession, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_MODE, IThread, IDebugConfiguration, VIEWLET_ID, REPL_ID, IConfig, ILaunch, IViewModel, IConfigurationManager, IDebugModel, IEnablement, IBreakpoint, IBreakpointData, ICompound, IGlobalConfig, IStackFrame, AdapterEndEvent, getStateLabel, IDebugSessionOptions, CONTEXT_DEBUG_UX } from 'vs/workbench/contrib/debug/common/debug'; import { isExtensionHostDebugging } from 'vs/workbench/contrib/debug/common/debugUtils'; import { isErrorWithActions, createErrorWithActions } from 'vs/base/common/errorsWithActions'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { withUndefinedAsNull } from 'vs/base/common/types'; const DEBUG_BREAKPOINTS_KEY = 'debug.breakpoint'; const DEBUG_FUNCTION_BREAKPOINTS_KEY = 'debug.functionbreakpoint'; @@ -85,6 +86,7 @@ export class DebugService implements IDebugService { private debugType: IContextKey; private debugState: IContextKey; private inDebugMode: IContextKey; + private debugUx: IContextKey; private breakpointsToSendOnResourceSaved: Set; private initializing = false; private previousState: State | undefined; @@ -126,6 +128,8 @@ export class DebugService implements IDebugService { this.debugType = CONTEXT_DEBUG_TYPE.bindTo(contextKeyService); this.debugState = CONTEXT_DEBUG_STATE.bindTo(contextKeyService); this.inDebugMode = CONTEXT_IN_DEBUG_MODE.bindTo(contextKeyService); + this.debugUx = CONTEXT_DEBUG_UX.bindTo(contextKeyService); + this.debugUx.set(!!this.configurationManager.selectedConfiguration.name ? 'default' : 'simple'); this.model = new DebugModel(this.loadBreakpoints(), this.loadFunctionBreakpoints(), this.loadExceptionBreakpoints(), this.loadDataBreakpoints(), this.loadWatchExpressions(), this.textFileService); @@ -169,6 +173,9 @@ export class DebugService implements IDebugService { this.toDispose.push(this.viewModel.onDidFocusSession(() => { this.onStateChange(); })); + this.toDispose.push(this.configurationManager.onDidSelectConfiguration(() => { + this.debugUx.set(!!(this.state !== State.Inactive || this.configurationManager.selectedConfiguration.name) ? 'default' : 'simple'); + })); } getModel(): IDebugModel { @@ -225,6 +232,7 @@ export class DebugService implements IDebugService { if (this.previousState !== state) { this.debugState.set(getStateLabel(state)); this.inDebugMode.set(state !== State.Inactive); + this.debugUx.set(!!(state !== State.Inactive || this.configurationManager.selectedConfiguration.name) ? 'default' : 'simple'); this.previousState = state; this._onDidChangeState.fire(state); } @@ -258,7 +266,7 @@ export class DebugService implements IDebugService { try { // make sure to save all files and that the configuration is up to date await this.extensionService.activateByEvent('onDebug'); - await this.textFileService.saveAll(); + await this.editorService.saveAll(); await this.configurationService.reloadConfiguration(launch ? launch.workspace : undefined); await this.extensionService.whenInstalledExtensionsRegistered(); @@ -488,8 +496,6 @@ export class DebugService implements IDebugService { } const errorMessage = error instanceof Error ? error.message : error; - this.telemetryDebugMisconfiguration(session.configuration ? session.configuration.type : undefined, errorMessage); - await this.showError(errorMessage, isErrorWithActions(error) ? error.actions : []); return false; } @@ -570,7 +576,7 @@ export class DebugService implements IDebugService { } async restartSession(session: IDebugSession, restartData?: any): Promise { - await this.textFileService.saveAll(); + await this.editorService.saveAll(); const isAutoRestart = !!restartData; const runTasks: () => Promise = async () => { @@ -583,19 +589,19 @@ export class DebugService implements IDebugService { return this.runTaskAndCheckErrors(session.root, session.configuration.preLaunchTask); }; - if (session.capabilities.supportsRestartRequest) { + if (isExtensionHostDebugging(session.configuration)) { const taskResult = await runTasks(); if (taskResult === TaskRunResult.Success) { - await session.restart(); + this.extensionHostDebugService.reload(session.getId()); } return; } - if (isExtensionHostDebugging(session.configuration)) { + if (session.capabilities.supportsRestartRequest) { const taskResult = await runTasks(); if (taskResult === TaskRunResult.Success) { - this.extensionHostDebugService.reload(session.getId()); + await session.restart(); } return; @@ -793,6 +799,7 @@ export class DebugService implements IDebugService { // Check that the task isn't busy and if it is, wait for it const busyTasks = await this.taskService.getBusyTasks(); if (busyTasks.filter(t => t._id === task._id).length) { + taskStarted = true; return inactivePromise; } // task is already running and isn't busy - nothing to do. @@ -808,7 +815,7 @@ export class DebugService implements IDebugService { return inactivePromise; } - return taskPromise; + return taskPromise.then(withUndefinedAsNull); }); return new Promise((c, e) => { @@ -1200,19 +1207,6 @@ export class DebugService implements IDebugService { }); } - private telemetryDebugMisconfiguration(debugType: string | undefined, message: string): Promise { - /* __GDPR__ - "debugMisconfiguration" : { - "type" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "error": { "classification": "CallstackOrException", "purpose": "FeatureInsight" } - } - */ - return this.telemetryService.publicLog('debugMisconfiguration', { - type: debugType, - error: message - }); - } - private telemetryDebugAddBreakpoint(breakpoint: IBreakpoint, context: string): Promise { /* __GDPR__ "debugAddBreakpoint" : { diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 9a9e68b396..321b3e5420 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -720,8 +720,8 @@ export class DebugSession implements IDebugSession { await this.debugService.sendAllBreakpoints(this); } finally { await sendConfigurationDone(); + await this.fetchThreads(); } - await this.fetchThreads(); })); this.rawListeners.push(this.raw.onDidStop(async event => { diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index 787cd1fff3..db8d0ec28a 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -19,7 +19,7 @@ import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/co import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Themable } from 'vs/workbench/common/theme'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, IThemeService } from 'vs/platform/theme/common/themeService'; import { registerColor, contrastBorder, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { localize } from 'vs/nls'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -41,12 +41,73 @@ export const debugToolBarBackground = registerColor('debugToolBar.background', { light: '#F3F3F3', hc: '#000000' }, localize('debugToolBarBackground', "Debug toolbar background color.")); + export const debugToolBarBorder = registerColor('debugToolBar.border', { dark: null, light: null, hc: null }, localize('debugToolBarBorder', "Debug toolbar border color.")); +export const debugIconStartForeground = registerColor('debugIcon.startForeground', { + dark: '#89D185', + light: '#388A34', + hc: '#89D185' +}, localize('debugIcon.startForeground', "Debug toolbar icon for start debugging.")); + +export const debugIconPauseForeground = registerColor('debugIcon.pauseForeground', { + dark: '#75BEFF', + light: '#007ACC', + hc: '#75BEFF' +}, localize('debugIcon.pauseForeground', "Debug toolbar icon for pause.")); + +export const debugIconStopForeground = registerColor('debugIcon.stopForeground', { + dark: '#F48771', + light: '#A1260D', + hc: '#F48771' +}, localize('debugIcon.stopForeground', "Debug toolbar icon for stop.")); + +export const debugIconDisconnectForeground = registerColor('debugIcon.disconnectForeground', { + dark: '#F48771', + light: '#A1260D', + hc: '#F48771' +}, localize('debugIcon.disconnectForeground', "Debug toolbar icon for disconnect.")); + +export const debugIconRestartForeground = registerColor('debugIcon.restartForeground', { + dark: '#89D185', + light: '#388A34', + hc: '#89D185' +}, localize('debugIcon.restartForeground', "Debug toolbar icon for restart.")); + +export const debugIconStepOverForeground = registerColor('debugIcon.stepOverForeground', { + dark: '#75BEFF', + light: '#007ACC', + hc: '#75BEFF' +}, localize('debugIcon.stepOverForeground', "Debug toolbar icon for step over.")); + +export const debugIconStepIntoForeground = registerColor('debugIcon.stepIntoForeground', { + dark: '#75BEFF', + light: '#007ACC', + hc: '#75BEFF' +}, localize('debugIcon.stepIntoForeground', "Debug toolbar icon for step into.")); + +export const debugIconStepOutForeground = registerColor('debugIcon.stepOutForeground', { + dark: '#75BEFF', + light: '#007ACC', + hc: '#75BEFF' +}, localize('debugIcon.stepOutForeground', "Debug toolbar icon for step over.")); + +export const debugIconContinueForeground = registerColor('debugIcon.continueForeground', { + dark: '#75BEFF', + light: '#007ACC', + hc: '#75BEFF' +}, localize('debugIcon.continueForeground', "Debug toolbar icon for continue.")); + +export const debugIconStepBackForeground = registerColor('debugIcon.stepBackForeground', { + dark: '#75BEFF', + light: '#007ACC', + hc: '#75BEFF' +}, localize('debugIcon.stepBackForeground', "Debug toolbar icon for step back.")); + export class DebugToolBar extends Themable implements IWorkbenchContribution { private $el: HTMLElement; @@ -56,6 +117,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { private updateScheduler: RunOnceScheduler; private debugToolBarMenu: IMenu; private disposeOnUpdate: IDisposable | undefined; + private yCoordinate = 0; private isVisible = false; private isBuilt = false; @@ -79,7 +141,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { this.$el = dom.$('div.debug-toolbar'); this.$el.style.top = `${layoutService.getTitleBarOffset()}px`; - this.dragArea = dom.append(this.$el, dom.$('div.drag-area')); + this.dragArea = dom.append(this.$el, dom.$('div.drag-area.codicon.codicon-gripper')); const actionBarContainer = dom.append(this.$el, dom.$('div.action-bar-container')); this.debugToolBarMenu = menuService.createMenu(MenuId.DebugToolBar, contextKeyService); @@ -146,7 +208,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { })); this._register(dom.addDisposableListener(window, dom.EventType.RESIZE, () => this.setCoordinates())); - this._register(dom.addDisposableListener(this.dragArea, dom.EventType.MOUSE_UP, (event: MouseEvent) => { + this._register(dom.addDisposableGenericMouseUpListner(this.dragArea, (event: MouseEvent) => { const mouseClickEvent = new StandardMouseEvent(event); if (mouseClickEvent.detail === 2) { // double click on debug bar centers it again #8250 @@ -156,10 +218,10 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { } })); - this._register(dom.addDisposableListener(this.dragArea, dom.EventType.MOUSE_DOWN, (event: MouseEvent) => { + this._register(dom.addDisposableGenericMouseDownListner(this.dragArea, (event: MouseEvent) => { dom.addClass(this.dragArea, 'dragged'); - const mouseMoveListener = dom.addDisposableListener(window, 'mousemove', (e: MouseEvent) => { + const mouseMoveListener = dom.addDisposableGenericMouseMoveListner(window, (e: MouseEvent) => { const mouseMoveEvent = new StandardMouseEvent(e); // Prevent default to stop editor selecting text #8524 mouseMoveEvent.preventDefault(); @@ -167,7 +229,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { this.setCoordinates(mouseMoveEvent.posx - 14, mouseMoveEvent.posy - this.layoutService.getTitleBarOffset()); }); - const mouseUpListener = dom.addDisposableListener(window, 'mouseup', (e: MouseEvent) => { + const mouseUpListener = dom.addDisposableGenericMouseUpListner(window, (e: MouseEvent) => { this.storePosition(); dom.removeClass(this.dragArea, 'dragged'); @@ -209,9 +271,10 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { } } - private setYCoordinate(y = 0): void { + private setYCoordinate(y = this.yCoordinate): void { const titlebarOffset = this.layoutService.getTitleBarOffset(); this.$el.style.top = `${titlebarOffset + y}px`; + this.yCoordinate = y; } private setCoordinates(x?: number, y?: number): void { @@ -289,3 +352,56 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { } } } + +registerThemingParticipant((theme, collector) => { + + const debugIconStartColor = theme.getColor(debugIconStartForeground); + if (debugIconStartColor) { + collector.addRule(`.monaco-workbench .codicon-debug-start { color: ${debugIconStartColor} !important; }`); + } + + const debugIconPauseColor = theme.getColor(debugIconPauseForeground); + if (debugIconPauseColor) { + collector.addRule(`.monaco-workbench .codicon-debug-pause { color: ${debugIconPauseColor} !important; }`); + } + + const debugIconStopColor = theme.getColor(debugIconStopForeground); + if (debugIconStopColor) { + collector.addRule(`.monaco-workbench .codicon-debug-stop { color: ${debugIconStopColor} !important; }`); + } + + const debugIconDisconnectColor = theme.getColor(debugIconDisconnectForeground); + if (debugIconDisconnectColor) { + collector.addRule(`.monaco-workbench .codicon-debug-disconnect { color: ${debugIconDisconnectColor} !important; }`); + } + + const debugIconRestartColor = theme.getColor(debugIconRestartForeground); + if (debugIconRestartColor) { + collector.addRule(`.monaco-workbench .codicon-debug-restart, .monaco-workbench .codicon-debug-restart-frame { color: ${debugIconRestartColor} !important; }`); + } + + const debugIconStepOverColor = theme.getColor(debugIconStepOverForeground); + if (debugIconStepOverColor) { + collector.addRule(`.monaco-workbench .codicon-debug-step-over { color: ${debugIconStepOverColor} !important; }`); + } + + const debugIconStepIntoColor = theme.getColor(debugIconStepIntoForeground); + if (debugIconStepIntoColor) { + collector.addRule(`.monaco-workbench .codicon-debug-step-into { color: ${debugIconStepIntoColor} !important; }`); + } + + const debugIconStepOutColor = theme.getColor(debugIconStepOutForeground); + if (debugIconStepOutColor) { + collector.addRule(`.monaco-workbench .codicon-debug-step-out { color: ${debugIconStepOutColor} !important; }`); + } + + const debugIconContinueColor = theme.getColor(debugIconContinueForeground); + if (debugIconContinueColor) { + collector.addRule(`.monaco-workbench .codicon-debug-continue,.monaco-workbench .codicon-debug-reverse-continue { color: ${debugIconContinueColor} !important; }`); + } + + const debugIconStepBackColor = theme.getColor(debugIconStepBackForeground); + if (debugIconStepBackColor) { + collector.addRule(`.monaco-workbench .codicon-debug-step-back { color: ${debugIconStepBackColor} !important; }`); + } +}); diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index f9835f38df..9d89a18432 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -9,7 +9,7 @@ import { IAction } from 'vs/base/common/actions'; import * as DOM from 'vs/base/browser/dom'; import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { ViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet'; -import { IDebugService, VIEWLET_ID, State, BREAKPOINTS_VIEW_ID, IDebugConfiguration, REPL_ID } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, VIEWLET_ID, State, BREAKPOINTS_VIEW_ID, IDebugConfiguration, REPL_ID, CONTEXT_DEBUG_UX, CONTEXT_DEBUG_UX_KEY } from 'vs/workbench/contrib/debug/common/debug'; import { StartAction, ConfigureAction, SelectAndStartAction, FocusSessionAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { StartDebugActionViewItem, FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -26,20 +26,21 @@ import { memoize } from 'vs/base/common/decorators'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { DebugToolBar } from 'vs/workbench/contrib/debug/browser/debugToolBar'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { ViewletPane } from 'vs/workbench/browser/parts/views/paneViewlet'; import { IMenu, MenuId, IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { TogglePanelAction } from 'vs/workbench/browser/panel'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { StartView } from 'vs/workbench/contrib/debug/browser/startView'; export class DebugViewlet extends ViewContainerViewlet { private startDebugActionViewItem: StartDebugActionViewItem | undefined; private progressResolve: (() => void) | undefined; - private breakpointView: ViewletPanel | undefined; - private panelListeners = new Map(); + private breakpointView: ViewletPane | undefined; + private paneListeners = new Map(); private debugToolBarMenu: IMenu | undefined; private disposeOnTitleUpdate: IDisposable | undefined; @@ -61,10 +62,16 @@ export class DebugViewlet extends ViewContainerViewlet { @IContextKeyService private readonly contextKeyService: IContextKeyService, @INotificationService private readonly notificationService: INotificationService ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, false, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); + super(VIEWLET_ID, `${VIEWLET_ID}.state`, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); this._register(this.debugService.onDidChangeState(state => this.onDebugServiceStateChange(state))); this._register(this.debugService.onDidNewSession(() => this.updateToolBar())); + this._register(this.contextKeyService.onDidChangeContext(e => { + if (e.affectsSome(new Set(CONTEXT_DEBUG_UX_KEY))) { + this.updateTitleArea(); + } + })); + this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateTitleArea())); this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('debug.toolBarLocation')) { @@ -83,6 +90,8 @@ export class DebugViewlet extends ViewContainerViewlet { if (this.startDebugActionViewItem) { this.startDebugActionViewItem.focus(); + } else { + this.focusView(StartView.ID); } } @@ -107,6 +116,9 @@ export class DebugViewlet extends ViewContainerViewlet { } getActions(): IAction[] { + if (CONTEXT_DEBUG_UX.getValue(this.contextKeyService) === 'simple') { + return []; + } if (this.showInitialDebugActions) { return [this.startAction, this.configureAction, this.toggleReplAction]; } @@ -181,32 +193,32 @@ export class DebugViewlet extends ViewContainerViewlet { } } - addPanels(panels: { panel: ViewletPanel, size: number, index?: number }[]): void { - super.addPanels(panels); + addPanes(panes: { pane: ViewletPane, size: number, index?: number }[]): void { + super.addPanes(panes); - for (const { panel } of panels) { + for (const { pane: pane } of panes) { // attach event listener to - if (panel.id === BREAKPOINTS_VIEW_ID) { - this.breakpointView = panel; + if (pane.id === BREAKPOINTS_VIEW_ID) { + this.breakpointView = pane; this.updateBreakpointsMaxSize(); } else { - this.panelListeners.set(panel.id, panel.onDidChange(() => this.updateBreakpointsMaxSize())); + this.paneListeners.set(pane.id, pane.onDidChange(() => this.updateBreakpointsMaxSize())); } } } - removePanels(panels: ViewletPanel[]): void { - super.removePanels(panels); - for (const panel of panels) { - dispose(this.panelListeners.get(panel.id)); - this.panelListeners.delete(panel.id); + removePanes(panes: ViewletPane[]): void { + super.removePanes(panes); + for (const pane of panes) { + dispose(this.paneListeners.get(pane.id)); + this.paneListeners.delete(pane.id); } } private updateBreakpointsMaxSize(): void { if (this.breakpointView) { // We need to update the breakpoints view since all other views are collapsed #25384 - const allOtherCollapsed = this.panels.every(view => !view.isExpanded() || view === this.breakpointView); + const allOtherCollapsed = this.panes.every(view => !view.isExpanded() || view === this.breakpointView); this.breakpointView.maximumBodySize = allOtherCollapsed ? Number.POSITIVE_INFINITY : this.breakpointView.minimumBodySize; } } @@ -220,6 +232,6 @@ class ToggleReplAction extends TogglePanelAction { @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IPanelService panelService: IPanelService ) { - super(id, label, REPL_ID, panelService, layoutService, 'debug-action toggle-repl'); + super(id, label, REPL_ID, panelService, layoutService, 'debug-action codicon-terminal'); } } diff --git a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts index fd539246de..19d3434cb8 100644 --- a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts +++ b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts @@ -16,6 +16,9 @@ import { URI } from 'vs/base/common/uri'; import { mapToSerializable } from 'vs/base/common/map'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkspaceProvider, IWorkspace } from 'vs/workbench/services/host/browser/browserHostService'; +import { IProcessEnvironment } from 'vs/base/common/platform'; +import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; +import { ILogService } from 'vs/platform/log/common/log'; class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient implements IExtensionHostDebugService { @@ -23,7 +26,8 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i constructor( @IRemoteAgentService remoteAgentService: IRemoteAgentService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @ILogService logService: ILogService ) { const connection = remoteAgentService.getConnection(); let channel: IChannel; @@ -32,7 +36,7 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i } else { channel = { call: async () => undefined, listen: () => Event.None } as any; // TODO@weinand TODO@isidorn fallback? - console.warn('Extension Host Debugging not available due to missing connection.'); + logService.warn('Extension Host Debugging not available due to missing connection.'); } super(channel); @@ -41,14 +45,9 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i this.workspaceProvider = environmentService.options.workspaceProvider; } else { this.workspaceProvider = { open: async () => undefined, workspace: undefined }; - console.warn('Extension Host Debugging not available due to missing workspace provider.'); + logService.warn('Extension Host Debugging not available due to missing workspace provider.'); } - this.registerListeners(environmentService); - } - - private registerListeners(environmentService: IWorkbenchEnvironmentService): void { - // Reload window on reload request this._register(this.onReload(event => { if (environmentService.isExtensionDevelopment && environmentService.debugExtensionHost.debugId === event.sessionId) { @@ -64,7 +63,8 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i })); } - async openExtensionDevelopmentHostWindow(args: string[]): Promise { + openExtensionDevelopmentHostWindow(args: string[], env: IProcessEnvironment): Promise { + if (!this.workspaceProvider.payload) { // TODO@Ben remove me once environment is adopted return this.openExtensionDevelopmentHostWindowLegacy(args); @@ -75,16 +75,31 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i const folderUriArg = this.findArgument('folder-uri', args); if (folderUriArg) { debugWorkspace = { folderUri: URI.parse(folderUriArg) }; + } else { + const fileUriArg = this.findArgument('file-uri', args); + if (fileUriArg && hasWorkspaceFileExtension(fileUriArg)) { + debugWorkspace = { workspaceUri: URI.parse(fileUriArg) }; + } } // Add environment parameters required for debug to work const environment = new Map(); + const fileUriArg = this.findArgument('file-uri', args); + if (fileUriArg && !hasWorkspaceFileExtension(fileUriArg)) { + environment.set('openFile', fileUriArg); + } + const extensionDevelopmentPath = this.findArgument('extensionDevelopmentPath', args); if (extensionDevelopmentPath) { environment.set('extensionDevelopmentPath', extensionDevelopmentPath); } + const extensionTestsPath = this.findArgument('extensionTestsPath', args); + if (extensionTestsPath) { + environment.set('extensionTestsPath', extensionTestsPath); + } + const debugId = this.findArgument('debugId', args); if (debugId) { environment.set('debugId', debugId); @@ -96,13 +111,13 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i } // Open debug window as new window. Pass ParsedArgs over. - this.workspaceProvider.open(debugWorkspace, { + return this.workspaceProvider.open(debugWorkspace, { reuse: false, // debugging always requires a new window payload: mapToSerializable(environment) // mandatory properties to enable debugging }); } - private async openExtensionDevelopmentHostWindowLegacy(args: string[]): Promise { + private openExtensionDevelopmentHostWindowLegacy(args: string[]): Promise { // we pass the "args" as query parameters of the URL let newAddress = `${document.location.origin}${document.location.pathname}?`; @@ -142,6 +157,11 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i addQueryParameter('extensionDevelopmentPath', ep); } + const etp = findArgument('extensionTestsPath'); + if (etp) { + addQueryParameter('extensionTestsPath', etp); + } + const di = findArgument('debugId'); if (di) { addQueryParameter('debugId', di); diff --git a/src/vs/workbench/contrib/debug/browser/linkDetector.ts b/src/vs/workbench/contrib/debug/browser/linkDetector.ts index 7690c87a85..3062b04218 100644 --- a/src/vs/workbench/contrib/debug/browser/linkDetector.ts +++ b/src/vs/workbench/contrib/debug/browser/linkDetector.ts @@ -12,6 +12,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; const CONTROL_CODES = '\\u0000-\\u0020\\u007f-\\u009f'; const WEB_LINK_REGEX = new RegExp('(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\\/\\/|data:|www\\.)[^\\s' + CONTROL_CODES + '"]{2,}[^\\s' + CONTROL_CODES + '"\')}\\],:;.!?]', 'ug'); @@ -37,7 +38,8 @@ export class LinkDetector { @IEditorService private readonly editorService: IEditorService, @IFileService private readonly fileService: IFileService, @IOpenerService private readonly openerService: IOpenerService, - @IEnvironmentService private readonly environmentService: IEnvironmentService + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService ) { // noop } @@ -96,7 +98,7 @@ export class LinkDetector { private createWebLink(url: string): Node { const link = this.createLink(url); const uri = URI.parse(url); - this.decorateLink(link, () => this.openerService.open(uri)); + this.decorateLink(link, () => this.openerService.open(uri, { allowTunneling: !!this.workbenchEnvironmentService.configuration.remoteAuthority })); return link; } @@ -142,7 +144,7 @@ export class LinkDetector { private decorateLink(link: HTMLElement, onclick: () => void) { link.classList.add('link'); link.title = platform.isMacintosh ? nls.localize('fileLinkMac', "Cmd + click to follow link") : nls.localize('fileLink', "Ctrl + click to follow link"); - link.onmousemove = (event) => link.classList.toggle('pointer', platform.isMacintosh ? event.metaKey : event.ctrlKey); + link.onmousemove = (event) => { link.classList.toggle('pointer', platform.isMacintosh ? event.metaKey : event.ctrlKey); }; link.onmouseleave = () => link.classList.remove('pointer'); link.onclick = (event) => { const selection = window.getSelection(); diff --git a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index 21941fdd9c..d7a87ebdef 100644 --- a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { normalize, isAbsolute, posix } from 'vs/base/common/path'; -import { IViewletPanelOptions, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { IViewletPaneOptions, ViewletPane } from 'vs/workbench/browser/parts/views/paneViewlet'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -34,6 +34,7 @@ import { dispose } from 'vs/base/common/lifecycle'; import { createMatches, FuzzyScore } from 'vs/base/common/filters'; import { DebugContentProvider } from 'vs/workbench/contrib/debug/common/debugContentProvider'; import { ILabelService } from 'vs/platform/label/common/label'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; const SMART = true; @@ -111,7 +112,11 @@ class BaseTreeItem { // a dynamic ID based on the parent chain; required for reparenting (see #55448) getId(): string { const parent = this.getParent(); - return parent ? `${parent.getId()}/${this._label}` : this._label; + return parent ? `${parent.getId()}/${this.getInternalId()}` : this.getInternalId(); + } + + getInternalId(): string { + return this._label; } // skips intermediate single-child nodes @@ -254,6 +259,10 @@ class SessionTreeItem extends BaseTreeItem { this._session = session; } + getInternalId(): string { + return this._session.getId(); + } + getSession(): IDebugSession { return this._session; } @@ -379,7 +388,7 @@ class SessionTreeItem extends BaseTreeItem { } } -export class LoadedScriptsView extends ViewletPanel { +export class LoadedScriptsView extends ViewletPane { private treeContainer!: HTMLElement; private loadedScriptsItemType: IContextKey; @@ -402,7 +411,7 @@ export class LoadedScriptsView extends ViewletPanel { @IDebugService private readonly debugService: IDebugService, @ILabelService private readonly labelService: ILabelService ) { - super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('loadedScriptsSection', "Loaded Scripts Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewletPaneOptions), ariaHeaderLabel: nls.localize('loadedScriptsSection', "Loaded Scripts Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); this.loadedScriptsItemType = CONTEXT_LOADED_SCRIPTS_ITEM_TYPE.bindTo(contextKeyService); } @@ -419,7 +428,7 @@ export class LoadedScriptsView extends ViewletPanel { this.treeLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); this._register(this.treeLabels); - this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'LoadedScriptsView', this.treeContainer, new LoadedScriptsDelegate(), + this.tree = this.instantiationService.createInstance>(WorkbenchAsyncDataTree, 'LoadedScriptsView', this.treeContainer, new LoadedScriptsDelegate(), [new LoadedScriptsRenderer(this.treeLabels)], new LoadedScriptsDataSource(), { @@ -432,6 +441,9 @@ export class LoadedScriptsView extends ViewletPanel { filter: this.filter, accessibilityProvider: new LoadedSciptsAccessibilityProvider(), ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'loadedScriptsAriaLabel' }, "Debug Loaded Scripts"), + overrideStyles: { + listBackground: SIDE_BAR_BACKGROUND + } } ); diff --git a/src/vs/workbench/contrib/debug/browser/media/add-dark.svg b/src/vs/workbench/contrib/debug/browser/media/add-dark.svg deleted file mode 100644 index 4d9389336b..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/add-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/add-hc.svg b/src/vs/workbench/contrib/debug/browser/media/add-hc.svg deleted file mode 100644 index fb50c6c284..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/add-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/add-light.svg b/src/vs/workbench/contrib/debug/browser/media/add-light.svg deleted file mode 100644 index 01a9de7d5a..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/add-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-conditional.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-conditional.svg deleted file mode 100644 index 382507ebcd..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-conditional.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-data-disabled.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-data-disabled.svg deleted file mode 100644 index a2c8c3417e..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-data-disabled.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-data-unverified.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-data-unverified.svg deleted file mode 100644 index 96dda92ee3..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-data-unverified.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-data.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-data.svg deleted file mode 100644 index 6752b060ae..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-data.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-disabled.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-disabled.svg deleted file mode 100644 index 84588f8eac..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-disabled.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-function-disabled.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-function-disabled.svg deleted file mode 100644 index cd71f6e462..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-function-disabled.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-function-unverified.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-function-unverified.svg deleted file mode 100644 index 9e2354d67b..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-function-unverified.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-function.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-function.svg deleted file mode 100644 index f25e57ffde..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-function.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-hint.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-hint.svg deleted file mode 100644 index d622c6cf0c..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-hint.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-log-disabled.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-log-disabled.svg deleted file mode 100644 index ea246058e0..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-log-disabled.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-log-unverified.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-log-unverified.svg deleted file mode 100644 index ae8ed0ba7b..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-log-unverified.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-log.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-log.svg deleted file mode 100644 index fc72afc7e2..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-log.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-unsupported.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-unsupported.svg deleted file mode 100644 index 624b9f60c8..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-unsupported.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-unverified.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-unverified.svg deleted file mode 100644 index 0f39b8b7c8..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-unverified.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint.svg deleted file mode 100644 index af02a87495..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css b/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css index b680b2806e..73d62b3489 100644 --- a/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css +++ b/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css @@ -16,7 +16,16 @@ flex-shrink: 0; } +.monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .breakpoint-select-container .monaco-select-box { + min-width: 100px; + min-height: 18px; + padding: 2px 20px 2px 8px; +} + +.monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .breakpoint-select-container:after { + right: 14px; +} + .monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .inputContainer { flex: 1; - margin-top: 8px; } diff --git a/src/vs/workbench/contrib/debug/browser/media/close-all-dark.svg b/src/vs/workbench/contrib/debug/browser/media/close-all-dark.svg deleted file mode 100644 index 35e5fb4422..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/close-all-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/close-all-hc.svg b/src/vs/workbench/contrib/debug/browser/media/close-all-hc.svg deleted file mode 100644 index def744d1ab..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/close-all-hc.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/close-all-light.svg b/src/vs/workbench/contrib/debug/browser/media/close-all-light.svg deleted file mode 100644 index 6c7cec7461..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/close-all-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/configure-dark.svg b/src/vs/workbench/contrib/debug/browser/media/configure-dark.svg deleted file mode 100644 index ace01a5ddf..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/configure-dark.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/configure-hc.svg b/src/vs/workbench/contrib/debug/browser/media/configure-hc.svg deleted file mode 100644 index bd59cb81f6..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/configure-hc.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/configure-light.svg b/src/vs/workbench/contrib/debug/browser/media/configure-light.svg deleted file mode 100644 index 4194780bba..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/configure-light.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/console-dark.svg b/src/vs/workbench/contrib/debug/browser/media/console-dark.svg deleted file mode 100644 index 1e2d3b4ee1..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/console-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/console-hc.svg b/src/vs/workbench/contrib/debug/browser/media/console-hc.svg deleted file mode 100644 index 44b59552d8..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/console-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/console-light.svg b/src/vs/workbench/contrib/debug/browser/media/console-light.svg deleted file mode 100644 index 429cb22b71..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/console-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/continue-dark.svg b/src/vs/workbench/contrib/debug/browser/media/continue-dark.svg deleted file mode 100644 index 7c58386ccc..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/continue-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/continue-light.svg b/src/vs/workbench/contrib/debug/browser/media/continue-light.svg deleted file mode 100644 index 7c58386ccc..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/continue-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/continue-white.svg b/src/vs/workbench/contrib/debug/browser/media/continue-white.svg deleted file mode 100644 index 69d48e9984..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/continue-white.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/current-and-breakpoint.svg b/src/vs/workbench/contrib/debug/browser/media/current-and-breakpoint.svg deleted file mode 100644 index 17d71fddac..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/current-and-breakpoint.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/current-arrow.svg b/src/vs/workbench/contrib/debug/browser/media/current-arrow.svg deleted file mode 100644 index 85f288efcd..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/current-arrow.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/debug-activity-bar.svg b/src/vs/workbench/contrib/debug/browser/media/debug-activity-bar.svg deleted file mode 100644 index fcb9413c8c..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/debug-activity-bar.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - 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 48e363422f..b009497a75 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css @@ -3,49 +3,43 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/* Activity Bar */ -.monaco-workbench .activitybar .monaco-action-bar .action-label.debug { - -webkit-mask: url('debug-activity-bar.svg') no-repeat 50% 50%; -} - -.monaco-editor .debug-top-stack-frame-column::before { - background: url('current-arrow.svg') center center no-repeat; -} - -.debug-breakpoint-hint { - background: url('breakpoint-hint.svg') center center no-repeat; +.codicon-debug-hint { cursor: pointer; } -.debug-breakpoint-disabled, -.monaco-editor .inline-breakpoint-widget.debug-breakpoint-disabled { - background: url('breakpoint-disabled.svg') center center no-repeat; +.codicon-debug-hint:not(*[class*='codicon-debug-breakpoint']) { + opacity: .4 !important; } -.monaco-editor .inline-breakpoint-widget.debug-breakpoint-disabled:hover { - background: url('breakpoint-hint.svg') center center no-repeat; +.inline-breakpoint-widget.codicon { + display: flex !important; + align-items: center; +} + +/* overlapped icons */ +.inline-breakpoint-widget.codicon-debug-breakpoint-stackframe-dot::after { + position: absolute; + top: 0; + left: 0; + bottom: 0; + margin: auto; + display: table; +} + +.codicon-debug-breakpoint.codicon-debug-breakpoint-stackframe-focused::after { + position: absolute; +} + +.inline-breakpoint-widget.codicon-debug-breakpoint-stackframe-dot::after { + content: "\eb8b"; +} + +.codicon-debug-breakpoint.codicon-debug-breakpoint-stackframe-focused::after { + content: "\eb8a"; } .monaco-editor .inline-breakpoint-widget.line-start { - left: -0.45em !important; -} - -.debug-breakpoint-unverified, -.monaco-editor .inline-breakpoint-widget.debug-breakpoint-unverified { - background: url('breakpoint-unverified.svg') center center no-repeat; -} - -.monaco-editor .debug-top-stack-frame { - background: url('current-arrow.svg') center center no-repeat; -} - -.monaco-editor .debug-focused-stack-frame { - background: url('stackframe-arrow.svg') center center no-repeat; -} - -.debug-breakpoint, -.monaco-editor .inline-breakpoint-widget { - background: url('breakpoint.svg') center center no-repeat; + left: -8px !important; } .monaco-editor .debug-breakpoint-placeholder::before, @@ -58,6 +52,14 @@ margin-left: 2px; } +/* Do not show call stack decoration when we plan to show breakpoint and top stack frame in one decoration */ +.monaco-editor .debug-breakpoint-placeholder ~ .debug-top-stack-frame-column::before { + width: 0em; + content: ""; + margin-right: 0px; + margin-left: 0px; +} + .monaco-editor .debug-top-stack-frame-column::before { height: 1.3em; } @@ -66,68 +68,6 @@ cursor: pointer; } -.debug-function-breakpoint { - background: url('breakpoint-function.svg') center center no-repeat; -} - -.debug-function-breakpoint-unverified { - background: url('breakpoint-function-unverified.svg') center center no-repeat; -} - -.debug-function-breakpoint-disabled { - background: url('breakpoint-function-disabled.svg') center center no-repeat; -} - -.debug-data-breakpoint { - background: url('breakpoint-data.svg') center center no-repeat; -} - -.debug-data-breakpoint-unverified { - background: url('breakpoint-data-unverified.svg') center center no-repeat; -} - -.debug-data-breakpoint-disabled { - background: url('breakpoint-data-disabled.svg') center center no-repeat; -} - -.debug-breakpoint-conditional, -.monaco-editor .inline-breakpoint-widget.debug-breakpoint-conditional { - background: url('breakpoint-conditional.svg') center center no-repeat; -} - -.debug-breakpoint-log, -.monaco-editor .inline-breakpoint-widget.debug-breakpoint-log { - background: url('breakpoint-log.svg') center center no-repeat; -} - -.debug-breakpoint-log-disabled, -.monaco-editor .inline-breakpoint-widget.debug-breakpoint-log-disabled { - background: url('breakpoint-log-disabled.svg') center center no-repeat; -} - -.debug-breakpoint-log-unverified, -.monaco-editor .inline-breakpoint-widget.debug-breakpoint-log-unverified { - background: url('breakpoint-log-unverified.svg') center center no-repeat; -} - -.debug-breakpoint-unsupported, -.monaco-editor .inline-breakpoint-widget.debug-breakpoint-unsupported { - background: url('breakpoint-unsupported.svg') center center no-repeat; -} - -.monaco-editor .debug-top-stack-frame.debug-breakpoint, -.monaco-editor .debug-top-stack-frame.debug-breakpoint-conditional, -.monaco-editor .debug-top-stack-frame.debug-breakpoint-log, -.monaco-editor .inline-breakpoint-widget.debug-top-stack-frame-column { - background: url('current-and-breakpoint.svg') center center no-repeat; -} - -.monaco-editor .debug-focused-stack-frame.debug-breakpoint, -.monaco-editor .debug-focused-stack-frame.debug-breakpoint-conditional, -.monaco-editor .debug-focused-stack-frame.debug-breakpoint-log { - background: url('stackframe-and-breakpoint.svg') center center no-repeat; -} - /* Error editor */ .debug-error-editor:focus { outline: none !important; diff --git a/src/vs/workbench/contrib/debug/browser/media/debugHover.css b/src/vs/workbench/contrib/debug/browser/media/debugHover.css index 79dd892078..95ac46899e 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugHover.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugHover.css @@ -10,6 +10,7 @@ animation-duration: 0.15s; animation-name: fadeIn; user-select: text; + -webkit-user-select: text; word-break: break-all; padding: 4px 5px; } @@ -38,6 +39,7 @@ .monaco-editor .debug-hover-widget .debug-hover-tree .monaco-list-row .monaco-tl-contents { user-select: text; + -webkit-user-select: text; } /* Disable tree highlight in debug hover tree. */ diff --git a/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css b/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css index f3a2c7cff7..357e8ab1c1 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css @@ -19,12 +19,19 @@ margin-right: 7px; } +.monaco-workbench .debug-toolbar .monaco-action-bar .action-item.select-container .monaco-select-box, +.monaco-workbench .start-debug-action-item .select-container .monaco-select-box { + padding: 0 22px 0 6px; +} + .monaco-workbench .debug-toolbar .drag-area { cursor: grab; height: 32px; width: 16px; - background: url('drag.svg') center center no-repeat; - background-size: 16px 16px; + opacity: 0.5; + display: flex; + align-items: center; + justify-content: center; } .monaco-workbench .debug-toolbar .drag-area.dragged { @@ -38,4 +45,7 @@ background-size: 16px; background-position: center center; background-repeat: no-repeat; + display: flex; + align-items: center; + justify-content: center; } diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index 23b491247a..4dd78b8c4d 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -13,40 +13,39 @@ height: 100%; } -/* Actionbar actions */ - -.monaco-workbench .debug-action.configure { - background: url('configure-light.svg') center center no-repeat; +.debug-viewlet .debug-start-view { + padding: 0 20px 0 20px; } -.vs-dark .monaco-workbench .debug-action.configure { - background: url('configure-dark.svg') center center no-repeat; +.debug-viewlet .debug-start-view .monaco-button, +.debug-viewlet .debug-start-view .section { + margin-top: 20px; } -.hc-black .monaco-workbench .debug-action.configure { - background: url('configure-hc.svg') center center no-repeat; +.debug-viewlet .debug-start-view .top-section { + margin-top: 10px; } -.monaco-workbench .debug-action.toggle-repl { - background: url('console-light.svg') center center no-repeat; +.debug-viewlet .debug-start-view .monaco-button { + max-width: 260px; + margin-left: auto; + margin-right: auto; + display: block; } -.vs-dark .monaco-workbench .debug-action.toggle-repl { - background: url('console-dark.svg') center center no-repeat; +.debug-viewlet .debug-start-view .click { + cursor: pointer; + color: #007ACC; } -.hc-black .monaco-workbench .debug-action.toggle-repl { - background: url('console-hc.svg') center center no-repeat; -} - -.monaco-workbench .debug-action.notification:before { +.monaco-workbench .debug-action.notification:after { content: ''; width: 6px; height: 6px; background-color: #CC6633; position: absolute; - top: 11px; - right: 5px; + top: 10px; + right: 6px; border-radius: 10px; border: 1px solid white; } @@ -65,24 +64,11 @@ border-radius: 4px; } -.monaco-workbench .part > .title > .title-actions .start-debug-action-item .icon { - height: 20px; - width: 20px; - background: url('start-light.svg') no-repeat; - background-size: 16px 16px; - background-position: center center; +.monaco-workbench .part > .title > .title-actions .start-debug-action-item .codicon { flex-shrink: 0; transition: transform 50ms ease; } -.vs-dark .monaco-workbench .part > .title > .title-actions .start-debug-action-item .icon { - background-image: url('start-dark.svg'); -} - -.hc-black .monaco-workbench .part > .title > .title-actions .start-debug-action-item .icon { - background-image: url('start-hc.svg'); -} - .monaco-workbench .monaco-action-bar .start-debug-action-item .configuration .monaco-select-box { border: none; margin-top: 0px; @@ -95,7 +81,7 @@ cursor: initial; } -.monaco-workbench .part > .title > .title-actions .start-debug-action-item .icon.active { +.monaco-workbench .part > .title > .title-actions .start-debug-action-item .codicon.active { transform: scale(1.272019649, 1.272019649); } @@ -117,6 +103,10 @@ color: #666; } +.debug-viewlet .monaco-list:focus .monaco-list-row.selected.focused .codicon { + color: inherit !important; +} + .debug-viewlet .disabled { opacity: 0.35; } @@ -179,9 +169,11 @@ text-transform: uppercase; } -.debug-viewlet .debug-call-stack .thread:hover > .state, -.debug-viewlet .debug-call-stack .session:hover > .state, -.debug-viewlet .debug-call-stack .monaco-list-row.focused .state { +.debug-viewlet .debug-call-stack .monaco-list-row:hover .state { + display: none; +} + +.debug-viewlet .debug-call-stack .monaco-list-row:hover .stack-frame.has-actions .file { display: none; } @@ -189,7 +181,6 @@ display: none; } -.debug-viewlet .debug-call-stack .monaco-list-row.focused .monaco-action-bar, .debug-viewlet .debug-call-stack .monaco-list-row:hover .monaco-action-bar { display: initial; } @@ -198,6 +189,7 @@ width: 16px; height: 100%; margin-right: 8px; + vertical-align: text-top; } .debug-viewlet .debug-call-stack .thread > .state > .label, @@ -224,6 +216,7 @@ flex: 1; flex-shrink: 0; min-width: fit-content; + min-width: -moz-fit-content; } .debug-viewlet .debug-call-stack .stack-frame.subtle { @@ -278,100 +271,8 @@ overflow: hidden; } -.debug-viewlet .debug-call-stack .debug-action.stop { - background: url('stop-light.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .monaco-list:focus .monaco-list-row.selected .debug-action.stop { - background: url('stop-white.svg') center center no-repeat; -} - -.vs-dark .debug-viewlet .debug-call-stack .debug-action.stop { - background: url('stop-dark.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .debug-action.disconnect { - background: url('disconnect-light.svg') center center no-repeat; -} - -.vs-dark .debug-viewlet .debug-call-stack .debug-action.disconnect { - background: url('disconnect-dark.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .monaco-list:focus .monaco-list-row.selected .debug-action.disconnect { - background: url('disconnect-white.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .debug-action.restart { - background: url('restart-light.svg') center center no-repeat; -} - -.vs-dark .debug-viewlet .debug-call-stack .debug-action.restart { - background: url('restart-dark.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .monaco-list:focus .monaco-list-row.selected .debug-action.restart { - background: url('restart-white.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .debug-action.step-over { - background: url('step-over-light.svg') center center no-repeat; -} - -.vs-dark .debug-viewlet .debug-call-stack .debug-action.step-over { - background: url('step-over-dark.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .monaco-list:focus .monaco-list-row.selected .debug-action.step-over { - background: url('step-over-white.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .debug-action.step-into { - background: url('step-into-light.svg') center center no-repeat; -} - -.vs-dark .debug-viewlet .debug-call-stack .debug-action.step-into { - background: url('step-into-dark.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .monaco-list:focus .monaco-list-row.selected .debug-action.step-into { - background: url('step-into-white.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .debug-action.step-out { - background: url('step-out-light.svg') center center no-repeat; -} - -.vs-dark .debug-viewlet .debug-call-stack .debug-action.step-out { - background: url('step-out-dark.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .monaco-list:focus .monaco-list-row.selected .debug-action.step-out { - background: url('step-out-white.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .debug-action.pause { - background: url('pause-light.svg') center center no-repeat; -} - -.vs-dark .debug-viewlet .debug-call-stack .debug-action.pause { - background: url('pause-dark.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .monaco-list:focus .monaco-list-row.selected .debug-action.pause { - background: url('pause-white.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .debug-action.continue { - background: url('continue-light.svg') center center no-repeat; -} - -.vs-dark .debug-viewlet .debug-call-stack .debug-action.continue { - background: url('continue-dark.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .monaco-list:focus .monaco-list-row.selected .debug-action.continue { - background: url('continue-white.svg') center center no-repeat; +.debug-viewlet .debug-call-stack .monaco-list:focus .monaco-list-row.selected .codicon { + color: inherit !important; } /* Variables & Expression view */ @@ -435,21 +336,6 @@ flex : 1; } -.debug-viewlet .debug-action.add-watch-expression, -.debug-viewlet .debug-action.add-function-breakpoint { - background: url('add-light.svg') center center no-repeat; -} - -.vs-dark .debug-viewlet .debug-action.add-watch-expression, -.vs-dark .debug-viewlet .debug-action.add-function-breakpoint { - background: url('add-dark.svg') center center no-repeat; -} - -.hc-black .debug-viewlet .debug-action.add-watch-expression, -.hc-black .debug-viewlet .debug-action.add-function-breakpoint { - background: url('add-hc.svg') center center no-repeat; -} - .vs-dark .debug-viewlet .monaco-list-row .expression .value.changed { animation-name: debugViewletValueChanged; } @@ -479,10 +365,17 @@ flex-shrink: 0; } -.debug-viewlet .debug-breakpoints .breakpoint > .icon { +.debug-viewlet .debug-breakpoints .breakpoint > .codicon { width: 19px; height: 19px; min-width: 19px; + display: flex; + align-items: center; + justify-content: center; +} + +.debug-viewlet .debug-breakpoints .breakpoint > .codicon-debug-breakpoint-stackframe-dot::before { + content: "\ea71"; } .debug-viewlet .debug-breakpoints .breakpoint > .file-path { @@ -499,30 +392,6 @@ text-overflow: ellipsis } -.debug-viewlet .debug-action.remove-all { - background: url('close-all-light.svg') center center no-repeat; -} - -.vs-dark .debug-viewlet .debug-action.remove-all { - background: url('close-all-dark.svg') center center no-repeat; -} - -.hc-black .debug-viewlet .debug-action.remove-all { - background: url('close-all-hc.svg') center center no-repeat; -} - -.debug-viewlet .debug-action.breakpoints-activate { - background: url('toggle-breakpoints-light.svg') center center no-repeat; -} - -.vs-dark .debug-viewlet .debug-action.breakpoints-activate { - background: url('toggle-breakpoints-dark.svg') center center no-repeat; -} - -.hc-black .debug-viewlet .debug-action.breakpoints-activate { - background: url('toggle-breakpoints-hc.svg') center center no-repeat; -} - /* No workspace view */ .debug-viewlet > .noworkspace-view { diff --git a/src/vs/workbench/contrib/debug/browser/media/disconnect-dark.svg b/src/vs/workbench/contrib/debug/browser/media/disconnect-dark.svg deleted file mode 100644 index 71aae0dd88..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/disconnect-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/disconnect-light.svg b/src/vs/workbench/contrib/debug/browser/media/disconnect-light.svg deleted file mode 100644 index 06fc4c3155..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/disconnect-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/disconnect-white.svg b/src/vs/workbench/contrib/debug/browser/media/disconnect-white.svg deleted file mode 100644 index 42e2e75e09..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/disconnect-white.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/drag.svg b/src/vs/workbench/contrib/debug/browser/media/drag.svg deleted file mode 100644 index b6b93f31fd..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/drag.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/exceptionWidget.css b/src/vs/workbench/contrib/debug/browser/media/exceptionWidget.css index d4ffe3099d..9801b3f07a 100644 --- a/src/vs/workbench/contrib/debug/browser/media/exceptionWidget.css +++ b/src/vs/workbench/contrib/debug/browser/media/exceptionWidget.css @@ -11,6 +11,7 @@ padding: 6px 10px; white-space: pre-wrap; user-select: text; + -webkit-user-select: text; } .monaco-editor .zone-widget .zone-widget-container.exception-widget .title { diff --git a/src/vs/workbench/contrib/debug/browser/media/pause-dark.svg b/src/vs/workbench/contrib/debug/browser/media/pause-dark.svg deleted file mode 100644 index 9cd9f46613..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/pause-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/pause-light.svg b/src/vs/workbench/contrib/debug/browser/media/pause-light.svg deleted file mode 100644 index 01d3cbc290..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/pause-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/pause-white.svg b/src/vs/workbench/contrib/debug/browser/media/pause-white.svg deleted file mode 100644 index bd634a35a5..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/pause-white.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/repl.css b/src/vs/workbench/contrib/debug/browser/media/repl.css index cdb38f82ba..5aee190454 100644 --- a/src/vs/workbench/contrib/debug/browser/media/repl.css +++ b/src/vs/workbench/contrib/debug/browser/media/repl.css @@ -13,6 +13,8 @@ .repl .repl-tree .monaco-tl-contents { user-select: text; + -webkit-user-select: text; + white-space: pre; } .repl .repl-tree.word-wrap .monaco-tl-contents { @@ -37,6 +39,16 @@ flex: 1; } +.repl .repl-tree .monaco-tl-contents .arrow { + position:absolute; + left: 2px; + opacity: 0.3; +} + +.vs-dark .repl .repl-tree .monaco-tl-contents .arrow { + opacity: 0.45; +} + .repl .repl-tree .output.expression.value-and-source .source { margin-left: 4px; margin-right: 8px; diff --git a/src/vs/workbench/contrib/debug/browser/media/restart-dark.svg b/src/vs/workbench/contrib/debug/browser/media/restart-dark.svg deleted file mode 100644 index fc48916d5a..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/restart-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/restart-light.svg b/src/vs/workbench/contrib/debug/browser/media/restart-light.svg deleted file mode 100644 index 4964d5bfaf..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/restart-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/restart-white.svg b/src/vs/workbench/contrib/debug/browser/media/restart-white.svg deleted file mode 100644 index cf498da0c7..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/restart-white.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/reverse-continue-dark.svg b/src/vs/workbench/contrib/debug/browser/media/reverse-continue-dark.svg deleted file mode 100644 index e0bbfb4202..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/reverse-continue-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/reverse-continue-light.svg b/src/vs/workbench/contrib/debug/browser/media/reverse-continue-light.svg deleted file mode 100644 index e0bbfb4202..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/reverse-continue-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/stackframe-and-breakpoint.svg b/src/vs/workbench/contrib/debug/browser/media/stackframe-and-breakpoint.svg deleted file mode 100644 index 3ce31c822c..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/stackframe-and-breakpoint.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/stackframe-arrow.svg b/src/vs/workbench/contrib/debug/browser/media/stackframe-arrow.svg deleted file mode 100644 index 38b63a34c5..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/stackframe-arrow.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/start-dark.svg b/src/vs/workbench/contrib/debug/browser/media/start-dark.svg deleted file mode 100644 index 5d91244bff..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/start-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/start-hc.svg b/src/vs/workbench/contrib/debug/browser/media/start-hc.svg deleted file mode 100644 index 5d91244bff..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/start-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/start-light.svg b/src/vs/workbench/contrib/debug/browser/media/start-light.svg deleted file mode 100644 index b6effa36f7..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/start-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/step-back-dark.svg b/src/vs/workbench/contrib/debug/browser/media/step-back-dark.svg deleted file mode 100644 index 5a6ada3e11..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/step-back-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/step-back-light.svg b/src/vs/workbench/contrib/debug/browser/media/step-back-light.svg deleted file mode 100644 index b5a994d425..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/step-back-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/step-into-dark.svg b/src/vs/workbench/contrib/debug/browser/media/step-into-dark.svg deleted file mode 100644 index 570ae02aaf..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/step-into-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/step-into-light.svg b/src/vs/workbench/contrib/debug/browser/media/step-into-light.svg deleted file mode 100644 index 55c47062f5..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/step-into-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/step-into-white.svg b/src/vs/workbench/contrib/debug/browser/media/step-into-white.svg deleted file mode 100644 index 77ef1cbf34..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/step-into-white.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/step-out-dark.svg b/src/vs/workbench/contrib/debug/browser/media/step-out-dark.svg deleted file mode 100644 index 33a7a2fdb7..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/step-out-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/step-out-light.svg b/src/vs/workbench/contrib/debug/browser/media/step-out-light.svg deleted file mode 100644 index 6ac2139659..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/step-out-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/step-out-white.svg b/src/vs/workbench/contrib/debug/browser/media/step-out-white.svg deleted file mode 100644 index 906cd8d33c..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/step-out-white.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/step-over-dark.svg b/src/vs/workbench/contrib/debug/browser/media/step-over-dark.svg deleted file mode 100644 index 5bf10674ee..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/step-over-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/step-over-light.svg b/src/vs/workbench/contrib/debug/browser/media/step-over-light.svg deleted file mode 100644 index b874a2564b..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/step-over-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/step-over-white.svg b/src/vs/workbench/contrib/debug/browser/media/step-over-white.svg deleted file mode 100644 index bac3022c88..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/step-over-white.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/stop-dark.svg b/src/vs/workbench/contrib/debug/browser/media/stop-dark.svg deleted file mode 100644 index 9a28f77a9f..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/stop-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/stop-light.svg b/src/vs/workbench/contrib/debug/browser/media/stop-light.svg deleted file mode 100644 index 9a28f77a9f..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/stop-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/stop-white.svg b/src/vs/workbench/contrib/debug/browser/media/stop-white.svg deleted file mode 100644 index f33eb6181d..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/stop-white.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/toggle-breakpoints-dark.svg b/src/vs/workbench/contrib/debug/browser/media/toggle-breakpoints-dark.svg deleted file mode 100644 index 2e8b9f34e5..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/toggle-breakpoints-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/toggle-breakpoints-hc.svg b/src/vs/workbench/contrib/debug/browser/media/toggle-breakpoints-hc.svg deleted file mode 100644 index ab1b9e54f9..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/toggle-breakpoints-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/toggle-breakpoints-light.svg b/src/vs/workbench/contrib/debug/browser/media/toggle-breakpoints-light.svg deleted file mode 100644 index 479dc5f81e..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/toggle-breakpoints-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 6f16f3b4d9..7204f2e38a 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -63,6 +63,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { PANEL_BACKGROUND } from 'vs/workbench/common/theme'; const $ = dom.$; @@ -344,7 +345,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati } focus(): void { - this.replInput.focus(); + setTimeout(() => this.replInput.focus(), 0); } getActionViewItem(action: IAction): IActionViewItem | undefined { @@ -406,7 +407,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati const wordWrap = this.configurationService.getValue('debug').console.wordWrap; dom.toggleClass(treeContainer, 'word-wrap', wordWrap); const linkDetector = this.instantiationService.createInstance(LinkDetector); - this.tree = this.instantiationService.createInstance( + this.tree = this.instantiationService.createInstance>( WorkbenchAsyncDataTree, 'DebugRepl', treeContainer, @@ -428,7 +429,10 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IReplElement) => e }, horizontalScrolling: !wordWrap, setRowLineHeight: false, - supportDynamicHeights: wordWrap + supportDynamicHeights: wordWrap, + overrideStyles: { + listBackground: PANEL_BACKGROUND + } }); this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); let lastSelectedString: string; @@ -606,6 +610,7 @@ class ReplEvaluationInputsRenderer implements ITreeRenderer { const config = this.configurationService.getValue('debug'); if (!config.console.wordWrap) { - return Math.ceil(1.4 * config.console.fontSize); + return this.estimateHeight(element, true); } return super.getHeight(element); } - protected estimateHeight(element: IReplElement): number { + protected estimateHeight(element: IReplElement, ignoreValueLength = false): number { const config = this.configurationService.getValue('debug'); const rowHeight = Math.ceil(1.4 * config.console.fontSize); const countNumberOfLines = (str: string) => Math.max(1, (str && str.match(/\r\n|\n/g) || []).length); @@ -821,7 +827,7 @@ class ReplDelegate extends CachedListVirtualDelegate { // For every 30 characters increase the number of lines needed if (hasValue(element)) { let value = element.value; - let valueRows = countNumberOfLines(value) + Math.floor(value.length / 30); + let valueRows = countNumberOfLines(value) + (ignoreValueLength ? 0 : Math.floor(value.length / 30)); return valueRows * rowHeight; } diff --git a/src/vs/workbench/contrib/debug/browser/startView.ts b/src/vs/workbench/contrib/debug/browser/startView.ts new file mode 100644 index 0000000000..88fa88dfaf --- /dev/null +++ b/src/vs/workbench/contrib/debug/browser/startView.ts @@ -0,0 +1,156 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dom from 'vs/base/browser/dom'; +import { Button } from 'vs/base/browser/ui/button/button'; +import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; +import { attachButtonStyler } from 'vs/platform/theme/common/styler'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { localize } from 'vs/nls'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { StartAction, RunAction, ConfigureAction } from 'vs/workbench/contrib/debug/browser/debugActions'; +import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { equals } from 'vs/base/common/arrays'; +const $ = dom.$; + +export class StartView extends ViewletPane { + + static ID = 'workbench.debug.startView'; + static LABEL = localize('start', "Start"); + + private debugButton!: Button; + private runButton!: Button; + private firstMessageContainer!: HTMLElement; + private secondMessageContainer!: HTMLElement; + private debuggerLabels: string[] | undefined = undefined; + + constructor( + options: IViewletViewOptions, + @IThemeService private readonly themeService: IThemeService, + @IKeybindingService keybindingService: IKeybindingService, + @IContextMenuService contextMenuService: IContextMenuService, + @IConfigurationService configurationService: IConfigurationService, + @IContextKeyService contextKeyService: IContextKeyService, + @ICommandService private readonly commandService: ICommandService, + @IDebugService private readonly debugService: IDebugService, + @IEditorService private readonly editorService: IEditorService, + @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, + @IFileDialogService private readonly dialogService: IFileDialogService + ) { + super({ ...(options as IViewletPaneOptions), ariaHeaderLabel: localize('debugStart', "Debug Start Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); + this._register(editorService.onDidActiveEditorChange(() => this.updateView())); + this._register(this.debugService.getConfigurationManager().onDidRegisterDebugger(() => this.updateView())); + } + + private updateView(): void { + const activeEditor = this.editorService.activeTextEditorWidget; + const debuggerLabels = this.debugService.getConfigurationManager().getDebuggerLabelsForEditor(activeEditor); + if (!equals(this.debuggerLabels, debuggerLabels)) { + this.debuggerLabels = debuggerLabels; + const enabled = this.debuggerLabels.length > 0; + + this.debugButton.enabled = enabled; + this.runButton.enabled = enabled; + this.debugButton.label = this.debuggerLabels.length !== 1 ? localize('debug', "Debug") : localize('debugWith', "Debug with {0}", this.debuggerLabels[0]); + this.runButton.label = this.debuggerLabels.length !== 1 ? localize('run', "Run") : localize('runWith', "Run with {0}", this.debuggerLabels[0]); + + const emptyWorkbench = this.workspaceContextService.getWorkbenchState() === WorkbenchState.EMPTY; + this.firstMessageContainer.innerHTML = ''; + this.secondMessageContainer.innerHTML = ''; + const secondMessageElement = $('span'); + this.secondMessageContainer.appendChild(secondMessageElement); + + const setSecondMessage = () => { + secondMessageElement.textContent = localize('specifyHowToRun', "To futher configure Debug and Run"); + const clickElement = $('span.click'); + clickElement.textContent = localize('configure', " create a launch.json file."); + clickElement.onclick = () => this.commandService.executeCommand(ConfigureAction.ID); + this.secondMessageContainer.appendChild(clickElement); + }; + const setSecondMessageWithFolder = () => { + secondMessageElement.textContent = localize('noLaunchConfiguration', "To futher configure Debug and Run, "); + const clickElement = $('span.click'); + clickElement.textContent = localize('openFolder', " open a folder"); + clickElement.onclick = () => this.dialogService.pickFolderAndOpen({ forceNewWindow: false }); + this.secondMessageContainer.appendChild(clickElement); + + const moreText = $('span.moreText'); + moreText.textContent = localize('andconfigure', " and create a launch.json file."); + this.secondMessageContainer.appendChild(moreText); + }; + + if (enabled && !emptyWorkbench) { + setSecondMessage(); + } + + if (enabled && emptyWorkbench) { + setSecondMessageWithFolder(); + } + + if (!enabled && !emptyWorkbench) { + const firstMessageElement = $('span'); + this.firstMessageContainer.appendChild(firstMessageElement); + firstMessageElement.textContent = localize('simplyDebugAndRun', "Open a file which can be debugged or run."); + + setSecondMessage(); + } + + if (!enabled && emptyWorkbench) { + const clickElement = $('span.click'); + clickElement.textContent = localize('openFile', "Open a file"); + clickElement.onclick = () => this.dialogService.pickFileAndOpen({ forceNewWindow: false }); + + this.firstMessageContainer.appendChild(clickElement); + const firstMessageElement = $('span'); + this.firstMessageContainer.appendChild(firstMessageElement); + firstMessageElement.textContent = localize('canBeDebuggedOrRun', " which can be debugged or run."); + + + setSecondMessageWithFolder(); + } + } + } + + protected renderBody(container: HTMLElement): void { + this.firstMessageContainer = $('.top-section'); + container.appendChild(this.firstMessageContainer); + + this.debugButton = new Button(container); + this._register(this.debugButton.onDidClick(() => { + this.commandService.executeCommand(StartAction.ID); + })); + attachButtonStyler(this.debugButton, this.themeService); + + this.runButton = new Button(container); + this.runButton.label = localize('run', "Run"); + + dom.addClass(container, 'debug-start-view'); + this._register(this.runButton.onDidClick(() => { + this.commandService.executeCommand(RunAction.ID); + })); + attachButtonStyler(this.runButton, this.themeService); + + this.secondMessageContainer = $('.section'); + container.appendChild(this.secondMessageContainer); + + this.updateView(); + } + + protected layoutBody(_: number, __: number): void { + // no-op + } + + focus(): void { + this.runButton.focus(); + } +} diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 98b9cf1c16..99589e4c6c 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -17,7 +17,7 @@ import { IAction, Action } from 'vs/base/common/actions'; import { CopyValueAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IViewletPanelOptions, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { IViewletPaneOptions, ViewletPane } from 'vs/workbench/browser/parts/views/paneViewlet'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer, ITreeNode, ITreeMouseEvent, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; @@ -30,13 +30,14 @@ import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabe import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { dispose } from 'vs/base/common/lifecycle'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; const $ = dom.$; let forgetScopes = true; export const variableSetEmitter = new Emitter(); -export class VariablesView extends ViewletPanel { +export class VariablesView extends ViewletPane { private onFocusStackFrameScheduler: RunOnceScheduler; private needsRefresh = false; @@ -53,7 +54,7 @@ export class VariablesView extends ViewletPanel { @IClipboardService private readonly clipboardService: IClipboardService, @IContextKeyService contextKeyService: IContextKeyService ) { - super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('variablesSection', "Variables Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewletPaneOptions), ariaHeaderLabel: nls.localize('variablesSection', "Variables Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); // Use scheduler to prevent unnecessary flashing this.onFocusStackFrameScheduler = new RunOnceScheduler(async () => { @@ -85,13 +86,16 @@ export class VariablesView extends ViewletPanel { dom.addClass(container, 'debug-variables'); const treeContainer = renderViewTree(container); - this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'VariablesView', treeContainer, new VariablesDelegate(), + this.tree = this.instantiationService.createInstance>(WorkbenchAsyncDataTree, 'VariablesView', treeContainer, new VariablesDelegate(), [this.instantiationService.createInstance(VariablesRenderer), new ScopesRenderer()], new VariablesDataSource(), { ariaLabel: nls.localize('variablesAriaTreeLabel', "Debug Variables"), accessibilityProvider: new VariablesAccessibilityProvider(), identityProvider: { getId: (element: IExpression | IScope) => element.getId() }, - keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IExpression | IScope) => e } + keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IExpression | IScope) => e }, + overrideStyles: { + listBackground: SIDE_BAR_BACKGROUND + } }); this.tree.setInput(this.debugService.getViewModel()); @@ -99,7 +103,7 @@ export class VariablesView extends ViewletPanel { CONTEXT_VARIABLES_FOCUSED.bindTo(this.tree.contextKeyService); if (this.toolbar) { - const collapseAction = new CollapseAction(this.tree, true, 'explorer-action collapse-explorer'); + const collapseAction = new CollapseAction(this.tree, true, 'explorer-action codicon-collapse-all'); this.toolbar.setActions([collapseAction])(); } this.tree.updateChildren(); diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 0f5f58a83c..b2b34fd35d 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -18,7 +18,7 @@ import { IAction, Action } from 'vs/base/common/actions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { renderExpressionValue, renderViewTree, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IViewletPanelOptions, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { IViewletPaneOptions, ViewletPane } from 'vs/workbench/browser/parts/views/paneViewlet'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; @@ -30,10 +30,11 @@ import { IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel import { variableSetEmitter, VariablesRenderer } from 'vs/workbench/contrib/debug/browser/variablesView'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { dispose } from 'vs/base/common/lifecycle'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; -export class WatchExpressionsView extends ViewletPanel { +export class WatchExpressionsView extends ViewletPane { private onWatchExpressionsUpdatedScheduler: RunOnceScheduler; private needsRefresh = false; @@ -48,7 +49,7 @@ export class WatchExpressionsView extends ViewletPanel { @IConfigurationService configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService, ) { - super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('watchExpressionsSection', "Watch Expressions Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewletPaneOptions), ariaHeaderLabel: nls.localize('watchExpressionsSection', "Watch Expressions Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); this.onWatchExpressionsUpdatedScheduler = new RunOnceScheduler(() => { this.needsRefresh = false; @@ -61,13 +62,16 @@ export class WatchExpressionsView extends ViewletPanel { const treeContainer = renderViewTree(container); const expressionsRenderer = this.instantiationService.createInstance(WatchExpressionsRenderer); - this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'WatchExpressions', treeContainer, new WatchExpressionsDelegate(), [expressionsRenderer, this.instantiationService.createInstance(VariablesRenderer)], + this.tree = this.instantiationService.createInstance>(WorkbenchAsyncDataTree, 'WatchExpressions', treeContainer, new WatchExpressionsDelegate(), [expressionsRenderer, this.instantiationService.createInstance(VariablesRenderer)], new WatchExpressionsDataSource(), { ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'watchAriaTreeLabel' }, "Debug Watch Expressions"), accessibilityProvider: new WatchExpressionsAccessibilityProvider(), identityProvider: { getId: (element: IExpression) => element.getId() }, keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IExpression) => e }, dnd: new WatchExpressionsDragAndDrop(this.debugService), + overrideStyles: { + listBackground: SIDE_BAR_BACKGROUND + } }); this.tree.setInput(this.debugService); @@ -75,18 +79,21 @@ export class WatchExpressionsView extends ViewletPanel { if (this.toolbar) { const addWatchExpressionAction = new AddWatchExpressionAction(AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL, this.debugService, this.keybindingService); - const collapseAction = new CollapseAction(this.tree, true, 'explorer-action collapse-explorer'); + const collapseAction = new CollapseAction(this.tree, true, 'explorer-action codicon-collapse-all'); const removeAllWatchExpressionsAction = new RemoveAllWatchExpressionsAction(RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL, this.debugService, this.keybindingService); this.toolbar.setActions([addWatchExpressionAction, collapseAction, removeAllWatchExpressionsAction])(); } this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); this._register(this.tree.onMouseDblClick(e => this.onMouseDblClick(e))); - this._register(this.debugService.getModel().onDidChangeWatchExpressions(we => { + this._register(this.debugService.getModel().onDidChangeWatchExpressions(async we => { if (!this.isBodyVisible()) { this.needsRefresh = true; } else { - this.tree.updateChildren(); + await this.tree.updateChildren(); + if (we instanceof Expression) { + this.tree.reveal(we); + } } })); this._register(this.debugService.getViewModel().onDidFocusStackFrame(() => { diff --git a/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts b/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts index 5c0c4ed2d7..be8cb3b8d2 100644 --- a/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts +++ b/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts @@ -18,13 +18,11 @@ export abstract class AbstractDebugAdapter implements IDebugAdapter { private requestCallback: ((request: DebugProtocol.Request) => void) | undefined; private eventCallback: ((request: DebugProtocol.Event) => void) | undefined; private messageCallback: ((message: DebugProtocol.ProtocolMessage) => void) | undefined; - protected readonly _onError: Emitter; - protected readonly _onExit: Emitter; + protected readonly _onError = new Emitter(); + protected readonly _onExit = new Emitter(); constructor() { this.sequence = 1; - this._onError = new Emitter(); - this._onExit = new Emitter(); } abstract startSession(): Promise; diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index e4f01e01ce..8773032454 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -9,7 +9,7 @@ import severity from 'vs/base/common/severity'; import { Event } from 'vs/base/common/event'; import { IJSONSchemaSnippet } from 'vs/base/common/jsonSchema'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import * as editorCommon from 'vs/editor/common/editorCommon'; import { ITextModel as EditorIModel } from 'vs/editor/common/model'; import { IEditor, ITextEditor } from 'vs/workbench/common/editor'; import { Position, IPosition } from 'vs/editor/common/core/position'; @@ -40,6 +40,8 @@ export const DEBUG_SERVICE_ID = 'debugService'; export const CONTEXT_DEBUG_TYPE = new RawContextKey('debugType', undefined); export const CONTEXT_DEBUG_CONFIGURATION_TYPE = new RawContextKey('debugConfigurationType', undefined); export const CONTEXT_DEBUG_STATE = new RawContextKey('debugState', 'inactive'); +export const CONTEXT_DEBUG_UX_KEY = 'debugUx'; +export const CONTEXT_DEBUG_UX = new RawContextKey(CONTEXT_DEBUG_UX_KEY, 'default'); export const CONTEXT_IN_DEBUG_MODE = new RawContextKey('inDebugMode', false); export const CONTEXT_IN_DEBUG_REPL = new RawContextKey('inDebugRepl', false); export const CONTEXT_BREAKPOINT_WIDGET_VISIBLE = new RawContextKey('breakpointWidgetVisible', false); @@ -468,6 +470,7 @@ export interface IDebugConfiguration { focusWindowOnBreak: boolean; onTaskErrors: 'debugAnyway' | 'showErrors' | 'prompt'; showBreakpointsInOverviewRuler: boolean; + showInlineBreakpointCandidates: boolean; } export interface IGlobalConfig { @@ -544,12 +547,17 @@ export interface IDebugAdapterServer { readonly host?: string; } -export interface IDebugAdapterImplementation { - readonly type: 'implementation'; - readonly implementation: any; +export interface IDebugAdapterInlineImpl extends IDisposable { + readonly onSendMessage: Event; + handleMessage(message: DebugProtocol.Message): void; } -export type IAdapterDescriptor = IDebugAdapterExecutable | IDebugAdapterServer | IDebugAdapterImplementation; +export interface IDebugAdapterImpl { + readonly type: 'implementation'; + readonly implementation: IDebugAdapterInlineImpl; +} + +export type IAdapterDescriptor = IDebugAdapterExecutable | IDebugAdapterServer | IDebugAdapterImpl; export interface IPlatformSpecificAdapterContribution { program?: string; @@ -628,8 +636,11 @@ export interface IConfigurationManager { */ onDidSelectConfiguration: Event; + onDidRegisterDebugger: Event; + activateDebuggers(activationEvent: string, debugType?: string): Promise; + getDebuggerLabelsForEditor(editor: editorCommon.IEditor | undefined): string[]; hasDebugConfigurationProvider(debugType: string): boolean; registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable; @@ -861,12 +872,12 @@ export const enum BreakpointWidgetContext { LOG_MESSAGE = 2 } -export interface IDebugEditorContribution extends IEditorContribution { +export interface IDebugEditorContribution extends editorCommon.IEditorContribution { showHover(range: Range, focus: boolean): Promise; addLaunchConfiguration(): Promise; } -export interface IBreakpointEditorContribution extends IEditorContribution { +export interface IBreakpointEditorContribution extends editorCommon.IEditorContribution { showBreakpointWidget(lineNumber: number, column: number | undefined, context?: BreakpointWidgetContext): void; closeBreakpointWidget(): void; } diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 6abab23082..d1b6e39ffb 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -157,6 +157,11 @@ export class ExpressionContainer implements IExpressionContainer { this.session = session; try { const response = await session.evaluate(expression, stackFrame ? stackFrame.frameId : undefined, context); + if (response && response.success === false) { + this.value = response.message || ''; + return false; + } + if (response && response.body) { this.value = response.body.result || ''; this.reference = response.body.variablesReference; @@ -810,9 +815,9 @@ export class DebugModel implements IDebugModel { private toDispose: lifecycle.IDisposable[]; private schedulers = new Map(); private breakpointsActivated = true; - private readonly _onDidChangeBreakpoints: Emitter; - private readonly _onDidChangeCallStack: Emitter; - private readonly _onDidChangeWatchExpressions: Emitter; + private readonly _onDidChangeBreakpoints = new Emitter(); + private readonly _onDidChangeCallStack = new Emitter(); + private readonly _onDidChangeWatchExpressions = new Emitter(); constructor( private breakpoints: Breakpoint[], @@ -824,9 +829,6 @@ export class DebugModel implements IDebugModel { ) { this.sessions = []; this.toDispose = []; - this._onDidChangeBreakpoints = new Emitter(); - this._onDidChangeCallStack = new Emitter(); - this._onDidChangeWatchExpressions = new Emitter(); } getId(): string { @@ -1077,7 +1079,7 @@ export class DebugModel implements IDebugModel { if (first.column && second.column) { return first.column - second.column; } - return -1; + return 1; } return first.lineNumber - second.lineNumber; @@ -1093,6 +1095,9 @@ export class DebugModel implements IDebugModel { } element.enabled = enable; + if (enable) { + this.breakpointsActivated = true; + } this._onDidChangeBreakpoints.fire({ changed: changed }); } @@ -1119,6 +1124,9 @@ export class DebugModel implements IDebugModel { } dbp.enabled = enable; }); + if (enable) { + this.breakpointsActivated = true; + } this._onDidChangeBreakpoints.fire({ changed: changed }); } diff --git a/src/vs/workbench/contrib/debug/common/debugUtils.ts b/src/vs/workbench/contrib/debug/common/debugUtils.ts index d6ca8b95b8..2cd10f460e 100644 --- a/src/vs/workbench/contrib/debug/common/debugUtils.ts +++ b/src/vs/workbench/contrib/debug/common/debugUtils.ts @@ -28,7 +28,15 @@ export function isSessionAttach(session: IDebugSession): boolean { } export function isExtensionHostDebugging(config: IConfig) { - return config.type && equalsIgnoreCase(config.type === 'vslsShare' ? (config).adapterProxy.configuration.type : config.type, 'extensionhost'); + if (!config.type) { + return false; + } + + const type = config.type === 'vslsShare' + ? (config).adapterProxy.configuration.type + : config.type; + + return equalsIgnoreCase(type, 'extensionhost') || equalsIgnoreCase(type, 'pwa-extensionhost'); } // only a debugger contributions with a label, program, or runtime attribute is considered a "defining" or "main" debugger contribution diff --git a/src/vs/workbench/contrib/debug/common/debugViewModel.ts b/src/vs/workbench/contrib/debug/common/debugViewModel.ts index cf696c3aeb..71f1b10eb6 100644 --- a/src/vs/workbench/contrib/debug/common/debugViewModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugViewModel.ts @@ -17,9 +17,9 @@ export class ViewModel implements IViewModel { private _focusedThread: IThread | undefined; private selectedExpression: IExpression | undefined; private selectedFunctionBreakpoint: IFunctionBreakpoint | undefined; - private readonly _onDidFocusSession: Emitter; - private readonly _onDidFocusStackFrame: Emitter<{ stackFrame: IStackFrame | undefined, explicit: boolean }>; - private readonly _onDidSelectExpression: Emitter; + private readonly _onDidFocusSession = new Emitter(); + private readonly _onDidFocusStackFrame = new Emitter<{ stackFrame: IStackFrame | undefined, explicit: boolean }>(); + private readonly _onDidSelectExpression = new Emitter(); private multiSessionView: boolean; private expressionSelectedContextKey: IContextKey; private breakpointSelectedContextKey: IContextKey; @@ -30,9 +30,6 @@ export class ViewModel implements IViewModel { private jumpToCursorSupported: IContextKey; constructor(contextKeyService: IContextKeyService) { - this._onDidFocusSession = new Emitter(); - this._onDidFocusStackFrame = new Emitter<{ stackFrame: IStackFrame, explicit: boolean }>(); - this._onDidSelectExpression = new Emitter(); this.multiSessionView = false; this.expressionSelectedContextKey = CONTEXT_EXPRESSION_SELECTED.bindTo(contextKeyService); this.breakpointSelectedContextKey = CONTEXT_BREAKPOINT_SELECTED.bindTo(contextKeyService); diff --git a/src/vs/workbench/contrib/debug/common/replModel.ts b/src/vs/workbench/contrib/debug/common/replModel.ts index 8f34880c69..c50c3eb5b1 100644 --- a/src/vs/workbench/contrib/debug/common/replModel.ts +++ b/src/vs/workbench/contrib/debug/common/replModel.ts @@ -98,10 +98,23 @@ export class ReplEvaluationInput implements IReplElement { } export class ReplEvaluationResult extends ExpressionContainer implements IReplElement { + private _available = true; + + get available(): boolean { + return this._available; + } + constructor() { super(undefined, undefined, 0, generateUuid()); } + async evaluateExpression(expression: string, session: IDebugSession | undefined, stackFrame: IStackFrame | undefined, context: string): Promise { + const result = await super.evaluateExpression(expression, session, stackFrame, context); + this._available = result; + + return result; + } + toString(): string { return `${this.value}`; } @@ -136,6 +149,7 @@ export class ReplModel { const previousElement = this.replElements.length ? this.replElements[this.replElements.length - 1] : undefined; if (previousElement instanceof SimpleReplElement && previousElement.severity === sev && !endsWith(previousElement.value, '\n') && !endsWith(previousElement.value, '\r\n')) { previousElement.value += data; + this._onDidChangeElements.fire(); } else { const element = new SimpleReplElement(session, `topReplElement:${topReplElementCounter++}`, data, sev, source); this.addReplElement(element); diff --git a/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts b/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts index 2f4212365d..44baf87d95 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts @@ -7,22 +7,14 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { ExtensionHostDebugChannelClient, ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; -import { IProcessEnvironment } from 'vs/base/common/platform'; -import { IElectronService } from 'vs/platform/electron/node/electron'; export class ExtensionHostDebugService extends ExtensionHostDebugChannelClient { constructor( - @IMainProcessService readonly mainProcessService: IMainProcessService, - @IElectronService private readonly electronService: IElectronService + @IMainProcessService readonly mainProcessService: IMainProcessService ) { super(mainProcessService.getChannel(ExtensionHostDebugBroadcastChannel.ChannelName)); } - - openExtensionDevelopmentHostWindow(args: string[], env: IProcessEnvironment): Promise { - // TODO@Isidor move into debug IPC channel (https://github.com/microsoft/vscode/issues/81060) - return this.electronService.openExtensionDevelopmentHostWindow(args, env); - } } registerSingleton(IExtensionHostDebugService, ExtensionHostDebugService, true); diff --git a/src/vs/workbench/contrib/debug/node/debugAdapter.ts b/src/vs/workbench/contrib/debug/node/debugAdapter.ts index d9b26f582d..c963625da1 100644 --- a/src/vs/workbench/contrib/debug/node/debugAdapter.ts +++ b/src/vs/workbench/contrib/debug/node/debugAdapter.ts @@ -214,14 +214,14 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter { this._onExit.fire(code); }); - this.serverProcess.stdout.on('close', () => { + this.serverProcess.stdout!.on('close', () => { this._onError.fire(new Error('read error')); }); - this.serverProcess.stdout.on('error', error => { + this.serverProcess.stdout!.on('error', error => { this._onError.fire(error); }); - this.serverProcess.stdin.on('error', error => { + this.serverProcess.stdin!.on('error', error => { this._onError.fire(error); }); @@ -231,7 +231,7 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter { // this.serverProcess.stdout.on('data', (data: string) => { // console.log('%c' + sanitize(data), 'background: #ddd; font-style: italic;'); // }); - this.serverProcess.stderr.on('data', (data: string) => { + this.serverProcess.stderr!.on('data', (data: string) => { const channel = outputService.getChannel(ExtensionsChannelId); if (channel) { channel.append(sanitize(data)); @@ -240,7 +240,7 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter { } // finally connect to the DA - this.connect(this.serverProcess.stdout, this.serverProcess.stdin); + this.connect(this.serverProcess.stdout!, this.serverProcess.stdin!); } catch (err) { this._onError.fire(err); diff --git a/src/vs/workbench/contrib/debug/node/terminals.ts b/src/vs/workbench/contrib/debug/node/terminals.ts index 32f31799d5..224c45f664 100644 --- a/src/vs/workbench/contrib/debug/node/terminals.ts +++ b/src/vs/workbench/contrib/debug/node/terminals.ts @@ -29,34 +29,49 @@ export function runInExternalTerminal(args: DebugProtocol.RunInTerminalRequestAr } } -export function hasChildProcesses(processId: number): boolean { - if (processId) { - try { - // if shell has at least one child process, assume that shell is busy - if (env.isWindows) { - const result = cp.spawnSync('wmic', ['process', 'get', 'ParentProcessId']); - if (result.stdout) { - const pids = result.stdout.toString().split('\r\n'); - if (!pids.some(p => parseInt(p) === processId)) { - return false; - } - } - } else { - const result = cp.spawnSync('/usr/bin/pgrep', ['-lP', String(processId)]); - if (result.stdout) { - const r = result.stdout.toString().trim(); - if (r.length === 0 || r.indexOf(' tmux') >= 0) { // ignore 'tmux'; see #43683 - return false; - } - } - } +function spawnAsPromised(command: string, args: string[]): Promise { + return new Promise((resolve, reject) => { + let stdout = ''; + const child = cp.spawn(command, args); + if (child.pid) { + child.stdout.on('data', (data: Buffer) => { + stdout += data.toString(); + }); } - catch (e) { - // silently ignore + child.on('error', err => { + reject(err); + }); + child.on('close', code => { + resolve(stdout); + }); + }); +} + +export function hasChildProcesses(processId: number | undefined): Promise { + if (processId) { + // if shell has at least one child process, assume that shell is busy + if (env.isWindows) { + return spawnAsPromised('wmic', ['process', 'get', 'ParentProcessId']).then(stdout => { + const pids = stdout.split('\r\n'); + return pids.some(p => parseInt(p) === processId); + }, error => { + return true; + }); + } else { + return spawnAsPromised('/usr/bin/pgrep', ['-lP', String(processId)]).then(stdout => { + const r = stdout.trim(); + if (r.length === 0 || r.indexOf(' tmux') >= 0) { // ignore 'tmux'; see #43683 + return false; + } else { + return true; + } + }, error => { + return true; + }); } } // fall back to safe side - return true; + return Promise.resolve(true); } const enum ShellType { cmd, powershell, bash } @@ -90,8 +105,6 @@ export function prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments shellType = ShellType.cmd; } else if (shell.indexOf('bash') >= 0) { shellType = ShellType.bash; - } else if (shell.indexOf('git\\bin\\bash.exe') >= 0) { - shellType = ShellType.bash; } let quote: (s: string) => string; diff --git a/src/vs/workbench/contrib/emmet/browser/actions/expandAbbreviation.ts b/src/vs/workbench/contrib/emmet/browser/actions/expandAbbreviation.ts index 4a502c0d08..da5b886f26 100644 --- a/src/vs/workbench/contrib/emmet/browser/actions/expandAbbreviation.ts +++ b/src/vs/workbench/contrib/emmet/browser/actions/expandAbbreviation.ts @@ -29,13 +29,12 @@ class ExpandAbbreviationAction extends EmmetEditorAction { ), weight: KeybindingWeight.EditorContrib }, - // {{SQL CARBON EDIT}} - Remove from menu - // menubarOpts: { - // menuId: MenuId.MenubarEditMenu, - // group: '5_insert', - // title: nls.localize({ key: 'miEmmetExpandAbbreviation', comment: ['&& denotes a mnemonic'] }, "Emmet: E&&xpand Abbreviation"), - // order: 3 - // } + /*menuOpts: { {{SQL CARBON EDIT}} - Remove from menu + menuId: MenuId.MenubarEditMenu, + group: '5_insert', + title: nls.localize({ key: 'miEmmetExpandAbbreviation', comment: ['&& denotes a mnemonic'] }, "Emmet: E&&xpand Abbreviation"), + order: 3 + }*/ }); } diff --git a/src/vs/workbench/contrib/emmet/browser/actions/showEmmetCommands.ts b/src/vs/workbench/contrib/emmet/browser/actions/showEmmetCommands.ts index 7e66a58198..4eb3eb766e 100644 --- a/src/vs/workbench/contrib/emmet/browser/actions/showEmmetCommands.ts +++ b/src/vs/workbench/contrib/emmet/browser/actions/showEmmetCommands.ts @@ -21,13 +21,12 @@ class ShowEmmetCommandsAction extends EditorAction { label: nls.localize('showEmmetCommands', "Show Emmet Commands"), alias: 'Show Emmet Commands', precondition: EditorContextKeys.writable, - // {{SQL CARBON EDIT}} - Remove from menu - // menubarOpts: { - // menuId: MenuId.MenubarEditMenu, - // group: '5_insert', - // title: nls.localize({ key: 'miShowEmmetCommands', comment: ['&& denotes a mnemonic'] }, "E&&mmet..."), - // order: 4 - // } + /*menuOpts: { {{SQL CARBON EDIT}} - Remove from menu + menuId: MenuId.MenubarEditMenu, + group: '5_insert', + title: nls.localize({ key: 'miShowEmmetCommands', comment: ['&& denotes a mnemonic'] }, "E&&mmet..."), + order: 4 + }*/ }); } diff --git a/src/vs/workbench/contrib/experiments/common/experimentService.ts b/src/vs/workbench/contrib/experiments/common/experimentService.ts index 04c6afaac8..97d5a685d7 100644 --- a/src/vs/workbench/contrib/experiments/common/experimentService.ts +++ b/src/vs/workbench/contrib/experiments/common/experimentService.ts @@ -6,7 +6,6 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Emitter, Event } from 'vs/base/common/event'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ITelemetryService, lastSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -20,7 +19,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { distinct } from 'vs/base/common/arrays'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IWorkspaceStatsService } from 'vs/workbench/contrib/stats/common/workspaceStats'; +import { IWorkspaceTagsService } from 'vs/workbench/contrib/tags/common/workspaceTags'; export const enum ExperimentState { Evaluating, @@ -120,13 +119,12 @@ export class ExperimentService extends Disposable implements IExperimentService @IStorageService private readonly storageService: IStorageService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @ITextFileService private readonly textFileService: ITextFileService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, @ITelemetryService private readonly telemetryService: ITelemetryService, @ILifecycleService private readonly lifecycleService: ILifecycleService, @IRequestService private readonly requestService: IRequestService, @IConfigurationService private readonly configurationService: IConfigurationService, @IProductService private readonly productService: IProductService, - @IWorkspaceStatsService private readonly workspaceStatsService: IWorkspaceStatsService + @IWorkspaceTagsService private readonly workspaceTagsService: IWorkspaceTagsService ) { super(); @@ -169,18 +167,22 @@ export class ExperimentService extends Disposable implements IExperimentService this.storageService.store(storageKey, JSON.stringify(experimentState), StorageScope.GLOBAL); } - protected getExperiments(): Promise { + protected async getExperiments(): Promise { if (!this.productService.experimentsUrl || this.configurationService.getValue('workbench.enableExperiments') === false) { - return Promise.resolve([]); + return []; } - return this.requestService.request({ type: 'GET', url: this.productService.experimentsUrl }, CancellationToken.None).then(context => { + + try { + const context = await this.requestService.request({ type: 'GET', url: this.productService.experimentsUrl }, CancellationToken.None); if (context.res.statusCode !== 200) { - return Promise.resolve(null); + return null; } - return asJson(context).then((result: any) => { - return result && Array.isArray(result['experiments']) ? result['experiments'] : []; - }); - }, () => Promise.resolve(null)); + const result: any = await asJson(context); + return result && Array.isArray(result['experiments']) ? result['experiments'] : []; + } catch (_e) { + // Bad request or invalid JSON + return null; + } } private loadExperiments(): Promise { @@ -333,7 +335,7 @@ export class ExperimentService extends Disposable implements IExperimentService return Promise.resolve(ExperimentState.NoRun); } - if (this.environmentService.appQuality === 'stable' && condition.insidersOnly === true) { + if (this.productService.quality === 'stable' && condition.insidersOnly === true) { return Promise.resolve(ExperimentState.NoRun); } @@ -422,11 +424,11 @@ export class ExperimentService extends Disposable implements IExperimentService filePathCheck = match(fileEdits.filePathPattern, event.resource.fsPath); } if (Array.isArray(fileEdits.workspaceIncludes) && fileEdits.workspaceIncludes.length) { - const tags = await this.workspaceStatsService.getTags(); + const tags = await this.workspaceTagsService.getTags(); workspaceCheck = !!tags && fileEdits.workspaceIncludes.some(x => !!tags[x]); } if (workspaceCheck && Array.isArray(fileEdits.workspaceExcludes) && fileEdits.workspaceExcludes.length) { - const tags = await this.workspaceStatsService.getTags(); + const tags = await this.workspaceTagsService.getTags(); workspaceCheck = !!tags && !fileEdits.workspaceExcludes.some(x => !!tags[x]); } if (filePathCheck && workspaceCheck) { diff --git a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts index d8e7eda424..3be4c05fe6 100644 --- a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts +++ b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts @@ -6,7 +6,6 @@ import * as assert from 'assert'; import { ExperimentActionType, ExperimentState, IExperiment, ExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { TestLifecycleService } from 'vs/workbench/test/workbenchTestServices'; import { IExtensionManagementService, DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, ILocalExtension @@ -27,6 +26,7 @@ import { URI } from 'vs/base/common/uri'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { IProductService } from 'vs/platform/product/common/productService'; interface ExperimentSettings { enabled?: boolean; @@ -88,7 +88,7 @@ suite('Experiment Service', () => { instantiationService.stub(IStorageService, >{ get: (a: string, b: StorageScope, c?: string) => c, getBoolean: (a: string, b: StorageScope, c?: boolean) => c, store: () => { }, remove: () => { } }); setup(() => { - instantiationService.stub(IEnvironmentService, {}); + instantiationService.stub(IProductService, {}); instantiationService.stub(IStorageService, >{ get: (a: string, b: StorageScope, c?: string) => c, getBoolean: (a: string, b: StorageScope, c?: boolean) => c, store: () => { }, remove: () => { } }); }); @@ -174,7 +174,7 @@ suite('Experiment Service', () => { ] }; - instantiationService.stub(IEnvironmentService, { appQuality: 'stable' }); + instantiationService.stub(IProductService, { quality: 'stable' }); testObject = instantiationService.createInstance(TestExperimentService); return testObject.getExperimentById('experiment1').then(result => { assert.equal(result.enabled, true); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 2f78e35b76..9e10aa8cc3 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -30,7 +30,7 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { CombinedInstallAction, UpdateAction, ExtensionEditorDropDownAction, ReloadAction, MaliciousStatusLabelAction, IgnoreExtensionRecommendationAction, UndoIgnoreExtensionRecommendationAction, EnableDropDownAction, DisableDropDownAction, StatusLabelAction, SetFileIconThemeAction, SetColorThemeAction, RemoteInstallAction, ExtensionToolTipAction, SystemDisabledWarningAction, LocalInstallAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -60,6 +60,7 @@ import { renderMarkdownDocument } from 'vs/workbench/contrib/markdown/common/mar import { IModeService } from 'vs/editor/common/services/modeService'; import { TokenizationRegistry } from 'vs/editor/common/modes'; import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization'; +import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { ExtensionsViewlet } from 'vs/workbench/contrib/extensions/browser/extensionsViewlet'; function removeEmbeddedSVGs(documentContent: string): string { @@ -471,10 +472,8 @@ export class ExtensionEditor extends BaseEditor { private setSubText(extension: IExtension, reloadAction: ReloadAction, template: IExtensionEditorTemplate): void { hide(template.subtextContainer); - const ignoreAction = this.instantiationService.createInstance(IgnoreExtensionRecommendationAction); - const undoIgnoreAction = this.instantiationService.createInstance(UndoIgnoreExtensionRecommendationAction); - ignoreAction.extension = extension; - undoIgnoreAction.extension = extension; + const ignoreAction = this.instantiationService.createInstance(IgnoreExtensionRecommendationAction, extension); + const undoIgnoreAction = this.instantiationService.createInstance(UndoIgnoreExtensionRecommendationAction, extension); ignoreAction.enabled = false; undoIgnoreAction.enabled = false; @@ -628,9 +627,10 @@ export class ExtensionEditor extends BaseEditor { if (!link) { return; } - // Whitelist supported schemes for links - if ([Schemas.http, Schemas.https, Schemas.mailto].indexOf(link.scheme) >= 0 || (link.scheme === 'command' && link.path === ShowCurrentReleaseNotesActionId)) { + if (matchesScheme(link, Schemas.http) || matchesScheme(link, Schemas.https) || matchesScheme(link, Schemas.mailto) + || (matchesScheme(link, Schemas.command) && URI.parse(link).path === ShowCurrentReleaseNotesActionId) + ) { this.openerService.open(link); } }, null, this.contentDisposables)); @@ -663,6 +663,8 @@ export class ExtensionEditor extends BaseEditor { body { padding: 10px 20px; line-height: 22px; + max-width: 780px; + margin: 0 auto; } img { @@ -865,6 +867,7 @@ export class ExtensionEditor extends BaseEditor { const renders = [ this.renderSettings(content, manifest, layout), this.renderCommands(content, manifest, layout), + this.renderCodeActions(content, manifest, layout), this.renderLanguages(content, manifest, layout), this.renderColorThemes(content, manifest, layout), this.renderIconThemes(content, manifest, layout), @@ -907,7 +910,11 @@ export class ExtensionEditor extends BaseEditor { append(template.content, scrollableContent.getDomNode()); this.contentDisposables.add(scrollableContent); - const dependenciesTree = this.instantiationService.createInstance(ExtensionsTree, new ExtensionData(extension, null, extension => extension.dependencies || [], this.extensionsWorkbenchService), content); + const dependenciesTree = this.instantiationService.createInstance(ExtensionsTree, + new ExtensionData(extension, null, extension => extension.dependencies || [], this.extensionsWorkbenchService), content, + { + listBackground: editorBackground + }); const layout = () => { scrollableContent.scanDomNode(); const scrollDimensions = scrollableContent.getScrollDimensions(); @@ -927,7 +934,11 @@ export class ExtensionEditor extends BaseEditor { append(template.content, scrollableContent.getDomNode()); this.contentDisposables.add(scrollableContent); - const extensionsPackTree = this.instantiationService.createInstance(ExtensionsTree, new ExtensionData(extension, null, extension => extension.extensionPack || [], this.extensionsWorkbenchService), content); + const extensionsPackTree = this.instantiationService.createInstance(ExtensionsTree, + new ExtensionData(extension, null, extension => extension.extensionPack || [], this.extensionsWorkbenchService), content, + { + listBackground: editorBackground + }); const layout = () => { scrollableContent.scanDomNode(); const scrollDimensions = scrollableContent.getScrollDimensions(); @@ -942,8 +953,7 @@ export class ExtensionEditor extends BaseEditor { } private renderSettings(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contributes = manifest.contributes; - const configuration = contributes && contributes.configuration; + const configuration = manifest.contributes?.configuration; let properties: any = {}; if (Array.isArray(configuration)) { configuration.forEach(config => { @@ -979,9 +989,7 @@ export class ExtensionEditor extends BaseEditor { } private renderDebuggers(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contributes = manifest.contributes; - const contrib = contributes && contributes.debuggers || []; - + const contrib = manifest.contributes?.debuggers || []; if (!contrib.length) { return false; } @@ -1004,10 +1012,9 @@ export class ExtensionEditor extends BaseEditor { } private renderViewContainers(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contributes = manifest.contributes; - const contrib = contributes && contributes.viewsContainers || {}; + const contrib = manifest.contributes?.viewsContainers || {}; - let viewContainers = Object.keys(contrib).reduce((result, location) => { + const viewContainers = Object.keys(contrib).reduce((result, location) => { let viewContainersForLocation: IViewContainer[] = contrib[location]; result.push(...viewContainersForLocation.map(viewContainer => ({ ...viewContainer, location }))); return result; @@ -1030,10 +1037,9 @@ export class ExtensionEditor extends BaseEditor { } private renderViews(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contributes = manifest.contributes; - const contrib = contributes && contributes.views || {}; + const contrib = manifest.contributes?.views || {}; - let views = Object.keys(contrib).reduce((result, location) => { + const views = Object.keys(contrib).reduce((result, location) => { let viewsForLocation: IView[] = contrib[location]; result.push(...viewsForLocation.map(view => ({ ...view, location }))); return result; @@ -1056,9 +1062,7 @@ export class ExtensionEditor extends BaseEditor { } private renderLocalizations(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contributes = manifest.contributes; - const localizations = contributes && contributes.localizations || []; - + const localizations = manifest.contributes?.localizations || []; if (!localizations.length) { return false; } @@ -1076,7 +1080,7 @@ export class ExtensionEditor extends BaseEditor { } private renderCustomEditors(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const webviewEditors = (manifest.contributes && manifest.contributes.webviewEditors) || []; + const webviewEditors = manifest.contributes?.webviewEditors || []; if (!webviewEditors.length) { return false; } @@ -1100,10 +1104,39 @@ export class ExtensionEditor extends BaseEditor { return true; } - private renderColorThemes(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contributes = manifest.contributes; - const contrib = contributes && contributes.themes || []; + private renderCodeActions(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { + const codeActions = manifest.contributes?.codeActions || []; + if (!codeActions.length) { + return false; + } + const flatActions = arrays.flatten( + codeActions.map(contribution => + contribution.actions.map(action => ({ ...action, languages: contribution.languages })))); + + const details = $('details', { open: true, ontoggle: onDetailsToggle }, + $('summary', { tabindex: '0' }, localize('codeActions', "Code Actions ({0})", flatActions.length)), + $('table', undefined, + $('tr', undefined, + $('th', undefined, localize('codeActions.title', "Title")), + $('th', undefined, localize('codeActions.kind', "Kind")), + $('th', undefined, localize('codeActions.description', "Description")), + $('th', undefined, localize('codeActions.languages', "Languages"))), + ...flatActions.map(action => + $('tr', undefined, + $('td', undefined, action.title), + $('td', undefined, $('code', undefined, action.kind)), + $('td', undefined, action.description ?? ''), + $('td', undefined, ...action.languages.map(language => $('code', undefined, language))))) + ) + ); + + append(container, details); + return true; + } + + private renderColorThemes(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { + const contrib = manifest.contributes?.themes || []; if (!contrib.length) { return false; } @@ -1118,9 +1151,7 @@ export class ExtensionEditor extends BaseEditor { } private renderIconThemes(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contributes = manifest.contributes; - const contrib = contributes && contributes.iconThemes || []; - + const contrib = manifest.contributes?.iconThemes || []; if (!contrib.length) { return false; } @@ -1135,10 +1166,8 @@ export class ExtensionEditor extends BaseEditor { } private renderColors(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contributes = manifest.contributes; - const colors = contributes && contributes.colors; - - if (!(colors && colors.length)) { + const colors = manifest.contributes?.colors || []; + if (!colors.length) { return false; } @@ -1180,9 +1209,7 @@ export class ExtensionEditor extends BaseEditor { private renderJSONValidation(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contributes = manifest.contributes; - const contrib = contributes && contributes.jsonValidation || []; - + const contrib = manifest.contributes?.jsonValidation || []; if (!contrib.length) { return false; } @@ -1204,8 +1231,7 @@ export class ExtensionEditor extends BaseEditor { } private renderCommands(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contributes = manifest.contributes; - const rawCommands = contributes && contributes.commands || []; + const rawCommands = manifest.contributes?.commands || []; const commands = rawCommands.map(c => ({ id: c.command, title: c.title, @@ -1215,7 +1241,7 @@ export class ExtensionEditor extends BaseEditor { const byId = arrays.index(commands, c => c.id); - const menus = contributes && contributes.menus || {}; + const menus = manifest.contributes?.menus || {}; Object.keys(menus).forEach(context => { menus[context].forEach(menu => { @@ -1231,7 +1257,7 @@ export class ExtensionEditor extends BaseEditor { }); }); - const rawKeybindings = contributes && contributes.keybindings ? (Array.isArray(contributes.keybindings) ? contributes.keybindings : [contributes.keybindings]) : []; + const rawKeybindings = manifest.contributes?.keybindings ? (Array.isArray(manifest.contributes.keybindings) ? manifest.contributes.keybindings : [manifest.contributes.keybindings]) : []; rawKeybindings.forEach(rawKeybinding => { const keybinding = this.resolveKeybinding(rawKeybinding); @@ -1285,7 +1311,7 @@ export class ExtensionEditor extends BaseEditor { private renderLanguages(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { const contributes = manifest.contributes; - const rawLanguages = contributes && contributes.languages || []; + const rawLanguages = contributes?.languages || []; const languages = rawLanguages.map(l => ({ id: l.id, name: (l.aliases || [])[0] || l.id, @@ -1296,8 +1322,7 @@ export class ExtensionEditor extends BaseEditor { const byId = arrays.index(languages, l => l.id); - const grammars = contributes && contributes.grammars || []; - + const grammars = contributes?.grammars || []; grammars.forEach(grammar => { let language = byId[grammar.language]; @@ -1310,8 +1335,7 @@ export class ExtensionEditor extends BaseEditor { } }); - const snippets = contributes && contributes.snippets || []; - + const snippets = contributes?.snippets || []; snippets.forEach(snippet => { let language = byId[snippet.language]; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionTipsService.ts b/src/vs/workbench/contrib/extensions/browser/extensionTipsService.ts index b0f6945bf1..81e355586c 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionTipsService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionTipsService.ts @@ -43,12 +43,12 @@ import { ExtensionType, ExtensionsPolicy, ExtensionsPolicyKey } from 'vs/platfor // import { extname } from 'vs/base/common/resources'; import { IExeBasedExtensionTip, IProductService } from 'vs/platform/product/common/productService'; import { timeout } from 'vs/base/common/async'; -import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; // {{SQL CARBON EDIT}} -import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; // {{SQL CARBON EDIT}} -import { IWorkspaceStatsService } from 'vs/workbench/contrib/stats/common/workspaceStats'; +import { IWorkspaceTagsService } from 'vs/workbench/contrib/tags/common/workspaceTags'; import { /*setImmediate,*/ isWeb } from 'vs/base/common/platform'; import { platform, env as processEnv } from 'vs/base/common/process'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; // {{SQL CARBON EDIT}} +import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; const milliSecondsInADay = 1000 * 60 * 60 * 24; const choiceNever = localize('neverShowAgain', "Don't Show Again"); @@ -112,10 +112,10 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe // @IViewletService private readonly viewletService: IViewletService, {{SQL CARBON EDIT}} comment out for no unused @INotificationService private readonly notificationService: INotificationService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @IAdsTelemetryService private readonly adsTelemetryService: IAdsTelemetryService, // {{SQL CARBON EDIT}} // @IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService, {{SQL CARBON EDIT}} comment out for no unused // @IExperimentService private readonly experimentService: IExperimentService, {{SQL CARBON EDIT}} comment out for no unused - @IAdsTelemetryService private readonly adsTelemetryService: IAdsTelemetryService, // {{SQL CARBON EDIT}} - @IWorkspaceStatsService private readonly workspaceStatsService: IWorkspaceStatsService, + @IWorkspaceTagsService private readonly workspaceTagsService: IWorkspaceTagsService, @IProductService private readonly productService: IProductService ) { super(); @@ -518,15 +518,6 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe private async promptForImportantExeBasedExtension(): Promise { - const storageKey = 'extensionsAssistant/workspaceRecommendationsIgnore'; - const config = this.configurationService.getValue(ConfigurationKey); - - if (config.ignoreRecommendations - || config.showRecommendationsOnlyOnDemand - || this.storageService.getBoolean(storageKey, StorageScope.WORKSPACE, false)) { - return false; - } - let recommendationsToSuggest = Object.keys(this._importantExeBasedRecommendations); const installed = await this.extensionManagementService.getInstalled(ExtensionType.User); @@ -539,13 +530,23 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe "exeName": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } } */ - this.telemetryService.publicLog('exeExtensionRecommendations:alreadyInstalled', { extensionId, exeName: tip.exeFriendlyName || basename(tip.windowsPath!) }); + this.telemetryService.publicLog('exeExtensionRecommendations:alreadyInstalled', { extensionId, exeName: basename(tip.windowsPath!) }); }); + if (recommendationsToSuggest.length === 0) { return false; } + const storageKey = 'extensionsAssistant/workspaceRecommendationsIgnore'; + const config = this.configurationService.getValue(ConfigurationKey); + + if (config.ignoreRecommendations + || config.showRecommendationsOnlyOnDemand + || this.storageService.getBoolean(storageKey, StorageScope.WORKSPACE, false)) { + return false; + } + recommendationsToSuggest = this.filterIgnoredOrNotAllowed(recommendationsToSuggest); if (recommendationsToSuggest.length === 0) { return false; @@ -1123,7 +1124,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe const storageKey = 'extensionsAssistant/dynamicWorkspaceRecommendations'; const workspaceUri = this.contextService.getWorkspace().folders[0].uri; - return Promise.all([this.workspaceStatsService.getHashedRemotesFromUri(workspaceUri, false), this.workspaceStatsService.getHashedRemotesFromUri(workspaceUri, true)]).then(([hashedRemotes1, hashedRemotes2]) => { + return Promise.all([this.workspaceTagsService.getHashedRemotesFromUri(workspaceUri, false), this.workspaceTagsService.getHashedRemotesFromUri(workspaceUri, true)]).then(([hashedRemotes1, hashedRemotes2]) => { const hashedRemotes = (hashedRemotes1 || []).concat(hashedRemotes2 || []); if (!hashedRemotes.length) { return undefined; @@ -1133,7 +1134,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe if (context.res.statusCode !== 200) { return Promise.resolve(undefined); } - return asJson(context).then((result: { [key: string]: any }) => { + return asJson(context).then((result: { [key: string]: any } | null) => { if (!result) { return; } diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 28454dbce4..858c08582c 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -3,7 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/extensions'; import { localize } from 'vs/nls'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -55,7 +54,7 @@ Registry.as(OutputExtensions.OutputChannels) // Quickopen Registry.as(Extensions.Quickopen).registerQuickOpenHandler( - new QuickOpenHandlerDescriptor( + QuickOpenHandlerDescriptor.create( ExtensionsHandler, ExtensionsHandler.ID, 'ext ', @@ -67,7 +66,7 @@ Registry.as(Extensions.Quickopen).registerQuickOpenHandler( // Editor Registry.as(EditorExtensions.Editors).registerEditor( - new EditorDescriptor( + EditorDescriptor.create( ExtensionEditor, ExtensionEditor.ID, localize('extension', "Extension") @@ -77,13 +76,12 @@ Registry.as(EditorExtensions.Editors).registerEditor( ]); // Viewlet -const viewletDescriptor = new ViewletDescriptor( +const viewletDescriptor = ViewletDescriptor.create( ExtensionsViewlet, VIEWLET_ID, localize('extensions', "Extensions"), - 'extensions', - // {{SQL CARBON EDIT}} - 14 + 'codicon-extensions', + 14 // {{SQL CARBON EDIT}} ); Registry.as(ViewletExtensions.Viewlets) @@ -92,67 +90,67 @@ Registry.as(ViewletExtensions.Viewlets) // Global actions const actionRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); -const openViewletActionDescriptor = new SyncActionDescriptor(OpenExtensionsViewletAction, OpenExtensionsViewletAction.ID, OpenExtensionsViewletAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_X }); +const openViewletActionDescriptor = SyncActionDescriptor.create(OpenExtensionsViewletAction, OpenExtensionsViewletAction.ID, OpenExtensionsViewletAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_X }); actionRegistry.registerWorkbenchAction(openViewletActionDescriptor, 'View: Show Extensions', localize('view', "View")); -const installActionDescriptor = new SyncActionDescriptor(InstallExtensionsAction, InstallExtensionsAction.ID, InstallExtensionsAction.LABEL); +const installActionDescriptor = SyncActionDescriptor.create(InstallExtensionsAction, InstallExtensionsAction.ID, InstallExtensionsAction.LABEL); actionRegistry.registerWorkbenchAction(installActionDescriptor, 'Extensions: Install Extensions', ExtensionsLabel); -const listOutdatedActionDescriptor = new SyncActionDescriptor(ShowOutdatedExtensionsAction, ShowOutdatedExtensionsAction.ID, ShowOutdatedExtensionsAction.LABEL); +const listOutdatedActionDescriptor = SyncActionDescriptor.create(ShowOutdatedExtensionsAction, ShowOutdatedExtensionsAction.ID, ShowOutdatedExtensionsAction.LABEL); actionRegistry.registerWorkbenchAction(listOutdatedActionDescriptor, 'Extensions: Show Outdated Extensions', ExtensionsLabel); -const recommendationsActionDescriptor = new SyncActionDescriptor(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, ShowRecommendedExtensionsAction.LABEL); +const recommendationsActionDescriptor = SyncActionDescriptor.create(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, ShowRecommendedExtensionsAction.LABEL); actionRegistry.registerWorkbenchAction(recommendationsActionDescriptor, 'Extensions: Show Recommended Extensions', ExtensionsLabel); -const keymapRecommendationsActionDescriptor = new SyncActionDescriptor(ShowRecommendedKeymapExtensionsAction, ShowRecommendedKeymapExtensionsAction.ID, ShowRecommendedKeymapExtensionsAction.SHORT_LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_M) }); +const keymapRecommendationsActionDescriptor = SyncActionDescriptor.create(ShowRecommendedKeymapExtensionsAction, ShowRecommendedKeymapExtensionsAction.ID, ShowRecommendedKeymapExtensionsAction.SHORT_LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_M) }); actionRegistry.registerWorkbenchAction(keymapRecommendationsActionDescriptor, 'Preferences: Keymaps', PreferencesLabel); -const languageExtensionsActionDescriptor = new SyncActionDescriptor(ShowLanguageExtensionsAction, ShowLanguageExtensionsAction.ID, ShowLanguageExtensionsAction.SHORT_LABEL); +const languageExtensionsActionDescriptor = SyncActionDescriptor.create(ShowLanguageExtensionsAction, ShowLanguageExtensionsAction.ID, ShowLanguageExtensionsAction.SHORT_LABEL); actionRegistry.registerWorkbenchAction(languageExtensionsActionDescriptor, 'Preferences: Language Extensions', PreferencesLabel); -const azureExtensionsActionDescriptor = new SyncActionDescriptor(ShowAzureExtensionsAction, ShowAzureExtensionsAction.ID, ShowAzureExtensionsAction.SHORT_LABEL); +const azureExtensionsActionDescriptor = SyncActionDescriptor.create(ShowAzureExtensionsAction, ShowAzureExtensionsAction.ID, ShowAzureExtensionsAction.SHORT_LABEL); actionRegistry.registerWorkbenchAction(azureExtensionsActionDescriptor, 'Preferences: Azure Extensions', PreferencesLabel); -const popularActionDescriptor = new SyncActionDescriptor(ShowPopularExtensionsAction, ShowPopularExtensionsAction.ID, ShowPopularExtensionsAction.LABEL); +const popularActionDescriptor = SyncActionDescriptor.create(ShowPopularExtensionsAction, ShowPopularExtensionsAction.ID, ShowPopularExtensionsAction.LABEL); actionRegistry.registerWorkbenchAction(popularActionDescriptor, 'Extensions: Show Popular Extensions', ExtensionsLabel); -const enabledActionDescriptor = new SyncActionDescriptor(ShowEnabledExtensionsAction, ShowEnabledExtensionsAction.ID, ShowEnabledExtensionsAction.LABEL); +const enabledActionDescriptor = SyncActionDescriptor.create(ShowEnabledExtensionsAction, ShowEnabledExtensionsAction.ID, ShowEnabledExtensionsAction.LABEL); actionRegistry.registerWorkbenchAction(enabledActionDescriptor, 'Extensions: Show Enabled Extensions', ExtensionsLabel); -const installedActionDescriptor = new SyncActionDescriptor(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, ShowInstalledExtensionsAction.LABEL); +const installedActionDescriptor = SyncActionDescriptor.create(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, ShowInstalledExtensionsAction.LABEL); actionRegistry.registerWorkbenchAction(installedActionDescriptor, 'Extensions: Show Installed Extensions', ExtensionsLabel); -const disabledActionDescriptor = new SyncActionDescriptor(ShowDisabledExtensionsAction, ShowDisabledExtensionsAction.ID, ShowDisabledExtensionsAction.LABEL); +const disabledActionDescriptor = SyncActionDescriptor.create(ShowDisabledExtensionsAction, ShowDisabledExtensionsAction.ID, ShowDisabledExtensionsAction.LABEL); actionRegistry.registerWorkbenchAction(disabledActionDescriptor, 'Extensions: Show Disabled Extensions', ExtensionsLabel); -const builtinActionDescriptor = new SyncActionDescriptor(ShowBuiltInExtensionsAction, ShowBuiltInExtensionsAction.ID, ShowBuiltInExtensionsAction.LABEL); +const builtinActionDescriptor = SyncActionDescriptor.create(ShowBuiltInExtensionsAction, ShowBuiltInExtensionsAction.ID, ShowBuiltInExtensionsAction.LABEL); actionRegistry.registerWorkbenchAction(builtinActionDescriptor, 'Extensions: Show Built-in Extensions', ExtensionsLabel); -const updateAllActionDescriptor = new SyncActionDescriptor(UpdateAllAction, UpdateAllAction.ID, UpdateAllAction.LABEL); +const updateAllActionDescriptor = SyncActionDescriptor.create(UpdateAllAction, UpdateAllAction.ID, UpdateAllAction.LABEL); actionRegistry.registerWorkbenchAction(updateAllActionDescriptor, 'Extensions: Update All Extensions', ExtensionsLabel); -const installVSIXActionDescriptor = new SyncActionDescriptor(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL); +const installVSIXActionDescriptor = SyncActionDescriptor.create(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL); actionRegistry.registerWorkbenchAction(installVSIXActionDescriptor, 'Extensions: Install from VSIX...', ExtensionsLabel); -const disableAllAction = new SyncActionDescriptor(DisableAllAction, DisableAllAction.ID, DisableAllAction.LABEL); +const disableAllAction = SyncActionDescriptor.create(DisableAllAction, DisableAllAction.ID, DisableAllAction.LABEL); actionRegistry.registerWorkbenchAction(disableAllAction, 'Extensions: Disable All Installed Extensions', ExtensionsLabel); -const disableAllWorkspaceAction = new SyncActionDescriptor(DisableAllWorkspaceAction, DisableAllWorkspaceAction.ID, DisableAllWorkspaceAction.LABEL); +const disableAllWorkspaceAction = SyncActionDescriptor.create(DisableAllWorkspaceAction, DisableAllWorkspaceAction.ID, DisableAllWorkspaceAction.LABEL); actionRegistry.registerWorkbenchAction(disableAllWorkspaceAction, 'Extensions: Disable All Installed Extensions for this Workspace', ExtensionsLabel); -const enableAllAction = new SyncActionDescriptor(EnableAllAction, EnableAllAction.ID, EnableAllAction.LABEL); +const enableAllAction = SyncActionDescriptor.create(EnableAllAction, EnableAllAction.ID, EnableAllAction.LABEL); actionRegistry.registerWorkbenchAction(enableAllAction, 'Extensions: Enable All Extensions', ExtensionsLabel); -const enableAllWorkspaceAction = new SyncActionDescriptor(EnableAllWorkspaceAction, EnableAllWorkspaceAction.ID, EnableAllWorkspaceAction.LABEL); +const enableAllWorkspaceAction = SyncActionDescriptor.create(EnableAllWorkspaceAction, EnableAllWorkspaceAction.ID, EnableAllWorkspaceAction.LABEL); actionRegistry.registerWorkbenchAction(enableAllWorkspaceAction, 'Extensions: Enable All Extensions for this Workspace', ExtensionsLabel); -const checkForUpdatesAction = new SyncActionDescriptor(CheckForUpdatesAction, CheckForUpdatesAction.ID, CheckForUpdatesAction.LABEL); +const checkForUpdatesAction = SyncActionDescriptor.create(CheckForUpdatesAction, CheckForUpdatesAction.ID, CheckForUpdatesAction.LABEL); actionRegistry.registerWorkbenchAction(checkForUpdatesAction, `Extensions: Check for Extension Updates`, ExtensionsLabel); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(EnableAutoUpdateAction, EnableAutoUpdateAction.ID, EnableAutoUpdateAction.LABEL), `Extensions: Enable Auto Updating Extensions`, ExtensionsLabel); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(DisableAutoUpdateAction, DisableAutoUpdateAction.ID, DisableAutoUpdateAction.LABEL), `Extensions: Disable Auto Updating Extensions`, ExtensionsLabel); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(InstallSpecificVersionOfExtensionAction, InstallSpecificVersionOfExtensionAction.ID, InstallSpecificVersionOfExtensionAction.LABEL), 'Install Specific Version of Extension...', ExtensionsLabel); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ReinstallAction, ReinstallAction.ID, ReinstallAction.LABEL), 'Reinstall Extension...', localize('developer', "Developer")); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(EnableAutoUpdateAction, EnableAutoUpdateAction.ID, EnableAutoUpdateAction.LABEL), `Extensions: Enable Auto Updating Extensions`, ExtensionsLabel); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(DisableAutoUpdateAction, DisableAutoUpdateAction.ID, DisableAutoUpdateAction.LABEL), `Extensions: Disable Auto Updating Extensions`, ExtensionsLabel); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(InstallSpecificVersionOfExtensionAction, InstallSpecificVersionOfExtensionAction.ID, InstallSpecificVersionOfExtensionAction.LABEL), 'Install Specific Version of Extension...', ExtensionsLabel); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ReinstallAction, ReinstallAction.ID, ReinstallAction.LABEL), 'Reinstall Extension...', localize('developer', "Developer")); Registry.as(ConfigurationExtensions.Configuration) .registerConfiguration({ @@ -281,12 +279,13 @@ CommandsRegistry.registerCommand({ throw new Error(localize('id required', "Extension id required.")); } const extensionManagementService = accessor.get(IExtensionManagementService); + const installed = await extensionManagementService.getInstalled(ExtensionType.User); + const [extensionToUninstall] = installed.filter(e => areSameExtensions(e.identifier, { id })); + if (!extensionToUninstall) { + throw new Error(localize('notInstalled', "Extension '{0}' is not installed. Make sure you use the full extension ID, including the publisher, e.g.: ms-vscode.csharp.", id)); + } + try { - const installed = await extensionManagementService.getInstalled(ExtensionType.User); - const [extensionToUninstall] = installed.filter(e => areSameExtensions(e.identifier, { id })); - if (!extensionToUninstall) { - return Promise.reject(new Error(localize('notInstalled', "Extension '{0}' is not installed. Make sure you use the full extension ID, including the publisher, e.g.: ms-vscode.csharp.", id))); - } await extensionManagementService.uninstall(extensionToUninstall, true); } catch (e) { onUnexpectedError(e); @@ -357,7 +356,7 @@ class ExtensionsContributions implements IWorkbenchContribution { if (canManageExtensions) { Registry.as(Extensions.Quickopen).registerQuickOpenHandler( - new QuickOpenHandlerDescriptor( + QuickOpenHandlerDescriptor.create( GalleryExtensionsHandler, GalleryExtensionsHandler.ID, 'ext install ', diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 48a98f3e52..bef9cdcedc 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -16,7 +16,7 @@ import { dispose, Disposable } from 'vs/base/common/lifecycle'; // {{SQL CARBON EDIT}} import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewlet, AutoUpdateConfigurationKey, IExtensionContainer, EXTENSIONS_CONFIG } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate'; -import { ExtensionsLabel, IGalleryExtension, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionsLabel, IGalleryExtension, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion, ILocalExtension, INSTALL_ERROR_NOT_SUPPORTED } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionTipsService, IExtensionRecommendation, IExtensionsConfigContent, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionType, ExtensionIdentifier, IExtensionDescription, IExtensionManifest, isLanguagePackExtension, ExtensionsPolicy, ExtensionsPolicyKey } from 'vs/platform/extensions/common/extensions'; // {{SQL CARBON EDIT}} @@ -55,12 +55,12 @@ import { alert } from 'vs/base/browser/ui/aria/aria'; import { coalesce } from 'vs/base/common/arrays'; import { IWorkbenchThemeService, COLOR_THEME_SETTING, ICON_THEME_SETTING, IFileIconTheme, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ILabelService } from 'vs/platform/label/common/label'; -import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; +import { prefersExecuteOnUI, prefersExecuteOnWorkspace } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IProductService } from 'vs/platform/product/common/productService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IFileDialogService, IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; // {{SQL CARBON EDIT}} @@ -76,31 +76,37 @@ export function toExtensionDescription(local: ILocalExtension): IExtensionDescri }; } -const promptDownloadManually = (extension: IGalleryExtension | undefined, message: string, error: Error, - instantiationService: IInstantiationService, notificationService: INotificationService, openerService: IOpenerService, productService: IProductService) => { - if (!extension || error.name === INSTALL_ERROR_INCOMPATIBLE || error.name === INSTALL_ERROR_MALICIOUS || !productService.extensionsGallery) { - return Promise.reject(error); - } else { - const downloadUrl = (extension.assets.downloadPage && extension.assets.downloadPage.uri) || extension.assets.download.uri; // {{SQL CARBON EDIT}} Use the URI directly since we don't have a marketplace hosting the packages - notificationService.prompt(Severity.Error, message, [{ - label: localize('download', "Download Manually"), - run: () => openerService.open(URI.parse(downloadUrl)).then(() => { - notificationService.prompt( - Severity.Info, - localize('install vsix', 'Once downloaded, please manually install the downloaded VSIX of \'{0}\'.', extension.identifier.id), - [{ - label: InstallVSIXAction.LABEL, - run: () => { - const action = instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL); - action.run(); - action.dispose(); - } - }] - ); - }) - }]); - return Promise.resolve(); - } +const promptDownloadManually = (extension: IGalleryExtension | undefined, message: string, error: Error, instantiationService: IInstantiationService): Promise => { + return instantiationService.invokeFunction(accessor => { + const productService = accessor.get(IProductService); + const openerService = accessor.get(IOpenerService); + const notificationService = accessor.get(INotificationService); + const dialogService = accessor.get(IDialogService); + const erorrsToShows = [INSTALL_ERROR_INCOMPATIBLE, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_NOT_SUPPORTED]; + if (!extension || erorrsToShows.indexOf(error.name) !== -1 || !productService.extensionsGallery) { + return dialogService.show(Severity.Error, error.message, []); + } else { + const downloadUrl = (extension.assets.downloadPage && extension.assets.downloadPage.uri) || extension.assets.download.uri; // {{SQL CARBON EDIT}} Use the URI directly since we don't have a marketplace hosting the packages + notificationService.prompt(Severity.Error, message, [{ + label: localize('download', "Download Manually"), + run: () => openerService.open(URI.parse(downloadUrl)).then(() => { + notificationService.prompt( + Severity.Info, + localize('install vsix', 'Once downloaded, please manually install the downloaded VSIX of \'{0}\'.', extension.identifier.id), + [{ + label: InstallVSIXAction.LABEL, + run: () => { + const action = instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL); + action.run(); + action.dispose(); + } + }] + ); + }) + }]); + return Promise.resolve(); + } + }); }; function getRelativeDateLabel(date: Date): string { @@ -138,9 +144,9 @@ function getRelativeDateLabel(date: Date): string { } export abstract class ExtensionAction extends Action implements IExtensionContainer { - private _extension: IExtension; - get extension(): IExtension { return this._extension; } - set extension(extension: IExtension) { this._extension = extension; this.update(); } + private _extension: IExtension | null = null; + get extension(): IExtension | null { return this._extension; } + set extension(extension: IExtension | null) { this._extension = extension; this.update(); } abstract update(): void; } @@ -153,7 +159,7 @@ export class InstallAction extends ExtensionAction { private static readonly InstallingClass = 'extension-action install installing'; - private _manifest: IExtensionManifest | null; + private _manifest: IExtensionManifest | null = null; set manifest(manifest: IExtensionManifest) { this._manifest = manifest; this.updateLabel(); @@ -163,7 +169,6 @@ export class InstallAction extends ExtensionAction { @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IInstantiationService private readonly instantiationService: IInstantiationService, @INotificationService private readonly notificationService: INotificationService, - @IOpenerService private readonly openerService: IOpenerService, @IExtensionService private readonly runtimeExtensionService: IExtensionService, @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, @IConfigurationService private readonly configurationService: IConfigurationService, @@ -196,12 +201,15 @@ export class InstallAction extends ExtensionAction { } private updateLabel(): void { + if (!this.extension) { + return; + } if (this.extension.state === ExtensionState.Installing) { this.label = InstallAction.INSTALLING_LABEL; this.tooltip = InstallAction.INSTALLING_LABEL; } else { if (this._manifest && this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) { - if (isUIExtension(this._manifest, this.productService, this.configurationService)) { + if (prefersExecuteOnUI(this._manifest, this.productService, this.configurationService)) { this.label = `${InstallAction.INSTALL_LABEL} ${localize('locally', "Locally")}`; this.tooltip = `${InstallAction.INSTALL_LABEL} ${localize('locally', "Locally")}`; } else { @@ -217,6 +225,9 @@ export class InstallAction extends ExtensionAction { } async run(): Promise { + if (!this.extension) { + return; + } this.extensionsWorkbenchService.open(this.extension); alert(localize('installExtensionStart', "Installing extension {0} started. An editor is now open with more details on this extension", this.extension.displayName)); @@ -263,7 +274,7 @@ export class InstallAction extends ExtensionAction { console.error(err); - return promptDownloadManually(extension.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", extension.identifier.id), err, this.instantiationService, this.notificationService, this.openerService, this.productService); + return promptDownloadManually(extension.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", extension.identifier.id), err, this.instantiationService); }); } @@ -315,7 +326,7 @@ export abstract class InstallInOtherServerAction extends ExtensionAction { // disabled by extension kind or it is a language pack extension && (this.extension.enablementState === EnablementState.DisabledByExtensionKind || isLanguagePackExtension(this.extension.local.manifest)) ) { - const extensionInOtherServer = this.extensionsWorkbenchService.installed.filter(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === this.server)[0]; + const extensionInOtherServer = this.extensionsWorkbenchService.installed.filter(e => areSameExtensions(e.identifier, this.extension!.identifier) && e.server === this.server)[0]; if (extensionInOtherServer) { // Getting installed in other server if (extensionInOtherServer.state === ExtensionState.Installing && !extensionInOtherServer.local) { @@ -332,6 +343,9 @@ export abstract class InstallInOtherServerAction extends ExtensionAction { } async run(): Promise { + if (!this.extension) { + return; + } if (this.server) { this.extensionsWorkbenchService.open(this.extension); alert(localize('installExtensionStart', "Installing extension {0} started. An editor is now open with more details on this extension", this.extension.displayName)); @@ -424,12 +438,14 @@ export class UninstallAction extends ExtensionAction { this.enabled = true; } - run(): Promise { + async run(): Promise { + if (!this.extension) { + return; + } alert(localize('uninstallExtensionStart', "Uninstalling extension {0} started.", this.extension.displayName)); return this.extensionsWorkbenchService.uninstall(this.extension).then(() => { - // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio - alert(localize('uninstallExtensionComplete', "Please reload Azure Data Studio to complete the uninstallation of the extension {0}.", this.extension.displayName)); + alert(localize('uninstallExtensionComplete', "Please reload Azure Data Studio to complete the uninstallation of the extension {0}.", this.extension!.displayName)); // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio }); } } @@ -510,8 +526,6 @@ export class UpdateAction extends ExtensionAction { @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IInstantiationService private readonly instantiationService: IInstantiationService, @INotificationService private readonly notificationService: INotificationService, - @IOpenerService private readonly openerService: IOpenerService, - @IProductService private readonly productService: IProductService ) { super(`extensions.update`, '', UpdateAction.DisabledClass, false); this.update(); @@ -540,14 +554,17 @@ export class UpdateAction extends ExtensionAction { this.label = this.extension.outdated ? this.getUpdateLabel(this.extension.latestVersion) : this.getUpdateLabel(); } - run(): Promise { + async run(): Promise { + if (!this.extension) { + return; + } alert(localize('updateExtensionStart', "Updating extension {0} to version {1} started.", this.extension.displayName, this.extension.latestVersion)); return this.install(this.extension); } private install(extension: IExtension): Promise { return this.extensionsWorkbenchService.install(extension).then(() => { - alert(localize('updateExtensionComplete', "Updating extension {0} to version {1} completed.", this.extension.displayName, this.extension.latestVersion)); + alert(localize('updateExtensionComplete', "Updating extension {0} to version {1} completed.", extension.displayName, extension.latestVersion)); }, err => { if (!extension.gallery) { return this.notificationService.error(err); @@ -562,7 +579,7 @@ export class UpdateAction extends ExtensionAction { console.error(err); - return promptDownloadManually(extension.gallery, localize('failedToUpdate', "Failed to update \'{0}\'.", extension.identifier.id), err, this.instantiationService, this.notificationService, this.openerService, this.productService); + return promptDownloadManually(extension.gallery, localize('failedToUpdate', "Failed to update \'{0}\'.", extension.identifier.id), err, this.instantiationService); }); } @@ -577,8 +594,6 @@ interface IExtensionActionViewItemOptions extends IActionViewItemOptions { export class ExtensionActionViewItem extends ActionViewItem { - protected options: IExtensionActionViewItemOptions; - constructor(context: any, action: IAction, options: IExtensionActionViewItemOptions = {}) { super(context, action, options); } @@ -586,14 +601,14 @@ export class ExtensionActionViewItem extends ActionViewItem { updateEnabled(): void { super.updateEnabled(); - if (this.label && this.options.tabOnlyOnFocus && this.getAction().enabled && !this._hasFocus) { + if (this.label && (this.options).tabOnlyOnFocus && this.getAction().enabled && !this._hasFocus) { DOM.removeTabIndexAndUpdateFocus(this.label); } } - private _hasFocus: boolean; + private _hasFocus: boolean = false; setFocus(value: boolean): void { - if (!this.options.tabOnlyOnFocus || this._hasFocus === value) { + if (!(this.options).tabOnlyOnFocus || this._hasFocus === value) { return; } this._hasFocus = value; @@ -620,7 +635,7 @@ export abstract class ExtensionDropDownAction extends ExtensionAction { super(id, label, cssClass, enabled); } - private _actionViewItem: DropDownMenuActionViewItem; + private _actionViewItem: DropDownMenuActionViewItem | null = null; createActionViewItem(): DropDownMenuActionViewItem { this._actionViewItem = this.instantiationService.createInstance(DropDownMenuActionViewItem, this, this.tabOnlyOnFocus); return this._actionViewItem; @@ -669,7 +684,7 @@ export class DropDownMenuActionViewItem extends ExtensionActionViewItem { export class ManageExtensionAction extends ExtensionDropDownAction { static readonly ID = 'extensions.manage'; - private static readonly Class = 'extension-action manage'; + private static readonly Class = 'extension-action manage codicon-gear'; private static readonly HideManageExtensionClass = `${ManageExtensionAction.Class} hide`; constructor( @@ -712,13 +727,14 @@ export class ManageExtensionAction extends ExtensionDropDownAction { groups.push([this.instantiationService.createInstance(UninstallAction)]); groups.push([this.instantiationService.createInstance(InstallAnotherVersionAction)]); - const extensionActions: ExtensionAction[] = [this.instantiationService.createInstance(ExtensionInfoAction)]; - if (this.extension.local && this.extension.local.manifest.contributes && this.extension.local.manifest.contributes.configuration) { - extensionActions.push(this.instantiationService.createInstance(ExtensionSettingsAction)); + if (this.extension) { + const extensionActions: ExtensionAction[] = [this.instantiationService.createInstance(ExtensionInfoAction)]; + if (this.extension.local && this.extension.local.manifest.contributes && this.extension.local.manifest.contributes.configuration) { + extensionActions.push(this.instantiationService.createInstance(ExtensionSettingsAction)); + } + groups.push(extensionActions); } - groups.push(extensionActions); - groups.forEach(group => group.forEach(extensionAction => extensionAction.extension = this.extension)); return groups; @@ -754,15 +770,13 @@ export class InstallAnotherVersionAction extends ExtensionAction { @IQuickInputService private readonly quickInputService: IQuickInputService, @IInstantiationService private readonly instantiationService: IInstantiationService, @INotificationService private readonly notificationService: INotificationService, - @IOpenerService private readonly openerService: IOpenerService, - @IProductService private readonly productService: IProductService ) { super(InstallAnotherVersionAction.ID, InstallAnotherVersionAction.LABEL); this.update(); } update(): void { - this.enabled = this.extension && !!this.extension.gallery; + this.enabled = !!this.extension && !!this.extension.gallery; } run(): Promise { @@ -772,19 +786,19 @@ export class InstallAnotherVersionAction extends ExtensionAction { return this.quickInputService.pick(this.getVersionEntries(), { placeHolder: localize('selectVersion', "Select Version to Install"), matchOnDetail: true }) .then(pick => { if (pick) { - if (this.extension.version === pick.id) { + if (this.extension!.version === pick.id) { return Promise.resolve(); } - const promise: Promise = pick.latest ? this.extensionsWorkbenchService.install(this.extension) : this.extensionsWorkbenchService.installVersion(this.extension, pick.id); + const promise: Promise = pick.latest ? this.extensionsWorkbenchService.install(this.extension!) : this.extensionsWorkbenchService.installVersion(this.extension!, pick.id); return promise .then(null, err => { - if (!this.extension.gallery) { + if (!this.extension!.gallery) { return this.notificationService.error(err); } console.error(err); - return promptDownloadManually(this.extension.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", this.extension.identifier.id), err, this.instantiationService, this.notificationService, this.openerService, this.productService); + return promptDownloadManually(this.extension!.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", this.extension!.identifier.id), err, this.instantiationService); }); } return null; @@ -792,8 +806,8 @@ export class InstallAnotherVersionAction extends ExtensionAction { } private getVersionEntries(): Promise<(IQuickPickItem & { latest: boolean, id: string })[]> { - return this.extensionGalleryService.getAllVersions(this.extension.gallery!, true) - .then(allVersions => allVersions.map((v, i) => ({ id: v.version, label: v.version, description: `${getRelativeDateLabel(new Date(Date.parse(v.date)))}${v.version === this.extension.version ? ` (${localize('current', "Current")})` : ''}`, latest: i === 0 }))); + return this.extensionGalleryService.getAllVersions(this.extension!.gallery!, true) + .then(allVersions => allVersions.map((v, i) => ({ id: v.version, label: v.version, description: `${getRelativeDateLabel(new Date(Date.parse(v.date)))}${v.version === this.extension!.version ? ` (${localize('current', "Current")})` : ''}`, latest: i === 0 }))); } } @@ -813,7 +827,10 @@ export class ExtensionInfoAction extends ExtensionAction { this.enabled = !!this.extension; } - run(): Promise { + async run(): Promise { + if (!this.extension) { + return; + } const name = localize('extensionInfoName', 'Name: {0}', this.extension.displayName); const id = localize('extensionInfoId', 'Id: {0}', this.extension.identifier.id); @@ -843,7 +860,11 @@ export class ExtensionSettingsAction extends ExtensionAction { update(): void { this.enabled = !!this.extension; } - run(): Promise { + + async run(): Promise { + if (!this.extension) { + return; + } this.preferencesService.openSettings(false, `@ext:${this.extension.identifier.id}`); return Promise.resolve(); } @@ -871,7 +892,10 @@ export class EnableForWorkspaceAction extends ExtensionAction { } } - run(): Promise { + async run(): Promise { + if (!this.extension) { + return; + } return this.extensionsWorkbenchService.setEnablement(this.extension, EnablementState.EnabledWorkspace); } } @@ -898,7 +922,10 @@ export class EnableGloballyAction extends ExtensionAction { } } - run(): Promise { + async run(): Promise { + if (!this.extension) { + return; + } return this.extensionsWorkbenchService.setEnablement(this.extension, EnablementState.EnabledGlobally); } } @@ -919,14 +946,17 @@ export class DisableForWorkspaceAction extends ExtensionAction { update(): void { this.enabled = false; - if (this.extension && this.extension.local && this.runningExtensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier) && this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY)) { + if (this.extension && this.extension.local && this.runningExtensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier) && this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY)) { this.enabled = this.extension.state === ExtensionState.Installed && (this.extension.enablementState === EnablementState.EnabledGlobally || this.extension.enablementState === EnablementState.EnabledWorkspace) && this.extensionEnablementService.canChangeEnablement(this.extension.local); } } - run(): Promise { + async run(): Promise { + if (!this.extension) { + return; + } return this.extensionsWorkbenchService.setEnablement(this.extension, EnablementState.DisabledWorkspace); } } @@ -946,14 +976,17 @@ export class DisableGloballyAction extends ExtensionAction { update(): void { this.enabled = false; - if (this.extension && this.extension.local && this.runningExtensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier))) { + if (this.extension && this.extension.local && this.runningExtensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier))) { this.enabled = this.extension.state === ExtensionState.Installed && (this.extension.enablementState === EnablementState.EnabledGlobally || this.extension.enablementState === EnablementState.EnabledWorkspace) && this.extensionEnablementService.canChangeEnablement(this.extension.local); } } - run(): Promise { + async run(): Promise { + if (!this.extension) { + return; + } return this.extensionsWorkbenchService.setEnablement(this.extension, EnablementState.DisabledGlobally); } } @@ -1038,6 +1071,7 @@ export class CheckForUpdatesAction extends Action { @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService, @IViewletService private readonly viewletService: IViewletService, + @IDialogService private readonly dialogService: IDialogService, @INotificationService private readonly notificationService: INotificationService ) { super(id, label, '', true); @@ -1046,7 +1080,7 @@ export class CheckForUpdatesAction extends Action { private checkUpdatesAndNotify(): void { const outdated = this.extensionsWorkbenchService.outdated; if (!outdated.length) { - this.notificationService.info(localize('noUpdatesAvailable', "All extensions are up to date.")); + this.dialogService.show(Severity.Info, localize('noUpdatesAvailable', "All extensions are up to date."), [localize('ok', "OK")]); return; } @@ -1138,8 +1172,6 @@ export class UpdateAllAction extends Action { @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @INotificationService private readonly notificationService: INotificationService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IOpenerService private readonly openerService: IOpenerService, - @IProductService private readonly productService: IProductService ) { super(id, label, '', false); @@ -1163,7 +1195,7 @@ export class UpdateAllAction extends Action { console.error(err); - return promptDownloadManually(extension.gallery, localize('failedToUpdate', "Failed to update \'{0}\'.", extension.identifier.id), err, this.instantiationService, this.notificationService, this.openerService, this.productService); + return promptDownloadManually(extension.gallery, localize('failedToUpdate', "Failed to update \'{0}\'.", extension.identifier.id), err, this.instantiationService); }); } } @@ -1181,7 +1213,9 @@ export class ReloadAction extends ExtensionAction { @IHostService private readonly hostService: IHostService, @IExtensionService private readonly extensionService: IExtensionService, @IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService, - @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService + @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, + @IProductService private readonly productService: IProductService, + @IConfigurationService private readonly configurationService: IConfigurationService, ) { super('extensions.reload', localize('reloadAction', "Reload"), ReloadAction.DisabledClass, false); this._register(this.extensionService.onDidChangeExtensions(this.updateRunningExtensions, this)); @@ -1210,11 +1244,11 @@ export class ReloadAction extends ExtensionAction { } private computeReloadState(): void { - if (!this._runningExtensions) { + if (!this._runningExtensions || !this.extension) { return; } const isUninstalled = this.extension.state === ExtensionState.Uninstalled; - const runningExtension = this._runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier))[0]; + const runningExtension = this._runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier))[0]; const isSameExtensionRunning = runningExtension && this.extension.server === this.extensionManagementServerService.getExtensionManagementServer(runningExtension.extensionLocation); if (isUninstalled) { @@ -1233,16 +1267,37 @@ export class ReloadAction extends ExtensionAction { // Extension is running if (runningExtension) { if (isEnabled) { - if (!this.extensionService.canAddExtension(toExtensionDescription(this.extension.local))) { - if (isSameExtensionRunning) { - if (this.extension.version !== runningExtension.version) { + // No Reload is required if extension can run without reload + if (this.extensionService.canAddExtension(toExtensionDescription(this.extension.local))) { + return; + } + if (isSameExtensionRunning) { + // Different version of same extension is running. Requires reload to run the current version + if (this.extension.version !== runningExtension.version) { + this.enabled = true; + this.label = localize('reloadRequired', "Reload Required"); + this.tooltip = localize('postUpdateTooltip', "Please reload Azure Data Studio to enable the updated extension."); // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio + } + } else { + const runningExtensionServer = this.extensionManagementServerService.getExtensionManagementServer(runningExtension.extensionLocation); + if (this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer) { + // This extension prefers to run on UI/Local side but is running in remote + if (prefersExecuteOnUI(this.extension.local!.manifest, this.productService, this.configurationService)) { this.enabled = true; this.label = localize('reloadRequired', "Reload Required"); - // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio - this.tooltip = localize('postUpdateTooltip', "Please reload Azure Data Studio to enable the updated extension."); + this.tooltip = localize('postEnableTooltip', "Please reload Azure Data Studio to enable this extension."); // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio + } + } + if (this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer) { + // This extension prefers to run on Workspace/Remote side but is running in local + if (prefersExecuteOnWorkspace(this.extension.local!.manifest, this.productService, this.configurationService)) { + this.enabled = true; + this.label = localize('reloadRequired', "Reload Required"); + this.tooltip = localize('postEnableTooltip', "Please reload Azure Data Studio to enable this extension."); // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio } } } + return; } else { if (isSameExtensionRunning) { this.enabled = true; @@ -1265,7 +1320,7 @@ export class ReloadAction extends ExtensionAction { const otherServer = this.extension.server ? this.extension.server === this.extensionManagementServerService.localExtensionManagementServer ? this.extensionManagementServerService.remoteExtensionManagementServer : this.extensionManagementServerService.localExtensionManagementServer : null; if (otherServer && this.extension.enablementState === EnablementState.DisabledByExtensionKind) { - const extensionInOtherServer = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === otherServer)[0]; + const extensionInOtherServer = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, this.extension!.identifier) && e.server === otherServer)[0]; // Same extension in other server exists and if (extensionInOtherServer && extensionInOtherServer.local && this.extensionEnablementService.isEnabled(extensionInOtherServer.local)) { this.enabled = true; @@ -1323,8 +1378,8 @@ export class SetColorThemeAction extends ExtensionAction { if (!this.enabled) { return; } - let extensionThemes = SetColorThemeAction.getColorThemes(this.colorThemes, this.extension); - const currentTheme = this.colorThemes.filter(t => t.settingsId === this.configurationService.getValue(COLOR_THEME_SETTING))[0]; + let extensionThemes = SetColorThemeAction.getColorThemes(this.colorThemes, this.extension!); + const currentTheme = this.colorThemes.filter(t => t.settingsId === this.configurationService.getValue(COLOR_THEME_SETTING))[0] || this.workbenchThemeService.getColorTheme(); showCurrentTheme = showCurrentTheme || extensionThemes.some(t => t.id === currentTheme.id); if (showCurrentTheme) { extensionThemes = extensionThemes.filter(t => t.id !== currentTheme.id); @@ -1389,7 +1444,7 @@ export class SetFileIconThemeAction extends ExtensionAction { if (!this.enabled) { return; } - let extensionThemes = SetFileIconThemeAction.getFileIconThemes(this.fileIconThemes, this.extension); + let extensionThemes = SetFileIconThemeAction.getFileIconThemes(this.fileIconThemes, this.extension!); const currentTheme = this.fileIconThemes.filter(t => t.settingsId === this.configurationService.getValue(ICON_THEME_SETTING))[0] || this.workbenchThemeService.getFileIconTheme(); showCurrentTheme = showCurrentTheme || extensionThemes.some(t => t.id === currentTheme.id); if (showCurrentTheme) { @@ -1518,7 +1573,7 @@ export class ClearExtensionsInputAction extends Action { value: string, @IViewletService private readonly viewletService: IViewletService ) { - super(id, label, 'clear-extensions', true); + super(id, label, 'codicon-clear-all', true); this.onSearchChange(value); this._register(onSearchChange(this.onSearchChange, this)); } @@ -1643,9 +1698,7 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action { label: string = InstallWorkspaceRecommendedExtensionsAction.LABEL, recommendations: IExtensionRecommendation[], @IViewletService private readonly viewletService: IViewletService, - @INotificationService private readonly notificationService: INotificationService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IOpenerService private readonly openerService: IOpenerService, @IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService, @IConfigurationService private readonly configurationService: IConfigurationService, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @@ -1676,7 +1729,7 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action { private async installExtension(extension: IExtension): Promise { try { if (extension.local && extension.gallery) { - if (isUIExtension(extension.local.manifest, this.productService, this.configurationService)) { + if (prefersExecuteOnUI(extension.local.manifest, this.productService, this.configurationService)) { if (this.extensionManagementServerService.localExtensionManagementServer) { await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(extension.gallery); return; @@ -1689,7 +1742,7 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action { await this.extensionWorkbenchService.install(extension); } catch (err) { console.error(err); - return promptDownloadManually(extension.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", extension.identifier.id), err, this.instantiationService, this.notificationService, this.openerService, this.productService); + return promptDownloadManually(extension.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", extension.identifier.id), err, this.instantiationService); } } } @@ -1704,11 +1757,8 @@ export class InstallRecommendedExtensionAction extends Action { constructor( extensionId: string, @IViewletService private readonly viewletService: IViewletService, - @INotificationService private readonly notificationService: INotificationService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IOpenerService private readonly openerService: IOpenerService, @IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService, - @IProductService private readonly productService: IProductService ) { super(InstallRecommendedExtensionAction.ID, InstallRecommendedExtensionAction.LABEL, undefined, false); this.extensionId = extensionId; @@ -1727,7 +1777,7 @@ export class InstallRecommendedExtensionAction extends Action { return this.extensionWorkbenchService.install(extension) .then(() => null, err => { console.error(err); - return promptDownloadManually(extension.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", extension.identifier.id), err, this.instantiationService, this.notificationService, this.openerService, this.productService); + return promptDownloadManually(extension.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", extension.identifier.id), err, this.instantiationService); }); } return null; @@ -1742,9 +1792,8 @@ export class IgnoreExtensionRecommendationAction extends Action { private static readonly Class = 'extension-action ignore'; - extension: IExtension; - constructor( + private readonly extension: IExtension, @IExtensionTipsService private readonly extensionsTipsService: IExtensionTipsService, ) { super(IgnoreExtensionRecommendationAction.ID, 'Ignore Recommendation'); @@ -1766,9 +1815,8 @@ export class UndoIgnoreExtensionRecommendationAction extends Action { private static readonly Class = 'extension-action undo-ignore'; - extension: IExtension; - constructor( + private readonly extension: IExtension, @IExtensionTipsService private readonly extensionsTipsService: IExtensionTipsService, ) { super(UndoIgnoreExtensionRecommendationAction.ID, 'Undo'); @@ -2124,7 +2172,7 @@ export abstract class AbstractConfigureRecommendedExtensionsAction extends Actio protected getWorkspaceFolderExtensionsConfigContent(extensionsFileResource: URI): Promise { return Promise.resolve(this.fileService.readFile(extensionsFileResource)) .then(content => { - return (json.parse(content.value.toString())); + return (json.parse(content.value.toString()) || {}) as IExtensionsConfigContent; // {{SQL CARBON EDIT}} strict-null-check }, err => ({ recommendations: [], unwantedRecommendations: [] })); } @@ -2412,9 +2460,9 @@ export class StatusLabelAction extends Action implements IExtensionContainer { private status: ExtensionState | null = null; private enablementState: EnablementState | null = null; - private _extension: IExtension; - get extension(): IExtension { return this._extension; } - set extension(extension: IExtension) { + private _extension: IExtension | null = null; + get extension(): IExtension | null { return this._extension; } + set extension(extension: IExtension | null) { if (!(this._extension && extension && areSameExtensions(this._extension.identifier, extension.identifier))) { // Different extension. Reset this.initialStatus = null; @@ -2455,21 +2503,21 @@ export class StatusLabelAction extends Action implements IExtensionContainer { const runningExtensions = await this.extensionService.getExtensions(); const canAddExtension = () => { - const runningExtension = runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier))[0]; - if (this.extension.local) { - if (runningExtension && this.extension.version === runningExtension.version) { + const runningExtension = runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier))[0]; + if (this.extension!.local) { + if (runningExtension && this.extension!.version === runningExtension.version) { return true; } - return this.extensionService.canAddExtension(toExtensionDescription(this.extension.local)); + return this.extensionService.canAddExtension(toExtensionDescription(this.extension!.local)); } return false; }; const canRemoveExtension = () => { - if (this.extension.local) { - if (runningExtensions.every(e => !(areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier) && this.extension.server === this.extensionManagementServerService.getExtensionManagementServer(e.extensionLocation)))) { + if (this.extension!.local) { + if (runningExtensions.every(e => !(areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier) && this.extension!.server === this.extensionManagementServerService.getExtensionManagementServer(e.extensionLocation)))) { return true; } - return this.extensionService.canRemoveExtension(toExtensionDescription(this.extension.local)); + return this.extensionService.canRemoveExtension(toExtensionDescription(this.extension!.local)); } return false; }; @@ -2572,7 +2620,7 @@ export class ExtensionToolTipAction extends ExtensionAction { return this.warningAction.tooltip; } if (this.extension && this.extension.local && this.extension.state === ExtensionState.Installed && this._runningExtensions) { - const isRunning = this._runningExtensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier)); + const isRunning = this._runningExtensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier)); const isEnabled = this.extensionEnablementService.isEnabled(this.extension.local); if (isEnabled && isRunning) { @@ -2609,8 +2657,8 @@ export class ExtensionToolTipAction extends ExtensionAction { export class SystemDisabledWarningAction extends ExtensionAction { private static readonly CLASS = 'system-disable'; - private static readonly WARNING_CLASS = `${SystemDisabledWarningAction.CLASS} warning`; - private static readonly INFO_CLASS = `${SystemDisabledWarningAction.CLASS} info`; + private static readonly WARNING_CLASS = `${SystemDisabledWarningAction.CLASS} codicon-warning`; + private static readonly INFO_CLASS = `${SystemDisabledWarningAction.CLASS} codicon-info`; updateWhenCounterExtensionChanges: boolean = true; private _runningExtensions: IExtensionDescription[] | null = null; @@ -2620,7 +2668,8 @@ export class SystemDisabledWarningAction extends ExtensionAction { @ILabelService private readonly labelService: ILabelService, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IExtensionService private readonly extensionService: IExtensionService, - @IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService, + @IProductService private readonly productService: IProductService, + @IConfigurationService private readonly configurationService: IConfigurationService, ) { super('extensions.install', '', `${SystemDisabledWarningAction.CLASS} hide`, false); this._register(this.labelService.onDidChangeFormatters(() => this.update(), this)); @@ -2641,37 +2690,48 @@ export class SystemDisabledWarningAction extends ExtensionAction { !this.extension.local || !this.extension.server || !this._runningExtensions || - !(this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) || this.extension.state !== ExtensionState.Installed ) { return; } - if (isLanguagePackExtension(this.extension.local.manifest)) { - if (!this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server !== this.extension.server)) { - this.class = `${SystemDisabledWarningAction.INFO_CLASS}`; - this.tooltip = this.extension.server === this.extensionManagementServerService.localExtensionManagementServer - ? localize('Install language pack also in remote server', "Install the language pack extension on '{0}' to enable it also there.", this.extensionManagementServerService.remoteExtensionManagementServer.label) - : localize('Install language pack also locally', "Install the language pack extension locally to enable it also there."); + if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) { + if (isLanguagePackExtension(this.extension.local.manifest)) { + if (!this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension!.identifier) && e.server !== this.extension!.server)) { + this.class = `${SystemDisabledWarningAction.INFO_CLASS}`; + this.tooltip = this.extension.server === this.extensionManagementServerService.localExtensionManagementServer + ? localize('Install language pack also in remote server', "Install the language pack extension on '{0}' to enable it also there.", this.extensionManagementServerService.remoteExtensionManagementServer.label) + : localize('Install language pack also locally', "Install the language pack extension locally to enable it also there."); + } + return; } - return; } - const runningExtension = this._runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier))[0]; - if (!runningExtension && this.extension.enablementState === EnablementState.DisabledByExtensionKind) { - this.class = `${SystemDisabledWarningAction.WARNING_CLASS}`; - const server = this.extensionManagementServerService.localExtensionManagementServer === this.extension.server ? this.extensionManagementServerService.remoteExtensionManagementServer : this.extensionManagementServerService.localExtensionManagementServer; - this.tooltip = localize('Install in other server to enable', "Install the extension on '{0}' to enable.", server.label); - return; + if (this.extension.enablementState === EnablementState.DisabledByExtensionKind) { + if (!this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension!.identifier) && e.server !== this.extension!.server)) { + const server = this.extensionManagementServerService.localExtensionManagementServer === this.extension.server ? this.extensionManagementServerService.remoteExtensionManagementServer : this.extensionManagementServerService.localExtensionManagementServer; + this.class = `${SystemDisabledWarningAction.WARNING_CLASS}`; + if (server) { + this.tooltip = localize('Install in other server to enable', "Install the extension on '{0}' to enable.", server.label); + } else { + this.tooltip = localize('disabled because of extension kind', "This extension cannot be enabled in the remote server."); + } + return; + } } - if (this.extensionEnablementService.isEnabled(this.extension.local)) { + if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) { + const runningExtension = this._runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier))[0]; const runningExtensionServer = runningExtension ? this.extensionManagementServerService.getExtensionManagementServer(runningExtension.extensionLocation) : null; if (this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer) { - this.class = `${SystemDisabledWarningAction.INFO_CLASS}`; - this.tooltip = localize('disabled locally', "Extension is enabled on '{0}' and disabled locally.", this.extensionManagementServerService.remoteExtensionManagementServer.label); + if (prefersExecuteOnWorkspace(this.extension.local!.manifest, this.productService, this.configurationService)) { + this.class = `${SystemDisabledWarningAction.INFO_CLASS}`; + this.tooltip = localize('disabled locally', "Extension is enabled on '{0}' and disabled locally.", this.extensionManagementServerService.remoteExtensionManagementServer.label); + } return; } if (this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer) { - this.class = `${SystemDisabledWarningAction.INFO_CLASS}`; - this.tooltip = localize('disabled remotely', "Extension is enabled locally and disabled on '{0}'.", this.extensionManagementServerService.remoteExtensionManagementServer.label); + if (prefersExecuteOnUI(this.extension.local!.manifest, this.productService, this.configurationService)) { + this.class = `${SystemDisabledWarningAction.INFO_CLASS}`; + this.tooltip = localize('disabled remotely', "Extension is enabled locally and disabled on '{0}'.", this.extensionManagementServerService.remoteExtensionManagementServer.label); + } return; } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsDependencyChecker.ts b/src/vs/workbench/contrib/extensions/browser/extensionsDependencyChecker.ts index 102eb8ee39..ce24b377d3 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsDependencyChecker.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsDependencyChecker.ts @@ -26,10 +26,10 @@ export class ExtensionDependencyChecker extends Disposable implements IWorkbench @IHostService private readonly hostService: IHostService ) { super(); - CommandsRegistry.registerCommand('workbench.extensions.installMissingDepenencies', () => this.installMissingDependencies()); + CommandsRegistry.registerCommand('workbench.extensions.installMissingDependencies', () => this.installMissingDependencies()); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { - id: 'workbench.extensions.installMissingDepenencies', + id: 'workbench.extensions.installMissingDependencies', category: localize('extensions', "Extensions"), title: localize('auto install missing deps', "Install Missing Dependencies") } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts index ec5dbe78f6..1ed548c5cc 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts @@ -21,6 +21,7 @@ import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/lis import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { CancellationToken } from 'vs/base/common/cancellation'; import { isNonEmptyArray } from 'vs/base/common/arrays'; +import { IColorMapping } from 'vs/platform/theme/common/styler'; export interface IExtensionTemplateData { icon: HTMLImageElement; @@ -179,6 +180,7 @@ export class ExtensionsTree extends WorkbenchAsyncDataTree + return this.progress(Promise.all(this.panes.map(view => (view).show(this.normalizedQuery()) .then(model => this.alertSearchResult(model.length, view.id)) ))).then(() => undefined); } - protected onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPanel[] { + protected onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPane[] { const addedViews = super.onDidAddViews(added); this.progress(Promise.all(addedViews.map(addedView => (addedView).show(this.normalizedQuery()) @@ -568,12 +568,12 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio } private count(): number { - return this.panels.reduce((count, view) => (view).count() + count, 0); + return this.panes.reduce((count, view) => (view).count() + count, 0); } private focusListView(): void { if (this.count() > 0) { - this.panels[0].focus(); + this.panes[0].focus(); } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index 2103ed95a9..48b57f60ab 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -32,7 +32,7 @@ import { InstallWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRe import { WorkbenchPagedList } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { distinct, coalesce, firstIndex } from 'vs/base/common/arrays'; import { IExperimentService, IExperiment, ExperimentActionType } from 'vs/workbench/contrib/experiments/common/experimentService'; @@ -47,6 +47,7 @@ import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async import { IProductService } from 'vs/platform/product/common/productService'; import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; class ExtensionsViewState extends Disposable implements IExtensionsViewState { @@ -72,7 +73,7 @@ export interface ExtensionsListViewOptions extends IViewletViewOptions { class ExtensionListViewWarning extends Error { } -export class ExtensionsListView extends ViewletPanel { +export class ExtensionsListView extends ViewletPane { protected readonly server: IExtensionManagementServer | undefined; private bodyTemplate: { @@ -105,7 +106,7 @@ export class ExtensionsListView extends ViewletPanel { @IProductService protected readonly productService: IProductService, @IContextKeyService contextKeyService: IContextKeyService, ) { - super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title, showActionsAlways: true }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewletPaneOptions), ariaHeaderLabel: options.title, showActionsAlways: true }, keybindingService, contextMenuService, configurationService, contextKeyService); this.server = options.server; } @@ -125,11 +126,14 @@ export class ExtensionsListView extends ViewletPanel { const delegate = new Delegate(); const extensionsViewState = new ExtensionsViewState(); const renderer = this.instantiationService.createInstance(Renderer, extensionsViewState); - this.list = this.instantiationService.createInstance(WorkbenchPagedList, 'Extensions', extensionsList, delegate, [renderer], { + this.list = this.instantiationService.createInstance>(WorkbenchPagedList, 'Extensions', extensionsList, delegate, [renderer], { ariaLabel: localize('extensions', "Extensions"), multipleSelectionSupport: false, setRowLineHeight: false, - horizontalScrolling: false + horizontalScrolling: false, + overrideStyles: { + listBackground: SIDE_BAR_BACKGROUND + } }); this._register(this.list.onContextMenu(e => this.onContextMenu(e), this)); this._register(this.list.onFocusChange(e => extensionsViewState.onFocusChange(coalesce(e.elements)), this)); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index b90e7b77e6..6da8615486 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -121,18 +121,18 @@ export class RatingsWidget extends ExtensionWidget { const rating = Math.round(this.extension.rating * 2) / 2; if (this.small) { - append(this.container, $('span.full.star')); + append(this.container, $('span.codicon.codicon-star-full')); const count = append(this.container, $('span.count')); count.textContent = String(rating); } else { for (let i = 1; i <= 5; i++) { if (rating >= i) { - append(this.container, $('span.full.star')); + append(this.container, $('span.codicon.codicon-star-full')); } else if (rating >= i - 0.5) { - append(this.container, $('span.half.star')); + append(this.container, $('span.codicon.codicon-star-half')); } else { - append(this.container, $('span.empty.star')); + append(this.container, $('span.codicon.codicon-star-empty')); } } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 56f1da277f..156b2fb3ba 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -525,12 +525,12 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension @IOpenerService private readonly openerService: IOpenerService // {{SQL CARBON EDIT}} ) { super(); - if (this.extensionManagementServerService.localExtensionManagementServer) { + if (extensionManagementServerService.localExtensionManagementServer) { this.localExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.localExtensionManagementServer, ext => this.getExtensionState(ext))); this._register(this.localExtensions.onChange(e => this._onChange.fire(e ? e.extension : undefined))); this._register(Event.filter(this.localExtensions.onChange, e => !!e && e.operation === InstallOperation.Install)(e => this.onDidInstallExtension(e!.extension))); } - if (this.extensionManagementServerService.remoteExtensionManagementServer) { + if (extensionManagementServerService.remoteExtensionManagementServer) { this.remoteExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.remoteExtensionManagementServer, ext => this.getExtensionState(ext))); this._register(this.remoteExtensions.onChange(e => this._onChange.fire(e ? e.extension : undefined))); this._register(Event.filter(this.remoteExtensions.onChange, e => !!e && e.operation === InstallOperation.Install)(e => this.onDidInstallExtension(e!.extension))); diff --git a/src/vs/workbench/contrib/extensions/browser/media/clear-dark.svg b/src/vs/workbench/contrib/extensions/browser/media/clear-dark.svg deleted file mode 100644 index 04d64ab41c..0000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/clear-dark.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/extensions/browser/media/clear-hc.svg b/src/vs/workbench/contrib/extensions/browser/media/clear-hc.svg deleted file mode 100644 index 44a41edd3b..0000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/clear-hc.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/extensions/browser/media/clear-light.svg b/src/vs/workbench/contrib/extensions/browser/media/clear-light.svg deleted file mode 100644 index f6a51c856f..0000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/clear-light.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/extensions/browser/media/configure-dark.svg b/src/vs/workbench/contrib/extensions/browser/media/configure-dark.svg deleted file mode 100644 index ace01a5ddf..0000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/configure-dark.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/vs/workbench/contrib/extensions/browser/media/configure-hc.svg b/src/vs/workbench/contrib/extensions/browser/media/configure-hc.svg deleted file mode 100644 index bd59cb81f6..0000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/configure-hc.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/vs/workbench/contrib/extensions/browser/media/configure-light.svg b/src/vs/workbench/contrib/extensions/browser/media/configure-light.svg deleted file mode 100644 index 4194780bba..0000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/configure-light.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css b/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css index 585422ab33..1c1dd0f47b 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css @@ -12,18 +12,6 @@ text-overflow: ellipsis; } -.monaco-action-bar .action-item .action-label.clear-extensions { - background: url('clear-light.svg') center center no-repeat; -} - -.vs-dark .monaco-action-bar .action-item .action-label.clear-extensions { - background: url('clear-dark.svg') center center no-repeat; -} - -.hc-black .monaco-action-bar .action-item .action-label.clear-extensions { - background: url('clear-hc.svg') center center no-repeat; -} - .monaco-action-bar .action-item .action-label.extension-action.multiserver.install:after, .monaco-action-bar .action-item .action-label.extension-action.multiserver.update:after, .monaco-action-bar .action-item .action-label.extension-action.extension-editor-dropdown-action.dropdown:after { @@ -70,12 +58,18 @@ .extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item > .action-label.disable-status { margin-left: 0; + margin-top: 6px; padding-left: 0; } -.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item > .action-label.system-disable, -.extensions-viewlet>.extensions .extension>.details>.footer>.monaco-action-bar .action-item .action-label.system-disable { - margin: 0.15em; +.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item > .action-label.system-disable { + margin-right: 0.15em; +} + +.extensions-viewlet>.extensions .extension>.details>.footer>.monaco-action-bar .action-item .action-label.system-disable.codicon-info, +.extensions-viewlet>.extensions .extension>.details>.footer>.monaco-action-bar .action-item .action-label.system-disable.codicon-warning { + margin-top: 0.25em; + margin-left: 0.1em; } .monaco-action-bar .action-item .action-label.system-disable.codicon { @@ -104,17 +98,10 @@ height: 18px; width: 10px; border: none; - background: url('configure-light.svg') center center no-repeat; + color: inherit; + background: none; outline-offset: 0px; - margin-top: 0.15em -} - -.vs-dark .extensions-viewlet>.extensions .extension>.details>.footer>.monaco-action-bar .action-item .action-label.extension-action.manage { - background: url('configure-dark.svg') center center no-repeat; -} - -.hc-black .extensions-viewlet>.extensions .extension>.details>.footer>.monaco-action-bar .action-item .action-label.extension-action.manage { - background: url('configure-hc.svg') center center no-repeat; + margin-top: 0.2em; } .extension-editor > .header.recommended > .details > .recommendation > .monaco-action-bar .actions-container { diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index aed18ad134..3dbadc23a9 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -54,6 +54,7 @@ padding-left: 20px; overflow: hidden; user-select: text; + -webkit-user-select: text; } .extension-editor > .header > .details > .title { @@ -77,6 +78,7 @@ padding: 0px 4px; border-radius: 4px; user-select: text; + -webkit-user-select: text; white-space: nowrap; } @@ -98,6 +100,7 @@ padding: 0px 4px; border-radius: 4px; user-select: none; + -webkit-user-select: none; } .extension-editor > .header > .details > .subtitle { @@ -111,6 +114,13 @@ font-size: 18px; } +.extension-editor > .header > .details > .subtitle, +.extension-editor > .header > .details > .subtitle .install, +.extension-editor > .header > .details > .subtitle .rating { + display: flex; + align-items: center; +} + .extension-editor > .header > .details > .subtitle > .install > .count { margin-left: 6px; } @@ -219,6 +229,7 @@ position: relative; overflow: hidden; user-select: text; + -webkit-user-select: text; } .extension-editor > .body > .content.loading { diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensions-activity-bar.svg b/src/vs/workbench/contrib/extensions/browser/media/extensions-activity-bar.svg deleted file mode 100644 index 93e4b554da..0000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/extensions-activity-bar.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensions.css b/src/vs/workbench/contrib/extensions/browser/media/extensions.css deleted file mode 100644 index d581b73aab..0000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/extensions.css +++ /dev/null @@ -1,8 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-workbench .activitybar > .content .monaco-action-bar .action-label.extensions { - -webkit-mask: url('extensions-activity-bar.svg') no-repeat 50% 50%; -} diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css b/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css index 7e0a854e3c..c734f95fd2 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css @@ -21,10 +21,11 @@ padding: 4px; border: 1px solid transparent; -webkit-appearance: textfield; + -moz-appearance: textfield; } .extensions-viewlet > .extensions { - height: calc(100% - 38px); + height: calc(100% - 41px); } .extensions-viewlet > .extensions .extension-view-header .monaco-action-bar { @@ -89,7 +90,7 @@ top: 1px; left: 1px; color: inherit; - font-size: 90%; + font-size: 80%; } .extensions-viewlet > .extensions .extension { @@ -190,20 +191,28 @@ font-size: 80%; padding-left: 6px; min-width: fit-content; + min-width: -moz-fit-content; } .extensions-viewlet:not(.narrow) > .extensions .extension > .details > .header-container > .header > .version { flex: 1; } +.extensions-viewlet > .extensions .extension > .details > .header-container > .header > .install-count, +.extensions-viewlet > .extensions .extension > .details > .header-container > .header > .ratings { + display: flex; + align-items: center; +} + .extensions-viewlet > .extensions .extension > .details > .header-container > .header > .install-count:not(:empty) { font-size: 80%; margin: 0 6px; } -.extensions-viewlet > .extensions .extension > .details > .header-container > .header > .install-count > .codicon { +.extensions-viewlet > .extensions .extension > .details > .header-container > .header .codicon { font-size: 120%; margin-right: 2px; + -webkit-mask: inherit; } .extensions-viewlet > .extensions .extension > .details > .header-container > .header > .ratings { diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionsWidgets.css b/src/vs/workbench/contrib/extensions/browser/media/extensionsWidgets.css index f267cc5de5..f2db18e4fa 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionsWidgets.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionsWidgets.css @@ -11,40 +11,24 @@ font-size: 80%; } -.extension-ratings > .star { - display: inline-block; - width: 16px; - height: 16px; - background-repeat: no-repeat; - background-position: center center; -} - -.extension-ratings > .star:not(:first-child) { +.extension-ratings > .codicon[class*='codicon-star']:not(:first-child) { margin-left: 3px; } -.extension-ratings.small > .star { - width: 10px; - height: 10px; - background-image: url('star-small.svg'); -} - -.extension-ratings > .full { - background-image: url('star-full.svg'); -} - -.extension-ratings > .half { - background-image: url('star-half.svg'); -} - -.extension-ratings > .empty { - background-image: url('star-empty.svg'); -} - .extension-ratings > .count { margin-left: 6px; } .extension-ratings.small > .count { - margin-left: 2px; -} \ No newline at end of file + margin-left: 0; +} + +/* TODO @misolori make this a color token */ +.extension-ratings .codicon-star-full, +.extension-ratings .codicon-star-half { + color: #FF8E00 !important; +} + +.extension-ratings .codicon-star-empty { + opacity: .4; +} diff --git a/src/vs/workbench/contrib/extensions/browser/media/star-empty.svg b/src/vs/workbench/contrib/extensions/browser/media/star-empty.svg deleted file mode 100644 index 999de68181..0000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/star-empty.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/contrib/extensions/browser/media/star-full.svg b/src/vs/workbench/contrib/extensions/browser/media/star-full.svg deleted file mode 100644 index 1603b080b2..0000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/star-full.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/extensions/browser/media/star-half.svg b/src/vs/workbench/contrib/extensions/browser/media/star-half.svg deleted file mode 100644 index 7615f58165..0000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/star-half.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/extensions/browser/media/star-small.svg b/src/vs/workbench/contrib/extensions/browser/media/star-small.svg deleted file mode 100644 index 41c128627f..0000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/star-small.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts index 39cfaeed38..f85d69efb1 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts @@ -81,7 +81,7 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio if (visible) { const indicator: IStatusbarEntry = { - text: nls.localize('profilingExtensionHost', "$(sync~spin) Profiling Extension Host"), + text: '$(sync~spin) ' + nls.localize('profilingExtensionHost', "Profiling Extension Host"), tooltip: nls.localize('selectAndStartDebug', "Click to stop profiling."), command: 'workbench.action.extensionHostProfilder.stop' }; @@ -89,7 +89,7 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio const timeStarted = Date.now(); const handle = setInterval(() => { if (this.profilingStatusBarIndicator) { - this.profilingStatusBarIndicator.update({ ...indicator, text: nls.localize('profilingExtensionHostTime', "$(sync~spin) Profiling Extension Host ({0} sec)", Math.round((new Date().getTime() - timeStarted) / 1000)), }); + this.profilingStatusBarIndicator.update({ ...indicator, text: '$(sync~spin) ' + nls.localize('profilingExtensionHostTime', "Profiling Extension Host ({0} sec)", Math.round((new Date().getTime() - timeStarted) / 1000)), }); } }, 1000); this.profilingStatusBarIndicatorLabelUpdater.value = toDisposable(() => clearInterval(handle)); diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts index 88e8721741..f6436341e1 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts @@ -34,7 +34,7 @@ workbenchRegistry.registerWorkbenchContribution(ExtensionsAutoProfiler, Lifecycl // Running Extensions Editor -const runtimeExtensionsEditorDescriptor = new EditorDescriptor( +const runtimeExtensionsEditorDescriptor = EditorDescriptor.create( RuntimeExtensionsEditor, RuntimeExtensionsEditor.ID, localize('runtimeExtension', "Running Extensions") @@ -58,7 +58,7 @@ Registry.as(EditorInputExtensions.EditorInputFactor // Global actions const actionRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowRuntimeExtensionsAction, ShowRuntimeExtensionsAction.ID, ShowRuntimeExtensionsAction.LABEL), 'Show Running Extensions', localize('developer', "Developer")); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ShowRuntimeExtensionsAction, ShowRuntimeExtensionsAction.ID, ShowRuntimeExtensionsAction.LABEL), 'Show Running Extensions', localize('developer', "Developer")); class ExtensionsContributions implements IWorkbenchContribution { @@ -66,7 +66,7 @@ class ExtensionsContributions implements IWorkbenchContribution { @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService ) { if (workbenchEnvironmentService.extensionsPath) { - const openExtensionsFolderActionDescriptor = new SyncActionDescriptor(OpenExtensionsFolderAction, OpenExtensionsFolderAction.ID, OpenExtensionsFolderAction.LABEL); + const openExtensionsFolderActionDescriptor = SyncActionDescriptor.create(OpenExtensionsFolderAction, OpenExtensionsFolderAction.ID, OpenExtensionsFolderAction.LABEL); actionRegistry.registerWorkbenchAction(openExtensionsFolderActionDescriptor, 'Extensions: Open Extensions Folder', ExtensionsLabel); } } @@ -102,7 +102,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: DebugExtensionHostAction.ID, title: DebugExtensionHostAction.LABEL, - iconLocation: { + icon: { dark: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/extensions/browser/media/start-dark.svg`)), light: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/extensions/browser/media/start-light.svg`)), } @@ -115,7 +115,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: StartExtensionHostProfileAction.ID, title: StartExtensionHostProfileAction.LABEL, - iconLocation: { + icon: { dark: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/extensions/browser/media/profile-start-dark.svg`)), light: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/extensions/browser/media/profile-start-light.svg`)), } @@ -128,7 +128,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: StopExtensionHostProfileAction.ID, title: StopExtensionHostProfileAction.LABEL, - iconLocation: { + icon: { dark: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/extensions/browser/media/profile-stop-dark.svg`)), light: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/extensions/browser/media/profile-stop-light.svg`)), } @@ -141,7 +141,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: SaveExtensionHostProfileAction.ID, title: SaveExtensionHostProfileAction.LABEL, - iconLocation: { + icon: { dark: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/extensions/browser/media/save-dark.svg`)), light: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/extensions/browser/media/save-light.svg`)), }, diff --git a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts index 84d8459b0a..237120bf10 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -45,6 +45,7 @@ import { SlowExtensionAction } from 'vs/workbench/contrib/extensions/electron-br import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; +import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; export const IExtensionHostProfileService = createDecorator('extensionHostProfileService'); export const CONTEXT_PROFILE_SESSION_STATE = new RawContextKey('profileSessionState', 'none'); @@ -405,12 +406,15 @@ export class RuntimeExtensionsEditor extends BaseEditor { } }; - this._list = this._instantiationService.createInstance(WorkbenchList, + this._list = this._instantiationService.createInstance>(WorkbenchList, 'RuntimeExtensions', parent, delegate, [renderer], { multipleSelectionSupport: false, setRowLineHeight: false, - horizontalScrolling: false + horizontalScrolling: false, + overrideStyles: { + listBackground: editorBackground + } }); this._list.splice(0, this._list.length, this._elements || undefined); diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index 1bba4ecdf8..0f84c9c4fb 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -497,7 +497,7 @@ suite('ExtensionsActions Test', () => { .then(extensions => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('extension-action manage', testObject.class); + assert.equal('extension-action manage codicon-gear', testObject.class); assert.equal('', testObject.tooltip); }); }); @@ -512,7 +512,7 @@ suite('ExtensionsActions Test', () => { .then(page => { testObject.extension = page.firstPage[0]; assert.ok(!testObject.enabled); - assert.equal('extension-action manage hide', testObject.class); + assert.equal('extension-action manage codicon-gear hide', testObject.class); assert.equal('', testObject.tooltip); }); }); @@ -529,7 +529,7 @@ suite('ExtensionsActions Test', () => { installEvent.fire({ identifier: gallery.identifier, gallery }); assert.ok(!testObject.enabled); - assert.equal('extension-action manage hide', testObject.class); + assert.equal('extension-action manage codicon-gear hide', testObject.class); assert.equal('', testObject.tooltip); }); }); @@ -547,7 +547,7 @@ suite('ExtensionsActions Test', () => { didInstallEvent.fire({ identifier: gallery.identifier, gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) }); assert.ok(testObject.enabled); - assert.equal('extension-action manage', testObject.class); + assert.equal('extension-action manage codicon-gear', testObject.class); assert.equal('', testObject.tooltip); }); }); @@ -562,7 +562,7 @@ suite('ExtensionsActions Test', () => { .then(extensions => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('extension-action manage', testObject.class); + assert.equal('extension-action manage codicon-gear', testObject.class); assert.equal('', testObject.tooltip); }); }); @@ -579,7 +579,7 @@ suite('ExtensionsActions Test', () => { uninstallEvent.fire(local.identifier); assert.ok(!testObject.enabled); - assert.equal('extension-action manage', testObject.class); + assert.equal('extension-action manage codicon-gear', testObject.class); assert.equal('Uninstalling', testObject.tooltip); }); }); @@ -1362,8 +1362,8 @@ suite('ExtensionsActions Test', () => { test('Test ReloadAction when extension is not installed but extension from different server is installed and running', async () => { // multi server setup const gallery = aGalleryExtension('a'); - const localExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file('pub.a') }); - const remoteExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); + const localExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file('pub.a') }); + const remoteExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([remoteExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1391,8 +1391,8 @@ suite('ExtensionsActions Test', () => { test('Test ReloadAction when extension is uninstalled but extension from different server is installed and running', async () => { // multi server setup const gallery = aGalleryExtension('a'); - const localExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file('pub.a') }); - const remoteExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); + const localExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file('pub.a') }); + const remoteExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); const localExtensionManagementService = createExtensionManagementService([localExtension]); const uninstallEvent = new Emitter(); const onDidUninstallEvent = new Emitter<{ identifier: IExtensionIdentifier }>(); @@ -1433,7 +1433,7 @@ suite('ExtensionsActions Test', () => { const remoteExtensionManagementService = createExtensionManagementService([]); const onDidInstallEvent = new Emitter(); remoteExtensionManagementService.onDidInstallExtension = onDidInstallEvent.event; - const localExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file('pub.a') }); + const localExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file('pub.a') }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), remoteExtensionManagementService); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1456,20 +1456,20 @@ suite('ExtensionsActions Test', () => { assert.ok(testObject.extension); assert.ok(!testObject.enabled); - const remoteExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); + const remoteExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); onDidInstallEvent.fire({ identifier: remoteExtension.identifier, local: remoteExtension, operation: InstallOperation.Install }); assert.ok(testObject.enabled); assert.equal(testObject.tooltip, 'Please reload Azure Data Studio to enable this extension.'); // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio }); - test('Test ReloadAction is disabled when remote ui extension is installed in local server', async () => { + test('Test ReloadAction when ui extension is disabled on remote server and installed in local server', async () => { // multi server setup const gallery = aGalleryExtension('a'); const localExtensionManagementService = createExtensionManagementService([]); const onDidInstallEvent = new Emitter(); localExtensionManagementService.onDidInstallExtension = onDidInstallEvent.event; - const remoteExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); + const remoteExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, localExtensionManagementService, createExtensionManagementService([remoteExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1478,7 +1478,7 @@ suite('ExtensionsActions Test', () => { const onDidChangeExtensionsEmitter: Emitter = new Emitter(); instantiationService.stub(IExtensionService, >{ - getExtensions: () => Promise.resolve([ExtensionsActions.toExtensionDescription(remoteExtension)]), + getExtensions: () => Promise.resolve([]), onDidChangeExtensions: onDidChangeExtensionsEmitter.event, canAddExtension: (extension) => false }); @@ -1492,20 +1492,21 @@ suite('ExtensionsActions Test', () => { assert.ok(testObject.extension); assert.ok(!testObject.enabled); - const localExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file('pub.a') }); + const localExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file('pub.a') }); onDidInstallEvent.fire({ identifier: localExtension.identifier, local: localExtension, operation: InstallOperation.Install }); - assert.ok(!testObject.enabled); + assert.ok(testObject.enabled); + assert.equal(testObject.tooltip, 'Please reload Azure Data Studio to enable this extension.'); // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio }); test('Test ReloadAction for remote ui extension is disabled when it is installed and enabled in local server', async () => { // multi server setup const gallery = aGalleryExtension('a'); - const localExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file('pub.a') }); + const localExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file('pub.a') }); const localExtensionManagementService = createExtensionManagementService([localExtension]); const onDidInstallEvent = new Emitter(); localExtensionManagementService.onDidInstallExtension = onDidInstallEvent.event; - const remoteExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); + const remoteExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, localExtensionManagementService, createExtensionManagementService([remoteExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1529,40 +1530,9 @@ suite('ExtensionsActions Test', () => { assert.ok(!testObject.enabled); }); - test('Test ReloadAction for local ui extension is disabled when it is installed and enabled in remote server', async () => { - // multi server setup - const gallery = aGalleryExtension('a'); - const localExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file('pub.a') }); - const localExtensionManagementService = createExtensionManagementService([localExtension]); - const onDidInstallEvent = new Emitter(); - localExtensionManagementService.onDidInstallExtension = onDidInstallEvent.event; - const remoteExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); - const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, localExtensionManagementService, createExtensionManagementService([remoteExtension])); - instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); - instantiationService.set(IExtensionsWorkbenchService, workbenchService); - - const onDidChangeExtensionsEmitter: Emitter = new Emitter(); - instantiationService.stub(IExtensionService, >{ - getExtensions: () => Promise.resolve([ExtensionsActions.toExtensionDescription(remoteExtension)]), - onDidChangeExtensions: onDidChangeExtensionsEmitter.event, - canAddExtension: (extension) => false - }); - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); - instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - - await workbenchService.queryGallery(CancellationToken.None); - const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); - testObject.extension = extensions[0]; - assert.ok(testObject.extension); - assert.ok(!testObject.enabled); - }); - test('Test remote install action is enabled for local workspace extension', async () => { // multi server setup - const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1586,7 +1556,7 @@ suite('ExtensionsActions Test', () => { const remoteExtensionManagementService: IExtensionManagementService = createExtensionManagementService(); const onInstallExtension = new Emitter(); remoteExtensionManagementService.onInstallExtension = onInstallExtension.event; - const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension]), remoteExtensionManagementService); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1619,7 +1589,7 @@ suite('ExtensionsActions Test', () => { remoteExtensionManagementService.onInstallExtension = onInstallExtension.event; const onDidInstallEvent = new Emitter(); remoteExtensionManagementService.onDidInstallExtension = onDidInstallEvent.event; - const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension]), remoteExtensionManagementService); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1644,14 +1614,14 @@ suite('ExtensionsActions Test', () => { assert.equal('Installing', testObject.label); assert.equal('extension-action install installing', testObject.class); - const installedExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const installedExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); onDidInstallEvent.fire({ identifier: installedExtension.identifier, local: installedExtension, operation: InstallOperation.Install }); assert.ok(!testObject.enabled); }); test('Test remote install action is enabled for disabled local workspace extension', async () => { // multi server setup - const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1673,7 +1643,7 @@ suite('ExtensionsActions Test', () => { test('Test remote install action is disabled when extension is not set', async () => { // multi server setup - const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension])); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); @@ -1708,7 +1678,7 @@ suite('ExtensionsActions Test', () => { test('Test remote install action is disabled for local workspace extension which is disabled in env', async () => { // multi server setup - const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension])); instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: true } as IWorkbenchEnvironmentService); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); @@ -1731,7 +1701,7 @@ suite('ExtensionsActions Test', () => { // single server setup const workbenchService = instantiationService.get(IExtensionsWorkbenchService); const extensionManagementServerService = instantiationService.get(IExtensionManagementServerService); - const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [localWorkspaceExtension]); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); @@ -1750,7 +1720,7 @@ suite('ExtensionsActions Test', () => { const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, extensionManagementService); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [localWorkspaceExtension]); const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); instantiationService.set(IExtensionsWorkbenchService, workbenchService); @@ -1771,8 +1741,8 @@ suite('ExtensionsActions Test', () => { test('Test remote install action is disabled for local workspace extension if it is installed in remote', async () => { // multi server setup - const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); - const remoteWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); + const remoteWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension]), createExtensionManagementService([remoteWorkspaceExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1792,7 +1762,7 @@ suite('ExtensionsActions Test', () => { test('Test remote install action is enabled for local workspace extension if it has not gallery', async () => { // multi server setup - const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1811,7 +1781,7 @@ suite('ExtensionsActions Test', () => { test('Test remote install action is disabled for local workspace system extension', async () => { // multi server setup - const localWorkspaceSystemExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`), type: ExtensionType.System }); + const localWorkspaceSystemExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`), type: ExtensionType.System }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceSystemExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1830,7 +1800,7 @@ suite('ExtensionsActions Test', () => { test('Test remote install action is disabled for local ui extension if it is not installed in remote', async () => { // multi server setup - const localUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`) }); + const localUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localUIExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1849,8 +1819,8 @@ suite('ExtensionsActions Test', () => { test('Test remote install action is disabled for local ui extension if it is also installed in remote', async () => { // multi server setup - const localUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`) }); - const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const localUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`) }); + const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localUIExtension]), createExtensionManagementService([remoteUIExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1913,9 +1883,9 @@ suite('ExtensionsActions Test', () => { assert.ok(!testObject.enabled); }); - test('Test local install action is disabled for remote ui extension', async () => { + test('Test local install action is enabled for remote ui extension', async () => { // multi server setup - const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1929,7 +1899,9 @@ suite('ExtensionsActions Test', () => { const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; - assert.ok(!testObject.enabled); + assert.ok(testObject.enabled); + assert.equal('Install Locally', testObject.label); + assert.equal('extension-action prominent install', testObject.class); }); test('Test local install action when installing remote ui extension', async () => { @@ -1937,7 +1909,7 @@ suite('ExtensionsActions Test', () => { const localExtensionManagementService: IExtensionManagementService = createExtensionManagementService(); const onInstallExtension = new Emitter(); localExtensionManagementService.onInstallExtension = onInstallExtension.event; - const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, localExtensionManagementService, createExtensionManagementService([remoteUIExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1953,12 +1925,56 @@ suite('ExtensionsActions Test', () => { const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + assert.equal('Install Locally', testObject.label); + assert.equal('extension-action prominent install', testObject.class); + + onInstallExtension.fire({ identifier: remoteUIExtension.identifier, gallery }); + assert.ok(testObject.enabled); + assert.equal('Installing', testObject.label); + assert.equal('extension-action install installing', testObject.class); + }); + + test('Test local install action when installing remote ui extension is finished', async () => { + // multi server setup + const localExtensionManagementService: IExtensionManagementService = createExtensionManagementService(); + const onInstallExtension = new Emitter(); + localExtensionManagementService.onInstallExtension = onInstallExtension.event; + const onDidInstallEvent = new Emitter(); + localExtensionManagementService.onDidInstallExtension = onDidInstallEvent.event; + const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, localExtensionManagementService, createExtensionManagementService([remoteUIExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IExtensionsWorkbenchService, workbenchService, 'open', undefined); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + const gallery = aGalleryExtension('a', { identifier: remoteUIExtension.identifier }); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); + await workbenchService.queryGallery(CancellationToken.None); + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + assert.equal('Install Locally', testObject.label); + assert.equal('extension-action prominent install', testObject.class); + + onInstallExtension.fire({ identifier: remoteUIExtension.identifier, gallery }); + assert.ok(testObject.enabled); + assert.equal('Installing', testObject.label); + assert.equal('extension-action install installing', testObject.class); + + const installedExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`) }); + onDidInstallEvent.fire({ identifier: installedExtension.identifier, local: installedExtension, operation: InstallOperation.Install }); assert.ok(!testObject.enabled); }); - test('Test local install action is disabled for disabled remote ui extension', async () => { + test('Test local install action is enabled for disabled remote ui extension', async () => { // multi server setup - const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1973,12 +1989,14 @@ suite('ExtensionsActions Test', () => { const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; - assert.ok(!testObject.enabled); + assert.ok(testObject.enabled); + assert.equal('Install Locally', testObject.label); + assert.equal('extension-action prominent install', testObject.class); }); test('Test local install action is disabled when extension is not set', async () => { // multi server setup - const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension])); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); @@ -2013,7 +2031,7 @@ suite('ExtensionsActions Test', () => { test('Test local install action is disabled for remote ui extension which is disabled in env', async () => { // multi server setup - const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: true } as IWorkbenchEnvironmentService); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); @@ -2034,7 +2052,7 @@ suite('ExtensionsActions Test', () => { test('Test local install action is disabled when local server is not available', async () => { // single server setup - const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aSingleRemoteExtensionManagementServerService(instantiationService, createExtensionManagementService([remoteUIExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -2054,8 +2072,8 @@ suite('ExtensionsActions Test', () => { test('Test local install action is disabled for remote ui extension if it is installed in local', async () => { // multi server setup - const localUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`) }); - const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const localUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`) }); + const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localUIExtension]), createExtensionManagementService([remoteUIExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -2073,13 +2091,13 @@ suite('ExtensionsActions Test', () => { assert.ok(!testObject.enabled); }); - test('Test local install action is disabled for remote UI extension if it uninstalled locally', async () => { + test('Test local install action is disabled for remoteUI extension if it is uninstalled locally', async () => { // multi server setup const extensionManagementService = instantiationService.get(IExtensionManagementService); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), extensionManagementService); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [remoteUIExtension]); const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); instantiationService.set(IExtensionsWorkbenchService, workbenchService); @@ -2091,15 +2109,16 @@ suite('ExtensionsActions Test', () => { const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; - assert.ok(!testObject.enabled); + assert.ok(testObject.enabled); + assert.equal('Install Locally', testObject.label); uninstallEvent.fire(remoteUIExtension.identifier); assert.ok(!testObject.enabled); }); - test('Test local install action is disabled for remote UI extension if it has gallery', async () => { + test('Test local install action is enabled for remote UI extension if it has gallery', async () => { // multi server setup - const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -2113,12 +2132,12 @@ suite('ExtensionsActions Test', () => { const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); testObject.extension = extensions[0]; assert.ok(testObject.extension); - assert.ok(!testObject.enabled); + assert.ok(testObject.enabled); }); test('Test local install action is disabled for remote UI system extension', async () => { // multi server setup - const remoteUISystemExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }), type: ExtensionType.System }); + const remoteUISystemExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }), type: ExtensionType.System }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUISystemExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -2137,7 +2156,7 @@ suite('ExtensionsActions Test', () => { test('Test local install action is disabled for remote workspace extension if it is not installed in local', async () => { // multi server setup - const remoteWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const remoteWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteWorkspaceExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -2156,8 +2175,8 @@ suite('ExtensionsActions Test', () => { test('Test local install action is disabled for remote workspace extension if it is also installed in local', async () => { // multi server setup - const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspae' }, { location: URI.file(`pub.a`) }); - const remoteWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspae'] }, { location: URI.file(`pub.a`) }); + const remoteWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension]), createExtensionManagementService([remoteWorkspaceExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts index 965d9a1493..8c571f30d7 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts @@ -206,7 +206,7 @@ suite.skip('ExtensionsTipsService Test', () => { // {{SQL CARBON EDIT}} skip sui ...{ extensionTips: { 'ms-vscode.csharp': '{**/*.cs,**/project.json,**/global.json,**/*.csproj,**/*.sln,**/appsettings.json}', - 'msjsdiag.debugger-for-chrome': '{**/*.ts,**/*.tsx**/*.js,**/*.jsx,**/*.es6,**/.babelrc}', + 'msjsdiag.debugger-for-chrome': '{**/*.ts,**/*.tsx,**/*.js,**/*.jsx,**/*.es6,**/*.mjs,**/*.cjs,**/.babelrc}', 'lukehoban.Go': '**/*.go' }, extensionImportantTips: { @@ -501,6 +501,7 @@ suite.skip('ExtensionsTipsService Test', () => { // {{SQL CARBON EDIT}} skip sui const ignoredExtensionId = 'Some.Extension'; instantiationService.stub(IStorageService, { // {{SQL CARBON EDIT}} strict-null-checks? get: (a: string, b: StorageScope, c?: boolean) => a === 'extensionsAssistant/ignored_recommendations' ? '["ms-vscode.vscode"]' : c, + getBoolean: (a: string, b: StorageScope, c: boolean) => c, store: (...args: any[]) => { storageSetterTarget(...args); } @@ -519,7 +520,7 @@ suite.skip('ExtensionsTipsService Test', () => { // {{SQL CARBON EDIT}} skip sui test('ExtensionTipsService: Get file based recommendations from storage (old format)', () => { const storedRecommendations = '["ms-vscode.csharp", "ms-python.python", "ms-vscode.vscode-typescript-tslint-plugin"]'; - instantiationService.stub(IStorageService, >{ get: (a: string, b: StorageScope, c?: string) => a === 'extensionsAssistant/recommendations' ? storedRecommendations : c }); + instantiationService.stub(IStorageService, >{ get: (a: string, b: StorageScope, c?: string) => a === 'extensionsAssistant/recommendations' ? storedRecommendations : c, getBoolean: (a: string, b: StorageScope, c: boolean) => c }); return setUpFolderWorkspace('myFolder', []).then(() => { testObject = instantiationService.createInstance(ExtensionTipsService); @@ -538,7 +539,7 @@ suite.skip('ExtensionsTipsService Test', () => { // {{SQL CARBON EDIT}} skip sui const now = Date.now(); const tenDaysOld = 10 * milliSecondsInADay; const storedRecommendations = `{"ms-vscode.csharp": ${now}, "ms-python.python": ${now}, "ms-vscode.vscode-typescript-tslint-plugin": ${now}, "lukehoban.Go": ${tenDaysOld}}`; - instantiationService.stub(IStorageService, >{ get: (a: string, b: StorageScope, c?: string) => a === 'extensionsAssistant/recommendations' ? storedRecommendations : c }); + instantiationService.stub(IStorageService, >{ get: (a: string, b: StorageScope, c?: string) => a === 'extensionsAssistant/recommendations' ? storedRecommendations : c, getBoolean: (a: string, b: StorageScope, c: boolean) => c }); return setUpFolderWorkspace('myFolder', []).then(() => { testObject = instantiationService.createInstance(ExtensionTipsService); diff --git a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts index f6b81551d4..3260eb613a 100644 --- a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts @@ -51,7 +51,7 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor { } } - getTitle(): string | undefined { + getTitle(): string { return this.input ? this.input.getName() : nls.localize('binaryFileEditor', "Binary File Viewer"); } } diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts index a76d73dfc7..d0326a0a15 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; import { IEditorViewState } from 'vs/editor/common/editorCommon'; import { toResource, SideBySideEditorInput, IWorkbenchEditorConfiguration, SideBySideEditor as SideBySideEditorChoice } from 'vs/workbench/common/editor'; -import { ITextFileService, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, ITextFileEditorModel, TextFileModelChangeEvent, ModelState } from 'vs/workbench/services/textfile/common/textfiles'; import { FileOperationEvent, FileOperation, IFileService, FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; @@ -27,7 +27,7 @@ import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor import { ResourceQueue, timeout } from 'vs/base/common/async'; import { onUnexpectedError } from 'vs/base/common/errors'; import { withNullAsUndefined } from 'vs/base/common/types'; -import { EditorActivation } from 'vs/platform/editor/common/editor'; +import { EditorActivation, ITextEditorOptions } from 'vs/platform/editor/common/editor'; // {{SQL CARBON EDIT}} import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput'; @@ -64,6 +64,9 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut // Update editors from disk changes this._register(this.fileService.onFileChanges(e => this.onFileChanges(e))); + // Open editors from dirty text file models + this._register(this.textFileService.models.onModelsDirty(e => this.onTextFilesDirty(e))); + // Editor changing this._register(this.editorService.onDidVisibleEditorsChange(() => this.handleOutOfWorkspaceWatchers())); @@ -98,7 +101,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut return resource ? this.textFileService.models.get(resource) : undefined; })) .filter(model => !model.isDirty()), - m => m.getResource().toString() + m => m.resource.toString() ).forEach(model => this.queueModelLoad(model)); } } @@ -219,9 +222,8 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut private handleMovedFileInOpenedEditors(oldResource: URI, newResource: URI): void { this.editorGroupService.groups.forEach(group => { group.editors.forEach(editor => { - // {{SQL CARBON EDIT}} - Support FileEditorInput or QueryInput - if (editor instanceof FileEditorInput || editor instanceof QueryEditorInput) { - const resource = editor.getResource(); + const resource = editor.getResource(); + if (resource && (editor instanceof FileEditorInput || editor instanceof QueryEditorInput || editor.handleMove)) { // {{SQL CARBON EDIT}} #TODO we can remove this edit by just implementing handlemove // Update Editor if file (or any parent of the input) got renamed or moved if (resources.isEqualOrParent(resource, oldResource)) { @@ -233,15 +235,27 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut reopenFileResource = resources.joinPath(newResource, resource.path.substr(index + oldResource.path.length + 1)); // parent folder got moved } + const options: ITextEditorOptions = { + preserveFocus: true, + pinned: group.isPinned(editor), + index: group.getIndexOfEditor(editor), + inactive: !group.isActive(editor), + }; + + if (editor.handleMove) { + const replacement = editor.handleMove(group.id, reopenFileResource, options); + if (replacement) { + this.editorService.replaceEditors([{ editor, replacement }], group); + return; + } + } + this.editorService.replaceEditors([{ editor: { resource }, replacement: { resource: reopenFileResource, options: { - preserveFocus: true, - pinned: group.isPinned(editor), - index: group.getIndexOfEditor(editor), - inactive: !group.isActive(editor), + ...options, viewState: this.getViewStateFor(oldResource, group) } }, @@ -304,7 +318,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut // and updated right after. distinct(coalesce([...e.getUpdated(), ...e.getAdded()] .map(u => this.textFileService.models.get(u.resource))) - .filter(model => model && !model.isDirty()), m => m.getResource().toString()) + .filter(model => model && !model.isDirty()), m => m.resource.toString()) .forEach(model => this.queueModelLoad(model)); } @@ -313,9 +327,9 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut // Load model to update (use a queue to prevent accumulation of loads // when the load actually takes long. At most we only want the queue // to have a size of 2 (1 running load and 1 queued load). - const queue = this.modelLoadQueue.queueFor(model.getResource()); + const queue = this.modelLoadQueue.queueFor(model.resource); if (queue.size <= 1) { - queue.queue(() => model.load().then(undefined, onUnexpectedError)); + queue.queue(() => model.load().then(undefined, onUnexpectedError)); } } @@ -367,6 +381,31 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut }); } + private onTextFilesDirty(e: readonly TextFileModelChangeEvent[]): void { + + // If files become dirty but are not opened, we open it in the background unless there are pending to be saved + this.doOpenDirtyResources(distinct(e.filter(e => { + + // Only dirty models that are not PENDING_SAVE + const model = this.textFileService.models.get(e.resource); + const shouldOpen = model?.isDirty() && !model.hasState(ModelState.PENDING_SAVE); + + // Only if not open already + return shouldOpen && !this.editorService.isOpen({ resource: e.resource }); + }).map(e => e.resource), r => r.toString())); + } + + private doOpenDirtyResources(resources: URI[]): void { + + // Open + this.editorService.openEditors(resources.map(resource => { + return { + resource, + options: { inactive: true, pinned: true, preserveFocus: true } + }; + })); + } + dispose(): void { super.dispose(); diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts index 84641c148b..811d7d9bcf 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts @@ -24,7 +24,6 @@ import { ITextResourceConfigurationService } from 'vs/editor/common/services/res import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ScrollType } from 'vs/editor/common/editorCommon'; -import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -54,11 +53,10 @@ export class TextFileEditor extends BaseTextEditor { @IEditorService editorService: IEditorService, @IThemeService themeService: IThemeService, @IEditorGroupsService editorGroupService: IEditorGroupsService, - @ITextFileService textFileService: ITextFileService, - @IHostService hostService: IHostService, + @ITextFileService private readonly textFileService: ITextFileService, @IExplorerService private readonly explorerService: IExplorerService ) { - super(TextFileEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, hostService); + super(TextFileEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorService, editorGroupService); this.updateRestoreViewStateConfiguration(); @@ -115,14 +113,6 @@ export class TextFileEditor extends BaseTextEditor { } } - setOptions(options: EditorOptions | undefined): void { - const textOptions = options as TextEditorOptions; - if (textOptions && isFunction(textOptions.apply)) { - const textEditor = assertIsDefined(this.getControl()); - textOptions.apply(textEditor, ScrollType.Smooth); - } - } - async setInput(input: FileEditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { // Update/clear view settings if input changes @@ -162,7 +152,11 @@ export class TextFileEditor extends BaseTextEditor { (options).apply(textEditor, ScrollType.Immediate); } - // Readonly flag + // Since the resolved model provides information about being readonly + // or not, we apply it here to the editor even though the editor input + // was already asked for being readonly or not. The rationale is that + // a resolved model might have more specific information about being + // readonly or not that the input did not have. textEditor.updateOptions({ readOnly: textFileModel.isReadonly() }); } catch (error) { this.handleSetInputError(error, input, options); @@ -241,8 +235,7 @@ export class TextFileEditor extends BaseTextEditor { } protected getAriaLabel(): string { - const input = this.input; - const inputName = input?.getName(); + const inputName = this.input?.getName(); let ariaLabel: string; if (inputName) { diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 9621d38615..cd242f385d 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -29,7 +29,7 @@ import { DelegatingEditorService } from 'vs/workbench/services/editor/browser/ed import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditor } from 'vs/workbench/common/editor'; -import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { ViewletPane } from 'vs/workbench/browser/parts/views/paneViewlet'; import { KeyChord, KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { Registry } from 'vs/platform/registry/common/platform'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; @@ -177,7 +177,7 @@ export class ExplorerViewlet extends ViewContainerViewlet { DOM.addClass(parent, 'explorer-viewlet'); } - protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewletPanel { + protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewletPane { if (viewDescriptor.id === ExplorerView.ID) { // Create a delegating editor service for the explorer to be able to delay the refresh in the opened // editors view above. This is a workaround for being able to double click on a file to make it pinned @@ -230,10 +230,6 @@ export class ExplorerViewlet extends ViewContainerViewlet { return this.getView(OpenEditorsView.ID); } - public getEmptyView(): EmptyView { - return this.getView(EmptyView.ID); - } - public setVisible(visible: boolean): void { this.viewletVisibleContextKey.set(visible); super.setVisible(visible); diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index 3384ab1e25..fdc0634308 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -5,12 +5,12 @@ import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ToggleAutoSaveAction, GlobalNewUntitledFileAction, FocusFilesExplorer, GlobalCompareResourcesAction, SaveAllAction, ShowActiveFileInExplorer, CollapseExplorerView, RefreshExplorerView, CompareWithClipboardAction, NEW_FILE_COMMAND_ID, NEW_FILE_LABEL, NEW_FOLDER_COMMAND_ID, NEW_FOLDER_LABEL, TRIGGER_RENAME_LABEL, MOVE_FILE_TO_TRASH_LABEL, COPY_FILE_LABEL, PASTE_FILE_LABEL, FileCopiedContext, renameHandler, moveFileToTrashHandler, copyFileHandler, pasteFileHandler, deleteFileHandler, cutFileHandler, DOWNLOAD_COMMAND_ID, openFilePreserveFocusHandler, GlobalNewUntitledPlainFileAction } from 'vs/workbench/contrib/files/browser/fileActions'; // {{SQL CARBON EDIT}} -- Add 'New File' command for plain untitled files -import { revertLocalChangesCommand, acceptLocalChangesCommand, CONFLICT_RESOLUTION_CONTEXT } from 'vs/workbench/contrib/files/browser/saveErrorHandler'; +import { ToggleAutoSaveAction, GlobalNewUntitledFileAction, FocusFilesExplorer, GlobalCompareResourcesAction, SaveAllAction, ShowActiveFileInExplorer, CollapseExplorerView, RefreshExplorerView, CompareWithClipboardAction, NEW_FILE_COMMAND_ID, NEW_FILE_LABEL, NEW_FOLDER_COMMAND_ID, NEW_FOLDER_LABEL, TRIGGER_RENAME_LABEL, MOVE_FILE_TO_TRASH_LABEL, COPY_FILE_LABEL, PASTE_FILE_LABEL, FileCopiedContext, renameHandler, moveFileToTrashHandler, copyFileHandler, pasteFileHandler, deleteFileHandler, cutFileHandler, DOWNLOAD_COMMAND_ID, openFilePreserveFocusHandler, DOWNLOAD_LABEL, GlobalNewUntitledPlainFileAction } from 'vs/workbench/contrib/files/browser/fileActions'; +import { revertLocalChangesCommand, acceptLocalChangesCommand, CONFLICT_RESOLUTION_CONTEXT } from 'vs/workbench/contrib/files/browser/textFileSaveErrorHandler'; import { SyncActionDescriptor, MenuId, MenuRegistry, ILocalizedString } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; -import { openWindowCommand, COPY_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, OPEN_TO_SIDE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_COMMAND_ID, SAVE_FILE_LABEL, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID, OpenEditorsGroupContext, COMPARE_WITH_SAVED_COMMAND_ID, COMPARE_RESOURCE_COMMAND_ID, SELECT_FOR_COMPARE_COMMAND_ID, ResourceSelectedForCompareContext, DirtyEditorContext, COMPARE_SELECTED_COMMAND_ID, REMOVE_ROOT_FOLDER_COMMAND_ID, REMOVE_ROOT_FOLDER_LABEL, SAVE_FILES_COMMAND_ID, COPY_RELATIVE_PATH_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_LABEL, newWindowCommand } from 'vs/workbench/contrib/files/browser/fileCommands'; +import { openWindowCommand, COPY_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, OPEN_TO_SIDE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_COMMAND_ID, SAVE_FILE_LABEL, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID, OpenEditorsGroupContext, COMPARE_WITH_SAVED_COMMAND_ID, COMPARE_RESOURCE_COMMAND_ID, SELECT_FOR_COMPARE_COMMAND_ID, ResourceSelectedForCompareContext, DirtyEditorContext, COMPARE_SELECTED_COMMAND_ID, REMOVE_ROOT_FOLDER_COMMAND_ID, REMOVE_ROOT_FOLDER_LABEL, SAVE_FILES_COMMAND_ID, COPY_RELATIVE_PATH_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_LABEL, newWindowCommand, ReadonlyEditorContext } from 'vs/workbench/contrib/files/browser/fileCommands'; import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -18,42 +18,41 @@ import { isMacintosh } from 'vs/base/common/platform'; import { FilesExplorerFocusCondition, ExplorerRootContext, ExplorerFolderContext, ExplorerResourceNotReadonlyContext, ExplorerResourceCut, IExplorerService, ExplorerResourceMoveableToTrash, ExplorerViewletVisibleContext } from 'vs/workbench/contrib/files/common/files'; import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL } from 'vs/workbench/browser/actions/workspaceCommands'; import { CLOSE_SAVED_EDITORS_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_EDITOR_COMMAND_ID, CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; -import { AutoSaveContext } from 'vs/workbench/services/textfile/common/textfiles'; +import { AutoSaveAfterShortDelayContext } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { ResourceContextKey } from 'vs/workbench/common/resources'; import { WorkbenchListDoubleSelection } from 'vs/platform/list/browser/listService'; -import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; -import { IsWebContext, WorkspaceFolderCountContext } from 'vs/workbench/browser/contextkeys'; +import { WorkspaceFolderCountContext, IsWebContext } from 'vs/workbench/browser/contextkeys'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { OpenFileFolderAction, OpenFileAction, OpenFolderAction, OpenWorkspaceAction } from 'vs/workbench/browser/actions/workspaceActions'; -import { ActiveEditorIsSaveableContext } from 'vs/workbench/common/editor'; +import { ActiveEditorIsReadonlyContext, DirtyWorkingCopiesContext, ActiveEditorContext } from 'vs/workbench/common/editor'; import { SidebarFocusContext } from 'vs/workbench/common/viewlet'; -import { registerAndGetAmdImageURL } from 'vs/base/common/amd'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; // Contribute Global Actions const category = { value: nls.localize('filesCategory', "File"), original: 'File' }; const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(SaveAllAction, SaveAllAction.ID, SaveAllAction.LABEL, { primary: undefined, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_S }, win: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_S) } }), 'File: Save All', category.value); -registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalCompareResourcesAction, GlobalCompareResourcesAction.ID, GlobalCompareResourcesAction.LABEL), 'File: Compare Active File With...', category.value); -registry.registerWorkbenchAction(new SyncActionDescriptor(FocusFilesExplorer, FocusFilesExplorer.ID, FocusFilesExplorer.LABEL), 'File: Focus on Files Explorer', category.value); -registry.registerWorkbenchAction(new SyncActionDescriptor(ShowActiveFileInExplorer, ShowActiveFileInExplorer.ID, ShowActiveFileInExplorer.LABEL), 'File: Reveal Active File in Side Bar', category.value); -registry.registerWorkbenchAction(new SyncActionDescriptor(CollapseExplorerView, CollapseExplorerView.ID, CollapseExplorerView.LABEL), 'File: Collapse Folders in Explorer', category.value); -registry.registerWorkbenchAction(new SyncActionDescriptor(RefreshExplorerView, RefreshExplorerView.ID, RefreshExplorerView.LABEL), 'File: Refresh Explorer', category.value); -registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalNewUntitledFileAction, GlobalNewUntitledFileAction.ID, GlobalNewUntitledFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_N }), 'File: New Untitled File', category.value); -registry.registerWorkbenchAction(new SyncActionDescriptor(CompareWithClipboardAction, CompareWithClipboardAction.ID, CompareWithClipboardAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_C) }), 'File: Compare Active File with Clipboard', category.value); -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleAutoSaveAction, ToggleAutoSaveAction.ID, ToggleAutoSaveAction.LABEL), 'File: Toggle Auto Save', category.value); -registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalNewUntitledPlainFileAction, GlobalNewUntitledPlainFileAction.ID, GlobalNewUntitledPlainFileAction.LABEL), 'File: New Plain Text File', category.value); // {{SQL CARBON EDIT}} -- Add 'New File' command for plain untitled files +registry.registerWorkbenchAction(SyncActionDescriptor.create(SaveAllAction, SaveAllAction.ID, SaveAllAction.LABEL, { primary: undefined, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_S }, win: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_S) } }), 'File: Save All', category.value); +registry.registerWorkbenchAction(SyncActionDescriptor.create(GlobalCompareResourcesAction, GlobalCompareResourcesAction.ID, GlobalCompareResourcesAction.LABEL), 'File: Compare Active File With...', category.value); +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusFilesExplorer, FocusFilesExplorer.ID, FocusFilesExplorer.LABEL), 'File: Focus on Files Explorer', category.value); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowActiveFileInExplorer, ShowActiveFileInExplorer.ID, ShowActiveFileInExplorer.LABEL), 'File: Reveal Active File in Side Bar', category.value); +registry.registerWorkbenchAction(SyncActionDescriptor.create(CollapseExplorerView, CollapseExplorerView.ID, CollapseExplorerView.LABEL), 'File: Collapse Folders in Explorer', category.value); +registry.registerWorkbenchAction(SyncActionDescriptor.create(RefreshExplorerView, RefreshExplorerView.ID, RefreshExplorerView.LABEL), 'File: Refresh Explorer', category.value); +registry.registerWorkbenchAction(SyncActionDescriptor.create(GlobalNewUntitledFileAction, GlobalNewUntitledFileAction.ID, GlobalNewUntitledFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_N }), 'File: New Untitled File', category.value); +registry.registerWorkbenchAction(SyncActionDescriptor.create(CompareWithClipboardAction, CompareWithClipboardAction.ID, CompareWithClipboardAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_C) }), 'File: Compare Active File with Clipboard', category.value); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleAutoSaveAction, ToggleAutoSaveAction.ID, ToggleAutoSaveAction.LABEL), 'File: Toggle Auto Save', category.value); +registry.registerWorkbenchAction(SyncActionDescriptor.create(GlobalNewUntitledPlainFileAction, GlobalNewUntitledPlainFileAction.ID, GlobalNewUntitledPlainFileAction.LABEL), 'File: New Plain Text File', category.value); // {{SQL CARBON EDIT}} -- Add 'New File' command for plain untitled files const workspacesCategory = nls.localize('workspaces', "Workspaces"); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenWorkspaceAction, OpenWorkspaceAction.ID, OpenWorkspaceAction.LABEL), 'Workspaces: Open Workspace...', workspacesCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenWorkspaceAction, OpenWorkspaceAction.ID, OpenWorkspaceAction.LABEL), 'Workspaces: Open Workspace...', workspacesCategory); const fileCategory = nls.localize('file', "File"); if (isMacintosh) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open...', fileCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open...', fileCategory); } else { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileAction, OpenFileAction.ID, OpenFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open File...', fileCategory); - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_O) }), 'File: Open Folder...', fileCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenFileAction, OpenFileAction.ID, OpenFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open File...', fileCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_O) }), 'File: Open Folder...', fileCategory); } // Commands @@ -78,7 +77,7 @@ const MOVE_FILE_TO_TRASH_ID = 'moveFileToTrash'; KeybindingsRegistry.registerCommandAndKeybindingRule({ id: MOVE_FILE_TO_TRASH_ID, weight: KeybindingWeight.WorkbenchContrib + explorerCommandsWeightBonus, - when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerRootContext.toNegated(), ExplorerResourceNotReadonlyContext, ExplorerResourceMoveableToTrash), + when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerResourceNotReadonlyContext, ExplorerResourceMoveableToTrash), primary: KeyCode.Delete, mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace @@ -90,7 +89,7 @@ const DELETE_FILE_ID = 'deleteFile'; KeybindingsRegistry.registerCommandAndKeybindingRule({ id: DELETE_FILE_ID, weight: KeybindingWeight.WorkbenchContrib + explorerCommandsWeightBonus, - when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerRootContext.toNegated(), ExplorerResourceNotReadonlyContext), + when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerResourceNotReadonlyContext), primary: KeyMod.Shift | KeyCode.Delete, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Backspace @@ -101,7 +100,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ KeybindingsRegistry.registerCommandAndKeybindingRule({ id: DELETE_FILE_ID, weight: KeybindingWeight.WorkbenchContrib + explorerCommandsWeightBonus, - when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerRootContext.toNegated(), ExplorerResourceNotReadonlyContext, ExplorerResourceMoveableToTrash.toNegated()), + when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerResourceNotReadonlyContext, ExplorerResourceMoveableToTrash.toNegated()), primary: KeyCode.Delete, mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace @@ -182,23 +181,17 @@ export function appendEditorTitleContextMenuItem(id: string, title: string, when } // Editor Title Menu for Conflict Resolution -appendSaveConflictEditorTitleAction('workbench.files.action.acceptLocalChanges', nls.localize('acceptLocalChanges', "Use your changes and overwrite file contents"), { - light: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/files/browser/media/check-light.svg`)), - dark: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/files/browser/media/check-dark.svg`)) -}, -10, acceptLocalChangesCommand); -appendSaveConflictEditorTitleAction('workbench.files.action.revertLocalChanges', nls.localize('revertLocalChanges', "Discard your changes and revert to file contents"), { - light: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/files/browser/media/undo-light.svg`)), - dark: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/files/browser/media/undo-dark.svg`)) -}, -9, revertLocalChangesCommand); +appendSaveConflictEditorTitleAction('workbench.files.action.acceptLocalChanges', nls.localize('acceptLocalChanges', "Use your changes and overwrite file contents"), { id: 'codicon/check' }, -10, acceptLocalChangesCommand); +appendSaveConflictEditorTitleAction('workbench.files.action.revertLocalChanges', nls.localize('revertLocalChanges', "Discard your changes and revert to file contents"), { id: 'codicon/discard' }, -9, revertLocalChangesCommand); -function appendSaveConflictEditorTitleAction(id: string, title: string, iconLocation: { dark: URI; light?: URI; }, order: number, command: ICommandHandler): void { +function appendSaveConflictEditorTitleAction(id: string, title: string, icon: ThemeIcon, order: number, command: ICommandHandler): void { // Command CommandsRegistry.registerCommand(id, command); // Action MenuRegistry.appendMenuItem(MenuId.EditorTitle, { - command: { id, title, iconLocation }, + command: { id, title, icon }, when: ContextKeyExpr.equals(CONFLICT_RESOLUTION_CONTEXT, true), group: 'navigation', order @@ -218,7 +211,6 @@ export function appendToCommandPalette(id: string, title: ILocalizedString, cate }); } -const downloadLabel = nls.localize('download', "Download"); appendToCommandPalette(COPY_PATH_COMMAND_ID, { value: nls.localize('copyPathOfActive', "Copy Path of Active File"), original: 'Copy Path of Active File' }, category); appendToCommandPalette(COPY_RELATIVE_PATH_COMMAND_ID, { value: nls.localize('copyRelativePathOfActive', "Copy Relative Path of Active File"), original: 'Copy Relative Path of Active File' }, category); appendToCommandPalette(SAVE_FILE_COMMAND_ID, { value: SAVE_FILE_LABEL, original: 'Save' }, category); @@ -231,7 +223,7 @@ appendToCommandPalette(SAVE_FILE_AS_COMMAND_ID, { value: SAVE_FILE_AS_LABEL, ori appendToCommandPalette(CLOSE_EDITOR_COMMAND_ID, { value: nls.localize('closeEditor', "Close Editor"), original: 'Close Editor' }, { value: nls.localize('view', "View"), original: 'View' }); appendToCommandPalette(NEW_FILE_COMMAND_ID, { value: NEW_FILE_LABEL, original: 'New File' }, category, WorkspaceFolderCountContext.notEqualsTo('0')); appendToCommandPalette(NEW_FOLDER_COMMAND_ID, { value: NEW_FOLDER_LABEL, original: 'New Folder' }, category, WorkspaceFolderCountContext.notEqualsTo('0')); -appendToCommandPalette(DOWNLOAD_COMMAND_ID, { value: downloadLabel, original: 'Download' }, category, ContextKeyExpr.and(IsWebContext.toNegated(), ResourceContextKey.Scheme.notEqualsTo(Schemas.file))); +appendToCommandPalette(DOWNLOAD_COMMAND_ID, { value: DOWNLOAD_LABEL, original: 'Download' }, category, ContextKeyExpr.and(ResourceContextKey.Scheme.notEqualsTo(Schemas.file))); // Menu registration - open editors @@ -243,7 +235,7 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { group: 'navigation', order: 10, command: openToSideCommand, - when: ResourceContextKey.IsFileSystemResource + when: ContextKeyExpr.or(ResourceContextKey.IsFileSystemResource, ResourceContextKey.Scheme.isEqualTo(Schemas.untitled)) }); MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { @@ -268,7 +260,19 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { title: SAVE_FILE_LABEL, precondition: DirtyEditorContext }, - when: ContextKeyExpr.and(ResourceContextKey.IsFileSystemResource, AutoSaveContext.notEqualsTo('afterDelay') && AutoSaveContext.notEqualsTo('')) + when: ContextKeyExpr.or( + // Untitled Editors + ResourceContextKey.Scheme.isEqualTo(Schemas.untitled), + // Or: + ContextKeyExpr.and( + // Not: editor groups + OpenEditorsGroupContext.toNegated(), + // Not: readonly editors + ReadonlyEditorContext.toNegated(), + // Not: auto save after short delay + AutoSaveAfterShortDelayContext.toNegated() + ) + ) }); MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { @@ -279,25 +283,28 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { title: nls.localize('revert', "Revert File"), precondition: DirtyEditorContext }, - when: ContextKeyExpr.and(ResourceContextKey.IsFileSystemResource, AutoSaveContext.notEqualsTo('afterDelay') && AutoSaveContext.notEqualsTo('')) -}); - -MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { - group: '2_save', - command: { - id: SAVE_FILE_AS_COMMAND_ID, - title: SAVE_FILE_AS_LABEL - }, - when: ResourceContextKey.Scheme.isEqualTo(Schemas.untitled) + when: ContextKeyExpr.and( + // Not: editor groups + OpenEditorsGroupContext.toNegated(), + // Not: readonly editors + ReadonlyEditorContext.toNegated(), + // Not: untitled editors (revert closes them) + ResourceContextKey.Scheme.notEqualsTo(Schemas.untitled), + // Not: auto save after short delay + AutoSaveAfterShortDelayContext.toNegated() + ) }); MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { group: '2_save', + order: 30, command: { id: SAVE_ALL_IN_GROUP_COMMAND_ID, - title: nls.localize('saveAll', "Save All") + title: nls.localize('saveAll', "Save All"), + precondition: DirtyWorkingCopiesContext }, - when: ContextKeyExpr.and(OpenEditorsGroupContext, AutoSaveContext.notEqualsTo('afterDelay') && AutoSaveContext.notEqualsTo('')) + // Editor Group + when: OpenEditorsGroupContext }); MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { @@ -308,7 +315,7 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { title: nls.localize('compareWithSaved', "Compare with Saved"), precondition: DirtyEditorContext }, - when: ContextKeyExpr.and(ResourceContextKey.IsFileSystemResource, AutoSaveContext.notEqualsTo('afterDelay') && AutoSaveContext.notEqualsTo(''), WorkbenchListDoubleSelection.toNegated()) + when: ContextKeyExpr.and(ResourceContextKey.IsFileSystemResource, AutoSaveAfterShortDelayContext.toNegated(), WorkbenchListDoubleSelection.toNegated()) }); const compareResourceCommand = { @@ -465,15 +472,15 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { when: ExplorerFolderContext }); -MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, ({ group: '5_cutcopypaste', order: 30, command: { id: DOWNLOAD_COMMAND_ID, - title: downloadLabel, + title: DOWNLOAD_LABEL, }, - when: ContextKeyExpr.and(IsWebContext.toNegated(), ResourceContextKey.Scheme.notEqualsTo(Schemas.file)) -}); + when: ContextKeyExpr.or(ContextKeyExpr.and(ResourceContextKey.Scheme.notEqualsTo(Schemas.file), IsWebContext.toNegated()), ContextKeyExpr.and(ResourceContextKey.Scheme.notEqualsTo(Schemas.file), ExplorerFolderContext.toNegated(), ExplorerRootContext.toNegated())) +})); MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { group: '6_copypath', @@ -496,7 +503,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { id: ADD_ROOT_FOLDER_COMMAND_ID, title: ADD_ROOT_FOLDER_LABEL }, - when: ContextKeyExpr.and(ExplorerRootContext) + when: ExplorerRootContext }); MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { @@ -591,7 +598,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { command: { id: SAVE_FILE_COMMAND_ID, title: nls.localize({ key: 'miSave', comment: ['&& denotes a mnemonic'] }, "&&Save"), - precondition: ContextKeyExpr.or(ActiveEditorIsSaveableContext, ContextKeyExpr.and(ExplorerViewletVisibleContext, SidebarFocusContext)) + precondition: ContextKeyExpr.or(ActiveEditorIsReadonlyContext.toNegated(), ContextKeyExpr.and(ExplorerViewletVisibleContext, SidebarFocusContext)) }, order: 1 }); @@ -601,7 +608,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { command: { id: SAVE_FILE_AS_COMMAND_ID, title: nls.localize({ key: 'miSaveAs', comment: ['&& denotes a mnemonic'] }, "Save &&As..."), - precondition: ContextKeyExpr.or(ActiveEditorIsSaveableContext, ContextKeyExpr.and(ExplorerViewletVisibleContext, SidebarFocusContext)) + // ActiveEditorContext is not 100% correct, but we lack a context for indicating "Save As..." support + precondition: ContextKeyExpr.or(ActiveEditorContext, ContextKeyExpr.and(ExplorerViewletVisibleContext, SidebarFocusContext)) }, order: 2 }); @@ -610,7 +618,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { group: '4_save', command: { id: SaveAllAction.ID, - title: nls.localize({ key: 'miSaveAll', comment: ['&& denotes a mnemonic'] }, "Save A&&ll") + title: nls.localize({ key: 'miSaveAll', comment: ['&& denotes a mnemonic'] }, "Save A&&ll"), + precondition: DirtyWorkingCopiesContext }, order: 3 }); @@ -667,7 +676,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { group: '6_close', command: { id: REVERT_FILE_COMMAND_ID, - title: nls.localize({ key: 'miRevert', comment: ['&& denotes a mnemonic'] }, "Re&&vert File") + title: nls.localize({ key: 'miRevert', comment: ['&& denotes a mnemonic'] }, "Re&&vert File"), + precondition: ContextKeyExpr.or(ActiveEditorIsReadonlyContext.toNegated(), ContextKeyExpr.and(ExplorerViewletVisibleContext, SidebarFocusContext)) }, order: 1 }); diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index dd8a33f374..e2825fad66 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -5,8 +5,7 @@ import 'vs/css!./media/fileactions'; import * as nls from 'vs/nls'; -import * as types from 'vs/base/common/types'; -import { isWindows } from 'vs/base/common/platform'; +import { isWindows, isWeb } from 'vs/base/common/platform'; import * as extpath from 'vs/base/common/extpath'; import { extname, basename } from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; @@ -17,10 +16,9 @@ import { Action } from 'vs/base/common/actions'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { VIEWLET_ID, IExplorerService, IFilesConfiguration } from 'vs/workbench/contrib/files/common/files'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IFileService, AutoSaveConfiguration } from 'vs/platform/files/common/files'; +import { IFileService } from 'vs/platform/files/common/files'; import { toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { ExplorerViewlet } from 'vs/workbench/contrib/files/browser/explorerViewlet'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -33,18 +31,20 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IListService, ListWidget } from 'vs/platform/list/browser/listService'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Schemas } from 'vs/base/common/network'; -import { IDialogService, IConfirmationResult, getConfirmMessage } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService, IConfirmationResult, getConfirmMessage, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Constants } from 'vs/base/common/uint'; import { CLOSE_EDITORS_AND_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { coalesce } from 'vs/base/common/arrays'; -import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; import { ExplorerItem, NewExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { onUnexpectedError, getErrorMessage } from 'vs/base/common/errors'; +import { asDomUri, triggerDownload } from 'vs/base/browser/dom'; +import { mnemonicButtonLabel } from 'vs/base/common/labels'; +import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { IWorkingCopyService, IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; export const NEW_FILE_COMMAND_ID = 'explorer.newFile'; export const NEW_FILE_LABEL = nls.localize('newFile', "New File"); @@ -62,6 +62,8 @@ export const PASTE_FILE_LABEL = nls.localize('pasteFile', "Paste"); export const FileCopiedContext = new RawContextKey('fileCopied', false); +export const DOWNLOAD_LABEL = nls.localize('download', "Download"); + const CONFIRM_DELETE_SETTING_KEY = 'explorer.confirmDelete'; function onError(notificationService: INotificationService, error: any): void { @@ -162,7 +164,7 @@ export class GlobalNewUntitledFileAction extends Action { } } -async function deleteFiles(textFileService: ITextFileService, dialogService: IDialogService, configurationService: IConfigurationService, fileService: IFileService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise { +async function deleteFiles(textFileService: ITextFileService, dialogService: IDialogService, configurationService: IConfigurationService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise { let primaryButton: string; if (useTrash) { primaryButton = isWindows ? nls.localize('deleteButtonLabelRecycleBin', "&&Move to Recycle Bin") : nls.localize({ key: 'deleteButtonLabelTrash', comment: ['&& denotes a mnemonic'] }, "&&Move to Trash"); @@ -258,7 +260,7 @@ async function deleteFiles(textFileService: ITextFileService, dialogService: IDi } // Call function - const servicePromise = Promise.all(distinctElements.map(e => fileService.del(e.resource, { useTrash: useTrash, recursive: true }))) + const servicePromise = Promise.all(distinctElements.map(e => textFileService.delete(e.resource, { useTrash: useTrash, recursive: true }))) .then(undefined, (error: any) => { // Handle error to delete file(s) from a modal confirmation dialog let errorMessage: string; @@ -287,14 +289,14 @@ async function deleteFiles(textFileService: ITextFileService, dialogService: IDi skipConfirm = true; - return deleteFiles(textFileService, dialogService, configurationService, fileService, elements, useTrash, skipConfirm); + return deleteFiles(textFileService, dialogService, configurationService, elements, useTrash, skipConfirm); } return Promise.resolve(); }); }); - return servicePromise; + return servicePromise.then(undefined); }); }); } @@ -513,26 +515,13 @@ export class ToggleAutoSaveAction extends Action { constructor( id: string, label: string, - @IConfigurationService private readonly configurationService: IConfigurationService + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService ) { super(id, label); } run(): Promise { - const setting = this.configurationService.inspect('files.autoSave'); - let userAutoSaveConfig = setting.user; - if (types.isUndefinedOrNull(userAutoSaveConfig)) { - userAutoSaveConfig = setting.default; // use default if setting not defined - } - - let newAutoSaveValue: string; - if ([AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE].some(s => s === userAutoSaveConfig)) { - newAutoSaveValue = AutoSaveConfiguration.OFF; - } else { - newAutoSaveValue = AutoSaveConfiguration.AFTER_DELAY; - } - - return this.configurationService.updateValue('files.autoSave', newAutoSaveValue, ConfigurationTarget.USER); + return this.filesConfigurationService.toggleAutoSave(); } } @@ -542,38 +531,30 @@ export abstract class BaseSaveAllAction extends Action { constructor( id: string, label: string, - @ITextFileService private readonly textFileService: ITextFileService, - @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, @ICommandService protected commandService: ICommandService, @INotificationService private notificationService: INotificationService, + @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService ) { super(id, label); - this.lastIsDirty = this.textFileService.isDirty(); + this.lastIsDirty = this.workingCopyService.hasDirty; this.enabled = this.lastIsDirty; this.registerListeners(); } - protected abstract includeUntitled(): boolean; protected abstract doRun(context: any): Promise; private registerListeners(): void { - // listen to files being changed locally - this._register(this.textFileService.models.onModelsDirty(e => this.updateEnablement(true))); - this._register(this.textFileService.models.onModelsSaved(e => this.updateEnablement(false))); - this._register(this.textFileService.models.onModelsReverted(e => this.updateEnablement(false))); - this._register(this.textFileService.models.onModelsSaveError(e => this.updateEnablement(true))); - - if (this.includeUntitled()) { - this._register(this.untitledEditorService.onDidChangeDirty(resource => this.updateEnablement(this.untitledEditorService.isDirty(resource)))); - } + // update enablement based on working copy changes + this._register(this.workingCopyService.onDidChangeDirty(w => this.updateEnablement(w))); } - private updateEnablement(isDirty: boolean): void { - if (this.lastIsDirty !== isDirty) { - this.enabled = this.textFileService.isDirty(); + private updateEnablement(workingCopy: IWorkingCopy): void { + const hasDirty = workingCopy.isDirty() || this.workingCopyService.hasDirty; + if (this.lastIsDirty !== hasDirty) { + this.enabled = hasDirty; this.lastIsDirty = this.enabled; } } @@ -599,10 +580,6 @@ export class SaveAllAction extends BaseSaveAllAction { protected doRun(context: any): Promise { return this.commandService.executeCommand(SAVE_ALL_COMMAND_ID); } - - protected includeUntitled(): boolean { - return true; - } } export class SaveAllInGroupAction extends BaseSaveAllAction { @@ -617,10 +594,6 @@ export class SaveAllInGroupAction extends BaseSaveAllAction { protected doRun(context: any): Promise { return this.commandService.executeCommand(SAVE_ALL_IN_GROUP_COMMAND_ID, {}, context); } - - protected includeUntitled(): boolean { - return true; - } } export class CloseGroupAction extends Action { @@ -886,22 +859,6 @@ class ClipboardContentProvider implements ITextModelContentProvider { } } -interface IExplorerContext { - stat?: ExplorerItem; - selection: ExplorerItem[]; -} - -function getContext(listWidget: ListWidget): IExplorerContext { - // These commands can only be triggered when explorer viewlet is visible so get it using the active viewlet - const tree = >listWidget; - const focus = tree.getFocus(); - const stat = focus.length ? focus[0] : undefined; - const selection = tree.getSelection(); - - // Only respect the selection if user clicked inside it (focus belongs to it) - return { stat, selection: selection && typeof stat !== 'undefined' && selection.indexOf(stat) >= 0 ? selection : [] }; -} - function onErrorWithRetry(notificationService: INotificationService, error: any, retry: () => Promise): void { notificationService.prompt(Severity.Error, toErrorMessage(error, false), [{ @@ -912,7 +869,6 @@ function onErrorWithRetry(notificationService: INotificationService, error: any, } async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boolean): Promise { - const listService = accessor.get(IListService); const explorerService = accessor.get(IExplorerService); const fileService = accessor.get(IFileService); const textFileService = accessor.get(ITextFileService); @@ -922,47 +878,45 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole await viewletService.openViewlet(VIEWLET_ID, true); - const list = listService.lastFocusedList; - if (list) { - const { stat } = getContext(list); - let folder: ExplorerItem; - if (stat) { - folder = stat.isDirectory ? stat : (stat.parent || explorerService.roots[0]); - } else { - folder = explorerService.roots[0]; - } - - if (folder.isReadonly) { - throw new Error('Parent folder is readonly.'); - } - - const newStat = new NewExplorerItem(folder, isFolder); - await folder.fetchChildren(fileService, explorerService); - - folder.addChild(newStat); - - const onSuccess = (value: string): Promise => { - const createPromise = isFolder ? fileService.createFolder(resources.joinPath(folder.resource, value)) : textFileService.create(resources.joinPath(folder.resource, value)); - return createPromise.then(created => { - refreshIfSeparator(value, explorerService); - return isFolder ? explorerService.select(created.resource, true) - : editorService.openEditor({ resource: created.resource, options: { pinned: true } }).then(() => undefined); - }, error => { - onErrorWithRetry(notificationService, error, () => onSuccess(value)); - }); - }; - - explorerService.setEditable(newStat, { - validationMessage: value => validateFileName(newStat, value), - onFinish: (value, success) => { - folder.removeChild(newStat); - explorerService.setEditable(newStat, null); - if (success) { - onSuccess(value); - } - } - }); + const stats = explorerService.getContext(false); + const stat = stats.length > 0 ? stats[0] : undefined; + let folder: ExplorerItem; + if (stat) { + folder = stat.isDirectory ? stat : (stat.parent || explorerService.roots[0]); + } else { + folder = explorerService.roots[0]; } + + if (folder.isReadonly) { + throw new Error('Parent folder is readonly.'); + } + + const newStat = new NewExplorerItem(folder, isFolder); + await folder.fetchChildren(fileService, explorerService); + + folder.addChild(newStat); + + const onSuccess = (value: string): Promise => { + const createPromise = isFolder ? fileService.createFolder(resources.joinPath(folder.resource, value)) : textFileService.create(resources.joinPath(folder.resource, value)); + return createPromise.then(created => { + refreshIfSeparator(value, explorerService); + return isFolder ? explorerService.select(created.resource, true) + : editorService.openEditor({ resource: created.resource, options: { pinned: true } }).then(() => undefined); + }, error => { + onErrorWithRetry(notificationService, error, () => onSuccess(value)); + }); + }; + + explorerService.setEditable(newStat, { + validationMessage: value => validateFileName(newStat, value), + onFinish: (value, success) => { + folder.removeChild(newStat); + explorerService.setEditable(newStat, null); + if (success) { + onSuccess(value); + } + } + }); } CommandsRegistry.registerCommand({ @@ -980,14 +934,11 @@ CommandsRegistry.registerCommand({ }); export const renameHandler = (accessor: ServicesAccessor) => { - const listService = accessor.get(IListService); const explorerService = accessor.get(IExplorerService); const textFileService = accessor.get(ITextFileService); - if (!listService.lastFocusedList) { - return; - } - const { stat } = getContext(listService.lastFocusedList); + const stats = explorerService.getContext(false); + const stat = stats.length > 0 ? stats[0] : undefined; if (!stat) { return; } @@ -1007,52 +958,37 @@ export const renameHandler = (accessor: ServicesAccessor) => { }); }; -export const moveFileToTrashHandler = (accessor: ServicesAccessor) => { - const listService = accessor.get(IListService); - if (!listService.lastFocusedList) { - return Promise.resolve(); +export const moveFileToTrashHandler = async (accessor: ServicesAccessor) => { + const explorerService = accessor.get(IExplorerService); + const stats = explorerService.getContext(true).filter(s => !s.isRoot); + if (stats.length) { + await deleteFiles(accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, true); } - const explorerContext = getContext(listService.lastFocusedList); - const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat!]; - - return deleteFiles(accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), accessor.get(IFileService), stats, true); }; -export const deleteFileHandler = (accessor: ServicesAccessor) => { - const listService = accessor.get(IListService); - if (!listService.lastFocusedList) { - return Promise.resolve(); - } - const explorerContext = getContext(listService.lastFocusedList); - const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat!]; +export const deleteFileHandler = async (accessor: ServicesAccessor) => { + const explorerService = accessor.get(IExplorerService); + const stats = explorerService.getContext(true).filter(s => !s.isRoot); - return deleteFiles(accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), accessor.get(IFileService), stats, false); + if (stats.length) { + await deleteFiles(accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, false); + } }; let pasteShouldMove = false; export const copyFileHandler = (accessor: ServicesAccessor) => { - const listService = accessor.get(IListService); - if (!listService.lastFocusedList) { - return; - } - const explorerContext = getContext(listService.lastFocusedList); const explorerService = accessor.get(IExplorerService); - if (explorerContext.stat) { - const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat]; + const stats = explorerService.getContext(true); + if (stats.length > 0) { explorerService.setToCopy(stats, false); pasteShouldMove = false; } }; export const cutFileHandler = (accessor: ServicesAccessor) => { - const listService = accessor.get(IListService); - if (!listService.lastFocusedList) { - return; - } - const explorerContext = getContext(listService.lastFocusedList); const explorerService = accessor.get(IExplorerService); - if (explorerContext.stat) { - const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat]; + const stats = explorerService.getContext(true); + if (stats.length > 0) { explorerService.setToCopy(stats, true); pasteShouldMove = true; } @@ -1060,27 +996,41 @@ export const cutFileHandler = (accessor: ServicesAccessor) => { export const DOWNLOAD_COMMAND_ID = 'explorer.download'; const downloadFileHandler = (accessor: ServicesAccessor) => { - const listService = accessor.get(IListService); - if (!listService.lastFocusedList) { - return; - } - const explorerContext = getContext(listService.lastFocusedList); const textFileService = accessor.get(ITextFileService); + const fileDialogService = accessor.get(IFileDialogService); + const explorerService = accessor.get(IExplorerService); + const stats = explorerService.getContext(true); - if (explorerContext.stat) { - const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat]; - stats.forEach(async s => { - await textFileService.saveAs(s.resource, undefined, { availableFileSystems: [Schemas.file] }); - }); - } + stats.forEach(async s => { + if (isWeb) { + if (!s.isDirectory) { + triggerDownload(asDomUri(s.resource), s.name); + } + } else { + let defaultUri = s.isDirectory ? fileDialogService.defaultFolderPath() : fileDialogService.defaultFilePath(); + if (defaultUri && !s.isDirectory) { + defaultUri = resources.joinPath(defaultUri, s.name); + } + + const destination = await fileDialogService.showSaveDialog({ + availableFileSystems: [Schemas.file], + saveLabel: mnemonicButtonLabel(nls.localize('download', "Download")), + title: s.isDirectory ? nls.localize('downloadFolder', "Download Folder") : nls.localize('downloadFile', "Download File"), + defaultUri + }); + if (destination) { + await textFileService.copy(s.resource, destination); + } + } + }); }; + CommandsRegistry.registerCommand({ id: DOWNLOAD_COMMAND_ID, handler: downloadFileHandler }); export const pasteFileHandler = async (accessor: ServicesAccessor) => { - const listService = accessor.get(IListService); const clipboardService = accessor.get(IClipboardService); const explorerService = accessor.get(IExplorerService); const fileService = accessor.get(IFileService); @@ -1089,72 +1039,65 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => { const editorService = accessor.get(IEditorService); const configurationService = accessor.get(IConfigurationService); - if (listService.lastFocusedList) { - const explorerContext = getContext(listService.lastFocusedList); - const toPaste = resources.distinctParents(clipboardService.readResources(), r => r); - const element = explorerContext.stat || explorerService.roots[0]; + const context = explorerService.getContext(true); + const toPaste = resources.distinctParents(clipboardService.readResources(), r => r); + const element = context.length ? context[0] : explorerService.roots[0]; - // Check if target is ancestor of pasted folder - const stats = await Promise.all(toPaste.map(async fileToPaste => { + // Check if target is ancestor of pasted folder + const stats = await Promise.all(toPaste.map(async fileToPaste => { - if (element.resource.toString() !== fileToPaste.toString() && resources.isEqualOrParent(element.resource, fileToPaste)) { - throw new Error(nls.localize('fileIsAncestor', "File to paste is an ancestor of the destination folder")); - } - - try { - const fileToPasteStat = await fileService.resolve(fileToPaste); - - // Find target - let target: ExplorerItem; - if (element.resource.toString() === fileToPaste.toString()) { - target = element.parent!; - } else { - target = element.isDirectory ? element : element.parent!; - } - - const incrementalNaming = configurationService.getValue().explorer.incrementalNaming; - const targetFile = findValidPasteFileTarget(target, { resource: fileToPaste, isDirectory: fileToPasteStat.isDirectory, allowOverwrite: pasteShouldMove }, incrementalNaming); - - // Move/Copy File - if (pasteShouldMove) { - return await textFileService.move(fileToPaste, targetFile); - } else { - return await fileService.copy(fileToPaste, targetFile); - } - } catch (e) { - onError(notificationService, new Error(nls.localize('fileDeleted', "File to paste was deleted or moved meanwhile. {0}", getErrorMessage(e)))); - return undefined; - } - })); - - if (pasteShouldMove) { - // Cut is done. Make sure to clear cut state. - explorerService.setToCopy([], false); + if (element.resource.toString() !== fileToPaste.toString() && resources.isEqualOrParent(element.resource, fileToPaste)) { + throw new Error(nls.localize('fileIsAncestor', "File to paste is an ancestor of the destination folder")); } - if (stats.length >= 1) { - const stat = stats[0]; - if (stat && !stat.isDirectory && stats.length === 1) { - await editorService.openEditor({ resource: stat.resource, options: { pinned: true, preserveFocus: true } }); + + try { + const fileToPasteStat = await fileService.resolve(fileToPaste); + + // Find target + let target: ExplorerItem; + if (element.resource.toString() === fileToPaste.toString()) { + target = element.parent!; + } else { + target = element.isDirectory ? element : element.parent!; } - if (stat) { - await explorerService.select(stat.resource); + + const incrementalNaming = configurationService.getValue().explorer.incrementalNaming; + const targetFile = findValidPasteFileTarget(target, { resource: fileToPaste, isDirectory: fileToPasteStat.isDirectory, allowOverwrite: pasteShouldMove }, incrementalNaming); + + // Move/Copy File + if (pasteShouldMove) { + return await textFileService.move(fileToPaste, targetFile); + } else { + return await textFileService.copy(fileToPaste, targetFile); } + } catch (e) { + onError(notificationService, new Error(nls.localize('fileDeleted', "The file to paste has been deleted or moved since you copied it. {0}", getErrorMessage(e)))); + return undefined; + } + })); + + if (pasteShouldMove) { + // Cut is done. Make sure to clear cut state. + explorerService.setToCopy([], false); + } + if (stats.length >= 1) { + const stat = stats[0]; + if (stat && !stat.isDirectory && stats.length === 1) { + await editorService.openEditor({ resource: stat.resource, options: { pinned: true, preserveFocus: true } }); + } + if (stat) { + await explorerService.select(stat.resource); } } }; export const openFilePreserveFocusHandler = async (accessor: ServicesAccessor) => { - const listService = accessor.get(IListService); const editorService = accessor.get(IEditorService); + const explorerService = accessor.get(IExplorerService); + const stats = explorerService.getContext(true); - if (listService.lastFocusedList) { - const explorerContext = getContext(listService.lastFocusedList); - if (explorerContext.stat) { - const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat]; - await editorService.openEditors(stats.filter(s => !s.isDirectory).map(s => ({ - resource: s.resource, - options: { preserveFocus: true } - }))); - } - } + await editorService.openEditors(stats.filter(s => !s.isDirectory).map(s => ({ + resource: s.resource, + options: { preserveFocus: true } + }))); }; diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index d9f683907a..7e46934f1b 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -5,47 +5,41 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; -import { toResource, IEditorCommandsContext, SideBySideEditor } from 'vs/workbench/common/editor'; +import { toResource, IEditorCommandsContext, SideBySideEditor, IEditorIdentifier, SaveReason, SideBySideEditorInput } from 'vs/workbench/common/editor'; import { IWindowOpenable, IOpenWindowOptions, isWorkspaceToOpen, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { ExplorerFocusCondition, TextFileContentProvider, VIEWLET_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; +import { ExplorerFocusCondition, TextFileContentProvider, VIEWLET_ID, IExplorerService, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext, FilesExplorerFocusCondition } from 'vs/workbench/contrib/files/common/files'; import { ExplorerViewlet } from 'vs/workbench/contrib/files/browser/explorerViewlet'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { ITextFileService, ISaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { IListService } from 'vs/platform/list/browser/listService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; +import { RawContextKey, IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IFileService } from 'vs/platform/files/common/files'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { IEditorViewState } from 'vs/editor/common/editorCommon'; -import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; import { isWindows } from 'vs/base/common/platform'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { getResourceForCommand, getMultiSelectedResources } from 'vs/workbench/contrib/files/browser/files'; +import { getResourceForCommand, getMultiSelectedResources, getOpenEditorsViewMultiSelection } from 'vs/workbench/contrib/files/browser/files'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; import { getMultiSelectedEditorContexts } from 'vs/workbench/browser/parts/editor/editorCommands'; import { Schemas } from 'vs/base/common/network'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { IEditorService, SIDE_GROUP, IResourceEditorReplacement } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorService, SIDE_GROUP, ISaveEditorsOptions } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorGroupsService, GroupsOrder, EditorsOrder, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ILabelService } from 'vs/platform/label/common/label'; -import { basename, toLocalResource, joinPath, isEqual } from 'vs/base/common/resources'; +import { basename, joinPath, isEqual } from 'vs/base/common/resources'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { UNTITLED_WORKSPACE_NAME } from 'vs/platform/workspaces/common/workspaces'; -import { withUndefinedAsNull } from 'vs/base/common/types'; - -// {{SQL CARBON EDIT}} -import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService'; +import { coalesce } from 'vs/base/common/arrays'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; // Commands @@ -76,11 +70,17 @@ export const SAVE_FILES_COMMAND_ID = 'workbench.action.files.saveFiles'; export const OpenEditorsGroupContext = new RawContextKey('groupFocusedInOpenEditors', false); export const DirtyEditorContext = new RawContextKey('dirtyEditor', false); +export const ReadonlyEditorContext = new RawContextKey('readonlyEditor', false); export const ResourceSelectedForCompareContext = new RawContextKey('resourceSelectedForCompare', false); export const REMOVE_ROOT_FOLDER_COMMAND_ID = 'removeRootFolder'; export const REMOVE_ROOT_FOLDER_LABEL = nls.localize('removeFolderFromWorkspace', "Remove Folder from Workspace"); +export const PREVIOUS_COMPRESSED_FOLDER = 'previousCompressedFolder'; +export const NEXT_COMPRESSED_FOLDER = 'nextCompressedFolder'; +export const FIRST_COMPRESSED_FOLDER = 'firstCompressedFolder'; +export const LAST_COMPRESSED_FOLDER = 'lastCompressedFolder'; + export const openWindowCommand = (accessor: ServicesAccessor, toOpen: IWindowOpenable[], options?: IOpenWindowOptions) => { if (Array.isArray(toOpen)) { const hostService = accessor.get(IHostService); @@ -106,205 +106,8 @@ export const newWindowCommand = (accessor: ServicesAccessor, options?: IOpenEmpt hostService.openWindow(options); }; -// {{SQL CARBON EDIT}} -async function save( - resource: URI | null, - isSaveAs: boolean, - options: ISaveOptions | undefined, - editorService: IEditorService, - fileService: IFileService, - untitledEditorService: IUntitledEditorService, - textFileService: ITextFileService, - editorGroupService: IEditorGroupsService, - queryEditorService: IQueryEditorService, - environmentService: IWorkbenchEnvironmentService -): Promise { - if (!resource || (!fileService.canHandleResource(resource) && resource.scheme !== Schemas.untitled)) { - return; // save is not supported - } - - // Save As (or Save untitled with associated path) - if (isSaveAs || resource.scheme === Schemas.untitled) { - return doSaveAs(resource, isSaveAs, options, editorService, fileService, untitledEditorService, textFileService, editorGroupService, queryEditorService, environmentService); // {{SQL CARBON EDIT}} add paramater - } - - // Save - return doSave(resource, options, editorService, textFileService); -} - -async function doSaveAs( - resource: URI, - isSaveAs: boolean, - options: ISaveOptions | undefined, - editorService: IEditorService, - fileService: IFileService, - untitledEditorService: IUntitledEditorService, - textFileService: ITextFileService, - editorGroupService: IEditorGroupsService, - queryEditorService: IQueryEditorService, - environmentService: IWorkbenchEnvironmentService -): Promise { - let viewStateOfSource: IEditorViewState | null = null; - const activeTextEditorWidget = getCodeEditor(editorService.activeTextEditorWidget); - if (activeTextEditorWidget) { - const activeResource = toResource(editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); - if (activeResource && (fileService.canHandleResource(activeResource) || resource.scheme === Schemas.untitled) && isEqual(activeResource, resource)) { - viewStateOfSource = activeTextEditorWidget.saveViewState(); - } - } - - // Special case: an untitled file with associated path gets saved directly unless "saveAs" is true - let target: URI | undefined; - if (!isSaveAs && resource.scheme === Schemas.untitled && untitledEditorService.hasAssociatedFilePath(resource)) { - const result = await textFileService.save(resource, options); - if (result) { - target = toLocalResource(resource, environmentService.configuration.remoteAuthority); - } - } - - // Otherwise, really "Save As..." - else { - - // Force a change to the file to trigger external watchers if any - // fixes https://github.com/Microsoft/vscode/issues/59655 - options = ensureForcedSave(options); - - target = await textFileService.saveAs(resource, undefined, options); - } - - if (!target || isEqual(target, resource)) { - return false; // save canceled or same resource used - } - - const replacement: IResourceInput = { - resource: target, - options: { - pinned: true, - viewState: viewStateOfSource || undefined - } - }; - - await Promise.all(editorGroupService.groups.map(group => - editorService.replaceEditors([{ - editor: { resource }, - replacement - }], group))).then(() => { - // {{SQL CARBON EDIT}} - queryEditorService.onSaveAsCompleted(resource, target); - return true; - }); - - return true; -} - -async function doSave( - resource: URI, - options: ISaveOptions | undefined, - editorService: IEditorService, - textFileService: ITextFileService -): Promise { - - // Pin the active editor if we are saving it - const activeControl = editorService.activeControl; - const activeEditorResource = activeControl?.input?.getResource(); - if (activeControl && activeEditorResource && isEqual(activeEditorResource, resource)) { - activeControl.group.pinEditor(activeControl.input); - } - - // Just save (force a change to the file to trigger external watchers if any) - options = ensureForcedSave(options); - - return textFileService.save(resource, options); -} - -function ensureForcedSave(options?: ISaveOptions): ISaveOptions { - if (!options) { - options = { force: true }; - } else { - options.force = true; - } - - return options; -} - -async function saveAll(saveAllArguments: any, editorService: IEditorService, untitledEditorService: IUntitledEditorService, - textFileService: ITextFileService, editorGroupService: IEditorGroupsService): Promise { - - // Store some properties per untitled file to restore later after save is completed - const groupIdToUntitledResourceInput = new Map(); - - editorGroupService.groups.forEach(group => { - const activeEditorResource = group.activeEditor && group.activeEditor.getResource(); - group.editors.forEach(e => { - const resource = e.getResource(); - if (resource && untitledEditorService.isDirty(resource)) { - if (!groupIdToUntitledResourceInput.has(group.id)) { - groupIdToUntitledResourceInput.set(group.id, []); - } - - groupIdToUntitledResourceInput.get(group.id)!.push({ - encoding: untitledEditorService.getEncoding(resource), - resource, - options: { - inactive: activeEditorResource ? !isEqual(activeEditorResource, resource) : true, - pinned: true, - preserveFocus: true, - index: group.getIndexOfEditor(e) - } - }); - } - }); - }); - - // Save all - const result = await textFileService.saveAll(saveAllArguments); - - // Update untitled resources to the saved ones, so we open the proper files - groupIdToUntitledResourceInput.forEach((inputs, groupId) => { - // {{SQL CARBON EDIT}} Update untitled resources to the saved ones, so we open the proper files - const replacementPairs: IResourceEditorReplacement[] = []; - inputs.forEach(i => { - const targetResult = result.results.filter(r => r.success && isEqual(r.source, i.resource)).pop(); - if (targetResult?.target) { - // i.resource = targetResult.target;let editor = i; - const editor = i; - const replacement: IResourceInput = { - resource: targetResult.target, - encoding: i.encoding, - options: { - pinned: true, - viewState: undefined - } - }; - replacementPairs.push({ editor: editor, replacement: replacement }); - } - }); - - editorService.replaceEditors(replacementPairs, groupId); - }); -} - // Command registration -CommandsRegistry.registerCommand({ - id: REVERT_FILE_COMMAND_ID, - handler: async (accessor, resource: URI | object) => { - const editorService = accessor.get(IEditorService); - const textFileService = accessor.get(ITextFileService); - const notificationService = accessor.get(INotificationService); - const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService) - .filter(resource => resource.scheme !== Schemas.untitled); - - if (resources.length) { - try { - await textFileService.revertAll(resources, { force: true }); - } catch (error) { - notificationService.error(nls.localize('genericRevertError', "Failed to revert '{0}': {1}", resources.map(r => basename(r)).join(', '), toErrorMessage(error, false))); - } - } - } -}); - KeybindingsRegistry.registerCommandAndKeybindingRule({ weight: KeybindingWeight.WorkbenchContrib, when: ExplorerFocusCondition, @@ -320,10 +123,13 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ // Set side input if (resources.length) { - const resolved = await fileService.resolveAll(resources.map(resource => ({ resource }))); + const untitledResources = resources.filter(resource => resource.scheme === Schemas.untitled); + const fileResources = resources.filter(resource => resource.scheme !== Schemas.untitled); + + const resolved = await fileService.resolveAll(fileResources.map(resource => ({ resource }))); const editors = resolved.filter(r => r.stat && r.success && !r.stat.isDirectory).map(r => ({ resource: r.stat!.resource - })); + })).concat(...untitledResources.map(untitledResource => ({ resource: untitledResource }))); await editorService.openEditors(editors, SIDE_GROUP); } @@ -505,40 +311,90 @@ CommandsRegistry.registerCommand({ } }); -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: SAVE_FILE_AS_COMMAND_ID, - weight: KeybindingWeight.WorkbenchContrib, - when: undefined, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_S, - handler: (accessor, resourceOrObject: URI | object | { from: string }) => { - const editorService = accessor.get(IEditorService); - let resource: URI | null = null; - if (resourceOrObject && 'from' in resourceOrObject && resourceOrObject.from === 'menu') { - resource = withUndefinedAsNull(toResource(editorService.activeEditor)); - } else { - resource = withUndefinedAsNull(getResourceForCommand(resourceOrObject, accessor.get(IListService), editorService)); - } +// Save / Save As / Save All / Revert - // {{SQL CARBON EDIT}} - return save(resource, true, undefined, editorService, accessor.get(IFileService), accessor.get(IUntitledEditorService), accessor.get(ITextFileService), accessor.get(IEditorGroupsService), accessor.get(IQueryEditorService), accessor.get(IWorkbenchEnvironmentService)); +async function saveSelectedEditors(accessor: ServicesAccessor, options?: ISaveEditorsOptions): Promise { + const listService = accessor.get(IListService); + const editorGroupService = accessor.get(IEditorGroupsService); + const codeEditorService = accessor.get(ICodeEditorService); + const textFileService = accessor.get(ITextFileService); + + // Retrieve selected or active editor + let editors = getOpenEditorsViewMultiSelection(listService, editorGroupService); + if (!editors) { + const activeGroup = editorGroupService.activeGroup; + if (activeGroup.activeEditor) { + editors = []; + + // Special treatment for side by side editors: if the active editor + // has 2 sides, we consider both, to support saving both sides. + // We only allow this when saving, not for "Save As". + // See also https://github.com/microsoft/vscode/issues/4180 + if (activeGroup.activeEditor instanceof SideBySideEditorInput && !options?.saveAs) { + editors.push({ groupId: activeGroup.id, editor: activeGroup.activeEditor.master }); + editors.push({ groupId: activeGroup.id, editor: activeGroup.activeEditor.details }); + } else { + editors.push({ groupId: activeGroup.id, editor: activeGroup.activeEditor }); + } + } } -}); + + if (!editors || editors.length === 0) { + return; // nothing to save + } + + // Save editors + await doSaveEditors(accessor, editors, options); + + // Special treatment for embedded editors: if we detect that focus is + // inside an embedded code editor, we save that model as well if we + // find it in our text file models. Currently, only textual editors + // support embedded editors. + const focusedCodeEditor = codeEditorService.getFocusedCodeEditor(); + if (focusedCodeEditor instanceof EmbeddedCodeEditorWidget) { + const resource = focusedCodeEditor.getModel()?.uri; + + // Check that the resource of the model was not saved already + if (resource && !editors.some(({ editor }) => isEqual(toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }), resource))) { + const model = textFileService.models.get(resource); + if (!model?.isReadonly()) { + await textFileService.save(resource, options); + } + } + } +} + +function saveDirtyEditorsOfGroups(accessor: ServicesAccessor, groups: ReadonlyArray, options?: ISaveEditorsOptions): Promise { + const dirtyEditors: IEditorIdentifier[] = []; + for (const group of groups) { + for (const editor of group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)) { + if (editor.isDirty()) { + dirtyEditors.push({ groupId: group.id, editor }); + } + } + } + + return doSaveEditors(accessor, dirtyEditors, options); +} + +async function doSaveEditors(accessor: ServicesAccessor, editors: IEditorIdentifier[], options?: ISaveEditorsOptions): Promise { + const editorService = accessor.get(IEditorService); + const notificationService = accessor.get(INotificationService); + + try { + await editorService.save(editors, options); + } catch (error) { + notificationService.error(nls.localize('genericSaveError', "Failed to save '{0}': {1}", editors.map(({ editor }) => editor.getName()).join(', '), toErrorMessage(error, false))); + } +} KeybindingsRegistry.registerCommandAndKeybindingRule({ when: undefined, weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.KEY_S, id: SAVE_FILE_COMMAND_ID, - handler: (accessor, resource: URI | object) => { - const editorService = accessor.get(IEditorService); - const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService); - - if (resources.length === 1) { - // If only one resource is selected explictly call save since the behavior is a bit different than save all #41841 - // {{SQL CARBON EDIT}} - return save(resources[0], false, undefined, editorService, accessor.get(IFileService), accessor.get(IUntitledEditorService), accessor.get(ITextFileService), accessor.get(IEditorGroupsService), accessor.get(IQueryEditorService), accessor.get(IWorkbenchEnvironmentService)); - } - return saveAll(resources, editorService, accessor.get(IUntitledEditorService), accessor.get(ITextFileService), accessor.get(IEditorGroupsService)); + handler: accessor => { + return saveSelectedEditors(accessor, { reason: SaveReason.EXPLICIT, force: true /* force save even when non-dirty */ }); } }); @@ -549,57 +405,80 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ win: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_S) }, id: SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, handler: accessor => { - const editorService = accessor.get(IEditorService); + return saveSelectedEditors(accessor, { reason: SaveReason.EXPLICIT, force: true /* force save even when non-dirty */, skipSaveParticipants: true }); + } +}); - const resource = toResource(editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); - if (resource) { - // {{SQL CARBON EDIT}} - return save(resource, false, { skipSaveParticipants: true }, editorService, accessor.get(IFileService), accessor.get(IUntitledEditorService), accessor.get(ITextFileService), accessor.get(IEditorGroupsService), accessor.get(IQueryEditorService), accessor.get(IWorkbenchEnvironmentService)); - } - - return undefined; +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: SAVE_FILE_AS_COMMAND_ID, + weight: KeybindingWeight.WorkbenchContrib, + when: undefined, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_S, + handler: accessor => { + return saveSelectedEditors(accessor, { reason: SaveReason.EXPLICIT, saveAs: true }); } }); CommandsRegistry.registerCommand({ id: SAVE_ALL_COMMAND_ID, handler: (accessor) => { - return saveAll(true, accessor.get(IEditorService), accessor.get(IUntitledEditorService), accessor.get(ITextFileService), accessor.get(IEditorGroupsService)); + return saveDirtyEditorsOfGroups(accessor, accessor.get(IEditorGroupsService).getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE), { reason: SaveReason.EXPLICIT }); } }); CommandsRegistry.registerCommand({ id: SAVE_ALL_IN_GROUP_COMMAND_ID, handler: (accessor, _: URI | object, editorContext: IEditorCommandsContext) => { - const contexts = getMultiSelectedEditorContexts(editorContext, accessor.get(IListService), accessor.get(IEditorGroupsService)); const editorGroupService = accessor.get(IEditorGroupsService); - let saveAllArg: any; + + const contexts = getMultiSelectedEditorContexts(editorContext, accessor.get(IListService), accessor.get(IEditorGroupsService)); + + let groups: ReadonlyArray | undefined = undefined; if (!contexts.length) { - saveAllArg = true; + groups = editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE); } else { - const fileService = accessor.get(IFileService); - saveAllArg = []; - contexts.forEach(context => { - const editorGroup = editorGroupService.getGroup(context.groupId); - if (editorGroup) { - editorGroup.editors.forEach(editor => { - const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); - if (resource && (resource.scheme === Schemas.untitled || fileService.canHandleResource(resource))) { - saveAllArg.push(resource); - } - }); - } - }); + groups = coalesce(contexts.map(context => editorGroupService.getGroup(context.groupId))); } - return saveAll(saveAllArg, accessor.get(IEditorService), accessor.get(IUntitledEditorService), accessor.get(ITextFileService), accessor.get(IEditorGroupsService)); + return saveDirtyEditorsOfGroups(accessor, groups, { reason: SaveReason.EXPLICIT }); } }); CommandsRegistry.registerCommand({ id: SAVE_FILES_COMMAND_ID, - handler: (accessor) => { - return saveAll(false, accessor.get(IEditorService), accessor.get(IUntitledEditorService), accessor.get(ITextFileService), accessor.get(IEditorGroupsService)); + handler: accessor => { + const editorService = accessor.get(IEditorService); + + return editorService.saveAll({ includeUntitled: false, reason: SaveReason.EXPLICIT }); + } +}); + +CommandsRegistry.registerCommand({ + id: REVERT_FILE_COMMAND_ID, + handler: async accessor => { + const notificationService = accessor.get(INotificationService); + const listService = accessor.get(IListService); + const editorGroupService = accessor.get(IEditorGroupsService); + const editorService = accessor.get(IEditorService); + + // Retrieve selected or active editor + let editors = getOpenEditorsViewMultiSelection(listService, editorGroupService); + if (!editors) { + const activeGroup = editorGroupService.activeGroup; + if (activeGroup.activeEditor) { + editors = [{ groupId: activeGroup.id, editor: activeGroup.activeEditor }]; + } + } + + if (!editors || editors.length === 0) { + return; // nothing to revert + } + + try { + await editorService.revert(editors.filter(({ editor }) => !editor.isUntitled() /* all except untitled */), { force: true }); + } catch (error) { + notificationService.error(nls.localize('genericRevertError', "Failed to revert '{0}': {1}", editors.map(({ editor }) => editor.getName()).join(', '), toErrorMessage(error, false))); + } } }); @@ -617,3 +496,81 @@ CommandsRegistry.registerCommand({ return workspaceEditingService.removeFolders(resources); } }); + +// Compressed item navigation + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + weight: KeybindingWeight.WorkbenchContrib + 10, + when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext.negate()), + primary: KeyCode.LeftArrow, + id: PREVIOUS_COMPRESSED_FOLDER, + handler: (accessor) => { + const viewletService = accessor.get(IViewletService); + const viewlet = viewletService.getActiveViewlet(); + + if (viewlet?.getId() !== VIEWLET_ID) { + return; + } + + const explorer = viewlet as ExplorerViewlet; + const view = explorer.getExplorerView(); + view.previousCompressedStat(); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + weight: KeybindingWeight.WorkbenchContrib + 10, + when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerCompressedFocusContext, ExplorerCompressedLastFocusContext.negate()), + primary: KeyCode.RightArrow, + id: NEXT_COMPRESSED_FOLDER, + handler: (accessor) => { + const viewletService = accessor.get(IViewletService); + const viewlet = viewletService.getActiveViewlet(); + + if (viewlet?.getId() !== VIEWLET_ID) { + return; + } + + const explorer = viewlet as ExplorerViewlet; + const view = explorer.getExplorerView(); + view.nextCompressedStat(); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + weight: KeybindingWeight.WorkbenchContrib + 10, + when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext.negate()), + primary: KeyCode.Home, + id: FIRST_COMPRESSED_FOLDER, + handler: (accessor) => { + const viewletService = accessor.get(IViewletService); + const viewlet = viewletService.getActiveViewlet(); + + if (viewlet?.getId() !== VIEWLET_ID) { + return; + } + + const explorer = viewlet as ExplorerViewlet; + const view = explorer.getExplorerView(); + view.firstCompressedStat(); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + weight: KeybindingWeight.WorkbenchContrib + 10, + when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerCompressedFocusContext, ExplorerCompressedLastFocusContext.negate()), + primary: KeyCode.End, + id: LAST_COMPRESSED_FOLDER, + handler: (accessor) => { + const viewletService = accessor.get(IViewletService); + const viewlet = viewletService.getActiveViewlet(); + + if (viewlet?.getId() !== VIEWLET_ID) { + return; + } + + const explorer = viewlet as ExplorerViewlet; + const view = explorer.getExplorerView(); + view.lastCompressedStat(); + } +}); diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 40b9527007..5cd844e60d 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -9,14 +9,14 @@ import * as nls from 'vs/nls'; import { sep } from 'vs/base/common/path'; import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IEditorInputFactory, EditorInput, IFileEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } from 'vs/workbench/common/editor'; import { AutoSaveConfiguration, HotExitConfiguration } from 'vs/platform/files/common/files'; import { VIEWLET_ID, SortOrderConfiguration, FILE_EDITOR_INPUT_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { FileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/fileEditorTracker'; -import { SaveErrorHandler } from 'vs/workbench/contrib/files/browser/saveErrorHandler'; +import { TextFileSaveErrorHandler } from 'vs/workbench/contrib/files/browser/textFileSaveErrorHandler'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { BinaryFileEditor } from 'vs/workbench/contrib/files/browser/editors/binaryFileEditor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -27,7 +27,6 @@ import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import * as platform from 'vs/base/common/platform'; import { ExplorerViewlet, ExplorerViewletViewsContribution } from 'vs/workbench/contrib/files/browser/explorerViewlet'; import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; -import { DataUriEditorInput } from 'vs/workbench/common/editor/dataUriEditorInput'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -38,6 +37,8 @@ import { ExplorerService } from 'vs/workbench/contrib/files/common/explorerServi import { SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/textfiles'; import { Schemas } from 'vs/base/common/network'; import { WorkspaceWatcher } from 'vs/workbench/contrib/files/common/workspaceWatcher'; +import { editorConfigurationBaseNode } from 'vs/editor/common/config/commonEditorConfig'; +import { DirtyFilesIndicator } from 'vs/workbench/contrib/files/common/dirtyFilesIndicator'; // Viewlet Action export class OpenExplorerViewletAction extends ShowViewletAction { @@ -73,13 +74,12 @@ class FileUriLabelContribution implements IWorkbenchContribution { } // Register Viewlet -Registry.as(ViewletExtensions.Viewlets).registerViewlet(new ViewletDescriptor( +Registry.as(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( ExplorerViewlet, VIEWLET_ID, nls.localize('explore', "Explorer"), - 'explore', - // {{SQL CARBON EDIT}} - 10 + 'codicon-files', + 10 // {{SQL CARBON EDIT}} )); registerSingleton(IExplorerService, ExplorerService, true); @@ -94,21 +94,20 @@ const openViewletKb: IKeybindings = { // Register Action to Open Viewlet const registry = Registry.as(ActionExtensions.WorkbenchActions); registry.registerWorkbenchAction( - new SyncActionDescriptor(OpenExplorerViewletAction, OpenExplorerViewletAction.ID, OpenExplorerViewletAction.LABEL, openViewletKb), + SyncActionDescriptor.create(OpenExplorerViewletAction, OpenExplorerViewletAction.ID, OpenExplorerViewletAction.LABEL, openViewletKb), 'View: Show Explorer', nls.localize('view', "View") ); // Register file editors Registry.as(EditorExtensions.Editors).registerEditor( - new EditorDescriptor( + EditorDescriptor.create( BinaryFileEditor, BinaryFileEditor.ID, nls.localize('binaryFileEditor', "Binary File Editor") ), [ - new SyncDescriptor(FileEditorInput), - new SyncDescriptor(DataUriEditorInput) + new SyncDescriptor(FileEditorInput) ] ); @@ -166,19 +165,22 @@ Registry.as(WorkbenchExtensions.Workbench).regi // Register File Editor Tracker Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(FileEditorTracker, LifecyclePhase.Starting); -// Register Save Error Handler -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(SaveErrorHandler, LifecyclePhase.Starting); +// Register Text File Save Error Handler +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(TextFileSaveErrorHandler, LifecyclePhase.Starting); // Register uri display for file uris Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(FileUriLabelContribution, LifecyclePhase.Starting); -// Workspace Watcher +// Register Workspace Watcher Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(WorkspaceWatcher, LifecyclePhase.Restored); +// Register Dirty Files Indicator +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DirtyFilesIndicator, LifecyclePhase.Starting); + // Configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); -const hotExitConfiguration = platform.isNative ? +const hotExitConfiguration: IConfigurationPropertySchema = platform.isNative ? { 'type': 'string', 'scope': ConfigurationScope.APPLICATION, @@ -341,10 +343,7 @@ configurationRegistry.registerConfiguration({ }); configurationRegistry.registerConfiguration({ - id: 'editor', - order: 5, - title: nls.localize('editorConfigurationTitle', "Editor"), - type: 'object', + ...editorConfigurationBaseNode, properties: { 'editor.formatOnSave': { 'type': 'boolean', @@ -425,7 +424,12 @@ configurationRegistry.registerConfiguration({ ], description: nls.localize('explorer.incrementalNaming', "Controls what naming strategy to use when a giving a new name to a duplicated explorer item on paste."), default: 'simple' - } + }, + 'explorer.compactFolders': { + 'type': 'boolean', + 'description': nls.localize('compressSingleChildFolders', "Controls whether the explorer should render folders in a compact form. In such a form, single child folders will be compressed in a combined tree element. Useful for Java package structures, for example."), + 'default': true + }, } }); diff --git a/src/vs/workbench/contrib/files/browser/files.ts b/src/vs/workbench/contrib/files/browser/files.ts index 26c8e6ddaa..cced7d2513 100644 --- a/src/vs/workbench/contrib/files/browser/files.ts +++ b/src/vs/workbench/contrib/files/browser/files.ts @@ -4,21 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { IListService, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; +import { IListService } from 'vs/platform/list/browser/listService'; import { OpenEditor } from 'vs/workbench/contrib/files/common/files'; -import { toResource, SideBySideEditor } from 'vs/workbench/common/editor'; +import { toResource, SideBySideEditor, IEditorIdentifier } from 'vs/workbench/common/editor'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { coalesce } from 'vs/base/common/arrays'; +import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -// Commands can get exeucted from a command pallete, from a context menu or from some list using a keybinding -// To cover all these cases we need to properly compute the resource on which the command is being executed -export function getResourceForCommand(resource: URI | object | undefined, listService: IListService, editorService: IEditorService): URI | undefined { - if (URI.isUri(resource)) { - return resource; - } - +function getFocus(listService: IListService): unknown | undefined { let list = listService.lastFocusedList; if (list?.getHTMLElement() === document.activeElement) { let focus: unknown; @@ -27,18 +23,31 @@ export function getResourceForCommand(resource: URI | object | undefined, listSe if (focused.length) { focus = focused[0]; } - } else if (list instanceof WorkbenchAsyncDataTree) { + } else if (list instanceof AsyncDataTree) { const focused = list.getFocus(); if (focused.length) { focus = focused[0]; } } - if (focus instanceof ExplorerItem) { - return focus.resource; - } else if (focus instanceof OpenEditor) { - return focus.getResource(); - } + return focus; + } + + return undefined; +} + +// Commands can get exeucted from a command pallete, from a context menu or from some list using a keybinding +// To cover all these cases we need to properly compute the resource on which the command is being executed +export function getResourceForCommand(resource: URI | object | undefined, listService: IListService, editorService: IEditorService): URI | undefined { + if (URI.isUri(resource)) { + return resource; + } + + const focus = getFocus(listService); + if (focus instanceof ExplorerItem) { + return focus.resource; + } else if (focus instanceof OpenEditor) { + return focus.getResource(); } return editorService.activeEditor ? toResource(editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }) : undefined; @@ -48,7 +57,7 @@ export function getMultiSelectedResources(resource: URI | object | undefined, li const list = listService.lastFocusedList; if (list?.getHTMLElement() === document.activeElement) { // Explorer - if (list instanceof WorkbenchAsyncDataTree) { + if (list instanceof AsyncDataTree) { const selection = list.getSelection().map((fs: ExplorerItem) => fs.resource); const focusedElements = list.getFocus(); const focus = focusedElements.length ? focusedElements[0] : undefined; @@ -82,3 +91,25 @@ export function getMultiSelectedResources(resource: URI | object | undefined, li const result = getResourceForCommand(resource, listService, editorService); return !!result ? [result] : []; } + +export function getOpenEditorsViewMultiSelection(listService: IListService, editorGroupService: IEditorGroupsService): Array | undefined { + const list = listService.lastFocusedList; + if (list?.getHTMLElement() === document.activeElement) { + // Open editors view + if (list instanceof List) { + const selection = coalesce(list.getSelectedElements().filter(s => s instanceof OpenEditor)); + const focusedElements = list.getFocusedElements(); + const focus = focusedElements.length ? focusedElements[0] : undefined; + let mainEditor: IEditorIdentifier | undefined = undefined; + if (focus instanceof OpenEditor) { + mainEditor = focus; + } + // We only respect the selection if it contains the main element. + if (selection.some(s => s === mainEditor)) { + return selection; + } + } + } + + return undefined; +} diff --git a/src/vs/workbench/contrib/files/browser/files.web.contribution.ts b/src/vs/workbench/contrib/files/browser/files.web.contribution.ts index 4ae1f3dc98..e7c162bf70 100644 --- a/src/vs/workbench/contrib/files/browser/files.web.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.web.contribution.ts @@ -10,13 +10,10 @@ import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileE import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; import { TextFileEditor } from 'vs/workbench/contrib/files/browser/editors/textFileEditor'; -import { DirtyFilesTracker } from 'vs/workbench/contrib/files/common/dirtyFilesTracker'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; // Register file editor Registry.as(EditorExtensions.Editors).registerEditor( - new EditorDescriptor( + EditorDescriptor.create( TextFileEditor, TextFileEditor.ID, nls.localize('textFileEditor', "Text File Editor") @@ -25,6 +22,3 @@ Registry.as(EditorExtensions.Editors).registerEditor( new SyncDescriptor(FileEditorInput) ] ); - -// Register Dirty Files Tracker -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DirtyFilesTracker, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/files/browser/media/check-dark.svg b/src/vs/workbench/contrib/files/browser/media/check-dark.svg deleted file mode 100644 index 51674695e1..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/check-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/files/browser/media/check-light.svg b/src/vs/workbench/contrib/files/browser/media/check-light.svg deleted file mode 100644 index 7b1da6d720..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/check-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/files/browser/media/collapse-all-dark.svg b/src/vs/workbench/contrib/files/browser/media/collapse-all-dark.svg deleted file mode 100644 index 4862c55dbe..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/collapse-all-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/files/browser/media/collapse-all-hc.svg b/src/vs/workbench/contrib/files/browser/media/collapse-all-hc.svg deleted file mode 100644 index 05f920b29b..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/collapse-all-hc.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/files/browser/media/collapse-all-light.svg b/src/vs/workbench/contrib/files/browser/media/collapse-all-light.svg deleted file mode 100644 index 6359b42e62..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/collapse-all-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css index f45c99095e..fc8387e3b9 100644 --- a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css +++ b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css @@ -3,11 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/* Activity Bar */ -.monaco-workbench .activitybar .monaco-action-bar .action-label.explore { - -webkit-mask: url('files-activity-bar.svg') no-repeat 50% 50%; -} - /* --- Explorer viewlet --- */ .explorer-viewlet, .explorer-folders-view { @@ -66,13 +61,14 @@ align-items: center; } -.explorer-viewlet .panel-header .count { +.explorer-viewlet .pane-header .count { min-width: fit-content; + min-width: -moz-fit-content; display: flex; align-items: center; } -.explorer-viewlet .panel-header .monaco-count-badge.hidden { +.explorer-viewlet .pane-header .monaco-count-badge.hidden { display: none; } @@ -123,6 +119,13 @@ padding: 0 20px 0 20px; } +.explorer-viewlet .explorer-empty-view .monaco-button { + max-width: 260px; + margin-left: auto; + margin-right: auto; + display: block; +} + .explorer-viewlet .explorer-item.nonexistent-root { opacity: 0.5; } @@ -132,6 +135,16 @@ line-height: normal; } +.explorer-viewlet .explorer-item .monaco-icon-name-container.multiple > .label-name > .monaco-highlighted-label { + padding: 1px; + border-radius: 3px; +} + +.explorer-viewlet .explorer-item .monaco-icon-name-container.multiple > .label-name:hover > .monaco-highlighted-label, +.explorer-viewlet .monaco-list .monaco-list-row.focused .explorer-item .monaco-icon-name-container.multiple > .label-name.active > .monaco-highlighted-label { + text-decoration: underline; +} + .monaco-workbench.linux .explorer-viewlet .explorer-item .monaco-inputbox, .monaco-workbench.mac .explorer-viewlet .explorer-item .monaco-inputbox { height: 22px; @@ -163,16 +176,3 @@ .hc-black .monaco-workbench .explorer-viewlet .editor-group { line-height: 20px; } - -/* TODO @misolori convert these to use icon font, for the debug viewlet */ -.monaco-workbench .explorer-action.collapse-explorer { - background: url("collapse-all-light.svg") 50% no-repeat; -} - -.vs-dark .monaco-workbench .explorer-action.collapse-explorer { - background: url("collapse-all-dark.svg") 50% no-repeat; -} - -.hc-black .monaco-workbench .explorer-action.collapse-explorer { - background: url("collapse-all-hc.svg") 50% no-repeat; -} diff --git a/src/vs/workbench/contrib/files/browser/media/fileactions.css b/src/vs/workbench/contrib/files/browser/media/fileactions.css index b97c0f5ac4..38b225f329 100644 --- a/src/vs/workbench/contrib/files/browser/media/fileactions.css +++ b/src/vs/workbench/contrib/files/browser/media/fileactions.css @@ -3,41 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/* Split editor vertical */ -.monaco-workbench .quick-open-sidebyside-vertical { - background-image: url("split-editor-vertical-light.svg"); -} - -.vs-dark .monaco-workbench .quick-open-sidebyside-vertical { - background-image: url("split-editor-vertical-dark.svg"); -} - -.hc-black .monaco-workbench .quick-open-sidebyside-vertical { - background-image: url("split-editor-vertical-hc.svg"); -} - -/* Split editor horizontal */ -.monaco-workbench .quick-open-sidebyside-horizontal { - background-image: url("split-editor-horizontal-light.svg"); -} - -.vs-dark .monaco-workbench .quick-open-sidebyside-horizontal { - background-image: url("split-editor-horizontal-dark.svg"); -} - -.hc-black .monaco-workbench .quick-open-sidebyside-horizontal { - background-image: url("split-editor-horizontal-hc.svg"); -} - -.monaco-workbench .file-editor-action.action-open-preview { - background: url("preview-light.svg") center center no-repeat; -} - -.vs-dark .monaco-workbench .file-editor-action.action-open-preview, -.hc-black .monaco-workbench .file-editor-action.action-open-preview { - background: url("preview-dark.svg") center center no-repeat; -} - .explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row.dirty:not(:hover) > .monaco-action-bar .codicon-close::before { content: "\ea71"; } diff --git a/src/vs/workbench/contrib/files/browser/media/files-activity-bar.svg b/src/vs/workbench/contrib/files/browser/media/files-activity-bar.svg deleted file mode 100644 index c109b13c3d..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/files-activity-bar.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/files/browser/media/preview-dark.svg b/src/vs/workbench/contrib/files/browser/media/preview-dark.svg deleted file mode 100644 index 1d59877196..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/preview-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/files/browser/media/preview-light.svg b/src/vs/workbench/contrib/files/browser/media/preview-light.svg deleted file mode 100644 index 3f1152fc3c..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/preview-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/files/browser/media/split-editor-horizontal-dark.svg b/src/vs/workbench/contrib/files/browser/media/split-editor-horizontal-dark.svg deleted file mode 100644 index 419c21be4f..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/split-editor-horizontal-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/files/browser/media/split-editor-horizontal-hc.svg b/src/vs/workbench/contrib/files/browser/media/split-editor-horizontal-hc.svg deleted file mode 100644 index 7565fd3c16..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/split-editor-horizontal-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/files/browser/media/split-editor-horizontal-light.svg b/src/vs/workbench/contrib/files/browser/media/split-editor-horizontal-light.svg deleted file mode 100644 index 405291c14d..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/split-editor-horizontal-light.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/files/browser/media/split-editor-vertical-dark.svg b/src/vs/workbench/contrib/files/browser/media/split-editor-vertical-dark.svg deleted file mode 100644 index 8c22a7c5bf..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/split-editor-vertical-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/files/browser/media/split-editor-vertical-hc.svg b/src/vs/workbench/contrib/files/browser/media/split-editor-vertical-hc.svg deleted file mode 100644 index 82c19d0c8f..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/split-editor-vertical-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/files/browser/media/split-editor-vertical-light.svg b/src/vs/workbench/contrib/files/browser/media/split-editor-vertical-light.svg deleted file mode 100644 index d5a2e9415b..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/split-editor-vertical-light.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/files/browser/media/undo-dark.svg b/src/vs/workbench/contrib/files/browser/media/undo-dark.svg deleted file mode 100644 index de85d6ba67..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/undo-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/files/browser/media/undo-light.svg b/src/vs/workbench/contrib/files/browser/media/undo-light.svg deleted file mode 100644 index b70626957d..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/undo-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts b/src/vs/workbench/contrib/files/browser/textFileSaveErrorHandler.ts similarity index 95% rename from src/vs/workbench/contrib/files/browser/saveErrorHandler.ts rename to src/vs/workbench/contrib/files/browser/textFileSaveErrorHandler.ts index 30b3fb65b8..045cac2555 100644 --- a/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts +++ b/src/vs/workbench/contrib/files/browser/textFileSaveErrorHandler.ts @@ -28,7 +28,7 @@ import { INotificationService, INotificationHandle, INotificationActions, Severi import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ExecuteCommandAction } from 'vs/platform/actions/common/actions'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IProductService } from 'vs/platform/product/common/productService'; import { Event } from 'vs/base/common/event'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { isWindows } from 'vs/base/common/platform'; @@ -41,8 +41,8 @@ const LEARN_MORE_DIRTY_WRITE_IGNORE_KEY = 'learnMoreDirtyWriteError'; const conflictEditorHelp = nls.localize('userGuide', "Use the actions in the editor tool bar to either undo your changes or overwrite the content of the file with your changes."); -// A handler for save error happening with conflict resolution actions -export class SaveErrorHandler extends Disposable implements ISaveErrorHandler, IWorkbenchContribution { +// A handler for text file save error happening with conflict resolution actions +export class TextFileSaveErrorHandler extends Disposable implements ISaveErrorHandler, IWorkbenchContribution { private messages: ResourceMap; private conflictResolutionContext: IContextKey; private activeConflictResolutionResource?: URI; @@ -103,7 +103,7 @@ export class SaveErrorHandler extends Disposable implements ISaveErrorHandler, I onSaveError(error: any, model: ITextFileEditorModel): void { const fileOperationError = error as FileOperationError; - const resource = model.getResource(); + const resource = model.resource; let message: string; const primaryActions: IAction[] = []; @@ -113,7 +113,7 @@ export class SaveErrorHandler extends Disposable implements ISaveErrorHandler, I if (fileOperationError.fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE) { // If the user tried to save from the opened conflict editor, show its message again - if (this.activeConflictResolutionResource && this.activeConflictResolutionResource.toString() === model.getResource().toString()) { + if (this.activeConflictResolutionResource && this.activeConflictResolutionResource.toString() === model.resource.toString()) { if (this.storageService.getBoolean(LEARN_MORE_DIRTY_WRITE_IGNORE_KEY, StorageScope.GLOBAL)) { return; // return if this message is ignored } @@ -178,7 +178,7 @@ export class SaveErrorHandler extends Disposable implements ISaveErrorHandler, I const actions: INotificationActions = { primary: primaryActions, secondary: secondaryActions }; const handle = this.notificationService.notify({ severity: Severity.Error, message, actions }); Event.once(handle.onDidClose)(() => { dispose(primaryActions), dispose(secondaryActions); }); - this.messages.set(model.getResource(), handle); + this.messages.set(model.resource, handle); } dispose(): void { @@ -236,16 +236,16 @@ class ResolveSaveConflictAction extends Action { @IEditorService private readonly editorService: IEditorService, @INotificationService private readonly notificationService: INotificationService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IEnvironmentService private readonly environmentService: IEnvironmentService + @IProductService private readonly productService: IProductService ) { super('workbench.files.action.resolveConflict', nls.localize('compareChanges', "Compare")); } async run(): Promise { if (!this.model.isDisposed()) { - const resource = this.model.getResource(); + const resource = this.model.resource; const name = basename(resource); - const editorLabel = nls.localize('saveConflictDiffLabel', "{0} (in file) ↔ {1} (in {2}) - Resolve save conflict", name, name, this.environmentService.appNameLong); + const editorLabel = nls.localize('saveConflictDiffLabel', "{0} (in file) ↔ {1} (in {2}) - Resolve save conflict", name, name, this.productService.nameLong); await TextFileContentProvider.open(resource, CONFLICT_RESOLUTION_SCHEME, editorLabel, this.editorService, { pinned: true }); @@ -332,7 +332,7 @@ export const acceptLocalChangesCommand = async (accessor: ServicesAccessor, reso await model.save(); // Reopen file input - await editorService.openEditor({ resource: model.getResource() }, group); + await editorService.openEditor({ resource: model.resource }, group); // Clean up group.closeEditor(editor); @@ -361,7 +361,7 @@ export const revertLocalChangesCommand = async (accessor: ServicesAccessor, reso await model.revert(); // Reopen file input - await editorService.openEditor({ resource: model.getResource() }, group); + await editorService.openEditor({ resource: model.resource }, group); // Clean up group.closeEditor(editor); diff --git a/src/vs/workbench/contrib/files/browser/views/emptyView.ts b/src/vs/workbench/contrib/files/browser/views/emptyView.ts index 933355641e..3cea362ef5 100644 --- a/src/vs/workbench/contrib/files/browser/views/emptyView.ts +++ b/src/vs/workbench/contrib/files/browser/views/emptyView.ts @@ -16,7 +16,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet'; import { ResourcesDropHandler, DragAndDropObserver } from 'vs/workbench/browser/dnd'; import { listDropBackground } from 'vs/platform/theme/common/colorRegistry'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; @@ -26,7 +26,7 @@ import { Schemas } from 'vs/base/common/network'; import { isWeb } from 'vs/base/common/platform'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -export class EmptyView extends ViewletPanel { +export class EmptyView extends ViewletPane { static readonly ID: string = 'workbench.explorer.emptyView'; static readonly NAME = nls.localize('noWorkspace', "No Folder Opened"); @@ -46,7 +46,7 @@ export class EmptyView extends ViewletPanel { @ILabelService private labelService: ILabelService, @IContextKeyService contextKeyService: IContextKeyService ) { - super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('explorerSection', "Files Explorer Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewletPaneOptions), ariaHeaderLabel: nls.localize('explorerSection', "Files Explorer Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); this._register(this.contextService.onDidChangeWorkbenchState(() => this.setLabels())); this._register(this.labelService.onDidChangeFormatters(() => this.setLabels())); } @@ -134,9 +134,8 @@ export class EmptyView extends ViewletPanel { // no-op } - focusBody(): void { - if (this.button) { - this.button.element.focus(); - } + focus(): void { + this.button.element.focus(); } + } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 05bf1a45d0..42474dd76f 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import * as perf from 'vs/base/common/performance'; import { IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; import { memoize } from 'vs/base/common/decorators'; -import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, IExplorerService, ExplorerResourceCut, ExplorerResourceMoveableToTrash } from 'vs/workbench/contrib/files/common/files'; +import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, IExplorerService, ExplorerResourceCut, ExplorerResourceMoveableToTrash, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext } from 'vs/workbench/contrib/files/common/files'; import { NewFolderAction, NewFileAction, FileCopiedContext, RefreshExplorerView, CollapseExplorerView } from 'vs/workbench/contrib/files/browser/fileActions'; import { toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; @@ -24,12 +24,12 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ResourceContextKey } from 'vs/workbench/common/resources'; import { IDecorationsService } from 'vs/workbench/services/decorations/browser/decorations'; -import { WorkbenchAsyncDataTree, TreeResourceNavigator2 } from 'vs/platform/list/browser/listService'; +import { TreeResourceNavigator2, WorkbenchCompressibleAsyncDataTree } from 'vs/platform/list/browser/listService'; import { DelayedDragHandler } from 'vs/base/browser/dnd'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { IViewletPanelOptions, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { IViewletPaneOptions, ViewletPane } from 'vs/workbench/browser/parts/views/paneViewlet'; import { ILabelService } from 'vs/platform/label/common/label'; -import { ExplorerDelegate, ExplorerAccessibilityProvider, ExplorerDataSource, FilesRenderer, FilesFilter, FileSorter, FileDragAndDrop } from 'vs/workbench/contrib/files/browser/views/explorerViewer'; +import { ExplorerDelegate, ExplorerDataSource, FilesRenderer, ICompressedNavigationController, FilesFilter, FileSorter, FileDragAndDrop, ExplorerCompressionDelegate, isCompressedFolderName } from 'vs/workbench/contrib/files/browser/views/explorerViewer'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; @@ -49,13 +49,26 @@ import { values } from 'vs/base/common/map'; import { first } from 'vs/base/common/arrays'; import { withNullAsUndefined } from 'vs/base/common/types'; import { IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; -import { dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { Event } from 'vs/base/common/event'; +import { attachStyler, IColorMapping } from 'vs/platform/theme/common/styler'; +import { ColorValue, listDropBackground } from 'vs/platform/theme/common/colorRegistry'; +import { Color } from 'vs/base/common/color'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; -export class ExplorerView extends ViewletPanel { +interface IExplorerViewColors extends IColorMapping { + listDropBackground?: ColorValue | undefined; +} + +interface IExplorerViewStyles { + listDropBackground?: Color; +} + +export class ExplorerView extends ViewletPane { static readonly ID: string = 'workbench.explorer.fileView'; static readonly TREE_VIEW_STATE_STORAGE_KEY: string = 'workbench.explorer.treeViewState'; - private tree!: WorkbenchAsyncDataTree; + private tree!: WorkbenchCompressibleAsyncDataTree; private filter!: FilesFilter; private resourceContext: ResourceContextKey; @@ -64,6 +77,14 @@ export class ExplorerView extends ViewletPanel { private rootContext: IContextKey; private resourceMoveableToTrash: IContextKey; + private renderer!: FilesRenderer; + + private styleElement!: HTMLStyleElement; + private compressedFocusContext: IContextKey; + private compressedFocusFirstContext: IContextKey; + private compressedFocusLastContext: IContextKey; + private compressedNavigationController: ICompressedNavigationController | undefined; + // Refresh is needed on the initial explorer open private shouldRefresh = true; private dragHandler!: DelayedDragHandler; @@ -71,7 +92,7 @@ export class ExplorerView extends ViewletPanel { private actions: IAction[] | undefined; constructor( - options: IViewletPanelOptions, + options: IViewletPaneOptions, @IContextMenuService contextMenuService: IContextMenuService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @@ -91,19 +112,24 @@ export class ExplorerView extends ViewletPanel { @IClipboardService private clipboardService: IClipboardService, @IFileService private readonly fileService: IFileService ) { - super({ ...(options as IViewletPanelOptions), id: ExplorerView.ID, ariaHeaderLabel: nls.localize('explorerSection', "Files Explorer Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewletPaneOptions), id: ExplorerView.ID, ariaHeaderLabel: nls.localize('explorerSection', "Files Explorer Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); this.resourceContext = instantiationService.createInstance(ResourceContextKey); this._register(this.resourceContext); + this.folderContext = ExplorerFolderContext.bindTo(contextKeyService); this.readonlyContext = ExplorerResourceReadonlyContext.bindTo(contextKeyService); this.rootContext = ExplorerRootContext.bindTo(contextKeyService); this.resourceMoveableToTrash = ExplorerResourceMoveableToTrash.bindTo(contextKeyService); + this.compressedFocusContext = ExplorerCompressedFocusContext.bindTo(contextKeyService); + this.compressedFocusFirstContext = ExplorerCompressedFirstFocusContext.bindTo(contextKeyService); + this.compressedFocusLastContext = ExplorerCompressedLastFocusContext.bindTo(contextKeyService); + + this.explorerService.registerContextProvider(this); const decorationProvider = new ExplorerDecorationsProvider(this.explorerService, contextService); this._register(decorationService.registerDecorationsProvider(decorationProvider)); this._register(decorationProvider); - this._register(this.resourceContext); } get name(): string { @@ -160,6 +186,10 @@ export class ExplorerView extends ViewletPanel { renderBody(container: HTMLElement): void { const treeContainer = DOM.append(container, DOM.$('.explorer-folders-view')); + + this.styleElement = DOM.createStyleSheet(treeContainer); + attachStyler(this.themeService, { listDropBackground }, this.styleListDropBackground.bind(this)); + this.createTree(treeContainer); if (this.toolbar) { @@ -253,6 +283,42 @@ export class ExplorerView extends ViewletPanel { } } + getContext(respectMultiSelection: boolean): ExplorerItem[] { + let focusedStat: ExplorerItem | undefined; + + if (this.compressedNavigationController) { + focusedStat = this.compressedNavigationController.current; + } else { + const focus = this.tree.getFocus(); + focusedStat = focus.length ? focus[0] : undefined; + } + + const selectedStats: ExplorerItem[] = []; + + for (const stat of this.tree.getSelection()) { + const controller = this.renderer.getCompressedNavigationController(stat); + + if (controller) { + selectedStats.push(...controller.items); + } else { + selectedStats.push(stat); + } + } + if (!focusedStat) { + if (respectMultiSelection) { + return selectedStats; + } else { + return []; + } + } + + if (respectMultiSelection && selectedStats.indexOf(focusedStat) >= 0) { + return selectedStats; + } + + return [focusedStat]; + } + private selectActiveFile(deselect?: boolean, reveal = this.autoReveal): void { if (this.autoReveal) { const activeFile = this.getActiveFile(); @@ -277,14 +343,17 @@ export class ExplorerView extends ViewletPanel { this._register(explorerLabels); const updateWidth = (stat: ExplorerItem) => this.tree.updateWidth(stat); - const filesRenderer = this.instantiationService.createInstance(FilesRenderer, explorerLabels, updateWidth); - this._register(filesRenderer); + this.renderer = this.instantiationService.createInstance(FilesRenderer, explorerLabels, updateWidth); + this._register(this.renderer); this._register(createFileIconThemableTreeContainerScope(container, this.themeService)); - this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'FileExplorer', container, new ExplorerDelegate(), [filesRenderer], + const isCompressionEnabled = () => this.configurationService.getValue('explorer.compactFolders'); + + this.tree = this.instantiationService.createInstance>(WorkbenchCompressibleAsyncDataTree, 'FileExplorer', container, new ExplorerDelegate(), new ExplorerCompressionDelegate(), [this.renderer], this.instantiationService.createInstance(ExplorerDataSource), { - accessibilityProvider: new ExplorerAccessibilityProvider(), + compressionEnabled: isCompressionEnabled(), + accessibilityProvider: this.renderer, ariaLabel: nls.localize('treeAriaLabel', "Files Explorer"), identityProvider: { getId: (stat: ExplorerItem) => { @@ -302,6 +371,13 @@ export class ExplorerView extends ViewletPanel { } return stat.name; + }, + getCompressedNodeKeyboardNavigationLabel: (stats: ExplorerItem[]) => { + if (stats.some(stat => this.explorerService.isEditable(stat))) { + return undefined; + } + + return stats.map(stat => stat.name).join('/'); } }, multipleSelectionSupport: true, @@ -309,10 +385,17 @@ export class ExplorerView extends ViewletPanel { sorter: this.instantiationService.createInstance(FileSorter), dnd: this.instantiationService.createInstance(FileDragAndDrop), autoExpandSingleChildren: true, - additionalScrollHeight: ExplorerDelegate.ITEM_HEIGHT + additionalScrollHeight: ExplorerDelegate.ITEM_HEIGHT, + overrideStyles: { + listBackground: SIDE_BAR_BACKGROUND + } }); this._register(this.tree); + // Bind configuration + const onDidChangeCompressionConfiguration = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('explorer.compactFolders')); + this._register(onDidChangeCompressionConfiguration(_ => this.tree.updateOptions({ compressionEnabled: isCompressionEnabled() }))); + // Bind context keys FilesExplorerFocusedContext.bindTo(this.tree.contextKeyService); ExplorerFocusedContext.bindTo(this.tree.contextKeyService); @@ -376,7 +459,7 @@ export class ExplorerView extends ViewletPanel { } } - private setContextKeys(stat: ExplorerItem | null): void { + private setContextKeys(stat: ExplorerItem | null | undefined): void { const isSingleFolder = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER; const resource = stat ? stat.resource : isSingleFolder ? this.contextService.getWorkspace().folders[0].uri : null; this.resourceContext.set(resource); @@ -386,7 +469,22 @@ export class ExplorerView extends ViewletPanel { } private onContextMenu(e: ITreeContextMenuEvent): void { - const stat = e.element; + const disposables = new DisposableStore(); + let stat = e.element; + let anchor = e.anchor; + + // Compressed folders + if (stat) { + const controller = this.renderer.getCompressedNavigationController(stat); + + if (controller) { + if (isCompressedFolderName(e.browserEvent.target)) { + anchor = controller.labels[controller.index]; + } else { + controller.last(); + } + } + } // update dynamic contexts this.fileCopiedContextKey.set(this.clipboardService.hasResources()); @@ -397,17 +495,17 @@ export class ExplorerView extends ViewletPanel { const actions: IAction[] = []; const roots = this.explorerService.roots; // If the click is outside of the elements pass the root resource if there is only one root. If there are multiple roots pass empty object. const arg = stat instanceof ExplorerItem ? stat.resource : roots.length === 1 ? roots[0].resource : {}; - const actionsDisposable = createAndFillInContextMenuActions(this.contributedContextMenu, { arg, shouldForwardArgs: true }, actions, this.contextMenuService); + disposables.add(createAndFillInContextMenuActions(this.contributedContextMenu, { arg, shouldForwardArgs: true }, actions, this.contextMenuService)); this.contextMenuService.showContextMenu({ - getAnchor: () => e.anchor, + getAnchor: () => anchor, getActions: () => actions, onHide: (wasCancelled?: boolean) => { if (wasCancelled) { this.tree.domFocus(); } - dispose(actionsDisposable); + disposables.dispose(); }, getActionsContext: () => stat && selection && selection.indexOf(stat) >= 0 ? selection.map((fs: ExplorerItem) => fs.resource) @@ -416,7 +514,7 @@ export class ExplorerView extends ViewletPanel { } private onFocusChanged(elements: ExplorerItem[]): void { - const stat = elements && elements.length ? elements[0] : null; + const stat = elements && elements.length ? elements[0] : undefined; this.setContextKeys(stat); if (stat) { @@ -426,6 +524,17 @@ export class ExplorerView extends ViewletPanel { } else { this.resourceMoveableToTrash.reset(); } + + this.compressedNavigationController = stat && this.renderer.getCompressedNavigationController(stat); + + if (!this.compressedNavigationController) { + this.compressedFocusContext.set(false); + return; + } + + this.compressedFocusContext.set(true); + // this.compressedNavigationController.last(); + this.updateCompressedNavigationContextKeys(this.compressedNavigationController); } // General methods @@ -523,7 +632,12 @@ export class ExplorerView extends ViewletPanel { return withNullAsUndefined(toResource(input, { supportSideBySide: SideBySideEditor.MASTER })); } - private async onSelectResource(resource: URI | undefined, reveal = this.autoReveal): Promise { + private async onSelectResource(resource: URI | undefined, reveal = this.autoReveal, retry = 0): Promise { + // do no retry more than once to prevent inifinite loops in cases of inconsistent model + if (retry === 2) { + return; + } + if (!resource || !this.isBodyVisible()) { return; } @@ -534,12 +648,18 @@ export class ExplorerView extends ViewletPanel { .sort((first, second) => second.resource.path.length - first.resource.path.length)[0]; while (item && item.resource.toString() !== resource.toString()) { + if (item.isDisposed) { + return this.onSelectResource(resource, reveal, retry + 1); + } await this.tree.expand(item); item = first(values(item.children), i => isEqualOrParent(resource, i.resource)); } if (item && item.parent) { if (reveal) { + if (item.isDisposed) { + return this.onSelectResource(resource, reveal, retry + 1); + } this.tree.reveal(item, 0.5); } @@ -563,6 +683,60 @@ export class ExplorerView extends ViewletPanel { this.tree.collapseAll(); } + previousCompressedStat(): void { + if (!this.compressedNavigationController) { + return; + } + + this.compressedNavigationController.previous(); + this.updateCompressedNavigationContextKeys(this.compressedNavigationController); + } + + nextCompressedStat(): void { + if (!this.compressedNavigationController) { + return; + } + + this.compressedNavigationController.next(); + this.updateCompressedNavigationContextKeys(this.compressedNavigationController); + } + + firstCompressedStat(): void { + if (!this.compressedNavigationController) { + return; + } + + this.compressedNavigationController.first(); + this.updateCompressedNavigationContextKeys(this.compressedNavigationController); + } + + lastCompressedStat(): void { + if (!this.compressedNavigationController) { + return; + } + + this.compressedNavigationController.last(); + this.updateCompressedNavigationContextKeys(this.compressedNavigationController); + } + + private updateCompressedNavigationContextKeys(controller: ICompressedNavigationController): void { + this.compressedFocusFirstContext.set(controller.index === 0); + this.compressedFocusLastContext.set(controller.index === controller.count - 1); + } + + styleListDropBackground(styles: IExplorerViewStyles): void { + const content: string[] = []; + + if (styles.listDropBackground) { + content.push(`.explorer-viewlet .explorer-item .monaco-icon-name-container.multiple > .label-name.drop-target > .monaco-highlighted-label { background-color: ${styles.listDropBackground}; }`); + } + + const newStyles = content.join('\n'); + if (newStyles !== this.styleElement.innerHTML) { + this.styleElement.innerHTML = newStyles; + } + } + dispose(): void { if (this.dragHandler) { this.dragHandler.dispose(); diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 4c876d23ab..2186296fbd 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -12,14 +12,14 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/ import { IFileService, FileKind, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IDisposable, Disposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { KeyCode } from 'vs/base/common/keyCodes'; import { IFileLabelOptions, IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; -import { ITreeRenderer, ITreeNode, ITreeFilter, TreeVisibility, TreeFilterResult, IAsyncDataSource, ITreeSorter, ITreeDragAndDrop, ITreeDragOverReaction, TreeDragOverBubble } from 'vs/base/browser/ui/tree/tree'; +import { ITreeNode, ITreeFilter, TreeVisibility, TreeFilterResult, IAsyncDataSource, ITreeSorter, ITreeDragAndDrop, ITreeDragOverReaction, TreeDragOverBubble } from 'vs/base/browser/ui/tree/tree'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; -import { IFilesConfiguration, IExplorerService, IEditableData } from 'vs/workbench/contrib/files/common/files'; +import { IFilesConfiguration, IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { dirname, joinPath, isEqualOrParent, basename, hasToIgnoreCase, distinctParents } from 'vs/base/common/resources'; import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import { localize } from 'vs/nls'; @@ -28,14 +28,14 @@ import { once } from 'vs/base/common/functional'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { equals, deepClone } from 'vs/base/common/objects'; import * as path from 'vs/base/common/path'; -import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; +import { ExplorerItem, NewExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { compareFileExtensions, compareFileNames } from 'vs/base/common/comparers'; -import { fillResourceDataTransfers, CodeDataTransfers, extractResources } from 'vs/workbench/browser/dnd'; +import { fillResourceDataTransfers, CodeDataTransfers, extractResources, containsDragType } from 'vs/workbench/browser/dnd'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IDragAndDropData, DataTransfers } from 'vs/base/browser/dnd'; import { Schemas } from 'vs/base/common/network'; import { DesktopDragAndDropData, ExternalElementsDragAndDropData, ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; -import { isMacintosh } from 'vs/base/common/platform'; +import { isMacintosh, isWeb } from 'vs/base/common/platform'; import { IDialogService, IConfirmation, getConfirmMessage } from 'vs/platform/dialogs/common/dialogs'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IHostService } from 'vs/workbench/services/host/browser/host'; @@ -46,7 +46,15 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces'; import { findValidPasteFileTarget } from 'vs/workbench/contrib/files/browser/fileActions'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; -import { Emitter } from 'vs/base/common/event'; +import { Emitter, Event, EventMultiplexer } from 'vs/base/common/event'; +import { ITreeCompressionDelegate } from 'vs/base/browser/ui/tree/asyncDataTree'; +import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree'; +import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { isNumber } from 'vs/base/common/types'; +import { domEvent } from 'vs/base/browser/event'; +import { IEditableData } from 'vs/workbench/common/views'; export class ExplorerDelegate implements IListVirtualDelegate { @@ -110,17 +118,111 @@ export class ExplorerDataSource implements IAsyncDataSource; + previous(): void; + next(): void; + first(): void; + last(): void; + setIndex(index: number): void; +} + +export class CompressedNavigationController implements ICompressedNavigationController, IDisposable { + + static ID = 0; + + private _index: number; + readonly labels: HTMLElement[]; + + get index(): number { return this._index; } + get count(): number { return this.items.length; } + get current(): ExplorerItem { return this.items[this._index]!; } + get currentId(): string { return `${this.id}_${this.index}`; } + + private _onDidChange = new Emitter(); + readonly onDidChange = this._onDidChange.event; + + constructor(private id: string, readonly items: ExplorerItem[], templateData: IFileTemplateData) { + this._index = items.length - 1; + this.labels = Array.from(templateData.container.querySelectorAll('.label-name')) as HTMLElement[]; + + for (let i = 0; i < items.length; i++) { + this.labels[i].setAttribute('aria-label', items[i].name); + } + + DOM.addClass(this.labels[this._index], 'active'); + } + + previous(): void { + if (this._index <= 0) { + return; + } + + this.setIndex(this._index - 1); + } + + next(): void { + if (this._index >= this.items.length - 1) { + return; + } + + this.setIndex(this._index + 1); + } + + first(): void { + if (this._index === 0) { + return; + } + + this.setIndex(0); + } + + last(): void { + if (this._index === this.items.length - 1) { + return; + } + + this.setIndex(this.items.length - 1); + } + + setIndex(index: number): void { + if (index < 0 || index >= this.items.length) { + return; + } + + DOM.removeClass(this.labels[this._index], 'active'); + this._index = index; + DOM.addClass(this.labels[this._index], 'active'); + + this._onDidChange.fire(); + } + + dispose(): void { + this._onDidChange.dispose(); + } +} + export interface IFileTemplateData { elementDisposable: IDisposable; label: IResourceLabel; container: HTMLElement; } -export class FilesRenderer implements ITreeRenderer, IDisposable { +export class FilesRenderer implements ICompressibleTreeRenderer, IAccessibilityProvider, IDisposable { static readonly ID = 'file'; private config: IFilesConfiguration; private configListener: IDisposable; + private compressedNavigationControllers = new Map(); + + private _onDidChangeActiveDescendant = new EventMultiplexer(); + readonly onDidChangeActiveDescendant = this._onDidChangeActiveDescendant.event; constructor( private labels: ResourceLabels, @@ -128,7 +230,8 @@ export class FilesRenderer implements ITreeRenderer(); this.configListener = this.configurationService.onDidChangeConfiguration(e => { @@ -154,28 +257,12 @@ export class FilesRenderer implements ITreeRenderer { - try { - this.updateWidth(stat); - } catch (e) { - // noop since the element might no longer be in the tree, no update of width necessery - } - }); + templateData.elementDisposable = this.renderStat(stat, stat.name, undefined, node.filterData, templateData); } // Input Box @@ -185,6 +272,77 @@ export class FilesRenderer implements ITreeRenderer, FuzzyScore>, index: number, templateData: IFileTemplateData, height: number | undefined): void { + templateData.elementDisposable.dispose(); + + const stat = node.element.elements[node.element.elements.length - 1]; + const editable = node.element.elements.filter(e => this.explorerService.isEditable(e)); + const editableData = editable.length === 0 ? undefined : this.explorerService.getEditableData(editable[0]); + + // File Label + if (!editableData) { + DOM.addClass(templateData.label.element, 'compressed'); + templateData.label.element.style.display = 'flex'; + + const disposables = new DisposableStore(); + const id = `compressed-explorer_${CompressedNavigationController.ID++}`; + + const label = node.element.elements.map(e => e.name); + disposables.add(this.renderStat(stat, label, id, node.filterData, templateData)); + + const compressedNavigationController = new CompressedNavigationController(id, node.element.elements, templateData); + disposables.add(compressedNavigationController); + this.compressedNavigationControllers.set(stat, compressedNavigationController); + + // accessibility + disposables.add(this._onDidChangeActiveDescendant.add(compressedNavigationController.onDidChange)); + + domEvent(templateData.container, 'mousedown')(e => { + const result = getIconLabelNameFromHTMLElement(e.target); + + if (result) { + compressedNavigationController.setIndex(result.index); + } + }, undefined, disposables); + + disposables.add(toDisposable(() => this.compressedNavigationControllers.delete(stat))); + + templateData.elementDisposable = disposables; + } + + // Input Box + else { + DOM.removeClass(templateData.label.element, 'compressed'); + templateData.label.element.style.display = 'none'; + templateData.elementDisposable = this.renderInputBox(templateData.container, editable[0], editableData); + } + } + + private renderStat(stat: ExplorerItem, label: string | string[], domId: string | undefined, filterData: FuzzyScore | undefined, templateData: IFileTemplateData): IDisposable { + templateData.label.element.style.display = 'flex'; + const extraClasses = ['explorer-item']; + if (this.explorerService.isCut(stat)) { + extraClasses.push('cut'); + } + + templateData.label.setResource({ resource: stat.resource, name: label }, { + fileKind: stat.isRoot ? FileKind.ROOT_FOLDER : stat.isDirectory ? FileKind.FOLDER : FileKind.FILE, + extraClasses, + fileDecorations: this.config.explorer.decorations, + matches: createMatches(filterData), + separator: this.labelService.getSeparator(stat.resource.scheme, stat.resource.authority), + domId + }); + + return templateData.label.onDidRender(() => { + try { + this.updateWidth(stat); + } catch (e) { + // noop since the element might no longer be in the tree, no update of width necessery + } + }); + } + private renderInputBox(container: HTMLElement, stat: ExplorerItem, editableData: IEditableData): IDisposable { // Use a file label only for the icon next to the input box @@ -198,6 +356,9 @@ export class FilesRenderer implements ITreeRenderer, index: number, templateData: IFileTemplateData): void { + disposeElement(element: ITreeNode, index: number, templateData: IFileTemplateData): void { + templateData.elementDisposable.dispose(); + } + + disposeCompressedElements(node: ITreeNode, FuzzyScore>, index: number, templateData: IFileTemplateData): void { templateData.elementDisposable.dispose(); } @@ -270,15 +435,24 @@ export class FilesRenderer implements ITreeRenderer { + // IAccessibilityProvider + getAriaLabel(element: ExplorerItem): string { return element.name; } + + getActiveDescendantId(stat: ExplorerItem): string | undefined { + const compressedNavigationController = this.compressedNavigationControllers.get(stat); + return compressedNavigationController?.currentId; + } + + dispose(): void { + this.configListener.dispose(); + } } interface CachedParsedExpression { @@ -427,9 +601,21 @@ export class FileSorter implements ITreeSorter { } } +const fileOverwriteConfirm = (name: string) => { + return { + message: localize('confirmOverwrite', "A file or folder with the name '{0}' already exists in the destination folder. Do you want to replace it?", name), + detail: localize('irreversible', "This action is irreversible!"), + primaryButton: localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"), + type: 'warning' + }; +}; + export class FileDragAndDrop implements ITreeDragAndDrop { private static readonly CONFIRM_DND_SETTING_KEY = 'explorer.confirmDragAndDrop'; + private compressedDragOverElement: HTMLElement | undefined; + private compressedDropTargetDisposable: IDisposable = Disposable.None; + private toDispose: IDisposable[]; private dropEnabled = false; @@ -460,19 +646,49 @@ export class FileDragAndDrop implements ITreeDragAndDrop { return false; } + // Compressed folders + if (target) { + const compressedTarget = FileDragAndDrop.getCompressedStatFromDragEvent(target, originalEvent); + + if (compressedTarget) { + const iconLabelName = getIconLabelNameFromHTMLElement(originalEvent.target); + + if (iconLabelName && iconLabelName.index < iconLabelName.count - 1) { + const result = this._onDragOver(data, compressedTarget, targetIndex, originalEvent); + + if (result) { + if (iconLabelName.element !== this.compressedDragOverElement) { + this.compressedDragOverElement = iconLabelName.element; + this.compressedDropTargetDisposable.dispose(); + this.compressedDropTargetDisposable = toDisposable(() => { + DOM.removeClass(iconLabelName.element, 'drop-target'); + this.compressedDragOverElement = undefined; + }); + + DOM.addClass(iconLabelName.element, 'drop-target'); + } + + return typeof result === 'boolean' ? result : { ...result, feedback: [] }; + } + + this.compressedDropTargetDisposable.dispose(); + return false; + } + } + } + + this.compressedDropTargetDisposable.dispose(); + return this._onDragOver(data, target, targetIndex, originalEvent); + } + + private _onDragOver(data: IDragAndDropData, target: ExplorerItem | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction { const isCopy = originalEvent && ((originalEvent.ctrlKey && !isMacintosh) || (originalEvent.altKey && isMacintosh)); const fromDesktop = data instanceof DesktopDragAndDropData; const effect = (fromDesktop || isCopy) ? ListDragOverEffect.Copy : ListDragOverEffect.Move; // Desktop DND - if (fromDesktop && originalEvent.dataTransfer) { - const types = originalEvent.dataTransfer.types; - const typesArray: string[] = []; - for (let i = 0; i < types.length; i++) { - typesArray.push(types[i].toLowerCase()); // somehow the types are lowercase - } - - if (typesArray.indexOf(DataTransfers.FILES.toLowerCase()) === -1 && typesArray.indexOf(CodeDataTransfers.FILES.toLowerCase()) === -1) { + if (fromDesktop) { + if (!containsDragType(originalEvent, DataTransfers.FILES, CodeDataTransfers.FILES)) { return false; } } @@ -484,7 +700,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { // In-Explorer DND else { - const items = (data as ElementsDragAndDropData).elements; + const items = FileDragAndDrop.getStatsFromDragAndDropData(data as ElementsDragAndDropData); if (!target) { // Dropping onto the empty area. Do not accept if items dragged are already @@ -559,16 +775,17 @@ export class FileDragAndDrop implements ITreeDragAndDrop { return element.resource.toString(); } - getDragLabel(elements: ExplorerItem[]): string | undefined { - if (elements.length > 1) { - return String(elements.length); + getDragLabel(elements: ExplorerItem[], originalEvent: DragEvent): string | undefined { + if (elements.length === 1) { + const stat = FileDragAndDrop.getCompressedStatFromDragEvent(elements[0], originalEvent); + return stat.name; } - return elements[0].name; + return String(elements.length); } onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void { - const items = (data as ElementsDragAndDropData).elements; + const items = FileDragAndDrop.getStatsFromDragAndDropData(data as ElementsDragAndDropData, originalEvent); if (items && items.length && originalEvent.dataTransfer) { // Apply some datatransfer types to allow for dragging the element outside of the application this.instantiationService.invokeFunction(fillResourceDataTransfers, items, originalEvent); @@ -583,6 +800,17 @@ export class FileDragAndDrop implements ITreeDragAndDrop { } drop(data: IDragAndDropData, target: ExplorerItem | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void { + this.compressedDropTargetDisposable.dispose(); + + // Find compressed target + if (target) { + const compressedTarget = FileDragAndDrop.getCompressedStatFromDragEvent(target, originalEvent); + + if (compressedTarget) { + target = compressedTarget; + } + } + // Find parent to add to if (!target) { target = this.explorerService.roots[this.explorerService.roots.length - 1]; @@ -596,14 +824,42 @@ export class FileDragAndDrop implements ITreeDragAndDrop { // Desktop DND (Import file) if (data instanceof DesktopDragAndDropData) { - this.handleExternalDrop(data, target, originalEvent).then(undefined, e => this.notificationService.warn(e)); + if (isWeb) { + this.handleWebExternalDrop(data, target, originalEvent).then(undefined, e => this.notificationService.warn(e)); + } else { + this.handleExternalDrop(data, target, originalEvent).then(undefined, e => this.notificationService.warn(e)); + } } // In-Explorer DND (Move/Copy file) else { - this.handleExplorerDrop(data, target, originalEvent).then(undefined, e => this.notificationService.warn(e)); + this.handleExplorerDrop(data as ElementsDragAndDropData, target, originalEvent).then(undefined, e => this.notificationService.warn(e)); } } + private async handleWebExternalDrop(data: DesktopDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise { + data.files.forEach(file => { + const reader = new FileReader(); + reader.readAsArrayBuffer(file); + reader.onload = async (event) => { + const name = file.name; + if (typeof name === 'string' && event.target?.result instanceof ArrayBuffer) { + if (target.getChild(name)) { + const { confirmed } = await this.dialogService.confirm(fileOverwriteConfirm(name)); + if (!confirmed) { + return; + } + } + + const resource = joinPath(target.resource, name); + await this.fileService.writeFile(resource, VSBuffer.wrap(new Uint8Array(event.target?.result))); + if (data.files.length === 1) { + await this.editorService.openEditor({ resource, options: { pinned: true } }); + } + } + }; + }); + } + private async handleExternalDrop(data: DesktopDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise { const droppedResources = extractResources(originalEvent, true); // Check for dropped external files to be folders @@ -644,11 +900,9 @@ export class FileDragAndDrop implements ITreeDragAndDrop { else if (target instanceof ExplorerItem) { return this.addResources(target, droppedResources.map(res => res.resource)); } - - return undefined; } - private async addResources(target: ExplorerItem, resources: URI[]): Promise { + private async addResources(target: ExplorerItem, resources: URI[]): Promise { if (resources && resources.length > 0) { // Resolve target to check for name collisions and ask user @@ -659,22 +913,16 @@ export class FileDragAndDrop implements ITreeDragAndDrop { if (targetStat.children) { const ignoreCase = hasToIgnoreCase(target.resource); targetStat.children.forEach(child => { - targetNames.add(ignoreCase ? child.name : child.name.toLowerCase()); + targetNames.add(ignoreCase ? child.name.toLowerCase() : child.name); }); } - const resourceExists = resources.some(resource => targetNames.has(!hasToIgnoreCase(resource) ? basename(resource) : basename(resource).toLowerCase())); + const filtered = resources.filter(resource => targetNames.has(!hasToIgnoreCase(resource) ? basename(resource) : basename(resource).toLowerCase())); + const resourceExists = filtered.length >= 1; if (resourceExists) { - const confirm: IConfirmation = { - message: localize('confirmOverwrite', "A file or folder with the same name already exists in the destination folder. Do you want to replace it?"), - detail: localize('irreversible', "This action is irreversible!"), - primaryButton: localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"), - type: 'warning' - }; - - const confirmationResult = await this.dialogService.confirm(confirm); + const confirmationResult = await this.dialogService.confirm(fileOverwriteConfirm(basename(filtered[0]))); if (!confirmationResult.confirmed) { - return []; + return; } } @@ -693,7 +941,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { } const copyTarget = joinPath(target.resource, basename(sourceFile)); - const stat = await this.fileService.copy(sourceFile, copyTarget, true); + const stat = await this.textFileService.copy(sourceFile, copyTarget, true); // if we only add one file, just open it directly if (resources.length === 1 && !stat.isDirectory) { this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } }); @@ -705,8 +953,8 @@ export class FileDragAndDrop implements ITreeDragAndDrop { } } - private async handleExplorerDrop(data: IDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise { - const elementsData = (data as ElementsDragAndDropData).elements; + private async handleExplorerDrop(data: ElementsDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise { + const elementsData = FileDragAndDrop.getStatsFromDragAndDropData(data); const items = distinctParents(elementsData, s => s.resource); const isCopy = (originalEvent.ctrlKey && !isMacintosh) || (originalEvent.altKey && isMacintosh); @@ -715,9 +963,9 @@ export class FileDragAndDrop implements ITreeDragAndDrop { if (confirmDragAndDrop) { const confirmation = await this.dialogService.confirm({ message: items.length > 1 && items.every(s => s.isRoot) ? localize('confirmRootsMove', "Are you sure you want to change the order of multiple root folders in your workspace?") - : items.length > 1 ? getConfirmMessage(localize('confirmMultiMove', "Are you sure you want to move the following {0} files?", items.length), items.map(s => s.resource)) + : items.length > 1 ? getConfirmMessage(localize('confirmMultiMove', "Are you sure you want to move the following {0} files into '{1}'?", items.length, target.name), items.map(s => s.resource)) : items[0].isRoot ? localize('confirmRootMove', "Are you sure you want to change the order of root folder '{0}' in your workspace?", items[0].name) - : localize('confirmMove', "Are you sure you want to move '{0}'?", items[0].name), + : localize('confirmMove', "Are you sure you want to move '{0}' into '{1}'?", items[0].name, target.name), checkbox: { label: localize('doNotAskAgain', "Do not ask me again") }, @@ -776,7 +1024,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { // Reuse duplicate action if user copies if (isCopy) { const incrementalNaming = this.configurationService.getValue().explorer.incrementalNaming; - const stat = await this.fileService.copy(source.resource, findValidPasteFileTarget(target, { resource: source.resource, isDirectory: source.isDirectory, allowOverwrite: false }, incrementalNaming)); + const stat = await this.textFileService.copy(source.resource, findValidPasteFileTarget(target, { resource: source.resource, isDirectory: source.isDirectory, allowOverwrite: false }, incrementalNaming)); if (!stat.isDirectory) { await this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } }); } @@ -819,4 +1067,75 @@ export class FileDragAndDrop implements ITreeDragAndDrop { } } } + + private static getStatsFromDragAndDropData(data: ElementsDragAndDropData, dragStartEvent?: DragEvent): ExplorerItem[] { + if (data.context) { + return data.context; + } + + // Detect compressed folder dragging + if (dragStartEvent && data.elements.length === 1) { + data.context = [FileDragAndDrop.getCompressedStatFromDragEvent(data.elements[0], dragStartEvent)]; + return data.context; + } + + return data.elements; + } + + private static getCompressedStatFromDragEvent(stat: ExplorerItem, dragEvent: DragEvent): ExplorerItem { + const target = document.elementFromPoint(dragEvent.clientX, dragEvent.clientY); + const iconLabelName = getIconLabelNameFromHTMLElement(target); + + if (iconLabelName) { + const { count, index } = iconLabelName; + + let i = count - 1; + while (i > index && stat.parent) { + stat = stat.parent; + i--; + } + + return stat; + } + + return stat; + } + + onDragEnd(): void { + this.compressedDropTargetDisposable.dispose(); + } +} + +function getIconLabelNameFromHTMLElement(target: HTMLElement | EventTarget | Element | null): { element: HTMLElement, count: number, index: number } | null { + if (!(target instanceof HTMLElement)) { + return null; + } + + let element: HTMLElement | null = target; + + while (element && !DOM.hasClass(element, 'monaco-list-row')) { + if (DOM.hasClass(element, 'label-name') && element.hasAttribute('data-icon-label-count')) { + const count = Number(element.getAttribute('data-icon-label-count')); + const index = Number(element.getAttribute('data-icon-label-index')); + + if (isNumber(count) && isNumber(index)) { + return { element: element, count, index }; + } + } + + element = element.parentElement; + } + + return null; +} + +export function isCompressedFolderName(target: HTMLElement | EventTarget | Element | null): boolean { + return !!getIconLabelNameFromHTMLElement(target); +} + +export class ExplorerCompressionDelegate implements ITreeCompressionDelegate { + + isIncompressible(stat: ExplorerItem): boolean { + return stat.isRoot || !stat.isDirectory || stat instanceof NewExplorerItem; + } } diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 50bde81d73..5146e1ed6e 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -15,8 +15,6 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IEditorInput, Verbosity } from 'vs/workbench/common/editor'; import { SaveAllAction, SaveAllInGroupAction, CloseGroupAction } from 'vs/workbench/contrib/files/browser/fileActions'; import { OpenEditorsFocusedContext, ExplorerFocusedContext, IFilesConfiguration, OpenEditor } from 'vs/workbench/contrib/files/common/files'; -import { ITextFileService, AutoSaveMode } from 'vs/workbench/services/textfile/common/textfiles'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { CloseAllEditorsAction, CloseEditorAction } from 'vs/workbench/browser/parts/editor/editorActions'; import { ToggleEditorLayoutAction } from 'vs/workbench/browser/actions/layoutActions'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -32,20 +30,23 @@ import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; -import { DirtyEditorContext, OpenEditorsGroupContext } from 'vs/workbench/contrib/files/browser/fileCommands'; +import { DirtyEditorContext, OpenEditorsGroupContext, ReadonlyEditorContext as ReadonlyEditorContext } from 'vs/workbench/contrib/files/browser/fileCommands'; import { ResourceContextKey } from 'vs/workbench/common/resources'; -import { ResourcesDropHandler, fillResourceDataTransfers, CodeDataTransfers } from 'vs/workbench/browser/dnd'; -import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { ResourcesDropHandler, fillResourceDataTransfers, CodeDataTransfers, containsDragType } from 'vs/workbench/browser/dnd'; +import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IDragAndDropData, DataTransfers } from 'vs/base/browser/dnd'; import { memoize } from 'vs/base/common/decorators'; import { ElementsDragAndDropData, DesktopDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { URI } from 'vs/base/common/uri'; -import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; +import { withUndefinedAsNull } from 'vs/base/common/types'; +import { isWeb } from 'vs/base/common/platform'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; const $ = dom.$; -export class OpenEditorsView extends ViewletPanel { +export class OpenEditorsView extends ViewletPane { private static readonly DEFAULT_VISIBLE_OPEN_EDITORS = 9; static readonly ID = 'workbench.explorer.openEditorsView'; @@ -61,24 +62,24 @@ export class OpenEditorsView extends ViewletPanel { private resourceContext!: ResourceContextKey; private groupFocusedContext!: IContextKey; private dirtyEditorFocusedContext!: IContextKey; + private readonlyEditorFocusedContext!: IContextKey; constructor( options: IViewletViewOptions, @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextMenuService contextMenuService: IContextMenuService, - @ITextFileService private readonly textFileService: ITextFileService, @IEditorService private readonly editorService: IEditorService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IConfigurationService configurationService: IConfigurationService, @IKeybindingService keybindingService: IKeybindingService, - @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IThemeService private readonly themeService: IThemeService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IMenuService private readonly menuService: IMenuService + @IMenuService private readonly menuService: IMenuService, + @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService ) { super({ - ...(options as IViewletPanelOptions), + ...(options as IViewletPaneOptions), ariaHeaderLabel: nls.localize({ key: 'openEditosrSection', comment: ['Open is an adjective'] }, "Open Editors Section"), }, keybindingService, contextMenuService, configurationService, contextKeyService); @@ -99,11 +100,7 @@ export class OpenEditorsView extends ViewletPanel { this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationChange(e))); // Handle dirty counter - this._register(this.untitledEditorService.onDidChangeDirty(() => this.updateDirtyIndicator())); - this._register(this.textFileService.models.onModelsDirty(() => this.updateDirtyIndicator())); - this._register(this.textFileService.models.onModelsSaved(() => this.updateDirtyIndicator())); - this._register(this.textFileService.models.onModelsSaveError(() => this.updateDirtyIndicator())); - this._register(this.textFileService.models.onModelsReverted(() => this.updateDirtyIndicator())); + this._register(this.workingCopyService.onDidChangeDirty(() => this.updateDirtyIndicator())); } private registerUpdateEvents(): void { @@ -218,7 +215,10 @@ export class OpenEditorsView extends ViewletPanel { new OpenEditorRenderer(this.listLabels, this.instantiationService, this.keybindingService, this.configurationService) ], { identityProvider: { getId: (element: OpenEditor | IEditorGroup) => element instanceof OpenEditor ? element.getId() : element.id.toString() }, - dnd: new OpenEditorsDragAndDrop(this.instantiationService, this.editorGroupService) + dnd: new OpenEditorsDragAndDrop(this.instantiationService, this.editorGroupService), + overrideStyles: { + listBackground: SIDE_BAR_BACKGROUND + } }); this._register(this.list); this._register(this.listLabels); @@ -236,16 +236,19 @@ export class OpenEditorsView extends ViewletPanel { this._register(this.resourceContext); this.groupFocusedContext = OpenEditorsGroupContext.bindTo(this.contextKeyService); this.dirtyEditorFocusedContext = DirtyEditorContext.bindTo(this.contextKeyService); + this.readonlyEditorFocusedContext = ReadonlyEditorContext.bindTo(this.contextKeyService); this._register(this.list.onContextMenu(e => this.onListContextMenu(e))); this.list.onFocusChange(e => { this.resourceContext.reset(); this.groupFocusedContext.reset(); this.dirtyEditorFocusedContext.reset(); + this.readonlyEditorFocusedContext.reset(); const element = e.elements.length ? e.elements[0] : undefined; if (element instanceof OpenEditor) { const resource = element.getResource(); - this.dirtyEditorFocusedContext.set(this.textFileService.isDirty(resource)); + this.dirtyEditorFocusedContext.set(element.editor.isDirty()); + this.readonlyEditorFocusedContext.set(element.editor.isReadonly()); this.resourceContext.set(withUndefinedAsNull(resource)); } else if (!!element) { this.groupFocusedContext.set(true); @@ -412,8 +415,7 @@ export class OpenEditorsView extends ViewletPanel { } private updateDirtyIndicator(): void { - let dirty = this.textFileService.getAutoSaveMode() !== AutoSaveMode.AFTER_SHORT_DELAY ? this.textFileService.getDirty().length - : this.untitledEditorService.getDirty().length; + let dirty = this.workingCopyService.dirtyCount; if (dirty === 0) { dom.addClass(this.dirtyCountElement, 'hidden'); } else { @@ -563,7 +565,6 @@ class OpenEditorRenderer implements IListRenderer 1) { return String(elements.length); } const element = elements[0]; - return element instanceof OpenEditor ? withNullAsUndefined(element.editor.getName()) : element.label; + return element instanceof OpenEditor ? element.editor.getName() : element.label; } onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void { @@ -644,16 +645,12 @@ class OpenEditorsDragAndDrop implements IListDragAndDrop 0; + } + + constructor( + @ILifecycleService private readonly lifecycleService: ILifecycleService, + @IActivityService private readonly activityService: IActivityService, + @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService + ) { + super(); + + this.registerListeners(); + } + + private registerListeners(): void { + + // Working copy dirty indicator + this._register(this.workingCopyService.onDidChangeDirty(c => this.onWorkingCopyDidChangeDirty(c))); + + // Lifecycle + this.lifecycleService.onShutdown(this.dispose, this); + } + + private onWorkingCopyDidChangeDirty(workingCopy: IWorkingCopy): void { + const gotDirty = workingCopy.isDirty(); + if (gotDirty && !!(workingCopy.capabilities & WorkingCopyCapabilities.AutoSave) && this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) { + return; // do not indicate dirty of working copies that are auto saved after short delay + } + + if (gotDirty || this.hasDirtyCount) { + this.updateActivityBadge(); + } + } + + private updateActivityBadge(): void { + const dirtyCount = this.workingCopyService.dirtyCount; + this.lastKnownDirtyCount = dirtyCount; + + // Indicate dirty count in badge if any + if (dirtyCount > 0) { + this.badgeHandle.value = this.activityService.showActivity( + VIEWLET_ID, + new NumberBadge(dirtyCount, num => num === 1 ? nls.localize('dirtyFile', "1 unsaved file") : nls.localize('dirtyFiles', "{0} unsaved files", dirtyCount)), + 'explorer-viewlet-label' + ); + } else { + this.badgeHandle.clear(); + } + } +} diff --git a/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts b/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts deleted file mode 100644 index 43bc61cbf2..0000000000 --- a/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts +++ /dev/null @@ -1,113 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { VIEWLET_ID } from 'vs/workbench/contrib/files/common/files'; -import { TextFileModelChangeEvent, ITextFileService, AutoSaveMode, ModelState } from 'vs/workbench/services/textfile/common/textfiles'; -import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; -import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import * as arrays from 'vs/base/common/arrays'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; - -export class DirtyFilesTracker extends Disposable implements IWorkbenchContribution { - private lastKnownDirtyCount: number | undefined; - private readonly badgeHandle = this._register(new MutableDisposable()); - - constructor( - @ITextFileService protected readonly textFileService: ITextFileService, - @ILifecycleService private readonly lifecycleService: ILifecycleService, - @IEditorService private readonly editorService: IEditorService, - @IActivityService private readonly activityService: IActivityService, - @IUntitledEditorService protected readonly untitledEditorService: IUntitledEditorService - ) { - super(); - - this.registerListeners(); - } - - private registerListeners(): void { - - // Local text file changes - this._register(this.untitledEditorService.onDidChangeDirty(e => this.onUntitledDidChangeDirty(e))); - this._register(this.textFileService.models.onModelsDirty(e => this.onTextFilesDirty(e))); - this._register(this.textFileService.models.onModelsSaved(e => this.onTextFilesSaved(e))); - this._register(this.textFileService.models.onModelsSaveError(e => this.onTextFilesSaveError(e))); - this._register(this.textFileService.models.onModelsReverted(e => this.onTextFilesReverted(e))); - - // Lifecycle - this.lifecycleService.onShutdown(this.dispose, this); - } - - private get hasDirtyCount(): boolean { - return typeof this.lastKnownDirtyCount === 'number' && this.lastKnownDirtyCount > 0; - } - - protected onUntitledDidChangeDirty(resource: URI): void { - const gotDirty = this.untitledEditorService.isDirty(resource); - - if (gotDirty || this.hasDirtyCount) { - this.updateActivityBadge(); - } - } - - protected onTextFilesDirty(e: readonly TextFileModelChangeEvent[]): void { - if (this.textFileService.getAutoSaveMode() !== AutoSaveMode.AFTER_SHORT_DELAY) { - this.updateActivityBadge(); // no indication needed when auto save is enabled for short delay - } - - // If files become dirty but are not opened, we open it in the background unless there are pending to be saved - this.doOpenDirtyResources(arrays.distinct(e.filter(e => { - - // Only dirty models that are not PENDING_SAVE - const model = this.textFileService.models.get(e.resource); - const shouldOpen = model?.isDirty() && !model.hasState(ModelState.PENDING_SAVE); - - // Only if not open already - return shouldOpen && !this.editorService.isOpen({ resource: e.resource }); - }).map(e => e.resource), r => r.toString())); - } - - private doOpenDirtyResources(resources: URI[]): void { - - // Open - this.editorService.openEditors(resources.map(resource => { - return { - resource, - options: { inactive: true, pinned: true, preserveFocus: true } - }; - })); - } - - protected onTextFilesSaved(e: readonly TextFileModelChangeEvent[]): void { - if (this.hasDirtyCount) { - this.updateActivityBadge(); - } - } - - protected onTextFilesSaveError(e: readonly TextFileModelChangeEvent[]): void { - this.updateActivityBadge(); - } - - protected onTextFilesReverted(e: readonly TextFileModelChangeEvent[]): void { - if (this.hasDirtyCount) { - this.updateActivityBadge(); - } - } - - private updateActivityBadge(): void { - const dirtyCount = this.textFileService.getDirty().length; - this.lastKnownDirtyCount = dirtyCount; - - this.badgeHandle.clear(); - - if (dirtyCount > 0) { - this.badgeHandle.value = this.activityService.showActivity(VIEWLET_ID, new NumberBadge(dirtyCount, num => num === 1 ? nls.localize('dirtyFile', "1 unsaved file") : nls.localize('dirtyFiles', "{0} unsaved files", dirtyCount)), 'explorer-viewlet-label'); - } - } -} diff --git a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts index 915d5e586d..f1a0ea6446 100644 --- a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts @@ -7,16 +7,19 @@ import { localize } from 'vs/nls'; import { createMemoizer } from 'vs/base/common/decorators'; import { dirname } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { EncodingMode, ConfirmResult, EditorInput, IFileEditorInput, ITextEditorModel, Verbosity, IRevertOptions } from 'vs/workbench/common/editor'; +import { EncodingMode, IFileEditorInput, ITextEditorModel, Verbosity, TextEditorInput, IRevertOptions } from 'vs/workbench/common/editor'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; -import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; -import { ITextFileService, AutoSaveMode, ModelState, TextFileModelChangeEvent, LoadReason, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; +import { FileOperationError, FileOperationResult, IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; +import { ITextFileService, ModelState, TextFileModelChangeEvent, LoadReason, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IReference } from 'vs/base/common/lifecycle'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { FILE_EDITOR_INPUT_ID, TEXT_FILE_EDITOR_ID, BINARY_FILE_EDITOR_ID } from 'vs/workbench/contrib/files/common/files'; import { ILabelService } from 'vs/platform/label/common/label'; +import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; const enum ForceOpenAs { None, @@ -27,7 +30,7 @@ const enum ForceOpenAs { /** * A file editor input is the input type for the file editor of file system resources. */ -export class FileEditorInput extends EditorInput implements IFileEditorInput { +export class FileEditorInput extends TextEditorInput implements IFileEditorInput { private static readonly MEMOIZER = createMemoizer(); @@ -38,20 +41,20 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { private textModelReference: Promise> | null = null; - /** - * An editor input who's contents are retrieved from file services. - */ constructor( - private resource: URI, + resource: URI, preferredEncoding: string | undefined, preferredMode: string | undefined, @IInstantiationService private readonly instantiationService: IInstantiationService, - @ITextFileService private readonly textFileService: ITextFileService, + @ITextFileService textFileService: ITextFileService, @ITextModelService private readonly textModelResolverService: ITextModelService, @ILabelService private readonly labelService: ILabelService, - @IFileService private readonly fileService: IFileService + @IFileService private readonly fileService: IFileService, + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, + @IEditorService editorService: IEditorService, + @IEditorGroupsService editorGroupService: IEditorGroupsService ) { - super(); + super(resource, editorService, editorGroupService, textFileService); if (preferredEncoding) { this.setPreferredEncoding(preferredEncoding); @@ -89,10 +92,6 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { } } - getResource(): URI { - return this.resource; - } - getEncoding(): string | undefined { const textModel = this.textFileService.models.get(this.resource); if (textModel) { @@ -217,13 +216,19 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { return localize('orphanedFile', "{0} (deleted)", label); } - if (model?.isReadonly()) { + if (this.isReadonly()) { return localize('readonlyFile', "{0} (read-only)", label); } return label; } + isReadonly(): boolean { + const model = this.textFileService.models.get(this.resource); + + return model?.isReadonly() || this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly); + } + isDirty(): boolean { const model = this.textFileService.models.get(this.resource); if (!model) { @@ -234,21 +239,13 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { return true; // always indicate dirty state if we are in conflict or error state } - if (this.textFileService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) { + if (this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) { return false; // fast auto save enabled so we do not declare dirty } return model.isDirty(); } - confirmSave(): Promise { - return this.textFileService.confirmSave([this.resource]); - } - - save(): Promise { - return this.textFileService.save(this.resource); - } - revert(options?: IRevertOptions): Promise { return this.textFileService.revert(this.resource, options); } diff --git a/src/vs/workbench/contrib/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts index d3b9d9834d..d1a400c5bf 100644 --- a/src/vs/workbench/contrib/files/common/explorerModel.ts +++ b/src/vs/workbench/contrib/files/common/explorerModel.ts @@ -8,7 +8,7 @@ import { isEqual } from 'vs/base/common/extpath'; import { posix } from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; import { ResourceMap } from 'vs/base/common/map'; -import { IFileStat, IFileService } from 'vs/platform/files/common/files'; +import { IFileStat, IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { rtrim, startsWithIgnoreCase, startsWith, equalsIgnoreCase } from 'vs/base/common/strings'; import { coalesce } from 'vs/base/common/arrays'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -75,6 +75,7 @@ export class ExplorerModel implements IDisposable { export class ExplorerItem { private _isDirectoryResolved: boolean; + private _isDisposed: boolean; public isError = false; constructor( @@ -87,6 +88,11 @@ export class ExplorerItem { private _mtime?: number, ) { this._isDirectoryResolved = false; + this._isDisposed = false; + } + + get isDisposed(): boolean { + return this._isDisposed; } get isDirectoryResolved(): boolean { @@ -148,8 +154,8 @@ export class ExplorerItem { return this === this.root; } - static create(raw: IFileStat, parent: ExplorerItem | undefined, resolveTo?: readonly URI[]): ExplorerItem { - const stat = new ExplorerItem(raw.resource, parent, raw.isDirectory, raw.isSymbolicLink, raw.isReadonly, raw.name, raw.mtime); + static create(service: IFileService, raw: IFileStat, parent: ExplorerItem | undefined, resolveTo?: readonly URI[]): ExplorerItem { + const stat = new ExplorerItem(raw.resource, parent, raw.isDirectory, raw.isSymbolicLink, service.hasCapability(raw.resource, FileSystemProviderCapabilities.Readonly), raw.name, raw.mtime); // Recursively add children if present if (stat.isDirectory) { @@ -164,7 +170,7 @@ export class ExplorerItem { // Recurse into children if (raw.children) { for (let i = 0, len = raw.children.length; i < len; i++) { - const child = ExplorerItem.create(raw.children[i], stat, resolveTo); + const child = ExplorerItem.create(service, raw.children[i], stat, resolveTo); stat.addChild(child); } } @@ -218,6 +224,7 @@ export class ExplorerItem { if (formerLocalChild) { ExplorerItem.mergeLocalWithDisk(diskChild, formerLocalChild); local.addChild(formerLocalChild); + oldLocalChildren.delete(diskChild.resource); } // New child: add @@ -225,6 +232,10 @@ export class ExplorerItem { local.addChild(diskChild); } }); + + for (let child of oldLocalChildren.values()) { + child._dispose(); + } } } @@ -249,7 +260,7 @@ export class ExplorerItem { const resolveMetadata = explorerService.sortOrder === 'modified'; try { const stat = await fileService.resolve(this.resource, { resolveSingleChildDescendants: true, resolveMetadata }); - const resolved = ExplorerItem.create(stat, this); + const resolved = ExplorerItem.create(fileService, stat, this); ExplorerItem.mergeLocalWithDisk(resolved, this); } catch (e) { this.isError = true; @@ -274,10 +285,20 @@ export class ExplorerItem { } forgetChildren(): void { + for (let c of this.children.values()) { + c._dispose(); + } this.children.clear(); this._isDirectoryResolved = false; } + private _dispose() { + this._isDisposed = true; + for (let child of this.children.values()) { + child._dispose(); + } + } + private getPlatformAwareName(name: string): string { return (!name || !resources.hasToIgnoreCase(this.resource)) ? name : name.toLowerCase(); } diff --git a/src/vs/workbench/contrib/files/common/explorerService.ts b/src/vs/workbench/contrib/files/common/explorerService.ts index 09bf8a88c2..fe157c809e 100644 --- a/src/vs/workbench/contrib/files/common/explorerService.ts +++ b/src/vs/workbench/contrib/files/common/explorerService.ts @@ -6,7 +6,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { IExplorerService, IEditableData, IFilesConfiguration, SortOrder, SortOrderConfiguration } from 'vs/workbench/contrib/files/common/files'; +import { IExplorerService, IFilesConfiguration, SortOrder, SortOrderConfiguration, IContextProvider } from 'vs/workbench/contrib/files/common/files'; import { ExplorerItem, ExplorerModel } from 'vs/workbench/contrib/files/common/explorerModel'; import { URI } from 'vs/base/common/uri'; import { FileOperationEvent, FileOperation, IFileStat, IFileService, FileChangesEvent, FILES_EXCLUDE_CONFIG, FileChangeType, IResolveFileOptions } from 'vs/platform/files/common/files'; @@ -18,6 +18,7 @@ import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/co import { IExpression } from 'vs/base/common/glob'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditableData } from 'vs/workbench/common/views'; function getFileEventsExcludes(configurationService: IConfigurationService, root?: URI): IExpression { const scope = root ? { resource: root } : undefined; @@ -41,6 +42,7 @@ export class ExplorerService implements IExplorerService { private _sortOrder: SortOrder; private cutItems: ExplorerItem[] | undefined; private fileSystemProviderSchemes = new Set(); + private contextProvider: IContextProvider | undefined; constructor( @IFileService private fileService: IFileService, @@ -48,7 +50,7 @@ export class ExplorerService implements IExplorerService { @IConfigurationService private configurationService: IConfigurationService, @IWorkspaceContextService private contextService: IWorkspaceContextService, @IClipboardService private clipboardService: IClipboardService, - @IEditorService private editorService: IEditorService + @IEditorService private editorService: IEditorService, ) { this._sortOrder = this.configurationService.getValue('explorer.sortOrder'); } @@ -81,6 +83,18 @@ export class ExplorerService implements IExplorerService { return this._sortOrder; } + registerContextProvider(contextProvider: IContextProvider): void { + this.contextProvider = contextProvider; + } + + getContext(respectMultiSelection: boolean): ExplorerItem[] { + if (!this.contextProvider) { + return []; + } + + return this.contextProvider.getContext(respectMultiSelection); + } + // Memoized locals @memoize private get fileEventsFilter(): ResourceGlobMatcher { const fileEventsFilter = this.instantiationService.createInstance( @@ -173,7 +187,7 @@ export class ExplorerService implements IExplorerService { const stat = await this.fileService.resolve(rootUri, options); // Convert to model - const modelStat = ExplorerItem.create(stat, undefined, options.resolveTo); + const modelStat = ExplorerItem.create(this.fileService, stat, undefined, options.resolveTo); // Update Input with disk Stat ExplorerItem.mergeLocalWithDisk(modelStat, root); const item = root.find(resource); @@ -217,11 +231,11 @@ export class ExplorerService implements IExplorerService { const thenable: Promise = p.isDirectoryResolved ? Promise.resolve(undefined) : this.fileService.resolve(p.resource, { resolveMetadata }); thenable.then(stat => { if (stat) { - const modelStat = ExplorerItem.create(stat, p.parent); + const modelStat = ExplorerItem.create(this.fileService, stat, p.parent); ExplorerItem.mergeLocalWithDisk(modelStat, p); } - const childElement = ExplorerItem.create(addedElement, p.parent); + const childElement = ExplorerItem.create(this.fileService, addedElement, p.parent); // Make sure to remove any previous version of the file if any p.removeChild(childElement); p.addChild(childElement); diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index 2db4997e4f..71a53e381e 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -17,8 +17,7 @@ import { IModeService, ILanguageSelection } from 'vs/editor/common/services/mode import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer } from 'vs/workbench/common/views'; -import { Schemas } from 'vs/base/common/network'; +import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer, IEditableData } from 'vs/workbench/common/views'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; @@ -30,16 +29,12 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic * Explorer viewlet id. */ export const VIEWLET_ID = 'workbench.view.explorer'; + /** * Explorer viewlet container. */ export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID); -export interface IEditableData { - validationMessage: (value: string) => string | null; - onFinish: (value: string, success: boolean) => void; -} - export interface IExplorerService { _serviceBrand: undefined; readonly roots: ExplorerItem[]; @@ -50,6 +45,7 @@ export interface IExplorerService { readonly onDidSelectResource: Event<{ resource?: URI, reveal?: boolean }>; readonly onDidCopyItems: Event<{ items: ExplorerItem[], cut: boolean, previouslyCutItems: ExplorerItem[] | undefined }>; + getContext(respectMultiSelection: boolean): ExplorerItem[]; setEditable(stat: ExplorerItem, data: IEditableData | null): void; getEditable(): { stat: ExplorerItem, data: IEditableData } | undefined; getEditableData(stat: ExplorerItem): IEditableData | undefined; @@ -65,7 +61,14 @@ export interface IExplorerService { * Will try to resolve the path in case the explorer is not yet expanded to the file yet. */ select(resource: URI, reveal?: boolean): Promise; + + registerContextProvider(contextProvider: IContextProvider): void; } + +export interface IContextProvider { + getContext(respectMultiSelection: boolean): ExplorerItem[]; +} + export const IExplorerService = createDecorator('explorerService'); /** @@ -83,6 +86,11 @@ export const OpenEditorsVisibleContext = new RawContextKey('openEditors export const OpenEditorsFocusedContext = new RawContextKey('openEditorsFocus', true); export const ExplorerFocusedContext = new RawContextKey('explorerViewletFocus', true); +// compressed nodes +export const ExplorerCompressedFocusContext = new RawContextKey('explorerViewletCompressedFocus', true); +export const ExplorerCompressedFirstFocusContext = new RawContextKey('explorerViewletCompressedFirstFocus', true); +export const ExplorerCompressedLastFocusContext = new RawContextKey('explorerViewletCompressedLastFocus', true); + export const FilesExplorerFocusCondition = ContextKeyExpr.and(ExplorerViewletVisibleContext, FilesExplorerFocusedContext, ContextKeyExpr.not(InputFocusedContextKey)); export const ExplorerFocusCondition = ContextKeyExpr.and(ExplorerViewletVisibleContext, ExplorerFocusedContext, ContextKeyExpr.not(InputFocusedContextKey)); @@ -101,7 +109,6 @@ export const FILE_EDITOR_INPUT_ID = 'workbench.editors.files.fileEditorInput'; */ export const BINARY_FILE_EDITOR_ID = 'workbench.editors.files.binaryFileEditor'; - export interface IFilesConfiguration extends PlatformIFilesConfiguration, IWorkbenchEditorConfiguration { explorer: { openEditors: { @@ -219,39 +226,35 @@ export class OpenEditor implements IEditorIdentifier { // noop } - public get editor() { + get editor() { return this._editor; } - public get editorIndex() { + get editorIndex() { return this._group.getIndexOfEditor(this.editor); } - public get group() { + get group() { return this._group; } - public get groupId() { + get groupId() { return this._group.id; } - public getId(): string { + getId(): string { return `openeditor:${this.groupId}:${this.editorIndex}:${this.editor.getName()}:${this.editor.getDescription()}`; } - public isPreview(): boolean { + isPreview(): boolean { return this._group.previewEditor === this.editor; } - public isUntitled(): boolean { - return !!toResource(this.editor, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: Schemas.untitled }); - } - - public isDirty(): boolean { + isDirty(): boolean { return this.editor.isDirty(); } - public getResource(): URI | undefined { + getResource(): URI | undefined { return toResource(this.editor, { supportSideBySide: SideBySideEditor.MASTER }); } } diff --git a/src/vs/workbench/contrib/files/electron-browser/dirtyFilesTracker.ts b/src/vs/workbench/contrib/files/electron-browser/dirtyFilesTracker.ts deleted file mode 100644 index ae35840849..0000000000 --- a/src/vs/workbench/contrib/files/electron-browser/dirtyFilesTracker.ts +++ /dev/null @@ -1,81 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { TextFileModelChangeEvent, ITextFileService, AutoSaveMode } from 'vs/workbench/services/textfile/common/textfiles'; -import { platform, Platform } from 'vs/base/common/platform'; -import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; -import { IActivityService } from 'vs/workbench/services/activity/common/activity'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { DirtyFilesTracker } from 'vs/workbench/contrib/files/common/dirtyFilesTracker'; -import { IElectronService } from 'vs/platform/electron/node/electron'; - -export class NativeDirtyFilesTracker extends DirtyFilesTracker { - private isDocumentedEdited: boolean; - - constructor( - @ITextFileService protected readonly textFileService: ITextFileService, - @ILifecycleService lifecycleService: ILifecycleService, - @IEditorService editorService: IEditorService, - @IActivityService activityService: IActivityService, - @IUntitledEditorService protected readonly untitledEditorService: IUntitledEditorService, - @IElectronService private readonly electronService: IElectronService - ) { - super(textFileService, lifecycleService, editorService, activityService, untitledEditorService); - - this.isDocumentedEdited = false; - } - - protected onUntitledDidChangeDirty(resource: URI): void { - const gotDirty = this.untitledEditorService.isDirty(resource); - if ((!this.isDocumentedEdited && gotDirty) || (this.isDocumentedEdited && !gotDirty)) { - this.updateDocumentEdited(); - } - - super.onUntitledDidChangeDirty(resource); - } - - protected onTextFilesDirty(e: readonly TextFileModelChangeEvent[]): void { - if ((this.textFileService.getAutoSaveMode() !== AutoSaveMode.AFTER_SHORT_DELAY) && !this.isDocumentedEdited) { - this.updateDocumentEdited(); // no indication needed when auto save is enabled for short delay - } - - super.onTextFilesDirty(e); - } - - protected onTextFilesSaved(e: readonly TextFileModelChangeEvent[]): void { - if (this.isDocumentedEdited) { - this.updateDocumentEdited(); - } - - super.onTextFilesSaved(e); - } - - protected onTextFilesSaveError(e: readonly TextFileModelChangeEvent[]): void { - if (!this.isDocumentedEdited) { - this.updateDocumentEdited(); - } - - super.onTextFilesSaveError(e); - } - - protected onTextFilesReverted(e: readonly TextFileModelChangeEvent[]): void { - if (this.isDocumentedEdited) { - this.updateDocumentEdited(); - } - - super.onTextFilesReverted(e); - } - - private updateDocumentEdited(): void { - if (platform === Platform.Mac) { - const hasDirtyFiles = this.textFileService.isDirty(); - this.isDocumentedEdited = hasDirtyFiles; - - this.electronService.setDocumentEdited(hasDirtyFiles); - } - } -} diff --git a/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts index 2008e6ad2e..70f22266a0 100644 --- a/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts @@ -86,4 +86,4 @@ const category = { value: nls.localize('filesCategory', "File"), original: 'File appendToCommandPalette(REVEAL_IN_OS_COMMAND_ID, { value: REVEAL_IN_OS_LABEL, original: isWindows ? 'Reveal in Explorer' : isMacintosh ? 'Reveal in Finder' : 'Open Containing Folder' }, category); const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(ShowOpenedFileInNewWindow, ShowOpenedFileInNewWindow.ID, ShowOpenedFileInNewWindow.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_O) }), 'File: Open Active File in New Window', category.value); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowOpenedFileInNewWindow, ShowOpenedFileInNewWindow.ID, ShowOpenedFileInNewWindow.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_O) }), 'File: Open Active File in New Window', category.value); diff --git a/src/vs/workbench/contrib/files/electron-browser/files.contribution.ts b/src/vs/workbench/contrib/files/electron-browser/files.contribution.ts index 2772a704ed..f00acc468f 100644 --- a/src/vs/workbench/contrib/files/electron-browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/electron-browser/files.contribution.ts @@ -10,13 +10,10 @@ import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileE import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; import { NativeTextFileEditor } from 'vs/workbench/contrib/files/electron-browser/textFileEditor'; -import { NativeDirtyFilesTracker } from 'vs/workbench/contrib/files/electron-browser/dirtyFilesTracker'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; // Register file editor Registry.as(EditorExtensions.Editors).registerEditor( - new EditorDescriptor( + EditorDescriptor.create( NativeTextFileEditor, NativeTextFileEditor.ID, nls.localize('textFileEditor', "Text File Editor") @@ -25,6 +22,3 @@ Registry.as(EditorExtensions.Editors).registerEditor( new SyncDescriptor(FileEditorInput) ] ); - -// Register Dirty Files Tracker -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(NativeDirtyFilesTracker, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/files/electron-browser/textFileEditor.ts b/src/vs/workbench/contrib/files/electron-browser/textFileEditor.ts index 7c19777ef1..ece48b108f 100644 --- a/src/vs/workbench/contrib/files/electron-browser/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/electron-browser/textFileEditor.ts @@ -10,7 +10,6 @@ import { EditorOptions } from 'vs/workbench/common/editor'; import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; import { MIN_MAX_MEMORY_SIZE_MB, FALLBACK_MAX_MEMORY_SIZE_MB } from 'vs/platform/files/node/files'; import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; -import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Action } from 'vs/base/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -22,7 +21,6 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { IElectronService } from 'vs/platform/electron/node/electron'; @@ -46,19 +44,18 @@ export class NativeTextFileEditor extends TextFileEditor { @ITextFileService textFileService: ITextFileService, @IElectronService private readonly electronService: IElectronService, @IPreferencesService private readonly preferencesService: IPreferencesService, - @IHostService hostService: IHostService, @IExplorerService explorerService: IExplorerService ) { - super(telemetryService, fileService, viewletService, instantiationService, contextService, storageService, configurationService, editorService, themeService, editorGroupService, textFileService, hostService, explorerService); + super(telemetryService, fileService, viewletService, instantiationService, contextService, storageService, configurationService, editorService, themeService, editorGroupService, textFileService, explorerService); } protected handleSetInputError(error: Error, input: FileEditorInput, options: EditorOptions | undefined): void { // Allow to restart with higher memory limit if the file is too large - if ((error).fileOperationResult === FileOperationResult.FILE_EXCEED_MEMORY_LIMIT) { + if ((error).fileOperationResult === FileOperationResult.FILE_EXCEEDS_MEMORY_LIMIT) { const memoryLimit = Math.max(MIN_MAX_MEMORY_SIZE_MB, +this.configurationService.getValue(undefined, 'files.maxMemoryForLargeFilesMB') || FALLBACK_MAX_MEMORY_SIZE_MB); - throw createErrorWithActions(toErrorMessage(error), { + throw createErrorWithActions(nls.localize('fileTooLargeForHeapError', "To open a file of this size, you need to restart and allow it to use more memory"), { actions: [ new Action('workbench.window.action.relaunchWithIncreasedMemoryLimit', nls.localize('relaunchWithIncreasedMemoryLimit', "Restart with {0} MB", memoryLimit), undefined, true, () => { return this.electronService.relaunch({ diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts index ea9ba2e522..a57d9e6d9a 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts @@ -141,7 +141,7 @@ suite('Files - FileEditorInput', () => { resolved.textEditorModel!.setValue('changed'); assert.ok(input.isDirty()); - await input.save(); + await input.save(0); assert.ok(!input.isDirty()); resolved.dispose(); }); @@ -153,8 +153,12 @@ suite('Files - FileEditorInput', () => { resolved.textEditorModel!.setValue('changed'); assert.ok(input.isDirty()); - await input.revert(); + assert.ok(await input.revert()); assert.ok(!input.isDirty()); + + input.dispose(); + assert.ok(input.isDisposed()); + resolved.dispose(); }); diff --git a/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts b/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts index eca779ecbf..43ee61266b 100644 --- a/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts +++ b/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts @@ -28,6 +28,7 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/ import { IModeService } from 'vs/editor/common/services/modeService'; import { ILabelService } from 'vs/platform/label/common/label'; import { IExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { editorConfigurationBaseNode } from 'vs/editor/common/config/commonEditorConfig'; type FormattingEditProvider = DocumentFormattingEditProvider | DocumentRangeFormattingEditProvider; @@ -106,7 +107,7 @@ class DefaultFormatter extends Disposable implements IWorkbenchContribution { const langName = this._modeService.getLanguageName(document.getModeId()) || document.getModeId(); const silent = mode === FormattingMode.Silent; const message = !defaultFormatterId - ? nls.localize('config.needed', "There are multiple formatters for {0}-files. Select a default formatter to continue.", DefaultFormatter._maybeQuotes(langName)) + ? nls.localize('config.needed', "There are multiple formatters for '{0}' files. Select a default formatter to continue.", DefaultFormatter._maybeQuotes(langName)) : nls.localize('config.bad', "Extension '{0}' is configured as formatter but not available. Select a different default formatter to continue.", defaultFormatterId); return new Promise((resolve, reject) => { @@ -134,7 +135,7 @@ class DefaultFormatter extends Disposable implements IWorkbenchContribution { }; }); const langName = this._modeService.getLanguageName(document.getModeId()) || document.getModeId(); - const pick = await this._quickInputService.pick(picks, { placeHolder: nls.localize('select', "Select a default formatter for {0}-files", DefaultFormatter._maybeQuotes(langName)) }); + const pick = await this._quickInputService.pick(picks, { placeHolder: nls.localize('select', "Select a default formatter for '{0}' files", DefaultFormatter._maybeQuotes(langName)) }); if (!pick || !formatter[pick.index].extensionId) { return undefined; } @@ -152,10 +153,7 @@ Registry.as(WorkbenchExtensions.Workbench).regi ); Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ - id: 'editor', - order: 5, - type: 'object', - overridable: true, + ...editorConfigurationBaseNode, properties: { [DefaultFormatter.configName]: { description: nls.localize('formatter.default', "Defines a default formatter which takes precedence over all other formatter settings. Must be the identifier of an extension contributing a formatter."), @@ -234,7 +232,7 @@ async function showFormatterPick(accessor: ServicesAccessor, model: ITextModel, } else if (pick === configurePick) { // config default const langName = modeService.getLanguageName(model.getModeId()) || model.getModeId(); - const pick = await quickPickService.pick(picks, { placeHolder: nls.localize('select', "Select a default formatter for {0}-files", DefaultFormatter._maybeQuotes(langName)) }); + const pick = await quickPickService.pick(picks, { placeHolder: nls.localize('select', "Select a default formatter for '{0}' files", DefaultFormatter._maybeQuotes(langName)) }); if (pick && formatters[pick.index].extensionId) { configService.updateValue(DefaultFormatter.configName, formatters[pick.index].extensionId!.value, overrides); } @@ -255,7 +253,7 @@ registerEditorAction(class FormatDocumentMultipleAction extends EditorAction { label: nls.localize('formatDocument.label.multiple', "Format Document With..."), alias: 'Format Document...', precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasMultipleDocumentFormattingProvider), - menuOpts: { + contextMenuOpts: { group: '1_modification', order: 1.3 } @@ -286,7 +284,7 @@ registerEditorAction(class FormatSelectionMultipleAction extends EditorAction { label: nls.localize('formatSelection.label.multiple', "Format Selection With..."), alias: 'Format Code...', precondition: ContextKeyExpr.and(ContextKeyExpr.and(EditorContextKeys.writable), EditorContextKeys.hasMultipleDocumentSelectionFormattingProvider), - menuOpts: { + contextMenuOpts: { when: ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection), group: '1_modification', order: 1.31 diff --git a/src/vs/workbench/contrib/format/browser/formatActionsNone.ts b/src/vs/workbench/contrib/format/browser/formatActionsNone.ts index 0f31b7b564..440298e64a 100644 --- a/src/vs/workbench/contrib/format/browser/formatActionsNone.ts +++ b/src/vs/workbench/contrib/format/browser/formatActionsNone.ts @@ -50,7 +50,7 @@ registerEditorAction(class FormatDocumentMultipleAction extends EditorAction { return commandService.executeCommand('editor.action.formatDocument'); } else { const langName = model.getLanguageIdentifier().language; - const message = nls.localize('no.provider', "There is no formatter for '{0}'-files installed.", langName); + const message = nls.localize('no.provider', "There is no formatter for '{0}' files installed.", langName); const choice = { label: nls.localize('install.formatter', "Install Formatter..."), run: () => showExtensionQuery(viewletService, `category:formatters ${langName}`) diff --git a/src/vs/workbench/contrib/issue/browser/issue.contribution.ts b/src/vs/workbench/contrib/issue/browser/issue.contribution.ts new file mode 100644 index 0000000000..b422a87249 --- /dev/null +++ b/src/vs/workbench/contrib/issue/browser/issue.contribution.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { ICommandAction, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { IWebIssueService, WebIssueService } from 'vs/workbench/contrib/issue/browser/issueService'; + +class RegisterIssueContribution implements IWorkbenchContribution { + + constructor(@IProductService readonly productService: IProductService) { + if (productService.reportIssueUrl) { + const helpCategory = { value: nls.localize('help', "Help"), original: 'Help' }; + const OpenIssueReporterActionId = 'workbench.action.openIssueReporter'; + const OpenIssueReporterActionLabel = nls.localize({ key: 'reportIssueInEnglish', comment: ['Translate this to "Report Issue in English" in all languages please!'] }, "Report Issue"); + + CommandsRegistry.registerCommand(OpenIssueReporterActionId, function (accessor, args?: [string]) { + let extensionId: string | undefined; + if (args && Array.isArray(args)) { + [extensionId] = args; + } + + return accessor.get(IWebIssueService).openReporter({ extensionId }); + }); + + const command: ICommandAction = { + id: OpenIssueReporterActionId, + title: { value: OpenIssueReporterActionLabel, original: 'Report Issue' }, + category: helpCategory + }; + + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command }); + } + } +} + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(RegisterIssueContribution, LifecyclePhase.Starting); + +registerSingleton(IWebIssueService, WebIssueService, true); diff --git a/src/vs/workbench/contrib/issue/browser/issueService.ts b/src/vs/workbench/contrib/issue/browser/issueService.ts new file mode 100644 index 0000000000..35edf62fce --- /dev/null +++ b/src/vs/workbench/contrib/issue/browser/issueService.ts @@ -0,0 +1,67 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import { normalizeGitHubUrl } from 'vs/code/common/issue/issueReporterUtil'; +import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IProductService } from 'vs/platform/product/common/productService'; + +export const IWebIssueService = createDecorator('webIssueService'); + +export interface IIssueReporterOptions { + extensionId?: string; +} + +export interface IWebIssueService { + _serviceBrand: undefined; + openReporter(options?: IIssueReporterOptions): Promise; +} + +export class WebIssueService implements IWebIssueService { + _serviceBrand: undefined; + + constructor( + @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @IOpenerService private readonly openerService: IOpenerService, + @IProductService private readonly productService: IProductService + ) { } + + async openReporter(options: IIssueReporterOptions): Promise { + let repositoryUrl = this.productService.reportIssueUrl; + if (options.extensionId) { + const extensionGitHubUrl = await this.getExtensionGitHubUrl(options.extensionId); + if (extensionGitHubUrl) { + repositoryUrl = extensionGitHubUrl + '/issues/new'; + } + } + + if (repositoryUrl) { + return this.openerService.open(URI.parse(repositoryUrl)).then(_ => { }); + } else { + throw new Error(`Unable to find issue reporting url for ${options.extensionId}`); + } + } + + private async getExtensionGitHubUrl(extensionId: string): Promise { + let repositoryUrl = ''; + + const extensions = await this.extensionManagementService.getInstalled(ExtensionType.User); + const selectedExtension = extensions.filter(ext => ext.identifier.id === extensionId)[0]; + const bugsUrl = selectedExtension?.manifest.bugs?.url; + const extensionUrl = selectedExtension?.manifest.repository?.url; + + // If given, try to match the extension's bug url + if (bugsUrl && bugsUrl.match(/^https?:\/\/github\.com\/(.*)/)) { + repositoryUrl = normalizeGitHubUrl(bugsUrl); + } else if (extensionUrl && extensionUrl.match(/^https?:\/\/github\.com\/(.*)/)) { + repositoryUrl = normalizeGitHubUrl(extensionUrl); + } + + return repositoryUrl; + } +} diff --git a/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts b/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts index 0dd6b7434b..d1f831f71e 100644 --- a/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts +++ b/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts @@ -19,7 +19,7 @@ const helpCategory = { value: nls.localize('help', "Help"), original: 'Help' }; const workbenchActionsRegistry = Registry.as(Extensions.WorkbenchActions); if (!!product.reportIssueUrl) { - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ReportPerformanceIssueUsingReporterAction, ReportPerformanceIssueUsingReporterAction.ID, ReportPerformanceIssueUsingReporterAction.LABEL), 'Help: Report Performance Issue', helpCategory.value); + workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ReportPerformanceIssueUsingReporterAction, ReportPerformanceIssueUsingReporterAction.ID, ReportPerformanceIssueUsingReporterAction.LABEL), 'Help: Report Performance Issue', helpCategory.value); const OpenIssueReporterActionId = 'workbench.action.openIssueReporter'; const OpenIssueReporterActionLabel = nls.localize({ key: 'reportIssueInEnglish', comment: ['Translate this to "Report Issue in English" in all languages please!'] }, "Report Issue"); @@ -43,7 +43,7 @@ if (!!product.reportIssueUrl) { } const developerCategory = nls.localize('developer', "Developer"); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenProcessExplorer, OpenProcessExplorer.ID, OpenProcessExplorer.LABEL), 'Developer: Open Process Explorer', developerCategory); +workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.create(OpenProcessExplorer, OpenProcessExplorer.ID, OpenProcessExplorer.LABEL), 'Developer: Open Process Explorer', developerCategory); registerSingleton(IWorkbenchIssueService, WorkbenchIssueService, true); diff --git a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts index 36d1c66a32..603250e515 100644 --- a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts +++ b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts @@ -29,7 +29,7 @@ import { ExtensionType } from 'vs/platform/extensions/common/extensions'; // Register action to configure locale and related settings const registry = Registry.as(Extensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(ConfigureLocaleAction, ConfigureLocaleAction.ID, ConfigureLocaleAction.LABEL), 'Configure Display Language'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ConfigureLocaleAction, ConfigureLocaleAction.ID, ConfigureLocaleAction.LABEL), 'Configure Display Language'); export class LocalizationWorkbenchContribution extends Disposable implements IWorkbenchContribution { constructor( diff --git a/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts b/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts index effc89cf54..d88c6a5fef 100644 --- a/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts +++ b/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts @@ -16,6 +16,7 @@ import { firstIndex } from 'vs/base/common/arrays'; import { IExtensionsViewlet, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IProductService } from 'vs/platform/product/common/productService'; export class ConfigureLocaleAction extends Action { public static readonly ID = 'workbench.action.configureLocale'; @@ -29,7 +30,8 @@ export class ConfigureLocaleAction extends Action { @IHostService private readonly hostService: IHostService, @INotificationService private readonly notificationService: INotificationService, @IViewletService private readonly viewletService: IViewletService, - @IDialogService private readonly dialogService: IDialogService + @IDialogService private readonly dialogService: IDialogService, + @IProductService private readonly productService: IProductService ) { super(id, label); } @@ -69,7 +71,7 @@ export class ConfigureLocaleAction extends Action { const restart = await this.dialogService.confirm({ type: 'info', message: localize('relaunchDisplayLanguageMessage', "A restart is required for the change in display language to take effect."), - detail: localize('relaunchDisplayLanguageDetail', "Press the restart button to restart {0} and change the display language.", this.environmentService.appNameLong), + detail: localize('relaunchDisplayLanguageDetail', "Press the restart button to restart {0} and change the display language.", this.productService.nameLong), primaryButton: localize('restart', "&&Restart") }); diff --git a/src/vs/workbench/contrib/logs/common/logs.contribution.ts b/src/vs/workbench/contrib/logs/common/logs.contribution.ts index f0e8009fd9..416ce8088f 100644 --- a/src/vs/workbench/contrib/logs/common/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/common/logs.contribution.ts @@ -22,11 +22,10 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { LogsDataCleaner } from 'vs/workbench/contrib/logs/common/logsDataCleaner'; -import { IProductService } from 'vs/platform/product/common/productService'; const workbenchActionsRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); const devCategory = nls.localize('developer', "Developer"); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(SetLogLevelAction, SetLogLevelAction.ID, SetLogLevelAction.LABEL), 'Developer: Set Log Level...', devCategory); +workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SetLogLevelAction, SetLogLevelAction.ID, SetLogLevelAction.LABEL), 'Developer: Set Log Level...', devCategory); class LogOutputChannels extends Disposable implements IWorkbenchContribution { @@ -35,7 +34,6 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { @ILogService private readonly logService: ILogService, @IFileService private readonly fileService: IFileService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IProductService private readonly productService: IProductService ) { super(); this.registerCommonContributions(); @@ -47,9 +45,7 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { } private registerCommonContributions(): void { - if (this.productService.settingsSyncStoreUrl) { - this.registerLogChannel(Constants.userDataSyncLogChannelId, nls.localize('userDataSyncLog', "Configuration Sync"), this.environmentService.userDataSyncLogResource); - } + this.registerLogChannel(Constants.userDataSyncLogChannelId, nls.localize('userDataSyncLog', "Sync"), this.environmentService.userDataSyncLogResource); this.registerLogChannel(Constants.rendererLogChannelId, nls.localize('rendererLog', "Window"), this.environmentService.logFile); } @@ -58,7 +54,7 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { const workbenchActionsRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); const devCategory = nls.localize('developer', "Developer"); - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenWindowSessionLogFileAction, OpenWindowSessionLogFileAction.ID, OpenWindowSessionLogFileAction.LABEL), 'Developer: Open Window Log File (Session)...', devCategory); + workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.create(OpenWindowSessionLogFileAction, OpenWindowSessionLogFileAction.ID, OpenWindowSessionLogFileAction.LABEL), 'Developer: Open Window Log File (Session)...', devCategory); } private registerNativeContributions(): void { diff --git a/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts b/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts index ccd3051cd3..fc45f2312c 100644 --- a/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts @@ -11,4 +11,4 @@ import { OpenLogsFolderAction } from 'vs/workbench/contrib/logs/electron-browser const workbenchActionsRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); const devCategory = nls.localize('developer', "Developer"); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenLogsFolderAction, OpenLogsFolderAction.ID, OpenLogsFolderAction.LABEL), 'Developer: Open Logs Folder', devCategory); +workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.create(OpenLogsFolderAction, OpenLogsFolderAction.ID, OpenLogsFolderAction.LABEL), 'Developer: Open Logs Folder', devCategory); diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index f480e69e46..ed52a509ee 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -81,13 +81,18 @@ Registry.as(Extensions.Configuration).registerConfigurat 'description': Messages.PROBLEMS_PANEL_CONFIGURATION_AUTO_REVEAL, 'type': 'boolean', 'default': true + }, + 'problems.showCurrentInStatus': { + 'description': Messages.PROBLEMS_PANEL_CONFIGURATION_SHOW_CURRENT_STATUS, + 'type': 'boolean', + 'default': false } } }); // markers panel -Registry.as(PanelExtensions.Panels).registerPanel(new PanelDescriptor( +Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( MarkersPanel, Constants.MARKERS_PANEL_ID, Messages.MARKERS_PANEL_TITLE_PROBLEMS, @@ -102,10 +107,10 @@ workbenchRegistry.registerWorkbenchContribution(ActivityUpdater, LifecyclePhase. // actions const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleMarkersPanelAction, ToggleMarkersPanelAction.ID, ToggleMarkersPanelAction.LABEL, { +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleMarkersPanelAction, ToggleMarkersPanelAction.ID, ToggleMarkersPanelAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_M }), 'View: Toggle Problems (Errors, Warnings, Infos)', Messages.MARKERS_PANEL_VIEW_CATEGORY); -registry.registerWorkbenchAction(new SyncActionDescriptor(ShowProblemsPanelAction, ShowProblemsPanelAction.ID, ShowProblemsPanelAction.LABEL), 'View: Focus Problems (Errors, Warnings, Infos)', Messages.MARKERS_PANEL_VIEW_CATEGORY); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowProblemsPanelAction, ShowProblemsPanelAction.ID, ShowProblemsPanelAction.LABEL), 'View: Focus Problems (Errors, Warnings, Infos)', Messages.MARKERS_PANEL_VIEW_CATEGORY); registerAction({ id: Constants.MARKER_COPY_ACTION_ID, title: { value: localize('copyMarker', "Copy"), original: 'Copy' }, diff --git a/src/vs/workbench/contrib/markers/browser/markers.ts b/src/vs/workbench/contrib/markers/browser/markers.ts index f949e2a2de..3f39caafa6 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.ts @@ -18,11 +18,6 @@ import { ResourceMap } from 'vs/base/common/map'; export const IMarkersWorkbenchService = createDecorator('markersWorkbenchService'); -export interface IFilter { - filterText: string; - useFilesExclude: boolean; -} - export interface IMarkersWorkbenchService { _serviceBrand: undefined; readonly markersModel: MarkersModel; @@ -38,7 +33,7 @@ export class MarkersWorkbenchService extends Disposable implements IMarkersWorkb @IInstantiationService instantiationService: IInstantiationService, ) { super(); - this.markersModel = this._register(instantiationService.createInstance(MarkersModel, this.readMarkers())); + this.markersModel = this._register(instantiationService.createInstance(MarkersModel)); this.markersModel.setResourceMarkers(groupBy(this.readMarkers(), compareMarkersByUri).map(group => [group[0].resource, group])); this._register(Event.debounce>(markerService.onMarkerChanged, (resourcesMap, resources) => { diff --git a/src/vs/workbench/contrib/markers/browser/markersFilterOptions.ts b/src/vs/workbench/contrib/markers/browser/markersFilterOptions.ts index 9418029d64..eceb750959 100644 --- a/src/vs/workbench/contrib/markers/browser/markersFilterOptions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersFilterOptions.ts @@ -3,8 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import Messages from 'vs/workbench/contrib/markers/browser/messages'; -import { IFilter, matchesPrefix, matchesFuzzy, matchesFuzzy2 } from 'vs/base/common/filters'; +import { IFilter, matchesFuzzy, matchesFuzzy2 } from 'vs/base/common/filters'; import { IExpression, splitGlobAware, getEmptyExpression } from 'vs/base/common/glob'; import * as strings from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; @@ -15,15 +14,18 @@ export class FilterOptions { static readonly _filter: IFilter = matchesFuzzy2; static readonly _messageFilter: IFilter = matchesFuzzy; - readonly filterErrors: boolean = false; - readonly filterWarnings: boolean = false; - readonly filterInfos: boolean = false; + readonly showWarnings: boolean = false; + readonly showErrors: boolean = false; + readonly showInfos: boolean = false; readonly textFilter: string = ''; readonly excludesMatcher: ResourceGlobMatcher; readonly includesMatcher: ResourceGlobMatcher; - constructor(readonly filter: string = '', filesExclude: { root: URI, expression: IExpression }[] | IExpression = []) { + constructor(readonly filter: string = '', filesExclude: { root: URI, expression: IExpression }[] | IExpression = [], showWarnings: boolean = false, showErrors: boolean = false, showInfos: boolean = false) { filter = filter.trim(); + this.showWarnings = showWarnings; + this.showErrors = showErrors; + this.showInfos = showInfos; const filesExcludeByRoot = Array.isArray(filesExclude) ? filesExclude : []; const excludesExpression: IExpression = Array.isArray(filesExclude) ? getEmptyExpression() : filesExclude; @@ -32,9 +34,6 @@ export class FilterOptions { if (filter) { const filters = splitGlobAware(filter, ',').map(s => s.trim()).filter(s => !!s.length); for (const f of filters) { - this.filterErrors = this.filterErrors || this.matches(f, Messages.MARKERS_PANEL_FILTER_ERRORS); - this.filterWarnings = this.filterWarnings || this.matches(f, Messages.MARKERS_PANEL_FILTER_WARNINGS); - this.filterInfos = this.filterInfos || this.matches(f, Messages.MARKERS_PANEL_FILTER_INFOS); if (strings.startsWith(f, '!')) { this.setPattern(excludesExpression, strings.ltrim(f, '!')); } else { @@ -56,9 +55,4 @@ export class FilterOptions { expression[`**/${pattern}/**`] = true; expression[`**/${pattern}`] = true; } - - private matches(prefix: string, word: string): boolean { - const result = matchesPrefix(prefix, word); - return !!(result && result.length > 0); - } } diff --git a/src/vs/workbench/contrib/markers/browser/markersModel.ts b/src/vs/workbench/contrib/markers/browser/markersModel.ts index 115c48cf9e..105fd3a783 100644 --- a/src/vs/workbench/contrib/markers/browser/markersModel.ts +++ b/src/vs/workbench/contrib/markers/browser/markersModel.ts @@ -117,6 +117,11 @@ export class MarkersModel { this.resourcesByUri = new Map(); } + private _total: number = 0; + get total(): number { + return this._total; + } + getResourceMarkers(resource: URI): ResourceMarkers | null { return withUndefinedAsNull(this.resourcesByUri.get(resource.toString())); } @@ -129,6 +134,7 @@ export class MarkersModel { if (resourceMarkers) { this.resourcesByUri.delete(resource.toString()); change.removed.push(resourceMarkers); + this._total -= resourceMarkers.markers.length; } } else { const resourceMarkersId = this.id(resource.toString()); @@ -149,12 +155,14 @@ export class MarkersModel { }), compareMarkers); if (resourceMarkers) { + this._total -= resourceMarkers.markers.length; resourceMarkers.markers = markers; change.updated.push(resourceMarkers); } else { resourceMarkers = new ResourceMarkers(resourceMarkersId, resource, markers); change.added.push(resourceMarkers); } + this._total += resourceMarkers.markers.length; this.resourcesByUri.set(resource.toString(), resourceMarkers); } } diff --git a/src/vs/workbench/contrib/markers/browser/markersPanel.ts b/src/vs/workbench/contrib/markers/browser/markersPanel.ts index b85b7b0ecf..40717bf147 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanel.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanel.ts @@ -12,7 +12,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Panel } from 'vs/workbench/browser/panel'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import Constants from 'vs/workbench/contrib/markers/browser/constants'; -import { Marker, ResourceMarkers, RelatedInformation, MarkersModel, MarkerChangesEvent } from 'vs/workbench/contrib/markers/browser/markersModel'; +import { Marker, ResourceMarkers, RelatedInformation, MarkerChangesEvent } from 'vs/workbench/contrib/markers/browser/markersModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { MarkersFilterActionViewItem, MarkersFilterAction, IMarkersFilterActionChangeEvent, IMarkerFilterController } from 'vs/workbench/contrib/markers/browser/markersPanelActions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -27,32 +27,25 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { Iterator } from 'vs/base/common/iterator'; import { ITreeElement, ITreeNode, ITreeContextMenuEvent, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { Relay, Event, Emitter } from 'vs/base/common/event'; -import { WorkbenchObjectTree, TreeResourceNavigator2, IListService } from 'vs/platform/list/browser/listService'; +import { WorkbenchObjectTree, TreeResourceNavigator2, IListService, IWorkbenchObjectTreeOptions } from 'vs/platform/list/browser/listService'; import { FilterOptions } from 'vs/workbench/contrib/markers/browser/markersFilterOptions'; import { IExpression } from 'vs/base/common/glob'; import { deepClone } from 'vs/base/common/objects'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { FilterData, Filter, VirtualDelegate, ResourceMarkersRenderer, MarkerRenderer, RelatedInformationRenderer, TreeElement, MarkersTreeAccessibilityProvider, MarkersViewModel, ResourceDragAndDrop } from 'vs/workbench/contrib/markers/browser/markersTreeViewer'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { Separator, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { Separator, ActionViewItem, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { KeyCode } from 'vs/base/common/keyCodes'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { domEvent } from 'vs/base/browser/event'; import { ResourceLabels } from 'vs/workbench/browser/labels'; import { IMarker } from 'vs/platform/markers/common/markers'; import { withUndefinedAsNull } from 'vs/base/common/types'; import { MementoObject } from 'vs/workbench/common/memento'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { IObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; - -function createModelIterator(model: MarkersModel): Iterator> { - const resourcesIt = Iterator.fromArray(model.resourceMarkers); - - return Iterator.map(resourcesIt, m => ({ element: m, children: createResourceMarkersIterator(m) })); -} +import { PANEL_BACKGROUND } from 'vs/workbench/common/theme'; function createResourceMarkersIterator(resourceMarkers: ResourceMarkers): Iterator> { const markersIt = Iterator.fromArray(resourceMarkers.markers); @@ -75,12 +68,12 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { private readonly filter: Filter; private tree!: MarkersTree; + private filterActionBar!: ActionBar; private messageBoxContainer!: HTMLElement; private ariaLabelElement!: HTMLElement; private readonly collapseAllAction: IAction; private readonly filterAction: MarkersFilterAction; - private filterInputActionViewItem: MarkersFilterActionViewItem | null = null; private readonly panelState: MementoObject; private panelFoucusContextKey: IContextKey; @@ -91,6 +84,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { private currentResourceGotAddedToMarkersData: boolean = false; readonly markersViewModel: MarkersViewModel; + private isSmallLayout: boolean = false; constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -118,7 +112,15 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { // actions this.collapseAllAction = this._register(new Action('vs.tree.collapse', localize('collapseAll', "Collapse All"), 'monaco-tree-action codicon-collapse-all', true, async () => this.collapseAll())); - this.filterAction = this._register(this.instantiationService.createInstance(MarkersFilterAction, { filterText: this.panelState['filter'] || '', filterHistory: this.panelState['filterHistory'] || [], useFilesExclude: !!this.panelState['useFilesExclude'] })); + this.filterAction = this._register(this.instantiationService.createInstance(MarkersFilterAction, { + filterText: this.panelState['filter'] || '', + filterHistory: this.panelState['filterHistory'] || [], + showErrors: this.panelState['showErrors'] !== false, + showWarnings: this.panelState['showWarnings'] !== false, + showInfos: this.panelState['showInfos'] !== false, + excludedFiles: !!this.panelState['useFilesExclude'], + activeFile: !!this.panelState['activeFile'] + })); } public create(parent: HTMLElement): void { @@ -129,6 +131,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { const container = dom.append(parent, dom.$('.markers-panel-container')); + this.createFilterActionBar(container); this.createArialLabelElement(container); this.createMessageBox(container); this.createTree(container); @@ -147,6 +150,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } })); + this.filterActionBar.push(this.filterAction); this.render(); } @@ -155,10 +159,15 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } public layout(dimension: dom.Dimension): void { - this.tree.layout(dimension.height, dimension.width); - if (this.filterInputActionViewItem) { - this.filterInputActionViewItem.toggleLayout(dimension.width < 1200); + const wasSmallLayout = this.isSmallLayout; + this.isSmallLayout = dimension.width < 600; + if (this.isSmallLayout !== wasSmallLayout) { + this.updateTitleArea(); + dom.toggleClass(this.filterActionBar.getContainer(), 'hide', !this.isSmallLayout); } + const treeHeight = this.isSmallLayout ? dimension.height - 44 : dimension.height; + this.tree.layout(treeHeight, dimension.width); + this.filterAction.layout(this.isSmallLayout ? dimension.width : dimension.width - 200); } public focus(): void { @@ -174,12 +183,13 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } public focusFilter(): void { - if (this.filterInputActionViewItem) { - this.filterInputActionViewItem.focus(); - } + this.filterAction.focus(); } public getActions(): IAction[] { + if (this.isSmallLayout) { + return [this.collapseAllAction]; + } return [this.filterAction, this.collapseAllAction]; } @@ -239,7 +249,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } else { if (markerOrChange.added.length || markerOrChange.removed.length) { // Reset complete tree - this.tree.setChildren(null, createModelIterator(this.markersWorkbenchService.markersModel)); + this.resetTree(); } else { // Update resource for (const updated of markerOrChange.updated) { @@ -249,7 +259,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } } else { // Reset complete tree - this.tree.setChildren(null, createModelIterator(this.markersWorkbenchService.markersModel)); + this.resetTree(); } const { total, filtered } = this.getFilterStats(); @@ -263,9 +273,24 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { this.refreshPanel(marker); } + private resetTree(): void { + let resourceMarkers: ResourceMarkers[] = []; + if (this.filterAction.activeFile) { + if (this.currentActiveResource) { + const activeResourceMarkers = this.markersWorkbenchService.markersModel.getResourceMarkers(this.currentActiveResource); + if (activeResourceMarkers) { + resourceMarkers = [activeResourceMarkers]; + } + } + } else { + resourceMarkers = this.markersWorkbenchService.markersModel.resourceMarkers; + } + this.tree.setChildren(null, Iterator.map(Iterator.fromArray(resourceMarkers), m => ({ element: m, children: createResourceMarkersIterator(m) }))); + } + private updateFilter() { this.cachedFilterStats = undefined; - this.filter.options = new FilterOptions(this.filterAction.filterText, this.getFilesExcludeExpressions()); + this.filter.options = new FilterOptions(this.filterAction.filterText, this.getFilesExcludeExpressions(), this.filterAction.showWarnings, this.filterAction.showErrors, this.filterAction.showInfos); this.tree.refilter(); this._onDidFilter.fire(); @@ -275,7 +300,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } private getFilesExcludeExpressions(): { root: URI, expression: IExpression }[] | IExpression { - if (!this.filterAction.useFilesExclude) { + if (!this.filterAction.excludedFiles) { return []; } @@ -289,6 +314,12 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { return deepClone(this.configurationService.getValue('files.exclude', { resource })) || {}; } + private createFilterActionBar(parent: HTMLElement): void { + this.filterActionBar = this._register(new ActionBar(parent, { actionViewItemProvider: action => this.getActionViewItem(action) })); + dom.addClass(this.filterActionBar.getContainer(), 'markers-panel-filter-container'); + dom.toggleClass(this.filterActionBar.getContainer(), 'hide', !this.isSmallLayout); + } + private createMessageBox(parent: HTMLElement): void { this.messageBoxContainer = dom.append(parent, dom.$('.message-box-container')); this.messageBoxContainer.setAttribute('aria-labelledby', 'markers-panel-arialabel'); @@ -329,8 +360,11 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { accessibilityProvider, identityProvider, dnd: new ResourceDragAndDrop(this.instantiationService), - expandOnlyOnTwistieClick: (e: TreeElement) => e instanceof Marker && e.relatedInformation.length > 0 - } + expandOnlyOnTwistieClick: (e: TreeElement) => e instanceof Marker && e.relatedInformation.length > 0, + overrideStyles: { + listBackground: PANEL_BACKGROUND + } + }, )); onDidChangeRenderNodeCount.input = this.tree.onDidChangeRenderNodeCount; @@ -362,15 +396,15 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { this._register(this.tree.onContextMenu(this.onContextMenu, this)); this._register(this.configurationService.onDidChangeConfiguration(e => { - if (this.filterAction.useFilesExclude && e.affectsConfiguration('files.exclude')) { + if (this.filterAction.excludedFiles && e.affectsConfiguration('files.exclude')) { this.updateFilter(); } })); // move focus to input, whenever a key is pressed in the panel container this._register(domEvent(parent, 'keydown')(e => { - if (this.filterInputActionViewItem && this.keybindingService.mightProducePrintableCharacter(new StandardKeyboardEvent(e))) { - this.filterInputActionViewItem.focus(); + if (this.keybindingService.mightProducePrintableCharacter(new StandardKeyboardEvent(e))) { + this.filterAction.focus(); } })); @@ -405,7 +439,9 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { })); this._register(this.tree.onDidChangeSelection(() => this.onSelected())); this._register(this.filterAction.onDidChange((event: IMarkersFilterActionChangeEvent) => { - if (event.filterText || event.useFilesExclude) { + if (event.activeFile) { + this.refreshPanel(); + } else if (event.filterText || event.excludedFiles || event.showWarnings || event.showErrors || event.showInfos) { this.updateFilter(); } })); @@ -447,6 +483,9 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { private onActiveEditorChanged(): void { this.setCurrentActiveEditor(); + if (this.filterAction.activeFile) { + this.refreshPanel(); + } this.autoReveal(); } @@ -469,7 +508,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { private render(): void { this.cachedFilterStats = undefined; - this.tree.setChildren(null, createModelIterator(this.markersWorkbenchService.markersModel)); + this.resetTree(); this.tree.toggleVisibility(this.isEmpty()); this.renderMessage(); } @@ -482,11 +521,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { this.messageBoxContainer.style.display = 'block'; this.messageBoxContainer.setAttribute('tabIndex', '0'); if (total > 0) { - if (this.filter.options.filter) { - this.renderFilteredByFilterMessage(this.messageBoxContainer); - } else { - this.renderFilteredByFilesExcludeMessage(this.messageBoxContainer); - } + this.renderFilteredByFilterMessage(this.messageBoxContainer); } else { this.renderNoProblemsMessage(this.messageBoxContainer); } @@ -501,37 +536,9 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } } - private renderFilteredByFilesExcludeMessage(container: HTMLElement) { - const span1 = dom.append(container, dom.$('span')); - span1.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_FILE_EXCLUSIONS_FILTER; - const link = dom.append(container, dom.$('a.messageAction')); - link.textContent = localize('disableFilesExclude', "Disable Files Exclude Filter."); - link.setAttribute('tabIndex', '0'); - dom.addStandardDisposableListener(link, dom.EventType.CLICK, () => this.filterAction.useFilesExclude = false); - dom.addStandardDisposableListener(link, dom.EventType.KEY_DOWN, (e: IKeyboardEvent) => { - if (e.equals(KeyCode.Enter) || e.equals(KeyCode.Space)) { - this.filterAction.useFilesExclude = false; - e.stopPropagation(); - } - }); - this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_FILE_EXCLUSIONS_FILTER); - } - private renderFilteredByFilterMessage(container: HTMLElement) { const span1 = dom.append(container, dom.$('span')); span1.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_FILTERS; - const link = dom.append(container, dom.$('a.messageAction')); - link.textContent = localize('clearFilter', "Clear Filter"); - link.setAttribute('tabIndex', '0'); - const span2 = dom.append(container, dom.$('span')); - span2.textContent = '.'; - dom.addStandardDisposableListener(link, dom.EventType.CLICK, () => this.filterAction.filterText = ''); - dom.addStandardDisposableListener(link, dom.EventType.KEY_DOWN, (e: IKeyboardEvent) => { - if (e.equals(KeyCode.Enter) || e.equals(KeyCode.Space)) { - this.filterAction.filterText = ''; - e.stopPropagation(); - } - }); this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_FILTERS); } @@ -542,32 +549,32 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } private autoReveal(focus: boolean = false): void { + // No need to auto reveal if active file filter is on + if (this.filterAction.activeFile) { + return; + } let autoReveal = this.configurationService.getValue('problems.autoReveal'); if (typeof autoReveal === 'boolean' && autoReveal) { - this.revealMarkersForCurrentActiveEditor(focus); - } - } + let currentActiveResource = this.getResourceForCurrentActiveResource(); + if (currentActiveResource) { + if (!this.tree.isCollapsed(currentActiveResource) && this.hasSelectedMarkerFor(currentActiveResource)) { + this.tree.reveal(this.tree.getSelection()[0], this.lastSelectedRelativeTop); + if (focus) { + this.tree.setFocus(this.tree.getSelection()); + } + } else { + this.tree.expand(currentActiveResource); + this.tree.reveal(currentActiveResource, 0); - private revealMarkersForCurrentActiveEditor(focus: boolean = false): void { - let currentActiveResource = this.getResourceForCurrentActiveResource(); - if (currentActiveResource) { - if (!this.tree.isCollapsed(currentActiveResource) && this.hasSelectedMarkerFor(currentActiveResource)) { - this.tree.reveal(this.tree.getSelection()[0], this.lastSelectedRelativeTop); - if (focus) { - this.tree.setFocus(this.tree.getSelection()); - } - } else { - this.tree.expand(currentActiveResource); - this.tree.reveal(currentActiveResource, 0); - - if (focus) { - this.tree.setFocus([currentActiveResource]); - this.tree.setSelection([currentActiveResource]); + if (focus) { + this.tree.setFocus([currentActiveResource]); + this.tree.setSelection([currentActiveResource]); + } } + } else if (focus) { + this.tree.setSelection([]); + this.tree.focusFirst(); } - } else if (focus) { - this.tree.setSelection([]); - this.tree.focusFirst(); } } @@ -671,8 +678,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { public getActionViewItem(action: IAction): IActionViewItem | undefined { if (action.id === MarkersFilterAction.ID) { - this.filterInputActionViewItem = this.instantiationService.createInstance(MarkersFilterActionViewItem, this.filterAction, this); - return this.filterInputActionViewItem; + return this.instantiationService.createInstance(MarkersFilterActionViewItem, this.filterAction, this); } return super.getActionViewItem(action); } @@ -691,20 +697,17 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { private computeFilterStats(): { total: number; filtered: number; } { const root = this.tree.getNode(); - let total = 0; let filtered = 0; for (const resourceMarkerNode of root.children) { for (const markerNode of resourceMarkerNode.children) { - total++; - if (resourceMarkerNode.visible && markerNode.visible) { filtered++; } } } - return { total, filtered }; + return { total: this.markersWorkbenchService.markersModel.total, filtered }; } private getTelemetryData({ source, code }: IMarker): any { @@ -714,7 +717,11 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { protected saveState(): void { this.panelState['filter'] = this.filterAction.filterText; this.panelState['filterHistory'] = this.filterAction.filterHistory; - this.panelState['useFilesExclude'] = this.filterAction.useFilesExclude; + this.panelState['showErrors'] = this.filterAction.showErrors; + this.panelState['showWarnings'] = this.filterAction.showWarnings; + this.panelState['showInfos'] = this.filterAction.showInfos; + this.panelState['useFilesExclude'] = this.filterAction.excludedFiles; + this.panelState['activeFile'] = this.filterAction.activeFile; this.panelState['multiline'] = this.markersViewModel.multiline; super.saveState(); @@ -729,7 +736,7 @@ class MarkersTree extends WorkbenchObjectTree { readonly container: HTMLElement, delegate: IListVirtualDelegate, renderers: ITreeRenderer[], - options: IObjectTreeOptions, + options: IWorkbenchObjectTreeOptions, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, diff --git a/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts b/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts index 746e5903e8..3910f09d18 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts @@ -5,7 +5,7 @@ import { Delayer } from 'vs/base/common/async'; import * as DOM from 'vs/base/browser/dom'; -import { Action, IActionChangeEvent, IAction } from 'vs/base/common/actions'; +import { Action, IActionChangeEvent, IAction, IActionRunner } from 'vs/base/common/actions'; import { HistoryInputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -15,14 +15,12 @@ import Messages from 'vs/workbench/contrib/markers/browser/messages'; import Constants from 'vs/workbench/contrib/markers/browser/constants'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { attachInputBoxStyler, attachStylerCallback, attachCheckboxStyler } from 'vs/platform/theme/common/styler'; -import { IMarkersWorkbenchService } from 'vs/workbench/contrib/markers/browser/markers'; +import { IThemeService, registerThemingParticipant, ICssStyleCollector, ITheme } from 'vs/platform/theme/common/themeService'; +import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; import { toDisposable } from 'vs/base/common/lifecycle'; -import { BaseActionViewItem, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; -import { badgeBackground, badgeForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; +import { BaseActionViewItem, ActionViewItem, ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { badgeBackground, badgeForeground, contrastBorder, inputActiveOptionBorder, inputActiveOptionBackground } from 'vs/platform/theme/common/colorRegistry'; import { localize } from 'vs/nls'; -import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedHistoryWidget'; import { Marker } from 'vs/workbench/contrib/markers/browser/markersModel'; @@ -30,6 +28,8 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { Event, Emitter } from 'vs/base/common/event'; import { FilterOptions } from 'vs/workbench/contrib/markers/browser/markersFilterOptions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdown'; +import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; export class ToggleMarkersPanelAction extends TogglePanelAction { @@ -38,8 +38,7 @@ export class ToggleMarkersPanelAction extends TogglePanelAction { constructor(id: string, label: string, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @IPanelService panelService: IPanelService, - @IMarkersWorkbenchService markersWorkbenchService: IMarkersWorkbenchService + @IPanelService panelService: IPanelService ) { super(id, label, Constants.MARKERS_PANEL_ID, panelService, layoutService); } @@ -64,23 +63,38 @@ export class ShowProblemsPanelAction extends Action { export interface IMarkersFilterActionChangeEvent extends IActionChangeEvent { filterText?: boolean; - useFilesExclude?: boolean; + excludedFiles?: boolean; + showWarnings?: boolean; + showErrors?: boolean; + showInfos?: boolean; + activeFile?: boolean; } export interface IMarkersFilterActionOptions { filterText: string; filterHistory: string[]; - useFilesExclude: boolean; + showErrors: boolean; + showWarnings: boolean; + showInfos: boolean; + excludedFiles: boolean; + activeFile: boolean; } export class MarkersFilterAction extends Action { public static readonly ID: string = 'workbench.actions.problems.filter'; + private readonly _onFocus: Emitter = this._register(new Emitter()); + readonly onFocus: Event = this._onFocus.event; + constructor(options: IMarkersFilterActionOptions) { super(MarkersFilterAction.ID, Messages.MARKERS_PANEL_ACTION_TOOLTIP_FILTER, 'markers-panel-action-filter', true); this._filterText = options.filterText; - this._useFilesExclude = options.useFilesExclude; + this._showErrors = options.showErrors; + this._showWarnings = options.showWarnings; + this._showInfos = options.showInfos; + this._excludedFiles = options.excludedFiles; + this._activeFile = options.activeFile; this.filterHistory = options.filterHistory; } @@ -97,14 +111,72 @@ export class MarkersFilterAction extends Action { filterHistory: string[]; - private _useFilesExclude: boolean; - get useFilesExclude(): boolean { - return this._useFilesExclude; + private _excludedFiles: boolean; + get excludedFiles(): boolean { + return this._excludedFiles; } - set useFilesExclude(filesExclude: boolean) { - if (this._useFilesExclude !== filesExclude) { - this._useFilesExclude = filesExclude; - this._onDidChange.fire({ useFilesExclude: true }); + set excludedFiles(filesExclude: boolean) { + if (this._excludedFiles !== filesExclude) { + this._excludedFiles = filesExclude; + this._onDidChange.fire({ excludedFiles: true }); + } + } + + private _activeFile: boolean; + get activeFile(): boolean { + return this._activeFile; + } + set activeFile(activeFile: boolean) { + if (this._activeFile !== activeFile) { + this._activeFile = activeFile; + this._onDidChange.fire({ activeFile: true }); + } + } + + private _showWarnings: boolean = true; + get showWarnings(): boolean { + return this._showWarnings; + } + set showWarnings(showWarnings: boolean) { + if (this._showWarnings !== showWarnings) { + this._showWarnings = showWarnings; + this._onDidChange.fire({ showWarnings: true }); + } + } + + private _showErrors: boolean = true; + get showErrors(): boolean { + return this._showErrors; + } + set showErrors(showErrors: boolean) { + if (this._showErrors !== showErrors) { + this._showErrors = showErrors; + this._onDidChange.fire({ showErrors: true }); + } + } + + private _showInfos: boolean = true; + get showInfos(): boolean { + return this._showInfos; + } + set showInfos(showInfos: boolean) { + if (this._showInfos !== showInfos) { + this._showInfos = showInfos; + this._onDidChange.fire({ showInfos: true }); + } + } + + focus(): void { + this._onFocus.fire(); + } + + layout(width: number): void { + if (width > 600) { + this.class = 'markers-panel-action-filter grow'; + } else if (width < 400) { + this.class = 'markers-panel-action-filter small'; + } else { + this.class = 'markers-panel-action-filter'; } } } @@ -115,6 +187,89 @@ export interface IMarkerFilterController { getFilterStats(): { total: number, filtered: number }; } +class FiltersDropdownMenuActionViewItem extends DropdownMenuActionViewItem { + + constructor( + action: IAction, private filterAction: MarkersFilterAction, actionRunner: IActionRunner, + @IContextMenuService contextMenuService: IContextMenuService + ) { + super(action, + { getActions: () => this.getActions() }, + contextMenuService, + action => undefined, + actionRunner!, + undefined, + action.class, + () => { return AnchorAlignment.RIGHT; }); + } + + render(container: HTMLElement): void { + super.render(container); + this.updateChecked(); + } + + private getActions(): IAction[] { + return [ + { + checked: this.filterAction.showErrors, + class: undefined, + enabled: true, + id: 'showErrors', + label: Messages.MARKERS_PANEL_FILTER_LABEL_SHOW_ERRORS, + run: async () => this.filterAction.showErrors = !this.filterAction.showErrors, + tooltip: '', + dispose: () => null + }, + { + checked: this.filterAction.showWarnings, + class: undefined, + enabled: true, + id: 'showWarnings', + label: Messages.MARKERS_PANEL_FILTER_LABEL_SHOW_WARNINGS, + run: async () => this.filterAction.showWarnings = !this.filterAction.showWarnings, + tooltip: '', + dispose: () => null + }, + { + checked: this.filterAction.showInfos, + class: undefined, + enabled: true, + id: 'showInfos', + label: Messages.MARKERS_PANEL_FILTER_LABEL_SHOW_INFOS, + run: async () => this.filterAction.showInfos = !this.filterAction.showInfos, + tooltip: '', + dispose: () => null + }, + new Separator(), + { + checked: this.filterAction.activeFile, + class: undefined, + enabled: true, + id: 'activeFile', + label: Messages.MARKERS_PANEL_FILTER_LABEL_ACTIVE_FILE, + run: async () => this.filterAction.activeFile = !this.filterAction.activeFile, + tooltip: '', + dispose: () => null + }, + { + checked: this.filterAction.excludedFiles, + class: undefined, + enabled: true, + id: 'useFilesExclude', + label: Messages.MARKERS_PANEL_FILTER_LABEL_EXCLUDED_FILES, + run: async () => this.filterAction.excludedFiles = !this.filterAction.excludedFiles, + tooltip: '', + dispose: () => null + }, + ]; + } + + updateChecked(): void { + DOM.toggleClass(this.element!, 'checked', this._action.checked); + } + +} + export class MarkersFilterActionViewItem extends BaseActionViewItem { private delayedFilterUpdate: Delayer; @@ -122,6 +277,7 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { private filterInputBox: HistoryInputBox | null = null; private filterBadge: HTMLElement | null = null; private focusContextKey: IContextKey; + private readonly filtersAction: IAction; constructor( readonly action: MarkersFilterAction, @@ -136,15 +292,20 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { this.focusContextKey = Constants.MarkerPanelFilterFocusContextKey.bindTo(contextKeyService); this.delayedFilterUpdate = new Delayer(200); this._register(toDisposable(() => this.delayedFilterUpdate.cancel())); + this._register(action.onFocus(() => this.focus())); + this.filtersAction = new Action('markersFiltersAction', Messages.MARKERS_PANEL_ACTION_TOOLTIP_MORE_FILTERS, 'markers-filters codicon-filter'); + this.filtersAction.checked = this.hasFiltersChanged(); + this._register(action.onDidChange(() => this.filtersAction.checked = this.hasFiltersChanged())); } render(container: HTMLElement): void { this.container = container; DOM.addClass(this.container, 'markers-panel-action-filter-container'); - const filterContainer = DOM.append(this.container, DOM.$('.markers-panel-action-filter')); - this.createInput(filterContainer); - this.createControls(filterContainer); + this.element = DOM.append(this.container, DOM.$('')); + this.element.className = this.action.class || ''; + this.createInput(this.element); + this.createControls(this.element); this.adjustInputBox(); } @@ -155,11 +316,8 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { } } - toggleLayout(small: boolean) { - if (this.container) { - DOM.toggleClass(this.container, 'small', small); - this.adjustInputBox(); - } + private hasFiltersChanged(): boolean { + return !this.action.showErrors || !this.action.showWarnings || !this.action.showInfos || this.action.excludedFiles || this.action.activeFile; } private createInput(container: HTMLElement): void { @@ -190,7 +348,7 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { private createControls(container: HTMLElement): void { const controlsContainer = DOM.append(container, DOM.$('.markers-panel-filter-controls')); this.createBadge(controlsContainer); - this.createFilesExcludeCheckbox(controlsContainer); + this.createFilters(controlsContainer); } private createBadge(container: HTMLElement): void { @@ -211,25 +369,16 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { this._register(this.filterController.onDidFilter(() => this.updateBadge())); } - private createFilesExcludeCheckbox(container: HTMLElement): void { - const filesExcludeFilter = this._register(new Checkbox({ - actionClassName: 'codicon codicon-exclude', - title: this.action.useFilesExclude ? Messages.MARKERS_PANEL_ACTION_TOOLTIP_DO_NOT_USE_FILES_EXCLUDE : Messages.MARKERS_PANEL_ACTION_TOOLTIP_USE_FILES_EXCLUDE, - isChecked: this.action.useFilesExclude - })); - this._register(filesExcludeFilter.onChange(() => { - filesExcludeFilter.domNode.title = filesExcludeFilter.checked ? Messages.MARKERS_PANEL_ACTION_TOOLTIP_DO_NOT_USE_FILES_EXCLUDE : Messages.MARKERS_PANEL_ACTION_TOOLTIP_USE_FILES_EXCLUDE; - this.action.useFilesExclude = filesExcludeFilter.checked; - this.focus(); - })); - this._register(this.action.onDidChange((event: IMarkersFilterActionChangeEvent) => { - if (event.useFilesExclude) { - filesExcludeFilter.checked = this.action.useFilesExclude; + private createFilters(container: HTMLElement): void { + const actionbar = this._register(new ActionBar(container, { + actionViewItemProvider: action => { + if (action.id === this.filtersAction.id) { + return this.instantiationService.createInstance(FiltersDropdownMenuActionViewItem, action, this.action, this.actionRunner); + } + return undefined; } })); - - this._register(attachCheckboxStyler(filesExcludeFilter, this.themeService)); - container.appendChild(filesExcludeFilter.domNode); + actionbar.push(this.filtersAction, { icon: true, label: false }); } private onDidInputChange(inputbox: HistoryInputBox) { @@ -249,8 +398,8 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { } private adjustInputBox(): void { - if (this.container && this.filterInputBox && this.filterBadge) { - this.filterInputBox.inputElement.style.paddingRight = DOM.hasClass(this.container, 'small') || DOM.hasClass(this.filterBadge, 'hidden') ? '25px' : '150px'; + if (this.element && this.filterInputBox && this.filterBadge) { + this.filterInputBox.inputElement.style.paddingRight = DOM.hasClass(this.element, 'small') || DOM.hasClass(this.filterBadge, 'hidden') ? '25px' : '150px'; } } @@ -280,9 +429,9 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { private reportFilteringUsed(): void { const filterOptions = this.filterController.getFilterOptions(); const data = { - errors: filterOptions.filterErrors, - warnings: filterOptions.filterWarnings, - infos: filterOptions.filterInfos, + errors: filterOptions.showErrors, + warnings: filterOptions.showWarnings, + infos: filterOptions.showInfos, }; /* __GDPR__ "problems.filter" : { @@ -293,6 +442,14 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { */ this.telemetryService.publicLog('problems.filter', data); } + + protected updateClass(): void { + if (this.element && this.container) { + this.element.className = this.action.class || ''; + DOM.toggleClass(this.container, 'grow', DOM.hasClass(this.element, 'grow')); + this.adjustInputBox(); + } + } } export class QuickFixAction extends Action { @@ -359,3 +516,14 @@ export class QuickFixActionViewItem extends ActionViewItem { } } } + +registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { + const inputActiveOptionBorderColor = theme.getColor(inputActiveOptionBorder); + if (inputActiveOptionBorderColor) { + collector.addRule(`.markers-panel-action-filter > .markers-panel-filter-controls > .monaco-action-bar .action-label.markers-filters.checked { border-color: ${inputActiveOptionBorderColor}; }`); + } + const inputActiveOptionBackgroundColor = theme.getColor(inputActiveOptionBackground); + if (inputActiveOptionBackgroundColor) { + collector.addRule(`.markers-panel-action-filter > .markers-panel-filter-controls > .monaco-action-bar .action-label.markers-filters.checked { background-color: ${inputActiveOptionBackgroundColor}; }`); + } +}); diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index c518ff7cbb..ee3aac6c82 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -37,7 +37,7 @@ import { CancelablePromise, createCancelablePromise, Delayer } from 'vs/base/com import { IModelService } from 'vs/editor/common/services/modelService'; import { Range } from 'vs/editor/common/core/range'; import { getCodeActions, CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; -import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; import { ITextModel } from 'vs/editor/common/model'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -254,7 +254,7 @@ class MarkerWidget extends Disposable { ) { super(); this.actionBar = this._register(new ActionBar(dom.append(parent, dom.$('.actions')), { - actionViewItemProvider: (action) => action.id === QuickFixAction.ID ? instantiationService.createInstance(QuickFixActionViewItem, action) : undefined + actionViewItemProvider: (action: QuickFixAction) => action.id === QuickFixAction.ID ? instantiationService.createInstance(QuickFixActionViewItem, action) : undefined })); this.icon = dom.append(parent, dom.$('')); this.multilineActionbar = this._register(new ActionBar(dom.append(parent, dom.$('.multiline-actions')))); @@ -314,6 +314,7 @@ class MarkerWidget extends Disposable { const lineMatches = filterData && filterData.lineMatches || []; let lastLineElement: HTMLElement | undefined = undefined; + this.messageAndDetailsContainer.title = element.marker.message; for (let index = 0; index < (multiline ? lines.length : 1); index++) { lastLineElement = dom.append(this.messageAndDetailsContainer, dom.$('.marker-message-line')); const messageElement = dom.append(lastLineElement, dom.$('.marker-message')); @@ -425,16 +426,21 @@ export class Filter implements ITreeFilter { } private filterMarker(marker: Marker, parentVisibility: TreeVisibility): TreeFilterResult { - if (this.options.filterErrors && MarkerSeverity.Error === marker.marker.severity) { - return true; + let shouldAppear: boolean = false; + if (this.options.showErrors && MarkerSeverity.Error === marker.marker.severity) { + shouldAppear = true; } - if (this.options.filterWarnings && MarkerSeverity.Warning === marker.marker.severity) { - return true; + if (this.options.showWarnings && MarkerSeverity.Warning === marker.marker.severity) { + shouldAppear = true; } - if (this.options.filterInfos && MarkerSeverity.Info === marker.marker.severity) { - return true; + if (this.options.showInfos && MarkerSeverity.Info === marker.marker.severity) { + shouldAppear = true; + } + + if (!shouldAppear) { + return false; } if (!this.options.textFilter) { @@ -546,7 +552,7 @@ export class MarkerViewModel extends Disposable { if (model) { if (!this.codeActionsPromise) { this.codeActionsPromise = createCancelablePromise(cancellationToken => { - return getCodeActions(model, new Range(this.marker.range.startLineNumber, this.marker.range.startColumn, this.marker.range.endLineNumber, this.marker.range.endColumn), { type: 'manual', filter: { kind: CodeActionKind.QuickFix } }, cancellationToken).then(actions => { + return getCodeActions(model, new Range(this.marker.range.startLineNumber, this.marker.range.startColumn, this.marker.range.endLineNumber, this.marker.range.endColumn), { type: 'manual', filter: { include: CodeActionKind.QuickFix } }, cancellationToken).then(actions => { return this._register(actions); }); }); @@ -558,7 +564,7 @@ export class MarkerViewModel extends Disposable { } private toActions(codeActions: CodeActionSet): IAction[] { - return codeActions.actions.map(codeAction => new Action( + return codeActions.validActions.map(codeAction => new Action( codeAction.command ? codeAction.command.id : codeAction.title, codeAction.title, undefined, diff --git a/src/vs/workbench/contrib/markers/browser/media/markers.css b/src/vs/workbench/contrib/markers/browser/media/markers.css index 11a6c524b3..afdf80d710 100644 --- a/src/vs/workbench/contrib/markers/browser/media/markers.css +++ b/src/vs/workbench/contrib/markers/browser/media/markers.css @@ -5,20 +5,9 @@ .monaco-action-bar .action-item.markers-panel-action-filter-container { cursor: default; - margin-right: 10px; - min-width: 150px; - max-width: 500px; display: flex; } -.monaco-action-bar .markers-panel-action-filter-container { - flex: 0.7; -} - -.monaco-action-bar .markers-panel-action-filter-container.small { - flex: 0.5; -} - .monaco-action-bar .markers-panel-action-filter { display: flex; align-items: center; @@ -40,7 +29,7 @@ position: absolute; top: 0px; bottom: 0; - right: 4px; + right: 0px; display: flex; align-items: center; } @@ -52,14 +41,40 @@ } .markers-panel-action-filter > .markers-panel-filter-controls > .markers-panel-filter-badge.hidden, -.markers-panel-action-filter-container.small .markers-panel-action-filter > .markers-panel-filter-controls > .markers-panel-filter-badge { +.markers-panel-action-filter.small > .markers-panel-filter-controls > .markers-panel-filter-badge { display: none; } +.markers-panel-action-filter > .markers-panel-filter-controls > .monaco-action-bar .action-label.markers-filters { + line-height: 20px; + height: 20px; + min-width: 22px; + margin-left: 4px; +} + +.panel > .title .monaco-action-bar .action-item.markers-panel-action-filter-container { + max-width: 600px; + min-width: 300px; + margin-right: 10px; +} + +.markers-panel-container .monaco-action-bar.markers-panel-filter-container .action-item.markers-panel-action-filter-container, +.panel > .title .monaco-action-bar .action-item.markers-panel-action-filter-container.grow { + flex: 1; +} + .markers-panel .markers-panel-container { height: 100%; } +.markers-panel .hide { + display: none; +} + +.markers-panel-container .monaco-action-bar.markers-panel-filter-container { + margin: 10px 20px; +} + .markers-panel .markers-panel-container .message-box-container { line-height: 22px; padding-left: 20px; @@ -132,17 +147,13 @@ margin-left: 6px; } -.markers-panel .monaco-tl-contents .marker-icon { +.markers-panel .monaco-tl-contents .codicon { margin-right: 6px; display: flex; align-items: center; justify-content: center; } -.markers-panel .monaco-tl-contents .actions .action-item { - margin-right: 2px; -} - .markers-panel .markers-panel-container .tree-container .monaco-tl-contents .marker-source, .markers-panel .markers-panel-container .tree-container .monaco-tl-contents .related-info-resource, .markers-panel .markers-panel-container .tree-container .monaco-tl-contents .related-info-resource-separator, @@ -155,7 +166,7 @@ font-weight: bold; } -.markers-panel .monaco-tl-contents .marker-icon { +.markers-panel .monaco-tl-contents .codicon { height: 22px; } @@ -178,9 +189,6 @@ .markers-panel .monaco-tl-contents .multiline-actions .action-label, .markers-panel .monaco-tl-contents .actions .action-label { width: 16px; - height: 100%; - background-position: 50% 50%; - background-repeat: no-repeat; } .markers-panel .monaco-tl-contents .multiline-actions .action-label { diff --git a/src/vs/workbench/contrib/markers/browser/messages.ts b/src/vs/workbench/contrib/markers/browser/messages.ts index 4e7c54e36c..baf1d1c2c2 100644 --- a/src/vs/workbench/contrib/markers/browser/messages.ts +++ b/src/vs/workbench/contrib/markers/browser/messages.ts @@ -16,15 +16,19 @@ export default class Messages { public static PROBLEMS_PANEL_CONFIGURATION_TITLE: string = nls.localize('problems.panel.configuration.title', "Problems View"); public static PROBLEMS_PANEL_CONFIGURATION_AUTO_REVEAL: string = nls.localize('problems.panel.configuration.autoreveal', "Controls whether Problems view should automatically reveal files when opening them."); + public static PROBLEMS_PANEL_CONFIGURATION_SHOW_CURRENT_STATUS: string = nls.localize('problems.panel.configuration.showCurrentInStatus', "When enabled shows the current problem in the status bar."); public static MARKERS_PANEL_TITLE_PROBLEMS: string = nls.localize('markers.panel.title.problems', "Problems"); public static MARKERS_PANEL_NO_PROBLEMS_BUILT: string = nls.localize('markers.panel.no.problems.build', "No problems have been detected in the workspace so far."); public static MARKERS_PANEL_NO_PROBLEMS_FILTERS: string = nls.localize('markers.panel.no.problems.filters', "No results found with provided filter criteria."); - public static MARKERS_PANEL_NO_PROBLEMS_FILE_EXCLUSIONS_FILTER: string = nls.localize('markers.panel.no.problems.file.exclusions', "All problems are hidden because files exclude filter is enabled."); - public static MARKERS_PANEL_ACTION_TOOLTIP_USE_FILES_EXCLUDE: string = nls.localize('markers.panel.action.useFilesExclude', "Filter using Files Exclude Setting"); - public static MARKERS_PANEL_ACTION_TOOLTIP_DO_NOT_USE_FILES_EXCLUDE: string = nls.localize('markers.panel.action.donotUseFilesExclude', "Do not use Files Exclude Setting"); + public static MARKERS_PANEL_ACTION_TOOLTIP_MORE_FILTERS: string = nls.localize('markers.panel.action.moreFilters', "More Filters..."); + public static MARKERS_PANEL_FILTER_LABEL_SHOW_ERRORS: string = nls.localize('markers.panel.filter.showErrors', "Show Errors"); + public static MARKERS_PANEL_FILTER_LABEL_SHOW_WARNINGS: string = nls.localize('markers.panel.filter.showWarnings', "Show Warnings"); + public static MARKERS_PANEL_FILTER_LABEL_SHOW_INFOS: string = nls.localize('markers.panel.filter.showInfos', "Show Infos"); + public static MARKERS_PANEL_FILTER_LABEL_EXCLUDED_FILES: string = nls.localize('markers.panel.filter.useFilesExclude', "Hide Excluded Files"); + public static MARKERS_PANEL_FILTER_LABEL_ACTIVE_FILE: string = nls.localize('markers.panel.filter.activeFile', "Show Active File Only"); public static MARKERS_PANEL_ACTION_TOOLTIP_FILTER: string = nls.localize('markers.panel.action.filter', "Filter Problems"); public static MARKERS_PANEL_ACTION_TOOLTIP_QUICKFIX: string = nls.localize('markers.panel.action.quickfix', "Show fixes"); public static MARKERS_PANEL_FILTER_ARIA_LABEL: string = nls.localize('markers.panel.filter.ariaLabel', "Filter Problems"); diff --git a/src/vs/workbench/contrib/outline/browser/outline.contribution.ts b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts index 893dca149b..1c5c3db2bc 100644 --- a/src/vs/workbench/contrib/outline/browser/outline.contribution.ts +++ b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts @@ -5,16 +5,18 @@ import { localize } from 'vs/nls'; import { IViewsRegistry, IViewDescriptor, Extensions as ViewExtensions } from 'vs/workbench/common/views'; -import { OutlinePanel } from './outlinePanel'; +import { OutlinePane } from './outlinePane'; import { VIEW_CONTAINER } from 'vs/workbench/contrib/files/common/files'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { OutlineConfigKeys, OutlineViewId } from 'vs/editor/contrib/documentSymbols/outline'; +// import './outlineNavigation'; + const _outlineDesc = { id: OutlineViewId, name: localize('name', "Outline"), - ctorDescriptor: { ctor: OutlinePanel }, + ctorDescriptor: { ctor: OutlinePane }, canToggleVisibility: true, hideByDefault: false, collapsed: true, @@ -51,135 +53,153 @@ Registry.as(ConfigurationExtensions.Configuration).regis 'type': 'boolean', 'default': true }, - 'outline.filteredTypes.file': { + 'outline.showFiles': { type: 'boolean', + overridable: true, default: true, - markdownDescription: localize('filteredTypes.file', "When set to `false` outline never shows `file`-symbols.") + markdownDescription: localize('filteredTypes.file', "When enabled outline shows `file`-symbols.") }, - 'outline.filteredTypes.module': { + 'outline.showModules': { type: 'boolean', + overridable: true, default: true, - markdownDescription: localize('filteredTypes.module', "When set to `false` outline never shows `module`-symbols.") + markdownDescription: localize('filteredTypes.module', "When enabled outline shows `module`-symbols.") }, - 'outline.filteredTypes.namespace': { + 'outline.showNamespaces': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.namespace', "When set to `false` outline never shows `namespace`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.namespace', "When enabled outline shows `namespace`-symbols.") }, - 'outline.filteredTypes.package': { + 'outline.showPackages': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.package', "When set to `false` outline never shows `package`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.package', "When enabled outline shows `package`-symbols.") }, - 'outline.filteredTypes.class': { + 'outline.showClasses': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.class', "When set to `false` outline never shows `class`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.class', "When enabled outline shows `class`-symbols.") }, - 'outline.filteredTypes.method': { + 'outline.showMethods': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.method', "When set to `false` outline never shows `method`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.method', "When enabled outline shows `method`-symbols.") }, - 'outline.filteredTypes.property': { + 'outline.showProperties': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.property', "When set to `false` outline never shows `property`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.property', "When enabled outline shows `property`-symbols.") }, - 'outline.filteredTypes.field': { + 'outline.showFields': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.field', "When set to `false` outline never shows `field`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.field', "When enabled outline shows `field`-symbols.") }, - 'outline.filteredTypes.constructor': { + 'outline.showConstructors': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.constructor', "When set to `false` outline never shows `constructor`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.constructor', "When enabled outline shows `constructor`-symbols.") }, - 'outline.filteredTypes.enum': { + 'outline.showEnums': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.enum', "When set to `false` outline never shows `enum`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.enum', "When enabled outline shows `enum`-symbols.") }, - 'outline.filteredTypes.interface': { + 'outline.showInterfaces': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.interface', "When set to `false` outline never shows `interface`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.interface', "When enabled outline shows `interface`-symbols.") }, - 'outline.filteredTypes.function': { + 'outline.showFunctions': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.function', "When set to `false` outline never shows `function`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.function', "When enabled outline shows `function`-symbols.") }, - 'outline.filteredTypes.variable': { + 'outline.showVariables': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.variable', "When set to `false` outline never shows `variable`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.variable', "When enabled outline shows `variable`-symbols.") }, - 'outline.filteredTypes.constant': { + 'outline.showConstants': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.constant', "When set to `false` outline never shows `constant`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.constant', "When enabled outline shows `constant`-symbols.") }, - 'outline.filteredTypes.string': { + 'outline.showStrings': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.string', "When set to `false` outline never shows `string`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.string', "When enabled outline shows `string`-symbols.") }, - 'outline.filteredTypes.number': { + 'outline.showNumbers': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.number', "When set to `false` outline never shows `number`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.number', "When enabled outline shows `number`-symbols.") }, - 'outline.filteredTypes.boolean': { + 'outline.showBooleans': { type: 'boolean', + overridable: true, default: true, - markdownDescription: localize('filteredTypes.boolean', "When set to `false` outline never shows `boolean`-symbols.") + markdownDescription: localize('filteredTypes.boolean', "When enabled outline shows `boolean`-symbols.") }, - 'outline.filteredTypes.array': { + 'outline.showArrays': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.array', "When set to `false` outline never shows `array`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.array', "When enabled outline shows `array`-symbols.") }, - 'outline.filteredTypes.object': { + 'outline.showObjects': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.object', "When set to `false` outline never shows `object`-symbols.") + markdownDescription: localize('filteredTypes.object', "When enabled outline shows `object`-symbols.") }, - 'outline.filteredTypes.key': { + 'outline.showKeys': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.key', "When set to `false` outline never shows `key`-symbols.") + markdownDescription: localize('filteredTypes.key', "When enabled outline shows `key`-symbols.") }, - 'outline.filteredTypes.null': { + 'outline.showNull': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.null', "When set to `false` outline never shows `null`-symbols.") + markdownDescription: localize('filteredTypes.null', "When enabled outline shows `null`-symbols.") }, - 'outline.filteredTypes.enumMember': { + 'outline.showEnumMembers': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.enumMember', "When set to `false` outline never shows `enumMember`-symbols.") + markdownDescription: localize('filteredTypes.enumMember', "When enabled outline shows `enumMember`-symbols.") }, - 'outline.filteredTypes.struct': { + 'outline.showStructs': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.struct', "When set to `false` outline never shows `struct`-symbols.") + markdownDescription: localize('filteredTypes.struct', "When enabled outline shows `struct`-symbols.") }, - 'outline.filteredTypes.event': { + 'outline.showEvents': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.event', "When set to `false` outline never shows `event`-symbols.") + markdownDescription: localize('filteredTypes.event', "When enabled outline shows `event`-symbols.") }, - 'outline.filteredTypes.operator': { + 'outline.showOperators': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.operator', "When set to `false` outline never shows `operator`-symbols.") + markdownDescription: localize('filteredTypes.operator', "When enabled outline shows `operator`-symbols.") }, - 'outline.filteredTypes.typeParameter': { + 'outline.showTypeParameters': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.typeParameter', "When set to `false` outline never shows `typeParameter`-symbols.") + markdownDescription: localize('filteredTypes.typeParameter', "When enabled outline shows `typeParameter`-symbols.") } } }); diff --git a/src/vs/workbench/contrib/outline/browser/outlineNavigation.ts b/src/vs/workbench/contrib/outline/browser/outlineNavigation.ts new file mode 100644 index 0000000000..a0b2d8722d --- /dev/null +++ b/src/vs/workbench/contrib/outline/browser/outlineNavigation.ts @@ -0,0 +1,202 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Range } from 'vs/editor/common/core/range'; +import { IPosition, Position } from 'vs/editor/common/core/position'; +import { OutlineElement, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; +import { localize } from 'vs/nls'; +import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { EditorStateCancellationTokenSource, CodeEditorStateFlag } from 'vs/editor/browser/core/editorState'; +import { EditorAction, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { forEach } from 'vs/base/common/collections'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { OutlineFilter } from 'vs/editor/contrib/documentSymbols/outlineTree'; +import { binarySearch } from 'vs/base/common/arrays'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; + +class FlatOutline { + + readonly elements: OutlineElement[] = []; + readonly _elementPositions: IPosition[]; + + constructor(model: OutlineModel, filter: OutlineFilter) { + + const walk = (element: TreeElement) => { + if (element instanceof OutlineElement && !filter.filter(element)) { + return; + } + if (element instanceof OutlineElement) { + this.elements.push(element); + } + forEach(element.children, entry => walk(entry.value)); + }; + + walk(model); + this.elements.sort(FlatOutline._compare); + this._elementPositions = this.elements.map(element => ({ + lineNumber: element.symbol.range.startLineNumber, + column: element.symbol.range.startColumn + })); + } + + private static _compare(a: TreeElement, b: TreeElement): number { + return (a instanceof OutlineElement && b instanceof OutlineElement) + ? Range.compareRangesUsingStarts(a.symbol.range, b.symbol.range) + : 0; + } + + find(position: IPosition, preferAfter: boolean): number { + const idx = binarySearch(this._elementPositions, position, Position.compare); + if (idx >= 0) { + return idx; + } else if (preferAfter) { + return ~idx; + } else { + return ~idx - 1; + } + } +} + +export class OutlineNavigation implements IEditorContribution { + + public static readonly ID = 'editor.contrib.OutlineNavigation'; + + public static get(editor: ICodeEditor): OutlineNavigation { + return editor.getContribution(OutlineNavigation.ID); + } + + private readonly _editor: ICodeEditor; + + private _cts?: CancellationTokenSource; + + constructor( + editor: ICodeEditor, + @ITextResourceConfigurationService private readonly _textResourceConfigService: ITextResourceConfigurationService, + ) { + this._editor = editor; + } + + dispose(): void { + if (this._cts) { + this._cts.dispose(true); + } + } + + async goto(up: boolean) { + + if (this._cts) { + this._cts.dispose(true); + } + + if (!this._editor.hasModel()) { + return; + } + + const textModel = this._editor.getModel(); + const position = this._editor.getPosition(); + + this._cts = new EditorStateCancellationTokenSource(this._editor, CodeEditorStateFlag.Position | CodeEditorStateFlag.Value | CodeEditorStateFlag.Scroll); + + const filter = new OutlineFilter('outline', this._textResourceConfigService); + const outlineModel = await OutlineModel.create(textModel, this._cts.token); + + if (this._cts.token.isCancellationRequested) { + return; + } + + const symbols = new FlatOutline(outlineModel, filter); + const idx = symbols.find(position, !up); + const element = symbols.elements[idx]; + + if (element) { + if (Range.containsPosition(element.symbol.selectionRange, position)) { + // at the "name" of a symbol -> move + const nextElement = symbols.elements[idx + (up ? -1 : +1)]; + this._revealElement(nextElement); + + } else { + // enclosing, lastBefore, or firstAfter element + this._revealElement(element); + } + } + } + + private _revealElement(element: OutlineElement | undefined): void { + if (!element) { + return; + } + const pos = Range.lift(element.symbol.selectionRange).getStartPosition(); + this._editor.setPosition(pos); + this._editor.revealPosition(pos, ScrollType.Smooth); + + const modelNow = this._editor.getModel(); + const ids = this._editor.deltaDecorations([], [{ + range: element.symbol.selectionRange, + options: { + className: 'symbolHighlight', + } + }]); + setTimeout(() => { + if (modelNow === this._editor.getModel()) { + this._editor.deltaDecorations(ids, []); + } + }, 350); + } +} + +registerEditorContribution(OutlineNavigation.ID, OutlineNavigation); + +registerEditorAction(class extends EditorAction { + + constructor() { + super({ + id: 'editor.action.gotoNextSymbol', + label: localize('label.next', "Go to Next Symbol"), + alias: 'Go to Next Symbol', + precondition: EditorContextKeys.hasDocumentSymbolProvider, + kbOpts: { + weight: KeybindingWeight.EditorContrib, + kbExpr: EditorContextKeys.focus, + primary: undefined, + mac: { + primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.DownArrow, + }, + } + }); + } + + run(_accessor: ServicesAccessor, editor: ICodeEditor): void | Promise { + OutlineNavigation.get(editor).goto(false); + } +}); + +registerEditorAction(class extends EditorAction { + + constructor() { + super({ + id: 'editor.action.gotoPrevSymbol', + label: localize('label.prev', "Go to Previous Symbol"), + alias: 'Go to Previous Symbol', + precondition: EditorContextKeys.hasDocumentSymbolProvider, + kbOpts: { + weight: KeybindingWeight.EditorContrib, + kbExpr: EditorContextKeys.focus, + primary: undefined, + mac: { + primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.UpArrow, + }, + } + }); + } + + run(_accessor: ServicesAccessor, editor: ICodeEditor): void | Promise { + OutlineNavigation.get(editor).goto(true); + } +}); diff --git a/src/vs/workbench/contrib/outline/browser/outlinePanel.css b/src/vs/workbench/contrib/outline/browser/outlinePane.css similarity index 67% rename from src/vs/workbench/contrib/outline/browser/outlinePanel.css rename to src/vs/workbench/contrib/outline/browser/outlinePane.css index 1bb11faee3..dfe10601a5 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePanel.css +++ b/src/vs/workbench/contrib/outline/browser/outlinePane.css @@ -3,45 +3,45 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .outline-panel { +.monaco-workbench .outline-pane { display: flex; flex-direction: column; } -.monaco-workbench .outline-panel .outline-progress { +.monaco-workbench .outline-pane .outline-progress { width: 100%; height: 2px; padding-bottom: 3px; position: absolute; } -.monaco-workbench .outline-panel .outline-progress .monaco-progress-container { +.monaco-workbench .outline-pane .outline-progress .monaco-progress-container { height: 2px; } -.monaco-workbench .outline-panel .outline-progress .monaco-progress-container .progress-bit { +.monaco-workbench .outline-pane .outline-progress .monaco-progress-container .progress-bit { height: 2px; } -.monaco-workbench .outline-panel .outline-tree { +.monaco-workbench .outline-pane .outline-tree { height: 100%; } -.monaco-workbench .outline-panel .outline-message { +.monaco-workbench .outline-pane .outline-message { display: none; padding: 10px 22px 0 22px; opacity: 0.5; } -.monaco-workbench .outline-panel.message .outline-message { +.monaco-workbench .outline-pane.message .outline-message { display: inherit; } -.monaco-workbench .outline-panel.message .outline-progress { +.monaco-workbench .outline-pane.message .outline-progress { display: none; } -.monaco-workbench .outline-panel.message .outline-tree { +.monaco-workbench .outline-pane.message .outline-tree { display: none; } diff --git a/src/vs/workbench/contrib/outline/browser/outlinePanel.ts b/src/vs/workbench/contrib/outline/browser/outlinePane.ts similarity index 95% rename from src/vs/workbench/contrib/outline/browser/outlinePanel.ts rename to src/vs/workbench/contrib/outline/browser/outlinePane.ts index 16792ed2c1..066171f163 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePanel.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePane.ts @@ -14,7 +14,7 @@ import { defaultGenerator } from 'vs/base/common/idGenerator'; import { dispose, IDisposable, toDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { LRUCache } from 'vs/base/common/map'; import { escape } from 'vs/base/common/strings'; -import 'vs/css!./outlinePanel'; +import 'vs/css!./outlinePane'; import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; @@ -34,7 +34,7 @@ import { WorkbenchDataTree } from 'vs/platform/list/browser/listService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { attachProgressBarStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { ViewletPane } from 'vs/workbench/browser/parts/views/paneViewlet'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { CollapseAction } from 'vs/workbench/browser/viewlet'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; @@ -47,6 +47,7 @@ import { basename } from 'vs/base/common/resources'; import { IDataSource } from 'vs/base/browser/ui/tree/tree'; import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; import { MarkerSeverity } from 'vs/platform/markers/common/markers'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; class RequestState { @@ -232,7 +233,7 @@ class OutlineViewState { } } -export class OutlinePanel extends ViewletPanel { +export class OutlinePane extends ViewletPane { private _disposables = new Array(); @@ -296,7 +297,7 @@ export class OutlinePanel extends ViewletPanel { protected renderBody(container: HTMLElement): void { this._domNode = container; this._domNode.tabIndex = 0; - dom.addClass(container, 'outline-panel'); + dom.addClass(container, 'outline-pane'); let progressContainer = dom.$('.outline-progress'); this._message = dom.$('.outline-message'); @@ -314,10 +315,10 @@ export class OutlinePanel extends ViewletPanel { this._treeRenderer = this._instantiationService.createInstance(OutlineElementRenderer); this._treeDataSource = new OutlineDataSource(); this._treeComparator = new OutlineItemComparator(this._outlineViewState.sortBy); - this._treeFilter = this._instantiationService.createInstance(OutlineFilter, 'outline.filteredTypes'); + this._treeFilter = this._instantiationService.createInstance(OutlineFilter, 'outline'); this._tree = this._instantiationService.createInstance( WorkbenchDataTree, - 'OutlinePanel', + 'OutlinePane', treeContainer, new OutlineVirtualDelegate(), [new OutlineGroupRenderer(), this._treeRenderer], @@ -330,7 +331,11 @@ export class OutlinePanel extends ViewletPanel { sorter: this._treeComparator, filter: this._treeFilter, identityProvider: new OutlineIdentityProvider(), - keyboardNavigationLabelProvider: new OutlineNavigationLabelProvider() + keyboardNavigationLabelProvider: new OutlineNavigationLabelProvider(), + hideTwistiesOfChildlessElements: true, + overrideStyles: { + listBackground: SIDE_BAR_BACKGROUND + } } ); @@ -367,8 +372,9 @@ export class OutlinePanel extends ViewletPanel { if (e.affectsConfiguration(OutlineConfigKeys.icons)) { this._tree.updateChildren(); } - if (e.affectsConfiguration('outline.filteredTypes')) { - this._treeFilter.update(); + // This is a temporary solution to try and minimize refilters while + // ConfigurationChangeEvents only provide the first section of the config path. + if (e.affectedKeys.some(key => key.search(/(outline|\[\w+\])/) === 0)) { this._tree.refilter(); } })); @@ -400,7 +406,7 @@ export class OutlinePanel extends ViewletPanel { const group = this._register(new RadioGroup([ new SimpleToggleAction(this._outlineViewState, localize('sortByPosition', "Sort By: Position"), () => this._outlineViewState.sortBy === OutlineSortOrder.ByPosition, _ => this._outlineViewState.sortBy = OutlineSortOrder.ByPosition), new SimpleToggleAction(this._outlineViewState, localize('sortByName', "Sort By: Name"), () => this._outlineViewState.sortBy === OutlineSortOrder.ByName, _ => this._outlineViewState.sortBy = OutlineSortOrder.ByName), - new SimpleToggleAction(this._outlineViewState, localize('sortByKind', "Sort By: Type"), () => this._outlineViewState.sortBy === OutlineSortOrder.ByKind, _ => this._outlineViewState.sortBy = OutlineSortOrder.ByKind), + new SimpleToggleAction(this._outlineViewState, localize('sortByKind', "Sort By: Category"), () => this._outlineViewState.sortBy === OutlineSortOrder.ByKind, _ => this._outlineViewState.sortBy = OutlineSortOrder.ByKind), ])); const result = [ new SimpleToggleAction(this._outlineViewState, localize('followCur', "Follow Cursor"), () => this._outlineViewState.followCursor, action => this._outlineViewState.followCursor = action.checked), @@ -465,15 +471,19 @@ export class OutlinePanel extends ViewletPanel { } const textModel = editor.getModel(); - const loadingMessage = oldModel && new TimeoutTimer( - () => this._showMessage(localize('loading', "Loading document symbols for '{0}'...", basename(textModel.uri))), - 100 - ); + + let loadingMessage: IDisposable | undefined; + if (!oldModel) { + loadingMessage = new TimeoutTimer( + () => this._showMessage(localize('loading', "Loading document symbols for '{0}'...", basename(textModel.uri))), + 100 + ); + } const requestDelay = OutlineModel.getRequestDelay(textModel); this._progressBar.infinite().show(requestDelay); - const createdModel = await OutlinePanel._createOutlineModel(textModel, this._editorDisposables); + const createdModel = await OutlinePane._createOutlineModel(textModel, this._editorDisposables); dispose(loadingMessage); if (!createdModel) { return; diff --git a/src/vs/workbench/contrib/output/browser/logViewer.ts b/src/vs/workbench/contrib/output/browser/logViewer.ts index c0ce08988d..d75c6574db 100644 --- a/src/vs/workbench/contrib/output/browser/logViewer.ts +++ b/src/vs/workbench/contrib/output/browser/logViewer.ts @@ -11,21 +11,18 @@ import { ITextResourceConfigurationService } from 'vs/editor/common/services/res import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { AbstractTextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { URI } from 'vs/base/common/uri'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { LOG_SCHEME, IFileOutputChannelDescriptor } from 'vs/workbench/contrib/output/common/output'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IHostService } from 'vs/workbench/services/host/browser/host'; export class LogViewerInput extends ResourceEditorInput { public static readonly ID = 'workbench.editorinputs.output'; - constructor(private outputChannelDescriptor: IFileOutputChannelDescriptor, + constructor(private readonly outputChannelDescriptor: IFileOutputChannelDescriptor, @ITextModelService textModelResolverService: ITextModelService ) { super(basename(outputChannelDescriptor.file.path), dirname(outputChannelDescriptor.file.path), URI.from({ scheme: LOG_SCHEME, path: outputChannelDescriptor.id }), undefined, textModelResolverService); @@ -48,15 +45,12 @@ export class LogViewer extends AbstractTextResourceEditor { @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, - @IConfigurationService baseConfigurationService: IConfigurationService, @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, @IThemeService themeService: IThemeService, @IEditorGroupsService editorGroupService: IEditorGroupsService, - @ITextFileService textFileService: ITextFileService, - @IEditorService editorService: IEditorService, - @IHostService hostService: IHostService + @IEditorService editorService: IEditorService ) { - super(LogViewer.LOG_VIEWER_EDITOR_ID, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorGroupService, textFileService, editorService, hostService); + super(LogViewer.LOG_VIEWER_EDITOR_ID, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorGroupService, editorService); } protected getConfigurationOverrides(): IEditorOptions { diff --git a/src/vs/workbench/contrib/output/browser/output.contribution.ts b/src/vs/workbench/contrib/output/browser/output.contribution.ts index e799362b6e..2102a266f2 100644 --- a/src/vs/workbench/contrib/output/browser/output.contribution.ts +++ b/src/vs/workbench/contrib/output/browser/output.contribution.ts @@ -41,7 +41,7 @@ ModesRegistry.registerLanguage({ }); // Register Output Panel -Registry.as(Extensions.Panels).registerPanel(new PanelDescriptor( +Registry.as(Extensions.Panels).registerPanel(PanelDescriptor.create( OutputPanel, OUTPUT_PANEL_ID, nls.localize('output', "Output"), @@ -51,7 +51,7 @@ Registry.as(Extensions.Panels).registerPanel(new PanelDescriptor( )); Registry.as(EditorExtensions.Editors).registerEditor( - new EditorDescriptor( + EditorDescriptor.create( LogViewer, LogViewer.LOG_VIEWER_EDITOR_ID, nls.localize('logViewer', "Log Viewer") @@ -74,19 +74,19 @@ Registry.as(WorkbenchExtensions.Workbench).regi // register toggle output action globally const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleOutputAction, ToggleOutputAction.ID, ToggleOutputAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleOutputAction, ToggleOutputAction.ID, ToggleOutputAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_U, linux: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_H) // On Ubuntu Ctrl+Shift+U is taken by some global OS command } }), 'View: Toggle Output', nls.localize('viewCategory', "View")); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ClearOutputAction, ClearOutputAction.ID, ClearOutputAction.LABEL), +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ClearOutputAction, ClearOutputAction.ID, ClearOutputAction.LABEL), 'View: Clear Output', nls.localize('viewCategory', "View")); const devCategory = nls.localize('developer', "Developer"); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowLogsOutputChannelAction, ShowLogsOutputChannelAction.ID, ShowLogsOutputChannelAction.LABEL), 'Developer: Show Logs...', devCategory); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenOutputLogFileAction, OpenOutputLogFileAction.ID, OpenOutputLogFileAction.LABEL), 'Developer: Open Log File...', devCategory); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ShowLogsOutputChannelAction, ShowLogsOutputChannelAction.ID, ShowLogsOutputChannelAction.LABEL), 'Developer: Show Logs...', devCategory); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(OpenOutputLogFileAction, OpenOutputLogFileAction.ID, OpenOutputLogFileAction.LABEL), 'Developer: Open Log File...', devCategory); // Define clear command, contribute to editor context menu registerAction({ diff --git a/src/vs/workbench/contrib/output/browser/outputActions.ts b/src/vs/workbench/contrib/output/browser/outputActions.ts index ca321d4bb3..6d09db645a 100644 --- a/src/vs/workbench/contrib/output/browser/outputActions.ts +++ b/src/vs/workbench/contrib/output/browser/outputActions.ts @@ -21,6 +21,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { LogViewerInput } from 'vs/workbench/contrib/output/browser/logViewer'; import { ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; +import { assertIsDefined } from 'vs/base/common/types'; export class ToggleOutputAction extends TogglePanelAction { @@ -262,7 +263,8 @@ export class OpenOutputLogFileAction extends Action { return this.quickInputService.pick(entries, { placeHolder: nls.localize('selectlogFile', "Select Log file") }) .then(entry => { if (entry) { - return this.editorService.openEditor(this.instantiationService.createInstance(LogViewerInput, entry.channel)).then(() => undefined); + assertIsDefined(entry.channel.file); + return this.editorService.openEditor(this.instantiationService.createInstance(LogViewerInput, entry.channel as IFileOutputChannelDescriptor)).then(() => undefined); } return undefined; }); diff --git a/src/vs/workbench/contrib/output/browser/outputPanel.ts b/src/vs/workbench/contrib/output/browser/outputPanel.ts index 463307df0b..840d46c3a1 100644 --- a/src/vs/workbench/contrib/output/browser/outputPanel.ts +++ b/src/vs/workbench/contrib/output/browser/outputPanel.ts @@ -19,12 +19,10 @@ import { AbstractTextResourceEditor } from 'vs/workbench/browser/parts/editor/te import { OUTPUT_PANEL_ID, IOutputService, CONTEXT_IN_OUTPUT } from 'vs/workbench/contrib/output/common/output'; import { SwitchOutputAction, SwitchOutputActionViewItem, ClearOutputAction, ToggleOrSetOutputScrollLockAction, OpenLogOutputFile } from 'vs/workbench/contrib/output/browser/outputActions'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IHostService } from 'vs/workbench/services/host/browser/host'; import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents'; export class OutputPanel extends AbstractTextResourceEditor { @@ -42,11 +40,9 @@ export class OutputPanel extends AbstractTextResourceEditor { @IOutputService private readonly outputService: IOutputService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IEditorGroupsService editorGroupService: IEditorGroupsService, - @ITextFileService textFileService: ITextFileService, - @IEditorService editorService: IEditorService, - @IHostService hostService: IHostService + @IEditorService editorService: IEditorService ) { - super(OUTPUT_PANEL_ID, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorGroupService, textFileService, editorService, hostService); + super(OUTPUT_PANEL_ID, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorGroupService, editorService); this.scopedInstantiationService = instantiationService; } diff --git a/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts b/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts index e63de97ca4..199997ee7c 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts @@ -18,6 +18,7 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { URI } from 'vs/base/common/uri'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IElectronService } from 'vs/platform/electron/node/electron'; +import { IProductService } from 'vs/platform/product/common/productService'; export class StartupProfiler implements IWorkbenchContribution { @@ -29,7 +30,8 @@ export class StartupProfiler implements IWorkbenchContribution { @ILifecycleService lifecycleService: ILifecycleService, @IExtensionService extensionService: IExtensionService, @IOpenerService private readonly _openerService: IOpenerService, - @IElectronService private readonly _electronService: IElectronService + @IElectronService private readonly _electronService: IElectronService, + @IProductService private readonly _productService: IProductService ) { // wait for everything to be ready Promise.all([ @@ -88,7 +90,7 @@ export class StartupProfiler implements IWorkbenchContribution { return this._dialogService.confirm({ type: 'info', message: localize('prof.thanks', "Thanks for helping us."), - detail: localize('prof.detail.restart', "A final restart is required to continue to use '{0}'. Again, thank you for your contribution.", this._environmentService.appNameLong), + detail: localize('prof.detail.restart', "A final restart is required to continue to use '{0}'. Again, thank you for your contribution.", this._productService.nameLong), primaryButton: localize('prof.restart', "Restart"), secondaryButton: undefined }).then(() => { diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts index 9b466b2ac3..655ff9e194 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts @@ -82,7 +82,7 @@ export class KeybindingsSearchWidget extends SearchWidget { stopRecordingKeys(): void { this._reset(); - this.recordDisposables.dispose(); + this.recordDisposables.clear(); } setInputValue(value: string): void { @@ -164,7 +164,7 @@ export class DefineKeybindingWidget extends Widget { readonly onShowExistingKeybidings: Event = this._onShowExistingKeybindings.event; constructor( - parent: HTMLElement, + parent: HTMLElement | null, @IInstantiationService private readonly instantiationService: IInstantiationService, @IThemeService private readonly themeService: IThemeService ) { diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index e05513d79d..af18b1667d 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -34,7 +34,7 @@ import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } import { IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode, ResolvedKeybinding } from 'vs/base/common/keyCodes'; -import { listHighlightForeground, badgeBackground, contrastBorder, badgeForeground, listActiveSelectionForeground, listInactiveSelectionForeground, listHoverForeground, listFocusForeground } from 'vs/platform/theme/common/colorRegistry'; +import { listHighlightForeground, badgeBackground, contrastBorder, badgeForeground, listActiveSelectionForeground, listInactiveSelectionForeground, listHoverForeground, listFocusForeground, editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; @@ -65,22 +65,22 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor private _onLayout: Emitter = this._register(new Emitter()); readonly onLayout: Event = this._onLayout.event; - private keybindingsEditorModel: KeybindingsEditorModel; + private keybindingsEditorModel: KeybindingsEditorModel | null = null; - private headerContainer: HTMLElement; - private actionsContainer: HTMLElement; - private searchWidget: KeybindingsSearchWidget; + private headerContainer!: HTMLElement; + private actionsContainer!: HTMLElement; + private searchWidget!: KeybindingsSearchWidget; - private overlayContainer: HTMLElement; - private defineKeybindingWidget: DefineKeybindingWidget; + private overlayContainer!: HTMLElement; + private defineKeybindingWidget!: DefineKeybindingWidget; private columnItems: ColumnItem[] = []; - private keybindingsListContainer: HTMLElement; - private unAssignedKeybindingItemToRevealAndFocus: IKeybindingItemEntry | null; - private listEntries: IListEntry[]; - private keybindingsList: WorkbenchList; + private keybindingsListContainer!: HTMLElement; + private unAssignedKeybindingItemToRevealAndFocus: IKeybindingItemEntry | null = null; + private listEntries: IListEntry[] = []; + private keybindingsList!: WorkbenchList; - private dimension: DOM.Dimension; + private dimension: DOM.Dimension | null = null; private delayedFiltering: Delayer; private latestEmptyFilters: string[] = []; private delayedFilterLogging: Delayer; @@ -88,11 +88,10 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor private keybindingFocusContextKey: IContextKey; private searchFocusContextKey: IContextKey; - private actionBar: ActionBar; - private sortByPrecedenceAction: Action; - private recordKeysAction: Action; + private readonly sortByPrecedenceAction: Action; + private readonly recordKeysAction: Action; - private ariaLabelElement: HTMLElement; + private ariaLabelElement!: HTMLElement; constructor( @ITelemetryService telemetryService: ITelemetryService, @@ -115,6 +114,16 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor this.searchFocusContextKey = CONTEXT_KEYBINDINGS_SEARCH_FOCUS.bindTo(this.contextKeyService); this.keybindingFocusContextKey = CONTEXT_KEYBINDING_FOCUS.bindTo(this.contextKeyService); this.delayedFilterLogging = new Delayer(1000); + + const recordKeysActionKeybinding = this.keybindingsService.lookupKeybinding(KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS); + const recordKeysActionLabel = localize('recordKeysLabel', "Record Keys"); + this.recordKeysAction = new Action(KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, recordKeysActionKeybinding ? localize('recordKeysLabelWithKeybinding', "{0} ({1})", recordKeysActionLabel, recordKeysActionKeybinding.getLabel()) : recordKeysActionLabel, 'codicon-record-keys'); + this.recordKeysAction.checked = false; + + const sortByPrecedenceActionKeybinding = this.keybindingsService.lookupKeybinding(KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE); + const sortByPrecedenceActionLabel = localize('sortByPrecedeneLabel', "Sort by Precedence"); + this.sortByPrecedenceAction = new Action('keybindings.editor.sortByPrecedence', sortByPrecedenceActionKeybinding ? localize('sortByPrecedeneLabelWithKeybinding', "{0} ({1})", sortByPrecedenceActionLabel, sortByPrecedenceActionKeybinding.getLabel()) : sortByPrecedenceActionLabel, 'codicon-sort-precedence'); + this.sortByPrecedenceAction.checked = false; } createEditor(parent: HTMLElement): void { @@ -298,7 +307,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor this.overlayContainer.style.position = 'absolute'; this.overlayContainer.style.zIndex = '10'; this.defineKeybindingWidget = this._register(this.instantiationService.createInstance(DefineKeybindingWidget, this.overlayContainer)); - this._register(this.defineKeybindingWidget.onDidChange(keybindingStr => this.defineKeybindingWidget.printExisting(this.keybindingsEditorModel.fetch(`"${keybindingStr}"`).length))); + this._register(this.defineKeybindingWidget.onDidChange(keybindingStr => this.defineKeybindingWidget.printExisting(this.keybindingsEditorModel!.fetch(`"${keybindingStr}"`).length))); this._register(this.defineKeybindingWidget.onShowExistingKeybidings(keybindingStr => this.searchWidget.setValue(`"${keybindingStr}"`))); this.hideOverlayContainer(); } @@ -337,10 +346,6 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor this.actionsContainer = DOM.append(searchContainer, DOM.$('.keybindings-search-actions-container')); const recordingBadge = this.createRecordingBadge(this.actionsContainer); - const sortByPrecedenceActionKeybinding = this.keybindingsService.lookupKeybinding(KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE); - const sortByPrecedenceActionLabel = localize('sortByPrecedeneLabel', "Sort by Precedence"); - this.sortByPrecedenceAction = new Action('keybindings.editor.sortByPrecedence', sortByPrecedenceActionKeybinding ? localize('sortByPrecedeneLabelWithKeybinding', "{0} ({1})", sortByPrecedenceActionLabel, sortByPrecedenceActionKeybinding.getLabel()) : sortByPrecedenceActionLabel, 'codicon-sort-precedence'); - this.sortByPrecedenceAction.checked = false; this._register(this.sortByPrecedenceAction.onDidChange(e => { if (e.checked !== undefined) { this.renderKeybindingsEntries(false); @@ -348,10 +353,6 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor this.updateSearchOptions(); })); - const recordKeysActionKeybinding = this.keybindingsService.lookupKeybinding(KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS); - const recordKeysActionLabel = localize('recordKeysLabel', "Record Keys"); - this.recordKeysAction = new Action(KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, recordKeysActionKeybinding ? localize('recordKeysLabelWithKeybinding', "{0} ({1})", recordKeysActionLabel, recordKeysActionKeybinding.getLabel()) : recordKeysActionLabel, 'codicon-record-keys'); - this.recordKeysAction.checked = false; this._register(this.recordKeysAction.onDidChange(e => { if (e.checked !== undefined) { DOM.toggleClass(recordingBadge, 'disabled', !e.checked); @@ -370,7 +371,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor } })); - this.actionBar = this._register(new ActionBar(this.actionsContainer, { + const actionBar = this._register(new ActionBar(this.actionsContainer, { animated: false, actionViewItemProvider: (action: Action) => { if (action.id === this.sortByPrecedenceAction.id) { @@ -383,7 +384,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor } })); - this.actionBar.push([this.recordKeysAction, this.sortByPrecedenceAction, clearInputAction], { label: false, icon: true }); + actionBar.push([this.recordKeysAction, this.sortByPrecedenceAction, clearInputAction], { label: false, icon: true }); } private updateSearchOptions(): void { @@ -456,7 +457,10 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor identityProvider: { getId: (e: IListEntry) => e.id }, ariaLabel: localize('keybindingsLabel', "Keybindings"), setRowLineHeight: false, - horizontalScrolling: false + horizontalScrolling: false, + overrideStyles: { + listBackground: editorBackground + } })); this._register(this.keybindingsList.onContextMenu(e => this.onContextMenu(e))); this._register(this.keybindingsList.onFocusChange(e => this.onFocusChange(e))); @@ -563,6 +567,9 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor } private layoutKeybindingsList(): void { + if (!this.dimension) { + return; + } let width = this.dimension.width - 27; for (const columnItem of this.columnItems) { if (columnItem.width && !columnItem.proportion) { @@ -855,7 +862,7 @@ abstract class Column extends Disposable { class ActionsColumn extends Column { - private actionBar: ActionBar; + private readonly actionBar: ActionBar; readonly element: HTMLElement; constructor( @@ -864,13 +871,8 @@ class ActionsColumn extends Column { @IKeybindingService private keybindingsService: IKeybindingService ) { super(keybindingsEditor); - this.element = this.create(parent); - } - - create(parent: HTMLElement): HTMLElement { - const actionsContainer = DOM.append(parent, $('.column.actions', { id: 'actions_' + ++Column.COUNTER })); - this.actionBar = new ActionBar(actionsContainer, { animated: false }); - return actionsContainer; + this.element = DOM.append(parent, $('.column.actions', { id: 'actions_' + ++Column.COUNTER })); + this.actionBar = new ActionBar(this.element, { animated: false }); } render(keybindingItemEntry: IKeybindingItemEntry): void { @@ -914,7 +916,7 @@ class ActionsColumn extends Column { class CommandColumn extends Column { - private commandColumn: HTMLElement; + private readonly commandColumn: HTMLElement; readonly element: HTMLElement; constructor( @@ -922,12 +924,7 @@ class CommandColumn extends Column { keybindingsEditor: IKeybindingsEditor, ) { super(keybindingsEditor); - this.element = this.create(parent); - } - - private create(parent: HTMLElement): HTMLElement { - this.commandColumn = DOM.append(parent, $('.column.command', { id: 'command_' + ++Column.COUNTER })); - return this.commandColumn; + this.element = this.commandColumn = DOM.append(parent, $('.column.command', { id: 'command_' + ++Column.COUNTER })); } render(keybindingItemEntry: IKeybindingItemEntry): void { @@ -962,7 +959,7 @@ class CommandColumn extends Column { class KeybindingColumn extends Column { - private keybindingLabel: HTMLElement; + private readonly keybindingLabel: HTMLElement; readonly element: HTMLElement; constructor( @@ -970,13 +967,9 @@ class KeybindingColumn extends Column { keybindingsEditor: IKeybindingsEditor, ) { super(keybindingsEditor); - this.element = this.create(parent); - } - private create(parent: HTMLElement): HTMLElement { - const column = DOM.append(parent, $('.column.keybinding', { id: 'keybinding_' + ++Column.COUNTER })); - this.keybindingLabel = DOM.append(column, $('div.keybinding-label')); - return column; + this.element = DOM.append(parent, $('.column.keybinding', { id: 'keybinding_' + ++Column.COUNTER })); + this.keybindingLabel = DOM.append(this.element, $('div.keybinding-label')); } render(keybindingItemEntry: IKeybindingItemEntry): void { @@ -994,7 +987,7 @@ class KeybindingColumn extends Column { class SourceColumn extends Column { - private sourceColumn: HTMLElement; + private readonly sourceColumn: HTMLElement; readonly element: HTMLElement; constructor( @@ -1002,12 +995,7 @@ class SourceColumn extends Column { keybindingsEditor: IKeybindingsEditor, ) { super(keybindingsEditor); - this.element = this.create(parent); - } - - create(parent: HTMLElement): HTMLElement { - this.sourceColumn = DOM.append(parent, $('.column.source', { id: 'source_' + ++Column.COUNTER })); - return this.sourceColumn; + this.element = this.sourceColumn = DOM.append(parent, $('.column.source', { id: 'source_' + ++Column.COUNTER })); } render(keybindingItemEntry: IKeybindingItemEntry): void { @@ -1024,8 +1012,8 @@ class SourceColumn extends Column { class WhenColumn extends Column { readonly element: HTMLElement; - private whenLabel: HTMLElement; - private whenInput: InputBox; + private readonly whenLabel: HTMLElement; + private readonly whenInput: InputBox; private readonly renderDisposables = this._register(new DisposableStore()); private _onDidAccept: Emitter = this._register(new Emitter()); @@ -1041,14 +1029,11 @@ class WhenColumn extends Column { @IThemeService private readonly themeService: IThemeService ) { super(keybindingsEditor); - this.element = this.create(parent); - } - private create(parent: HTMLElement): HTMLElement { - const column = DOM.append(parent, $('.column.when', { id: 'when_' + ++Column.COUNTER })); + this.element = DOM.append(parent, $('.column.when', { id: 'when_' + ++Column.COUNTER })); - this.whenLabel = DOM.append(column, $('div.when-label')); - this.whenInput = new InputBox(column, this.contextViewService, { + this.whenLabel = DOM.append(this.element, $('div.when-label')); + this.whenInput = new InputBox(this.element, this.contextViewService, { validationOptions: { validation: (value) => { try { @@ -1068,8 +1053,6 @@ class WhenColumn extends Column { this._register(attachInputBoxStyler(this.whenInput, this.themeService)); this._register(DOM.addStandardDisposableListener(this.whenInput.inputElement, DOM.EventType.KEY_DOWN, e => this.onInputKeyDown(e))); this._register(DOM.addDisposableListener(this.whenInput.inputElement, DOM.EventType.BLUR, () => this.cancelEditing())); - - return column; } private onInputKeyDown(e: IKeyboardEvent): void { @@ -1151,7 +1134,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { collector.addRule(`.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list:focus .monaco-list-row.selected > .column .monaco-keybinding-key { color: ${listActiveSelectionForegroundColor}; }`); } const listInactiveFocusAndSelectionForegroundColor = theme.getColor(listInactiveSelectionForeground); - if (listActiveSelectionForegroundColor) { + if (listInactiveFocusAndSelectionForegroundColor) { collector.addRule(`.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list .monaco-list-row.selected > .column .monaco-keybinding-key { color: ${listInactiveFocusAndSelectionForegroundColor}; }`); } const listHoverForegroundColor = theme.getColor(listHoverForeground); diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts index e5bab5617b..2973c2fce7 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts @@ -14,7 +14,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { registerEditorContribution, ServicesAccessor, registerEditorCommand, EditorCommand } from 'vs/editor/browser/editorExtensions'; -import { ICodeEditor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; import { SmartSnippetInserter } from 'vs/workbench/contrib/preferences/common/smartSnippetInserter'; import { DefineKeybindingOverlayWidget } from 'vs/workbench/contrib/preferences/browser/keybindingWidgets'; @@ -30,6 +30,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { KeybindingParser } from 'vs/base/common/keybindingParser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { equals } from 'vs/base/common/arrays'; +import { assertIsDefined } from 'vs/base/common/types'; const NLS_LAUNCH_MESSAGE = nls.localize('defineKeybinding.start', "Define Keybinding"); const NLS_KB_LAYOUT_ERROR_MESSAGE = nls.localize('defineKeybinding.kbLayoutErrorMessage', "You won't be able to produce this key combination under your current keyboard layout."); @@ -164,14 +165,14 @@ export class KeybindingEditorDecorationsRenderer extends Disposable { private _dec: string[] = []; constructor( - private _editor: IActiveCodeEditor, + private _editor: ICodeEditor, @IKeybindingService private readonly _keybindingService: IKeybindingService, ) { super(); this._updateDecorations = this._register(new RunOnceScheduler(() => this._updateDecorationsNow(), 500)); - const model = this._editor.getModel(); + const model = assertIsDefined(this._editor.getModel()); this._register(model.onDidChangeContent(() => this._updateDecorations.schedule())); this._register(this._keybindingService.onDidUpdateKeybindings((e) => this._updateDecorations.schedule())); this._register({ @@ -184,7 +185,7 @@ export class KeybindingEditorDecorationsRenderer extends Disposable { } private _updateDecorationsNow(): void { - const model = this._editor.getModel(); + const model = assertIsDefined(this._editor.getModel()); const newDecorations: IModelDeltaDecoration[] = []; diff --git a/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts b/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts index b3af1dcb00..6e45800a7f 100644 --- a/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts +++ b/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts @@ -176,4 +176,4 @@ export class KeyboardLayoutPickerAction extends Action { } const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(KeyboardLayoutPickerAction, KeyboardLayoutPickerAction.ID, KeyboardLayoutPickerAction.LABEL, {}), 'Preferences: Change Keyboard Layout', nls.localize('preferences', "Preferences")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(KeyboardLayoutPickerAction, KeyboardLayoutPickerAction.ID, KeyboardLayoutPickerAction.LABEL, {}), 'Preferences: Change Keyboard Layout', nls.localize('preferences', "Preferences")); diff --git a/src/vs/workbench/contrib/preferences/browser/media/preferences.css b/src/vs/workbench/contrib/preferences/browser/media/preferences.css index 4dcf552240..bca0c46733 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/preferences.css +++ b/src/vs/workbench/contrib/preferences/browser/media/preferences.css @@ -139,6 +139,7 @@ .monaco-editor .settings-header-widget .title-container { display: flex; user-select: none; + -webkit-user-select: none; } .vs .monaco-editor .settings-header-widget .title-container { @@ -170,6 +171,7 @@ cursor: pointer; font-weight: bold; user-select: none; + -webkit-user-select: none; display: flex; } diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index f44008484f..f55f1a9ed1 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -216,14 +216,13 @@ overflow: hidden; text-overflow: ellipsis; line-height: 22px; - opacity: 0.9; flex-shrink: 1; } .settings-editor > .settings-body .settings-toc-container .monaco-list-row .settings-toc-count { display: none; line-height: 22px; - opacity: 0.7; + opacity: 0.8; margin-left: 3px; } @@ -231,17 +230,8 @@ display: block; } -.settings-editor > .settings-body .settings-toc-container .monaco-list-row .monaco-tl-twistie { - opacity: 0.9; -} - -.settings-editor > .settings-body .settings-toc-container .monaco-list-row.selected .monaco-tl-twistie { - opacity: 1; -} - .settings-editor > .settings-body .settings-toc-container .monaco-list-row.selected .settings-toc-entry { font-weight: bold; - opacity: 1; } .settings-editor > .settings-body .settings-tree-container { @@ -345,6 +335,7 @@ .settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-category { font-weight: 600; user-select: text; + -webkit-user-select: text; } .settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-category { @@ -354,15 +345,18 @@ .settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-deprecation-message { margin-top: 3px; user-select: text; + -webkit-user-select: text; + display: none; +} + +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents.is-deprecated .setting-item-deprecation-message { + display: block; } .settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description { margin-top: -1px; user-select: text; -} - -.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-deprecation-message { - position: absolute; + -webkit-user-select: text; } .settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-validation-message { @@ -395,6 +389,11 @@ -webkit-appearance: none !important; } +.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-number input[type=number] { + /* Hide arrow button that shows in type=number fields */ + -moz-appearance: textfield !important; +} + .settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown * { margin: 0px; } @@ -480,6 +479,7 @@ width: initial; font: inherit; height: 26px; + padding: 2px 8px; } .settings-editor > .settings-body > .settings-tree-container .setting-item-new-extensions { diff --git a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts index d26257ee93..97832ccb3d 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts @@ -39,10 +39,9 @@ import { ExplorerRootContext, ExplorerFolderContext } from 'vs/workbench/contrib import { ILabelService } from 'vs/platform/label/common/label'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; -import { registerAndGetAmdImageURL } from 'vs/base/common/amd'; Registry.as(EditorExtensions.Editors).registerEditor( - new EditorDescriptor( + EditorDescriptor.create( PreferencesEditor, PreferencesEditor.ID, nls.localize('defaultPreferencesEditor', "Default Preferences Editor") @@ -53,7 +52,7 @@ Registry.as(EditorExtensions.Editors).registerEditor( ); Registry.as(EditorExtensions.Editors).registerEditor( - new EditorDescriptor( + EditorDescriptor.create( SettingsEditor2, SettingsEditor2.ID, nls.localize('settingsEditor2', "Settings Editor 2") @@ -64,7 +63,7 @@ Registry.as(EditorExtensions.Editors).registerEditor( ); Registry.as(EditorExtensions.Editors).registerEditor( - new EditorDescriptor( + EditorDescriptor.create( KeybindingsEditor, KeybindingsEditor.ID, nls.localize('keybindingsEditor', "Keybindings Editor") @@ -199,15 +198,15 @@ Registry.as(EditorInputExtensions.EditorInputFactor // Contribute Global Actions const category = nls.localize('preferences', "Preferences"); const registry = Registry.as(Extensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenRawDefaultSettingsAction, OpenRawDefaultSettingsAction.ID, OpenRawDefaultSettingsAction.LABEL), 'Preferences: Open Default Settings (JSON)', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenSettingsJsonAction, OpenSettingsJsonAction.ID, OpenSettingsJsonAction.LABEL), 'Preferences: Open Settings (JSON)', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenSettings2Action, OpenSettings2Action.ID, OpenSettings2Action.LABEL), 'Preferences: Open Settings (UI)', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenGlobalSettingsAction, OpenGlobalSettingsAction.ID, OpenGlobalSettingsAction.LABEL), 'Preferences: Open User Settings', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenRawDefaultSettingsAction, OpenRawDefaultSettingsAction.ID, OpenRawDefaultSettingsAction.LABEL), 'Preferences: Open Default Settings (JSON)', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenSettingsJsonAction, OpenSettingsJsonAction.ID, OpenSettingsJsonAction.LABEL), 'Preferences: Open Settings (JSON)', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenSettings2Action, OpenSettings2Action.ID, OpenSettings2Action.LABEL), 'Preferences: Open Settings (UI)', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenGlobalSettingsAction, OpenGlobalSettingsAction.ID, OpenGlobalSettingsAction.LABEL), 'Preferences: Open User Settings', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenGlobalKeybindingsAction, OpenGlobalKeybindingsAction.ID, OpenGlobalKeybindingsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_S) }), 'Preferences: Open Keyboard Shortcuts', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenDefaultKeybindingsFileAction, OpenDefaultKeybindingsFileAction.ID, OpenDefaultKeybindingsFileAction.LABEL), 'Preferences: Open Default Keyboard Shortcuts (JSON)', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenGlobalKeybindingsFileAction, OpenGlobalKeybindingsFileAction.ID, OpenGlobalKeybindingsFileAction.LABEL, { primary: 0 }), 'Preferences: Open Keyboard Shortcuts (JSON)', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(ConfigureLanguageBasedSettingsAction, ConfigureLanguageBasedSettingsAction.ID, ConfigureLanguageBasedSettingsAction.LABEL), 'Preferences: Configure Language Specific Settings...', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenGlobalKeybindingsAction, OpenGlobalKeybindingsAction.ID, OpenGlobalKeybindingsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_S) }), 'Preferences: Open Keyboard Shortcuts', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenDefaultKeybindingsFileAction, OpenDefaultKeybindingsFileAction.ID, OpenDefaultKeybindingsFileAction.LABEL), 'Preferences: Open Default Keyboard Shortcuts (JSON)', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenGlobalKeybindingsFileAction, OpenGlobalKeybindingsFileAction.ID, OpenGlobalKeybindingsFileAction.LABEL, { primary: 0 }), 'Preferences: Open Keyboard Shortcuts (JSON)', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ConfigureLanguageBasedSettingsAction, ConfigureLanguageBasedSettingsAction.ID, ConfigureLanguageBasedSettingsAction.LABEL), 'Preferences: Configure Language Specific Settings...', category); KeybindingsRegistry.registerCommandAndKeybindingRule({ id: SETTINGS_COMMAND_OPEN_SETTINGS, @@ -215,7 +214,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ when: null, primary: KeyMod.CtrlCmd | KeyCode.US_COMMA, handler: (accessor, args: string | undefined) => { - accessor.get(IPreferencesService).openSettings(undefined, typeof args === 'string' ? args : undefined); + const query = typeof args === 'string' ? args : undefined; + accessor.get(IPreferencesService).openSettings(query ? false : undefined, query); } }); @@ -367,8 +367,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); -const PREFERENCES_EDITOR_LIGHT_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/preferences/browser/media/preferences-editor-light.svg`)); -const PREFERENCES_EDITOR_DARK_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/preferences/browser/media/preferences-editor-dark.svg`)); class PreferencesActionsContribution extends Disposable implements IWorkbenchContribution { constructor( @@ -383,10 +381,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon command: { id: OpenGlobalKeybindingsAction.ID, title: OpenGlobalKeybindingsAction.LABEL, - iconLocation: { - light: PREFERENCES_EDITOR_LIGHT_ICON_URI, - dark: PREFERENCES_EDITOR_DARK_ICON_URI - } + icon: { id: 'codicon/go-to-file' } }, when: ResourceContextKey.Resource.isEqualTo(environmentService.keybindingsResource.toString()), group: 'navigation', @@ -399,10 +394,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon command: { id: commandId, title: OpenSettings2Action.LABEL, - iconLocation: { - light: PREFERENCES_EDITOR_LIGHT_ICON_URI, - dark: PREFERENCES_EDITOR_DARK_ICON_URI - } + icon: { id: 'codicon/go-to-file' } }, when: ResourceContextKey.Resource.isEqualTo(environmentService.settingsResource.toString()), group: 'navigation', @@ -440,10 +432,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon command: { id: commandId, title: OpenSettings2Action.LABEL, - iconLocation: { - light: PREFERENCES_EDITOR_LIGHT_ICON_URI, - dark: PREFERENCES_EDITOR_DARK_ICON_URI - } + icon: { id: 'codicon/go-to-file' } }, when: ContextKeyExpr.and(ResourceContextKey.Resource.isEqualTo(this.preferencesService.workspaceSettingsResource!.toString()), WorkbenchStateContext.isEqualTo('workspace')), group: 'navigation', @@ -468,10 +457,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon command: { id: commandId, title: OpenSettings2Action.LABEL, - iconLocation: { - light: PREFERENCES_EDITOR_LIGHT_ICON_URI, - dark: PREFERENCES_EDITOR_DARK_ICON_URI - } + icon: { id: 'codicon/go-to-file' } }, when: ContextKeyExpr.and(ResourceContextKey.Resource.isEqualTo(this.preferencesService.getFolderSettingsResource(folder.uri)!.toString())), group: 'navigation', @@ -535,10 +521,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: OpenGlobalKeybindingsFileAction.ID, title: OpenGlobalKeybindingsFileAction.LABEL, - iconLocation: { - light: PREFERENCES_EDITOR_LIGHT_ICON_URI, - dark: PREFERENCES_EDITOR_DARK_ICON_URI - } + icon: { id: 'codicon/go-to-file' } }, when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR), group: 'navigation', @@ -819,10 +802,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON, title: nls.localize('openSettingsJson', "Open Settings (JSON)"), - iconLocation: { - dark: PREFERENCES_EDITOR_DARK_ICON_URI, - light: PREFERENCES_EDITOR_LIGHT_ICON_URI - } + icon: { id: 'codicon/go-to-file' } }, group: 'navigation', order: 1, diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts index bbf8290bfa..d5caf5f72d 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts @@ -52,8 +52,6 @@ import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor import { IFilterResult, IPreferencesService, ISetting, ISettingsEditorModel, ISettingsGroup, SettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences'; import { DefaultPreferencesEditorInput, PreferencesEditorInput } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; import { DefaultSettingsEditorModel, SettingsEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IHostService } from 'vs/workbench/services/host/browser/host'; import { withNullAsUndefined, withUndefinedAsNull, assertIsDefined } from 'vs/base/common/types'; export class PreferencesEditor extends BaseEditor { @@ -254,7 +252,7 @@ export class PreferencesEditor extends BaseEditor { if (this.editorService.activeControl !== this) { this.focus(); } - const promise: Promise = this.input && this.input.isDirty() ? this.input.save() : Promise.resolve(true); + const promise: Promise = this.input && this.input.isDirty() ? this.input.save(this.group!.id) : Promise.resolve(true); promise.then(() => { if (target === ConfigurationTarget.USER_LOCAL) { this.preferencesService.switchSettings(ConfigurationTarget.USER_LOCAL, this.preferencesService.userSettingsResource, true); @@ -634,7 +632,7 @@ class PreferencesRenderersController extends Disposable { if (filterResult) { filterResult.query = filter; - filterResult.exactMatch = searchResult && searchResult.exactMatch; + filterResult.exactMatch = !!searchResult && searchResult.exactMatch; } return filterResult; @@ -978,12 +976,10 @@ export class DefaultPreferencesEditor extends BaseTextEditor { @IStorageService storageService: IStorageService, @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, @IThemeService themeService: IThemeService, - @ITextFileService textFileService: ITextFileService, @IEditorGroupsService editorGroupService: IEditorGroupsService, - @IEditorService editorService: IEditorService, - @IHostService hostService: IHostService + @IEditorService editorService: IEditorService ) { - super(DefaultPreferencesEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, hostService); + super(DefaultPreferencesEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorService, editorGroupService); } private static _getContributions(): IEditorContributionDescription[] { diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index e3b608b324..3d98f2f14c 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -1019,7 +1019,7 @@ class UnsupportedSettingsRenderer extends Disposable { severity: MarkerSeverity.Hint, tags: [MarkerTag.Unnecessary], ...setting.range, - message: nls.localize('unsupportedRemoteMachineSetting', "This setting cannot be applied now. It will be applied when you open local window.") + message: nls.localize('unsupportedRemoteMachineSetting', "This setting cannot be applied in this window. It will be applied when you open local window.") }); } } @@ -1054,7 +1054,7 @@ class UnsupportedSettingsRenderer extends Disposable { severity: MarkerSeverity.Hint, tags: [MarkerTag.Unnecessary], ...setting.range, - message: nls.localize('unsupportedWindowSetting', "This setting cannot be applied now. It will be applied when you open this folder directly.") + message: nls.localize('unsupportedWindowSetting', "This setting cannot be applied in this workspace. It will be applied when you open the containing workspace folder directly.") }); } } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index f641763207..93337e2b88 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -4,13 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import * as DOM from 'vs/base/browser/dom'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { ITreeElement } from 'vs/base/browser/ui/tree/tree'; +import { Action } from 'vs/base/common/actions'; import * as arrays from 'vs/base/common/arrays'; import { Delayer, ThrottledDelayer, timeout } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import * as collections from 'vs/base/common/collections'; import { getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors'; import { Iterator } from 'vs/base/common/iterator'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import * as platform from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; import { isArray, withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; @@ -39,13 +44,11 @@ import { AbstractSettingRenderer, ISettingLinkClickEvent, ISettingOverrideClickE import { ISettingsEditorViewState, parseQuery, SearchResultIdx, SearchResultModel, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeModel, SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; import { settingsTextInputBorder } from 'vs/workbench/contrib/preferences/browser/settingsWidgets'; import { createTOCIterator, TOCTree, TOCTreeModel } from 'vs/workbench/contrib/preferences/browser/tocTree'; -import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, EXTENSION_SETTING_TAG, IPreferencesSearchService, ISearchProvider, MODIFIED_SETTING_TAG, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS } from 'vs/workbench/contrib/preferences/common/preferences'; +import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, EXTENSION_SETTING_TAG, IPreferencesSearchService, ISearchProvider, MODIFIED_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IPreferencesService, ISearchResult, ISettingsEditorModel, ISettingsEditorOptions, SettingsEditorOptions, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; import { Settings2EditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; -import { Action } from 'vs/base/common/actions'; -import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; function createGroupIterator(group: SettingsTreeGroupElement): Iterator> { const groupsIt = Iterator.fromArray(group.children); @@ -98,6 +101,7 @@ export class SettingsEditor2 extends BaseEditor { private headerContainer!: HTMLElement; private searchWidget!: SuggestEnabledInput; private countElement!: HTMLElement; + private controlsElement!: HTMLElement; private settingsTargetsWidget!: SettingsTargetsWidget; private settingsTreeContainer!: HTMLElement; @@ -134,9 +138,6 @@ export class SettingsEditor2 extends BaseEditor { private scheduledRefreshes: Map; private lastFocusedSettingElement: string | null = null; - private actionBar: ActionBar; - private actionsContainer: HTMLElement; - /** Don't spam warnings */ private hasWarnedMissingSettings = false; @@ -218,6 +219,7 @@ export class SettingsEditor2 extends BaseEditor { this.createHeader(this.rootElement); this.createBody(this.rootElement); + this.addCtrlAInterceptor(this.rootElement); this.updateStyles(); } @@ -308,7 +310,8 @@ export class SettingsEditor2 extends BaseEditor { this.layoutTrees(dimension); const innerWidth = Math.min(1000, dimension.width) - 24 * 2; // 24px padding on left and right; - const monacoWidth = innerWidth - 10 - this.countElement.clientWidth - 12; // minus padding inside inputbox, countElement width, extra padding before countElement + // minus padding inside inputbox, countElement width, controls width, extra padding before countElement + const monacoWidth = innerWidth - 10 - this.countElement.clientWidth - this.controlsElement.clientWidth - 12; this.searchWidget.layout({ height: 20, width: monacoWidth }); DOM.toggleClass(this.rootElement, 'mid-width', dimension.width < 1000 && dimension.width >= 600); @@ -330,6 +333,10 @@ export class SettingsEditor2 extends BaseEditor { this.focusSearch(); } + onHide(): void { + this.searchWidget.onHide(); + } + focusSettings(): void { // Update ARIA global labels const labelElement = this.settingsAriaExtraLabelsContainer.querySelector('#settings_aria_more_actions_shortcut_label'); @@ -378,6 +385,7 @@ export class SettingsEditor2 extends BaseEditor { clearSearchResults(): void { this.searchWidget.setValue(''); + this.focusSearch(); } clearSearchFilters(): void { @@ -390,17 +398,12 @@ export class SettingsEditor2 extends BaseEditor { this.searchWidget.setValue(query.trim()); } - clearSearch(): void { - this.clearSearchResults(); - this.focusSearch(); - } - private createHeader(parent: HTMLElement): void { this.headerContainer = DOM.append(parent, $('.settings-header')); const searchContainer = DOM.append(this.headerContainer, $('.search-container')); - const clearInputAction = new Action(SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, localize('clearInput', "Clear Settings Search Input"), 'codicon-clear-all', false, () => { this.clearSearch(); return Promise.resolve(null); }); + const clearInputAction = new Action(SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, localize('clearInput', "Clear Settings Search Input"), 'codicon-clear-all', false, () => { this.clearSearchResults(); return Promise.resolve(null); }); const searchBoxLabel = localize('SearchSettings.AriaLabel', "Search settings"); this.searchWidget = this._register(this.instantiationService.createInstance(SuggestEnabledInput, `${SettingsEditor2.ID}.searchbox`, searchContainer, { @@ -449,14 +452,14 @@ export class SettingsEditor2 extends BaseEditor { this.settingsTargetsWidget.settingsTarget = ConfigurationTarget.USER_LOCAL; this.settingsTargetsWidget.onDidTargetChange(target => this.onDidSettingsTargetChange(target)); - this.actionsContainer = DOM.append(searchContainer, DOM.$('.settings-clear-widget')); + this.controlsElement = DOM.append(searchContainer, DOM.$('.settings-clear-widget')); - this.actionBar = this._register(new ActionBar(this.actionsContainer, { + const actionBar = this._register(new ActionBar(this.controlsElement, { animated: false, actionViewItemProvider: (action: Action) => { return undefined; } })); - this.actionBar.push([clearInputAction], { label: false, icon: true }); + actionBar.push([clearInputAction], { label: false, icon: true }); } private onDidSettingsTargetChange(target: SettingsTarget): void { @@ -544,7 +547,6 @@ export class SettingsEditor2 extends BaseEditor { this._register(DOM.addDisposableListener(clearSearch, DOM.EventType.CLICK, (e: MouseEvent) => { DOM.EventHelper.stop(e, false); this.clearSearchResults(); - this.focusSearch(); })); DOM.append(this.noResultsMessage, clearSearchContainer); @@ -561,7 +563,11 @@ export class SettingsEditor2 extends BaseEditor { if (DOM.findParentWithClass(e.relatedTarget, 'settings-editor-tree')) { if (this.settingsTree.scrollTop > 0) { const firstElement = this.settingsTree.firstVisibleElement; - this.settingsTree.reveal(firstElement, 0.1); + + if (typeof firstElement !== 'undefined') { + this.settingsTree.reveal(firstElement, 0.1); + } + return true; } } else { @@ -594,6 +600,21 @@ export class SettingsEditor2 extends BaseEditor { ); } + private addCtrlAInterceptor(container: HTMLElement): void { + this._register(DOM.addStandardDisposableListener(container, DOM.EventType.KEY_DOWN, (e: StandardKeyboardEvent) => { + if ( + e.keyCode === KeyCode.KEY_A && + (platform.isMacintosh ? e.metaKey : e.ctrlKey) && + e.target.tagName !== 'TEXTAREA' && + e.target.tagName !== 'INPUT' + ) { + // Avoid browser ctrl+a + e.browserEvent.stopPropagation(); + e.browserEvent.preventDefault(); + } + })); + } + private createFocusSink(container: HTMLElement, callback: (e: any) => boolean, label: string): HTMLElement { const listFocusSink = DOM.append(container, $('.settings-tree-focus-sink')); listFocusSink.setAttribute('aria-label', label); @@ -1316,7 +1337,7 @@ export class SettingsEditor2 extends BaseEditor { private _filterOrSearchPreferencesModel(filter: string, model: ISettingsEditorModel, provider?: ISearchProvider, token?: CancellationToken): Promise { const searchP = provider ? provider.searchModel(model, token) : Promise.resolve(null); return searchP - .then(null, err => { + .then(undefined, err => { if (isPromiseCanceledError(err)) { return Promise.reject(err); } else { @@ -1332,7 +1353,7 @@ export class SettingsEditor2 extends BaseEditor { this.telemetryService.publicLog('settingsEditor.searchError', { message, filter }); this.logService.info('Setting search error: ' + message); } - return Promise.resolve(null); + return null; } }); } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index a10b2cac51..cf9b21d442 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -147,7 +147,7 @@ export const tocData: ITOCEntry = { }, { id: 'features/extensions', - label: localize('extensionViewlet', "Extension Viewlet"), + label: localize('extensions', "Extensions"), settings: ['extensions.*'] }, { @@ -202,9 +202,9 @@ export const tocData: ITOCEntry = { settings: ['telemetry.*'] }, { - id: 'application/configurationSync', - label: localize('configuration sync', "Configuration Sync"), - settings: ['configurationSync.*'] + id: 'application/sync', + label: localize('sync', "Sync"), + settings: ['sync.*'] } ] } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index cecbbf2ff3..1410081828 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -28,7 +28,6 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { dispose, IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ISpliceable } from 'vs/base/common/sequence'; import { escapeRegExpCharacters, startsWith } from 'vs/base/common/strings'; -import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -37,7 +36,7 @@ import { IContextMenuService, IContextViewService } from 'vs/platform/contextvie import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { errorForeground, focusBorder, foreground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, transparent } from 'vs/platform/theme/common/colorRegistry'; +import { errorForeground, focusBorder, foreground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { attachButtonStyler, attachInputBoxStyler, attachSelectBoxStyler, attachStyler } from 'vs/platform/theme/common/styler'; import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ITOCEntry } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; @@ -47,6 +46,8 @@ import { SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/ import { ISetting, ISettingsGroup, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { isArray } from 'vs/base/common/types'; +import { BrowserFeatures } from 'vs/base/browser/canIUse'; +import { isIOS } from 'vs/base/common/platform'; const $ = DOM.$; @@ -466,7 +467,10 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre } const onChange = (value: any) => this._onDidChangeSetting.fire({ key: element.setting.key, value, type: template.context!.valueType }); - template.deprecationWarningElement.innerText = element.setting.deprecationMessage || ''; + const deprecationText = element.setting.deprecationMessage || ''; + template.deprecationWarningElement.innerText = deprecationText; + DOM.toggleClass(template.containerElement, 'is-deprecated', !!deprecationText); + this.renderValue(element, template, onChange); } @@ -485,15 +489,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre }; this._onDidClickSettingLink.fire(e); } else { - let uri: URI | undefined; - try { - uri = URI.parse(content); - } catch (err) { - // ignore - } - if (uri) { - this._openerService.open(uri).catch(onUnexpectedError); - } + this._openerService.open(content).catch(onUnexpectedError); } }, disposeables @@ -918,7 +914,9 @@ export class SettingEnumRenderer extends AbstractSettingRenderer implements ITre renderTemplate(container: HTMLElement): ISettingEnumItemTemplate { const common = this.renderCommonTemplate(null, container, 'enum'); - const selectBox = new SelectBox([], 0, this._contextViewService, undefined, { useCustomDrawn: true }); + const selectBox = new SelectBox([], 0, this._contextViewService, undefined, { + useCustomDrawn: !(isIOS && BrowserFeatures.pointerEvents) + }); common.toDispose.push(selectBox); common.toDispose.push(attachSelectBoxStyler(selectBox, this._themeService, { @@ -1456,8 +1454,6 @@ export class SettingsTree extends ObjectTree { @IThemeService themeService: IThemeService, @IInstantiationService instantiationService: IInstantiationService, ) { - const treeClass = 'settings-editor-tree'; - super('SettingsTree', container, new SettingsTreeDelegate(), renderers, @@ -1470,7 +1466,7 @@ export class SettingsTree extends ObjectTree { return e.id; } }, - styleController: new DefaultStyleController(DOM.createStyleSheet(container), treeClass), + styleController: id => new DefaultStyleController(DOM.createStyleSheet(container), id), filter: instantiationService.createInstance(SettingsTreeFilter, viewState) }); @@ -1488,6 +1484,8 @@ export class SettingsTree extends ObjectTree { // applying an opacity to the link color. const fgWithOpacity = new Color(new RGBA(foregroundColor.rgba.r, foregroundColor.rgba.g, foregroundColor.rgba.b, 0.9)); collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description { color: ${fgWithOpacity}; }`); + + collector.addRule(`.settings-editor > .settings-body .settings-toc-container .monaco-list-row:not(.selected) { color: ${fgWithOpacity}; }`); } const errorColor = theme.getColor(errorForeground); @@ -1523,23 +1521,24 @@ export class SettingsTree extends ObjectTree { } })); - this.getHTMLElement().classList.add(treeClass); + this.getHTMLElement().classList.add('settings-editor-tree'); this.disposables.add(attachStyler(themeService, { - listActiveSelectionBackground: transparent(Color.white, 0), + listBackground: editorBackground, + listActiveSelectionBackground: editorBackground, listActiveSelectionForeground: foreground, - listFocusAndSelectionBackground: transparent(Color.white, 0), + listFocusAndSelectionBackground: editorBackground, listFocusAndSelectionForeground: foreground, - listFocusBackground: transparent(Color.white, 0), + listFocusBackground: editorBackground, listFocusForeground: foreground, listHoverForeground: foreground, - listHoverBackground: transparent(Color.white, 0), - listHoverOutline: transparent(Color.white, 0), - listFocusOutline: transparent(Color.white, 0), - listInactiveSelectionBackground: transparent(Color.white, 0), + listHoverBackground: editorBackground, + listHoverOutline: editorBackground, + listFocusOutline: editorBackground, + listInactiveSelectionBackground: editorBackground, listInactiveSelectionForeground: foreground, - listInactiveFocusBackground: transparent(Color.white, 0), - listInactiveFocusOutline: transparent(Color.white, 0) + listInactiveFocusBackground: editorBackground, + listInactiveFocusOutline: editorBackground }, colors => { this.style(colors); })); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts index a92878548b..566133afd9 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts @@ -23,33 +23,33 @@ import { disposableTimeout } from 'vs/base/common/async'; import { isUndefinedOrNull } from 'vs/base/common/types'; const $ = DOM.$; -export const settingsHeaderForeground = registerColor('settings.headerForeground', { light: '#444444', dark: '#e7e7e7', hc: '#ffffff' }, localize('headerForeground', "(For settings editor preview) The foreground color for a section header or active title.")); +export const settingsHeaderForeground = registerColor('settings.headerForeground', { light: '#444444', dark: '#e7e7e7', hc: '#ffffff' }, localize('headerForeground', "The foreground color for a section header or active title.")); export const modifiedItemIndicator = registerColor('settings.modifiedItemIndicator', { light: new Color(new RGBA(102, 175, 224)), dark: new Color(new RGBA(12, 125, 157)), hc: new Color(new RGBA(0, 73, 122)) -}, localize('modifiedItemForeground', "(For settings editor preview) The color of the modified setting indicator.")); +}, localize('modifiedItemForeground', "The color of the modified setting indicator.")); // Enum control colors -export const settingsSelectBackground = registerColor('settings.dropdownBackground', { dark: selectBackground, light: selectBackground, hc: selectBackground }, localize('settingsDropdownBackground', "(For settings editor preview) Settings editor dropdown background.")); -export const settingsSelectForeground = registerColor('settings.dropdownForeground', { dark: selectForeground, light: selectForeground, hc: selectForeground }, localize('settingsDropdownForeground', "(For settings editor preview) Settings editor dropdown foreground.")); -export const settingsSelectBorder = registerColor('settings.dropdownBorder', { dark: selectBorder, light: selectBorder, hc: selectBorder }, localize('settingsDropdownBorder', "(For settings editor preview) Settings editor dropdown border.")); -export const settingsSelectListBorder = registerColor('settings.dropdownListBorder', { dark: editorWidgetBorder, light: editorWidgetBorder, hc: editorWidgetBorder }, localize('settingsDropdownListBorder', "(For settings editor preview) Settings editor dropdown list border. This surrounds the options and separates the options from the description.")); +export const settingsSelectBackground = registerColor('settings.dropdownBackground', { dark: selectBackground, light: selectBackground, hc: selectBackground }, localize('settingsDropdownBackground', "Settings editor dropdown background.")); +export const settingsSelectForeground = registerColor('settings.dropdownForeground', { dark: selectForeground, light: selectForeground, hc: selectForeground }, localize('settingsDropdownForeground', "Settings editor dropdown foreground.")); +export const settingsSelectBorder = registerColor('settings.dropdownBorder', { dark: selectBorder, light: selectBorder, hc: selectBorder }, localize('settingsDropdownBorder', "Settings editor dropdown border.")); +export const settingsSelectListBorder = registerColor('settings.dropdownListBorder', { dark: editorWidgetBorder, light: editorWidgetBorder, hc: editorWidgetBorder }, localize('settingsDropdownListBorder', "Settings editor dropdown list border. This surrounds the options and separates the options from the description.")); // Bool control colors -export const settingsCheckboxBackground = registerColor('settings.checkboxBackground', { dark: simpleCheckboxBackground, light: simpleCheckboxBackground, hc: simpleCheckboxBackground }, localize('settingsCheckboxBackground', "(For settings editor preview) Settings editor checkbox background.")); -export const settingsCheckboxForeground = registerColor('settings.checkboxForeground', { dark: simpleCheckboxForeground, light: simpleCheckboxForeground, hc: simpleCheckboxForeground }, localize('settingsCheckboxForeground', "(For settings editor preview) Settings editor checkbox foreground.")); -export const settingsCheckboxBorder = registerColor('settings.checkboxBorder', { dark: simpleCheckboxBorder, light: simpleCheckboxBorder, hc: simpleCheckboxBorder }, localize('settingsCheckboxBorder', "(For settings editor preview) Settings editor checkbox border.")); +export const settingsCheckboxBackground = registerColor('settings.checkboxBackground', { dark: simpleCheckboxBackground, light: simpleCheckboxBackground, hc: simpleCheckboxBackground }, localize('settingsCheckboxBackground', "Settings editor checkbox background.")); +export const settingsCheckboxForeground = registerColor('settings.checkboxForeground', { dark: simpleCheckboxForeground, light: simpleCheckboxForeground, hc: simpleCheckboxForeground }, localize('settingsCheckboxForeground', "Settings editor checkbox foreground.")); +export const settingsCheckboxBorder = registerColor('settings.checkboxBorder', { dark: simpleCheckboxBorder, light: simpleCheckboxBorder, hc: simpleCheckboxBorder }, localize('settingsCheckboxBorder', "Settings editor checkbox border.")); // Text control colors -export const settingsTextInputBackground = registerColor('settings.textInputBackground', { dark: inputBackground, light: inputBackground, hc: inputBackground }, localize('textInputBoxBackground', "(For settings editor preview) Settings editor text input box background.")); -export const settingsTextInputForeground = registerColor('settings.textInputForeground', { dark: inputForeground, light: inputForeground, hc: inputForeground }, localize('textInputBoxForeground', "(For settings editor preview) Settings editor text input box foreground.")); -export const settingsTextInputBorder = registerColor('settings.textInputBorder', { dark: inputBorder, light: inputBorder, hc: inputBorder }, localize('textInputBoxBorder', "(For settings editor preview) Settings editor text input box border.")); +export const settingsTextInputBackground = registerColor('settings.textInputBackground', { dark: inputBackground, light: inputBackground, hc: inputBackground }, localize('textInputBoxBackground', "Settings editor text input box background.")); +export const settingsTextInputForeground = registerColor('settings.textInputForeground', { dark: inputForeground, light: inputForeground, hc: inputForeground }, localize('textInputBoxForeground', "Settings editor text input box foreground.")); +export const settingsTextInputBorder = registerColor('settings.textInputBorder', { dark: inputBorder, light: inputBorder, hc: inputBorder }, localize('textInputBoxBorder', "Settings editor text input box border.")); // Number control colors -export const settingsNumberInputBackground = registerColor('settings.numberInputBackground', { dark: inputBackground, light: inputBackground, hc: inputBackground }, localize('numberInputBoxBackground', "(For settings editor preview) Settings editor number input box background.")); -export const settingsNumberInputForeground = registerColor('settings.numberInputForeground', { dark: inputForeground, light: inputForeground, hc: inputForeground }, localize('numberInputBoxForeground', "(For settings editor preview) Settings editor number input box foreground.")); -export const settingsNumberInputBorder = registerColor('settings.numberInputBorder', { dark: inputBorder, light: inputBorder, hc: inputBorder }, localize('numberInputBoxBorder', "(For settings editor preview) Settings editor number input box border.")); +export const settingsNumberInputBackground = registerColor('settings.numberInputBackground', { dark: inputBackground, light: inputBackground, hc: inputBackground }, localize('numberInputBoxBackground', "Settings editor number input box background.")); +export const settingsNumberInputForeground = registerColor('settings.numberInputForeground', { dark: inputForeground, light: inputForeground, hc: inputForeground }, localize('numberInputBoxForeground', "Settings editor number input box foreground.")); +export const settingsNumberInputBorder = registerColor('settings.numberInputBorder', { dark: inputBorder, light: inputBorder, hc: inputBorder }, localize('numberInputBoxBorder', "Settings editor number input box border.")); registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const checkboxBackgroundColor = theme.getColor(settingsCheckboxBackground); diff --git a/src/vs/workbench/contrib/preferences/browser/tocTree.ts b/src/vs/workbench/contrib/preferences/browser/tocTree.ts index 4f22c431e6..2b9feaf98c 100644 --- a/src/vs/workbench/contrib/preferences/browser/tocTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/tocTree.ts @@ -10,7 +10,7 @@ import { IObjectTreeOptions, ObjectTree } from 'vs/base/browser/ui/tree/objectTr import { ITreeElement, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { Iterator } from 'vs/base/common/iterator'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; +import { editorBackground, transparent, foreground } from 'vs/platform/theme/common/colorRegistry'; import { attachStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { SettingsTreeFilter } from 'vs/workbench/contrib/preferences/browser/settingsTree'; @@ -189,7 +189,6 @@ export class TOCTree extends ObjectTree { ) { // test open mode - const treeClass = 'settings-toc-tree'; const filter = instantiationService.createInstance(SettingsTreeFilter, viewState); const options: IObjectTreeOptions = { filter, @@ -199,7 +198,7 @@ export class TOCTree extends ObjectTree { return e.id; } }, - styleController: new DefaultStyleController(DOM.createStyleSheet(container), treeClass), + styleController: id => new DefaultStyleController(DOM.createStyleSheet(container), id), accessibilityProvider: instantiationService.createInstance(SettingsAccessibilityProvider), collapseByDefault: true }; @@ -209,16 +208,15 @@ export class TOCTree extends ObjectTree { [new TOCRenderer()], options); - this.getHTMLElement().classList.add(treeClass); - this.disposables.add(attachStyler(themeService, { + listBackground: editorBackground, listActiveSelectionBackground: editorBackground, listActiveSelectionForeground: settingsHeaderForeground, listFocusAndSelectionBackground: editorBackground, listFocusAndSelectionForeground: settingsHeaderForeground, listFocusBackground: editorBackground, - listFocusForeground: settingsHeaderForeground, - listHoverForeground: settingsHeaderForeground, + listFocusForeground: transparent(foreground, 0.9), + listHoverForeground: transparent(foreground, 0.9), listHoverBackground: editorBackground, listInactiveSelectionBackground: editorBackground, listInactiveSelectionForeground: settingsHeaderForeground, diff --git a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts index 997f8f0fc2..a3c3fc7099 100644 --- a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts @@ -32,6 +32,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { Disposable, DisposableStore, IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; import { timeout } from 'vs/base/common/async'; +import { isFirefox } from 'vs/base/browser/browser'; export const ALL_COMMANDS_PREFIX = '>'; @@ -213,7 +214,7 @@ class CommandPaletteEditorAction extends EditorAction { label: localize('showCommands.label', "Command Palette..."), alias: 'Command Palette', precondition: undefined, - menuOpts: { + contextMenuOpts: { group: 'z_commands', order: 1 } @@ -313,8 +314,7 @@ abstract class BaseCommandEntry extends QuickOpenEntryGroup { // Indicate onBeforeRun this.onBeforeRun(this.commandId); - // Use a timeout to give the quick open widget a chance to close itself first - setTimeout(async () => { + const commandRunner = (async () => { if (action && (!(action instanceof Action) || action.enabled)) { try { this.telemetryService.publicLog2('workbenchActionExecuted', { id: action.id, from: 'quick open' }); @@ -335,7 +335,16 @@ abstract class BaseCommandEntry extends QuickOpenEntryGroup { } else { this.notificationService.info(localize('actionNotEnabled', "Command '{0}' is not enabled in the current context.", this.getLabel())); } - }, 50); + }); + + // Use a timeout to give the quick open widget a chance to close itself first + // Firefox: since the browser is quite picky for certain commands, we do not + // use a timeout (https://github.com/microsoft/vscode/issues/83288) + if (!isFirefox) { + setTimeout(() => commandRunner(), 50); + } else { + commandRunner(); + } } private onError(error?: Error): void { diff --git a/src/vs/workbench/contrib/quickopen/browser/quickopen.contribution.ts b/src/vs/workbench/contrib/quickopen/browser/quickopen.contribution.ts index 29dd046e0c..7cc96014e0 100644 --- a/src/vs/workbench/contrib/quickopen/browser/quickopen.contribution.ts +++ b/src/vs/workbench/contrib/quickopen/browser/quickopen.contribution.ts @@ -21,18 +21,18 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co // Register Actions const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(ClearCommandHistoryAction, ClearCommandHistoryAction.ID, ClearCommandHistoryAction.LABEL), 'Clear Command History'); -registry.registerWorkbenchAction(new SyncActionDescriptor(ShowAllCommandsAction, ShowAllCommandsAction.ID, ShowAllCommandsAction.LABEL, { +registry.registerWorkbenchAction(SyncActionDescriptor.create(ClearCommandHistoryAction, ClearCommandHistoryAction.ID, ClearCommandHistoryAction.LABEL), 'Clear Command History'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowAllCommandsAction, ShowAllCommandsAction.ID, ShowAllCommandsAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_P, secondary: [KeyCode.F1] }), 'Show All Commands'); -registry.registerWorkbenchAction(new SyncActionDescriptor(GotoLineAction, GotoLineAction.ID, GotoLineAction.LABEL, { +registry.registerWorkbenchAction(SyncActionDescriptor.create(GotoLineAction, GotoLineAction.ID, GotoLineAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_G, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_G } }), 'Go to Line...'); -registry.registerWorkbenchAction(new SyncActionDescriptor(GotoSymbolAction, GotoSymbolAction.ID, GotoSymbolAction.LABEL, { +registry.registerWorkbenchAction(SyncActionDescriptor.create(GotoSymbolAction, GotoSymbolAction.ID, GotoSymbolAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_O }), 'Go to Symbol in File...'); @@ -42,8 +42,8 @@ const inViewsPickerContext = ContextKeyExpr.and(inQuickOpenContext, ContextKeyEx const viewPickerKeybinding = { primary: KeyMod.CtrlCmd | KeyCode.KEY_Q, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_Q }, linux: { primary: 0 } }; const viewCategory = nls.localize('view', "View"); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenViewPickerAction, OpenViewPickerAction.ID, OpenViewPickerAction.LABEL), 'View: Open View', viewCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenViewPickerAction, QuickOpenViewPickerAction.ID, QuickOpenViewPickerAction.LABEL, viewPickerKeybinding), 'View: Quick Open View', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenViewPickerAction, OpenViewPickerAction.ID, OpenViewPickerAction.LABEL), 'View: Open View', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenViewPickerAction, QuickOpenViewPickerAction.ID, QuickOpenViewPickerAction.LABEL, viewPickerKeybinding), 'View: Quick Open View', viewCategory); const quickOpenNavigateNextInViewPickerId = 'workbench.action.quickOpenNavigateNextInViewPicker'; KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -72,7 +72,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ // Register Quick Open Handler Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - new QuickOpenHandlerDescriptor( + QuickOpenHandlerDescriptor.create( CommandsHandler, CommandsHandler.ID, ALL_COMMANDS_PREFIX, @@ -82,7 +82,7 @@ Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpen ); Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - new QuickOpenHandlerDescriptor( + QuickOpenHandlerDescriptor.create( GotoLineHandler, GotoLineHandler.ID, GOTO_LINE_PREFIX, @@ -98,7 +98,7 @@ Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpen ); Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - new QuickOpenHandlerDescriptor( + QuickOpenHandlerDescriptor.create( GotoSymbolHandler, GotoSymbolHandler.ID, GOTO_SYMBOL_PREFIX, @@ -119,7 +119,7 @@ Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpen ); Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - new QuickOpenHandlerDescriptor( + QuickOpenHandlerDescriptor.create( HelpHandler, HelpHandler.ID, HELP_PREFIX, @@ -129,7 +129,7 @@ Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpen ); Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - new QuickOpenHandlerDescriptor( + QuickOpenHandlerDescriptor.create( ViewPickerHandler, ViewPickerHandler.ID, VIEW_PICKER_PREFIX, diff --git a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts index aea45f60e9..1c24bae37c 100644 --- a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts @@ -10,7 +10,6 @@ import { IWindowsConfiguration } from 'vs/platform/windows/common/windows'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { localize } from 'vs/nls'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -20,13 +19,13 @@ import { isMacintosh, isNative } from 'vs/base/common/platform'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IProductService } from 'vs/platform/product/common/productService'; interface IConfiguration extends IWindowsConfiguration { update: { mode: string; }; telemetry: { enableCrashReporter: boolean }; workbench: { list: { horizontalScrolling: boolean } }; debug: { console: { wordWrap: boolean } }; - configurationSync: { enableAuth: boolean }; } export class SettingsChangeRelauncher extends Disposable implements IWorkbenchContribution { @@ -39,12 +38,11 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo private enableCrashReporter: boolean | undefined; private treeHorizontalScrolling: boolean | undefined; private debugConsoleWordWrap: boolean | undefined; - private enableConfigSyncAuth: boolean | undefined; constructor( @IHostService private readonly hostService: IHostService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IEnvironmentService private readonly envService: IEnvironmentService, + @IProductService private readonly productService: IProductService, @IDialogService private readonly dialogService: IDialogService ) { super(); @@ -107,12 +105,6 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo } } - // Configuration Sync Auth - if (typeof config.configurationSync?.enableAuth === 'boolean' && config.configurationSync.enableAuth !== this.enableConfigSyncAuth) { - this.enableConfigSyncAuth = config.configurationSync.enableAuth; - changed = true; - } - // Notify only when changed and we are the focused window (avoids notification spam across windows) if (notify && changed) { this.doConfirm( @@ -120,8 +112,8 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo localize('relaunchSettingMessage', "A setting has changed that requires a restart to take effect.") : localize('relaunchSettingMessageWeb', "A setting has changed that requires a reload to take effect."), isNative ? - localize('relaunchSettingDetail', "Press the restart button to restart {0} and enable the setting.", this.envService.appNameLong) : - localize('relaunchSettingDetailWeb', "Press the reload button to reload {0} and enable the setting.", this.envService.appNameLong), + localize('relaunchSettingDetail', "Press the restart button to restart {0} and enable the setting.", this.productService.nameLong) : + localize('relaunchSettingDetailWeb', "Press the reload button to reload {0} and enable the setting.", this.productService.nameLong), isNative ? localize('restart', "&&Restart") : localize('restartWeb', "&&Reload"), diff --git a/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts b/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts new file mode 100644 index 0000000000..b7b4066a75 --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts @@ -0,0 +1,114 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import * as dom from 'vs/base/browser/dom'; + +import { IActionRunner, IAction, Action } from 'vs/base/common/actions'; +import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { attachSelectBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { selectBorder } from 'vs/platform/theme/common/colorRegistry'; +import { IRemoteExplorerService, REMOTE_EXPLORER_TYPE_KEY } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; +import { IViewDescriptor } from 'vs/workbench/common/views'; +import { startsWith } from 'vs/base/common/strings'; +import { isStringArray } from 'vs/base/common/types'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; + +export interface IRemoteSelectItem extends ISelectOptionItem { + authority: string[]; +} + +export class SwitchRemoteViewItem extends SelectActionViewItem { + + actionRunner!: IActionRunner; + + constructor( + action: IAction, + private readonly optionsItems: IRemoteSelectItem[], + @IThemeService private readonly themeService: IThemeService, + @IContextViewService contextViewService: IContextViewService, + @IRemoteExplorerService remoteExplorerService: IRemoteExplorerService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IStorageService private readonly storageService: IStorageService + ) { + super(null, action, optionsItems, 0, contextViewService, { ariaLabel: nls.localize('remotes', 'Switch Remote') }); + this._register(attachSelectBoxStyler(this.selectBox, themeService, { + selectBackground: SIDE_BAR_BACKGROUND + })); + + this.setSelectionForConnection(optionsItems, environmentService, remoteExplorerService); + } + + private setSelectionForConnection(optionsItems: IRemoteSelectItem[], environmentService: IWorkbenchEnvironmentService, remoteExplorerService: IRemoteExplorerService) { + if (this.optionsItems.length > 0) { + let index = 0; + const remoteAuthority = environmentService.configuration.remoteAuthority; + const explorerType: string | undefined = remoteAuthority ? remoteAuthority.split('+')[0] : + this.storageService.get(REMOTE_EXPLORER_TYPE_KEY, StorageScope.WORKSPACE) ?? this.storageService.get(REMOTE_EXPLORER_TYPE_KEY, StorageScope.GLOBAL); + if (explorerType) { + index = this.getOptionIndexForExplorerType(optionsItems, explorerType); + } + this.select(index); + remoteExplorerService.targetType = optionsItems[index].authority[0]; + } + } + + private getOptionIndexForExplorerType(optionsItems: IRemoteSelectItem[], explorerType: string): number { + let index = 0; + for (let optionIterator = 0; (optionIterator < this.optionsItems.length) && (index === 0); optionIterator++) { + for (let authorityIterator = 0; authorityIterator < optionsItems[optionIterator].authority.length; authorityIterator++) { + if (optionsItems[optionIterator].authority[authorityIterator] === explorerType) { + index = optionIterator; + break; + } + } + } + return index; + } + + render(container: HTMLElement) { + super.render(container); + dom.addClass(container, 'switch-remote'); + this._register(attachStylerCallback(this.themeService, { selectBorder }, colors => { + container.style.border = colors.selectBorder ? `1px solid ${colors.selectBorder}` : ''; + })); + } + + protected getActionContext(_: string, index: number): any { + return this.optionsItems[index]; + } + + static createOptionItems(views: IViewDescriptor[]): IRemoteSelectItem[] { + let options: IRemoteSelectItem[] = []; + views.forEach(view => { + if (view.group && startsWith(view.group, 'targets') && view.remoteAuthority) { + options.push({ text: view.name, authority: isStringArray(view.remoteAuthority) ? view.remoteAuthority : [view.remoteAuthority] }); + } + }); + return options; + } +} + +export class SwitchRemoteAction extends Action { + + public static readonly ID = 'remote.explorer.switch'; + public static readonly LABEL = nls.localize('remote.explorer.switch', "Switch Remote"); + + constructor( + id: string, label: string, + @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService + ) { + super(id, label); + } + + public async run(item: IRemoteSelectItem): Promise { + this.remoteExplorerService.targetType = item.authority[0]; + } +} diff --git a/src/vs/workbench/contrib/remote/browser/help-documentation-dark.svg b/src/vs/workbench/contrib/remote/browser/help-documentation-dark.svg deleted file mode 100644 index 2673902c68..0000000000 --- a/src/vs/workbench/contrib/remote/browser/help-documentation-dark.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - diff --git a/src/vs/workbench/contrib/remote/browser/help-documentation-hc.svg b/src/vs/workbench/contrib/remote/browser/help-documentation-hc.svg deleted file mode 100644 index e8dc8205ba..0000000000 --- a/src/vs/workbench/contrib/remote/browser/help-documentation-hc.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - diff --git a/src/vs/workbench/contrib/remote/browser/help-documentation-light.svg b/src/vs/workbench/contrib/remote/browser/help-documentation-light.svg deleted file mode 100644 index 4a3009baee..0000000000 --- a/src/vs/workbench/contrib/remote/browser/help-documentation-light.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - diff --git a/src/vs/workbench/contrib/remote/browser/help-feedback-dark.svg b/src/vs/workbench/contrib/remote/browser/help-feedback-dark.svg deleted file mode 100644 index 5d99408934..0000000000 --- a/src/vs/workbench/contrib/remote/browser/help-feedback-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/remote/browser/help-feedback-hc.svg b/src/vs/workbench/contrib/remote/browser/help-feedback-hc.svg deleted file mode 100644 index 941430e9dd..0000000000 --- a/src/vs/workbench/contrib/remote/browser/help-feedback-hc.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/remote/browser/help-feedback-light.svg b/src/vs/workbench/contrib/remote/browser/help-feedback-light.svg deleted file mode 100644 index 72437202b7..0000000000 --- a/src/vs/workbench/contrib/remote/browser/help-feedback-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/remote/browser/help-getting-started-dark.svg b/src/vs/workbench/contrib/remote/browser/help-getting-started-dark.svg deleted file mode 100644 index 0ea65d8319..0000000000 --- a/src/vs/workbench/contrib/remote/browser/help-getting-started-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/remote/browser/help-getting-started-hc.svg b/src/vs/workbench/contrib/remote/browser/help-getting-started-hc.svg deleted file mode 100644 index 5bb05d3d8c..0000000000 --- a/src/vs/workbench/contrib/remote/browser/help-getting-started-hc.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/remote/browser/help-getting-started-light.svg b/src/vs/workbench/contrib/remote/browser/help-getting-started-light.svg deleted file mode 100644 index 46cde7f7cc..0000000000 --- a/src/vs/workbench/contrib/remote/browser/help-getting-started-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/remote/browser/help-report-issue-dark.svg b/src/vs/workbench/contrib/remote/browser/help-report-issue-dark.svg deleted file mode 100644 index 0117ceb7de..0000000000 --- a/src/vs/workbench/contrib/remote/browser/help-report-issue-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/remote/browser/help-report-issue-hc.svg b/src/vs/workbench/contrib/remote/browser/help-report-issue-hc.svg deleted file mode 100644 index b0c521b7dc..0000000000 --- a/src/vs/workbench/contrib/remote/browser/help-report-issue-hc.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/remote/browser/help-report-issue-light.svg b/src/vs/workbench/contrib/remote/browser/help-report-issue-light.svg deleted file mode 100644 index 5da9322b6a..0000000000 --- a/src/vs/workbench/contrib/remote/browser/help-report-issue-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/remote/browser/help-review-issues-dark.svg b/src/vs/workbench/contrib/remote/browser/help-review-issues-dark.svg deleted file mode 100644 index 21eec9cbcb..0000000000 --- a/src/vs/workbench/contrib/remote/browser/help-review-issues-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/remote/browser/help-review-issues-hc.svg b/src/vs/workbench/contrib/remote/browser/help-review-issues-hc.svg deleted file mode 100644 index 94013ea52a..0000000000 --- a/src/vs/workbench/contrib/remote/browser/help-review-issues-hc.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/remote/browser/help-review-issues-light.svg b/src/vs/workbench/contrib/remote/browser/help-review-issues-light.svg deleted file mode 100644 index 826d0eefbf..0000000000 --- a/src/vs/workbench/contrib/remote/browser/help-review-issues-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/src/vs/editor/browser/widget/media/tokens.css b/src/vs/workbench/contrib/remote/browser/media/tunnelView.css similarity index 73% rename from src/vs/editor/browser/widget/media/tokens.css rename to src/vs/workbench/contrib/remote/browser/media/tunnelView.css index 23d80dd9be..258a252ffb 100644 --- a/src/vs/editor/browser/widget/media/tokens.css +++ b/src/vs/workbench/contrib/remote/browser/media/tunnelView.css @@ -3,7 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-editor .vs-whitespace { - display:inline-block; +.customview-tree .tunnel-view-label { + flex: 1; } +.customview-tree .tunnel-view-label .action-label.codicon { + margin-top: 4px; +} diff --git a/src/vs/workbench/contrib/remote/browser/remote-activity-bar.svg b/src/vs/workbench/contrib/remote/browser/remote-activity-bar.svg deleted file mode 100644 index 029e6b051c..0000000000 --- a/src/vs/workbench/contrib/remote/browser/remote-activity-bar.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index f8df07e692..33b76f6db1 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -5,7 +5,6 @@ import 'vs/css!./remoteViewlet'; import * as nls from 'vs/nls'; -import * as dom from 'vs/base/browser/dom'; import { URI } from 'vs/base/common/uri'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -16,22 +15,14 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { ViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet'; +import { FilterViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { VIEWLET_ID, VIEW_CONTAINER } from 'vs/workbench/contrib/remote/common/remote.contribution'; -import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; -import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IViewDescriptor, IViewsRegistry, Extensions } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { WorkbenchAsyncDataTree, TreeResourceNavigator2 } from 'vs/platform/list/browser/listService'; -import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { ITreeRenderer, ITreeNode, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; -import { Event } from 'vs/base/common/event'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, ShowViewletAction } from 'vs/workbench/browser/viewlet'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -47,245 +38,105 @@ import Severity from 'vs/base/common/severity'; import { ReloadWindowAction } from 'vs/workbench/browser/actions/windowActions'; import { IDisposable } from 'vs/base/common/lifecycle'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { SwitchRemoteViewItem, SwitchRemoteAction } from 'vs/workbench/contrib/remote/browser/explorerViewItems'; +import { Action, IActionViewItem, IAction } from 'vs/base/common/actions'; +import { isStringArray } from 'vs/base/common/types'; +import { IRemoteExplorerService, HelpInformation } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; - -interface HelpInformation { - extensionDescription: IExtensionDescription; - getStarted?: string; - documentation?: string; - feedback?: string; - issues?: string; -} - -const remoteHelpExtPoint = ExtensionsRegistry.registerExtensionPoint({ - extensionPoint: 'remoteHelp', - jsonSchema: { - description: nls.localize('RemoteHelpInformationExtPoint', 'Contributes help information for Remote'), - type: 'object', - properties: { - 'getStarted': { - description: nls.localize('RemoteHelpInformationExtPoint.getStarted', "The url to your project's Getting Started page"), - type: 'string' - }, - 'documentation': { - description: nls.localize('RemoteHelpInformationExtPoint.documentation', "The url to your project's documentation page"), - type: 'string' - }, - 'feedback': { - description: nls.localize('RemoteHelpInformationExtPoint.feedback', "The url to your project's feedback reporter"), - type: 'string' - }, - 'issues': { - description: nls.localize('RemoteHelpInformationExtPoint.issues', "The url to your project's issues list"), - type: 'string' - } - } - } -}); - -interface IViewModel { - helpInformations: HelpInformation[]; -} - -class HelpTreeVirtualDelegate implements IListVirtualDelegate { - getHeight(element: IHelpItem): number { - return 22; - } - - getTemplateId(element: IHelpItem): string { - return 'HelpItemTemplate'; - } -} - -interface IHelpItemTemplateData { - parent: HTMLElement; - icon: HTMLElement; -} - -class HelpTreeRenderer implements ITreeRenderer { - templateId: string = 'HelpItemTemplate'; - - renderTemplate(container: HTMLElement): IHelpItemTemplateData { - dom.addClass(container, 'remote-help-tree-node-item'); - - const icon = dom.append(container, dom.$('.remote-help-tree-node-item-icon')); - - const data = Object.create(null); - data.parent = container; - data.icon = icon; - - return data; - } - - renderElement(element: ITreeNode, index: number, templateData: IHelpItemTemplateData, height: number | undefined): void { - const container = templateData.parent; - dom.append(container, templateData.icon); - dom.addClass(templateData.icon, element.element.key); - const labelContainer = dom.append(container, dom.$('.help-item-label')); - labelContainer.innerText = element.element.label; - } - - disposeTemplate(templateData: IHelpItemTemplateData): void { - - } -} - -class HelpDataSource implements IAsyncDataSource { - hasChildren(element: any) { - return element instanceof HelpModel; - } - - getChildren(element: any) { - if (element instanceof HelpModel && element.items) { - return element.items; - } - - return []; - } -} - -interface IHelpItem { - key: string; - label: string; - handleClick(): Promise; -} - -class HelpItem implements IHelpItem { - constructor( - public key: string, - public label: string, - public values: { extensionDescription: IExtensionDescription; url: string }[], - private openerService: IOpenerService, - private quickInputService: IQuickInputService - ) { - } - - async handleClick() { - if (this.values.length > 1) { - let actions = this.values.map(value => { - return { - label: value.extensionDescription.displayName || value.extensionDescription.identifier.value, - description: value.url - }; - }); - - const action = await this.quickInputService.pick(actions, { placeHolder: nls.localize('pickRemoteExtension', "Select url to open") }); - - if (action) { - await this.openerService.open(URI.parse(action.label)); - } - } else { - await this.openerService.open(URI.parse(this.values[0].url)); - } - } -} - -class IssueReporterItem implements IHelpItem { - constructor( - public key: string, - public label: string, - public extensionDescriptions: IExtensionDescription[], - private quickInputService: IQuickInputService, - private commandService: ICommandService - ) { - } - - async handleClick() { - if (this.extensionDescriptions.length > 1) { - let actions = this.extensionDescriptions.map(extension => { - return { - label: extension.displayName || extension.identifier.value, - identifier: extension.identifier - }; - }); - - const action = await this.quickInputService.pick(actions, { placeHolder: nls.localize('pickRemoteExtensionToReportIssue', "Select an extension to report issue") }); - - if (action) { - await this.commandService.executeCommand('workbench.action.openIssueReporter', [action.identifier.value]); - } - } else { - await this.commandService.executeCommand('workbench.action.openIssueReporter', [this.extensionDescriptions[0].identifier.value]); - } - } -} +import { startsWith } from 'vs/base/common/strings'; +import { TunnelPanelDescriptor, TunnelViewModel } from 'vs/workbench/contrib/remote/browser/tunnelView'; +import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views'; +import { ViewletPane } from 'vs/workbench/browser/parts/views/paneViewlet'; class HelpModel { items: IHelpItem[] | undefined; constructor( - viewModel: IViewModel, openerService: IOpenerService, quickInputService: IQuickInputService, - commandService: ICommandService + commandService: ICommandService, + remoteExplorerService: IRemoteExplorerService, + environmentService: IWorkbenchEnvironmentService ) { let helpItems: IHelpItem[] = []; - const getStarted = viewModel.helpInformations.filter(info => info.getStarted); + const getStarted = remoteExplorerService.helpInformation.filter(info => info.getStarted); if (getStarted.length) { helpItems.push(new HelpItem( - 'getStarted', - nls.localize('remote.help.getStarted', "Get Started"), + ['getStarted'], + nls.localize('remote.help.getStarted', "$(star) Get Started"), getStarted.map((info: HelpInformation) => ({ extensionDescription: info.extensionDescription, - url: info.getStarted! + url: info.getStarted!, + remoteAuthority: (typeof info.remoteName === 'string') ? [info.remoteName] : info.remoteName })), - openerService, - quickInputService + quickInputService, + environmentService, + openerService )); } - const documentation = viewModel.helpInformations.filter(info => info.documentation); + const documentation = remoteExplorerService.helpInformation.filter(info => info.documentation); if (documentation.length) { helpItems.push(new HelpItem( - 'documentation', - nls.localize('remote.help.documentation', "Read Documentation"), + ['documentation'], + nls.localize('remote.help.documentation', "$(book) Read Documentation"), documentation.map((info: HelpInformation) => ({ extensionDescription: info.extensionDescription, - url: info.documentation! + url: info.documentation!, + remoteAuthority: (typeof info.remoteName === 'string') ? [info.remoteName] : info.remoteName })), - openerService, - quickInputService + quickInputService, + environmentService, + openerService )); } - const feedback = viewModel.helpInformations.filter(info => info.feedback); + const feedback = remoteExplorerService.helpInformation.filter(info => info.feedback); if (feedback.length) { helpItems.push(new HelpItem( - 'feedback', - nls.localize('remote.help.feedback', "Provide Feedback"), + ['feedback'], + nls.localize('remote.help.feedback', "$(twitter) Provide Feedback"), feedback.map((info: HelpInformation) => ({ extensionDescription: info.extensionDescription, - url: info.feedback! + url: info.feedback!, + remoteAuthority: (typeof info.remoteName === 'string') ? [info.remoteName] : info.remoteName })), - openerService, - quickInputService + quickInputService, + environmentService, + openerService )); } - const issues = viewModel.helpInformations.filter(info => info.issues); + const issues = remoteExplorerService.helpInformation.filter(info => info.issues); if (issues.length) { helpItems.push(new HelpItem( - 'issues', - nls.localize('remote.help.issues', "Review Issues"), + ['issues'], + nls.localize('remote.help.issues', "$(issues) Review Issues"), issues.map((info: HelpInformation) => ({ extensionDescription: info.extensionDescription, - url: info.issues! + url: info.issues!, + remoteAuthority: (typeof info.remoteName === 'string') ? [info.remoteName] : info.remoteName })), - openerService, - quickInputService + quickInputService, + environmentService, + openerService )); } if (helpItems.length) { helpItems.push(new IssueReporterItem( - 'issueReporter', - nls.localize('remote.help.report', "Report Issue"), - viewModel.helpInformations.map(info => info.extensionDescription), + ['issueReporter'], + nls.localize('remote.help.report', "$(comment) Report Issue"), + remoteExplorerService.helpInformation.map(info => ({ + extensionDescription: info.extensionDescription, + remoteAuthority: (typeof info.remoteName === 'string') ? [info.remoteName] : info.remoteName + })), quickInputService, + environmentService, commandService )); } @@ -296,79 +147,126 @@ class HelpModel { } } -class HelpPanel extends ViewletPanel { - static readonly ID = '~remote.helpPanel'; - static readonly TITLE = nls.localize('remote.help', "Help and feedback"); - private tree!: WorkbenchAsyncDataTree; +interface IHelpItem extends IQuickPickItem { + label: string; + handleClick(): Promise; +} +abstract class HelpItemBase implements IHelpItem { constructor( - protected viewModel: IViewModel, - options: IViewletPanelOptions, - @IKeybindingService protected keybindingService: IKeybindingService, - @IContextMenuService protected contextMenuService: IContextMenuService, - @IContextKeyService protected contextKeyService: IContextKeyService, - @IConfigurationService protected configurationService: IConfigurationService, - @IInstantiationService protected readonly instantiationService: IInstantiationService, - @IOpenerService protected openerService: IOpenerService, - @IQuickInputService protected quickInputService: IQuickInputService, - @ICommandService protected commandService: ICommandService - - + public iconClasses: string[], + public label: string, + public values: { extensionDescription: IExtensionDescription, url?: string, remoteAuthority: string[] | undefined }[], + private quickInputService: IQuickInputService, + private environmentService: IWorkbenchEnvironmentService ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService); + iconClasses.push('remote-help-tree-node-item-icon'); } - protected renderBody(container: HTMLElement): void { - dom.addClass(container, 'remote-help'); - const treeContainer = document.createElement('div'); - dom.addClass(treeContainer, 'remote-help-content'); - container.appendChild(treeContainer); - - this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, - 'RemoteHelp', - treeContainer, - new HelpTreeVirtualDelegate(), - [new HelpTreeRenderer()], - new HelpDataSource(), - { - keyboardSupport: true, + async handleClick() { + const remoteAuthority = this.environmentService.configuration.remoteAuthority; + if (remoteAuthority) { + for (let value of this.values) { + if (value.remoteAuthority) { + for (let authority of value.remoteAuthority) { + if (startsWith(remoteAuthority, authority)) { + await this.takeAction(value.extensionDescription, value.url); + return; + } + } + } } - ); + } - const model = new HelpModel(this.viewModel, this.openerService, this.quickInputService, this.commandService); + if (this.values.length > 1) { + let actions = this.values.map(value => { + return { + label: value.extensionDescription.displayName || value.extensionDescription.identifier.value, + description: value.url, + extensionDescription: value.extensionDescription + }; + }); - this.tree.setInput(model); + const action = await this.quickInputService.pick(actions, { placeHolder: nls.localize('pickRemoteExtension', "Select url to open") }); - const helpItemNavigator = this._register(new TreeResourceNavigator2(this.tree, { openOnFocus: false, openOnSelection: false })); - - this._register(Event.debounce(helpItemNavigator.onDidOpenResource, (last, event) => event, 75, true)(e => { - e.element.handleClick(); - })); + if (action) { + await this.takeAction(action.extensionDescription, action.description); + } + } else { + await this.takeAction(this.values[0].extensionDescription, this.values[0].url); + } } - protected layoutBody(height: number, width: number): void { - this.tree.layout(height, width); + protected abstract takeAction(extensionDescription: IExtensionDescription, url?: string): Promise; +} + +class HelpItem extends HelpItemBase { + constructor( + iconClasses: string[], + label: string, + values: { extensionDescription: IExtensionDescription; url: string, remoteAuthority: string[] | undefined }[], + quickInputService: IQuickInputService, + environmentService: IWorkbenchEnvironmentService, + private openerService: IOpenerService + ) { + super(iconClasses, label, values, quickInputService, environmentService); + } + + protected async takeAction(extensionDescription: IExtensionDescription, url: string): Promise { + await this.openerService.open(URI.parse(url)); } } -class HelpPanelDescriptor implements IViewDescriptor { - readonly id = HelpPanel.ID; - readonly name = HelpPanel.TITLE; - readonly ctorDescriptor: { ctor: any, arguments?: any[] }; - readonly canToggleVisibility = true; - readonly hideByDefault = false; - readonly workspace = true; +class IssueReporterItem extends HelpItemBase { + constructor( + iconClasses: string[], + label: string, + values: { extensionDescription: IExtensionDescription; remoteAuthority: string[] | undefined }[], + quickInputService: IQuickInputService, + environmentService: IWorkbenchEnvironmentService, + private commandService: ICommandService, + ) { + super(iconClasses, label, values, quickInputService, environmentService); + } - constructor(viewModel: IViewModel) { - this.ctorDescriptor = { ctor: HelpPanel, arguments: [viewModel] }; + protected async takeAction(extensionDescription: IExtensionDescription): Promise { + await this.commandService.executeCommand('workbench.action.openIssueReporter', [extensionDescription.identifier.value]); } } +class HelpAction extends Action { + static readonly ID = 'remote.explorer.help'; + static readonly LABEL = nls.localize('remote.explorer.help', "Help and Feedback"); + private helpModel: HelpModel; -export class RemoteViewlet extends ViewContainerViewlet implements IViewModel { - private helpPanelDescriptor = new HelpPanelDescriptor(this); + constructor(id: string, + label: string, + @IOpenerService private readonly openerService: IOpenerService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @ICommandService private readonly commandService: ICommandService, + @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService + ) { + super(id, label, 'codicon codicon-question'); + this.helpModel = new HelpModel(openerService, quickInputService, commandService, remoteExplorerService, environmentService); + } - helpInformations: HelpInformation[] = []; + async run(event?: any): Promise { + if (!this.helpModel.items) { + this.helpModel = new HelpModel(this.openerService, this.quickInputService, this.commandService, this.remoteExplorerService, this.environmentService); + } + if (this.helpModel.items) { + const selection = await this.quickInputService.pick(this.helpModel.items, { placeHolder: nls.localize('remote.explorer.helpPlaceholder', "Help and Feedback") }); + if (selection) { + return selection.handleClick(); + } + } + } +} + +export class RemoteViewlet extends FilterViewContainerViewlet { + private actions: IAction[] | undefined; + private tunnelPanelDescriptor: TunnelPanelDescriptor | undefined; constructor( @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @@ -380,88 +278,59 @@ export class RemoteViewlet extends ViewContainerViewlet implements IViewModel { @IThemeService themeService: IThemeService, @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, - @IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService, + @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); - - remoteHelpExtPoint.setHandler((extensions) => { - let helpInformation: HelpInformation[] = []; - for (let extension of extensions) { - this._handleRemoteInfoExtensionPoint(extension, helpInformation); - } - - this.helpInformations = helpInformation; - - const viewsRegistry = Registry.as(Extensions.ViewsRegistry); - if (this.helpInformations.length) { - viewsRegistry.registerViews([this.helpPanelDescriptor], VIEW_CONTAINER); - } else { - viewsRegistry.deregisterViews([this.helpPanelDescriptor], VIEW_CONTAINER); - } - }); + super(VIEWLET_ID, remoteExplorerService.onDidChangeTargetType, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); } - private _handleRemoteInfoExtensionPoint(extension: IExtensionPointUser, helpInformation: HelpInformation[]) { - if (!extension.description.enableProposedApi) { - return; - } - - if (!extension.value.documentation && !extension.value.feedback && !extension.value.getStarted && !extension.value.issues) { - return; - } - - helpInformation.push({ - extensionDescription: extension.description, - getStarted: extension.value.getStarted, - documentation: extension.value.documentation, - feedback: extension.value.feedback, - issues: extension.value.issues - }); + protected getFilterOn(viewDescriptor: IViewDescriptor): string | undefined { + return isStringArray(viewDescriptor.remoteAuthority) ? viewDescriptor.remoteAuthority[0] : viewDescriptor.remoteAuthority; } - onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPanel[] { - // too late, already added to the view model - const result = super.onDidAddViews(added); + public getActionViewItem(action: Action): IActionViewItem | undefined { + if (action.id === SwitchRemoteAction.ID) { + return this.instantiationService.createInstance(SwitchRemoteViewItem, action, SwitchRemoteViewItem.createOptionItems(Registry.as(Extensions.ViewsRegistry).getViews(VIEW_CONTAINER))); + } - const remoteAuthority = this.environmentService.configuration.remoteAuthority; - if (remoteAuthority) { - const actualRemoteAuthority = remoteAuthority.split('+')[0]; - added.forEach((descriptor) => { - const panel = this.getView(descriptor.viewDescriptor.id); - if (!panel) { - return; - } + return super.getActionViewItem(action); + } - const descriptorAuthority = descriptor.viewDescriptor.remoteAuthority; - if (typeof descriptorAuthority === 'undefined') { - panel.setExpanded(true); - } else if (descriptor.viewDescriptor.id === HelpPanel.ID) { - // Do nothing, keep the default behavior for Help - } else { - const descriptorAuthorityArr = Array.isArray(descriptorAuthority) ? descriptorAuthority : [descriptorAuthority]; - if (descriptorAuthorityArr.indexOf(actualRemoteAuthority) >= 0) { - panel.setExpanded(true); - } else { - panel.setExpanded(false); - } - } + public getActions(): IAction[] { + if (!this.actions) { + this.actions = [ + this.instantiationService.createInstance(SwitchRemoteAction, SwitchRemoteAction.ID, SwitchRemoteAction.LABEL), + this.instantiationService.createInstance(HelpAction, HelpAction.ID, HelpAction.LABEL) + ]; + this.actions.forEach(a => { + this._register(a); }); } - - return result; + return this.actions; } getTitle(): string { const title = nls.localize('remote.explorer', "Remote Explorer"); return title; } + + onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPane[] { + // Call to super MUST be first, since registering the additional view will cause this to be called again. + const panels: ViewletPane[] = super.onDidAddViews(added); + if (this.environmentService.configuration.remoteAuthority && !this.tunnelPanelDescriptor && this.configurationService.getValue('remote.forwardedPortsView.visible')) { + this.tunnelPanelDescriptor = new TunnelPanelDescriptor(new TunnelViewModel(this.remoteExplorerService), this.environmentService); + const viewsRegistry = Registry.as(Extensions.ViewsRegistry); + viewsRegistry.registerViews([this.tunnelPanelDescriptor!], VIEW_CONTAINER); + } + return panels; + } } -Registry.as(ViewletExtensions.Viewlets).registerViewlet(new ViewletDescriptor( +Registry.as(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( RemoteViewlet, VIEWLET_ID, nls.localize('remote.explorer', "Remote Explorer"), - 'remote', + 'codicon-remote-explorer', 4 )); @@ -477,7 +346,7 @@ class OpenRemoteViewletAction extends ShowViewletAction { // Register Action to Open Viewlet Registry.as(WorkbenchActionExtensions.WorkbenchActions).registerWorkbenchAction( - new SyncActionDescriptor(OpenRemoteViewletAction, VIEWLET_ID, nls.localize('toggleRemoteViewlet', "Show Remote Explorer"), { + SyncActionDescriptor.create(OpenRemoteViewletAction, VIEWLET_ID, nls.localize('toggleRemoteViewlet', "Show Remote Explorer"), { primary: 0 }), 'View: Show Remote Explorer', @@ -525,7 +394,7 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { let reconnectWaitEvent: ReconnectionWaitEvent | null = null; let disposableListener: IDisposable | null = null; - function showProgress(location: ProgressLocation, buttons?: string[]) { + function showProgress(location: ProgressLocation, buttons: { label: string, callback: () => void }[]) { if (currentProgressPromiseResolve) { currentProgressPromiseResolve(); } @@ -536,12 +405,12 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { if (location === ProgressLocation.Dialog) { // Show dialog progressService!.withProgress( - { location: ProgressLocation.Dialog, buttons }, + { location: ProgressLocation.Dialog, buttons: buttons.map(button => button.label) }, (progress) => { if (progressReporter) { progressReporter.currentProgress = progress; } return promise; }, (choice?) => { // Handle choice from dialog - if (choice === 0 && buttons && reconnectWaitEvent) { - reconnectWaitEvent.skipWait(); + if (buttons[choice]) { + buttons[choice].callback(); } else { showProgress(ProgressLocation.Notification, buttons); } @@ -551,12 +420,12 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { } else { // Show notification progressService!.withProgress( - { location: ProgressLocation.Notification, buttons }, + { location: ProgressLocation.Notification, buttons: buttons.map(button => button.label) }, (progress) => { if (progressReporter) { progressReporter.currentProgress = progress; } return promise; }, (choice?) => { - // Handle choice from notification - if (choice === 0 && buttons && reconnectWaitEvent) { - reconnectWaitEvent.skipWait(); + // Handle choice from dialog + if (buttons[choice]) { + buttons[choice].callback(); } else { hideProgress(); } @@ -572,6 +441,22 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { currentProgressPromiseResolve = null; } + const reconnectButton = { + label: nls.localize('reconnectNow', "Reconnect Now"), + callback: () => { + if (reconnectWaitEvent) { + reconnectWaitEvent.skipWait(); + } + } + }; + + const reloadButton = { + label: nls.localize('reloadWindow', "Reload Window"), + callback: () => { + commandService.executeCommand(ReloadWindowAction.ID); + } + }; + connection.onDidStateChange((e) => { if (currentTimer) { currentTimer.dispose(); @@ -586,7 +471,7 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { case PersistentConnectionEventType.ConnectionLost: if (!currentProgressPromiseResolve) { progressReporter = new ProgressReporter(null); - showProgress(ProgressLocation.Dialog, [nls.localize('reconnectNow', "Reconnect Now")]); + showProgress(ProgressLocation.Dialog, [reconnectButton, reloadButton]); } progressReporter!.report(nls.localize('connectionLost', "Connection Lost")); @@ -594,12 +479,12 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { case PersistentConnectionEventType.ReconnectionWait: hideProgress(); reconnectWaitEvent = e; - showProgress(lastLocation || ProgressLocation.Notification, [nls.localize('reconnectNow', "Reconnect Now")]); + showProgress(lastLocation || ProgressLocation.Notification, [reconnectButton, reloadButton]); currentTimer = new ReconnectionTimer(progressReporter!, Date.now() + 1000 * e.durationSeconds); break; case PersistentConnectionEventType.ReconnectionRunning: hideProgress(); - showProgress(lastLocation || ProgressLocation.Notification); + showProgress(lastLocation || ProgressLocation.Notification, [reloadButton]); progressReporter!.report(nls.localize('reconnectionRunning', "Attempting to reconnect...")); // Register to listen for quick input is opened @@ -609,7 +494,7 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { // Need to move from dialog if being shown and user needs to type in a prompt if (lastLocation === ProgressLocation.Dialog && progressReporter !== null) { hideProgress(); - showProgress(ProgressLocation.Notification); + showProgress(ProgressLocation.Notification, [reloadButton]); progressReporter.report(); } } diff --git a/src/vs/workbench/contrib/remote/browser/remoteViewlet.css b/src/vs/workbench/contrib/remote/browser/remoteViewlet.css index a278b060ec..55384a1d20 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteViewlet.css +++ b/src/vs/workbench/contrib/remote/browser/remoteViewlet.css @@ -3,90 +3,37 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .activitybar>.content .monaco-action-bar .action-label.remote { - -webkit-mask: url('remote-activity-bar.svg') no-repeat 50% 50%; -} - -.remote-help-content .monaco-list .monaco-list-row .remote-help-tree-node-item { - display: flex; - height: 22px; - line-height: 22px; - flex: 1; - text-overflow: ellipsis; - overflow: hidden; - flex-wrap: nowrap; -} - -.remote-help-content .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon { +.remote-help-tree-node-item-icon { background-size: 16px; background-position: left center; background-repeat: no-repeat; - padding-right: 6px; - width: 16px; - height: 22px; -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.remote-help-tree-node-item-icon .monaco-icon-label-description-container { + padding-left: 22px; } .remote-help-content .monaco-list .monaco-list-row .monaco-tl-twistie { width: 0px !important; } -.vs .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.getStarted { - background-image: url('help-getting-started-light.svg') +.monaco-workbench .part > .title > .title-actions .switch-remote { + display: flex; + align-items: center; + font-size: 11px; + margin-right: 0.3em; + height: 20px; + flex-shrink: 1; + margin-top: 7px; } -.vs .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.documentation { - background-image: url('help-documentation-light.svg') +.switch-remote > .monaco-select-box { + border: none; + display: block; } -.vs .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.feedback { - background-image: url('help-feedback-light.svg') -} - -.vs .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.issues { - background-image: url('help-review-issues-light.svg') -} - -.vs .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.issueReporter { - background-image: url('help-report-issue-light.svg') -} - -.vs-dark .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.getStarted { - background-image: url('help-getting-started-dark.svg') -} - -.vs-dark .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.documentation { - background-image: url('help-documentation-dark.svg') -} - -.vs-dark .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.feedback { - background-image: url('help-feedback-dark.svg') -} - -.vs-dark .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.issues { - background-image: url('help-review-issues-dark.svg') -} - -.vs-dark .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.issueReporter { - background-image: url('help-report-issue-dark.svg') -} - -.hc-black .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.getStarted { - background-image: url('help-getting-started-hc.svg') -} - -.hc-black .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.documentation { - background-image: url('help-documentation-hc.svg') -} - -.hc-black .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.feedback { - background-image: url('help-feedback-hc.svg') -} - -.hc-black .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.issues { - background-image: url('help-review-issues-hc.svg') -} - -.hc-black .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.issueReporter { - background-image: url('help-report-issue-hc.svg') +.monaco-workbench .part > .title > .title-actions .switch-remote > .monaco-select-box { + padding-left: 3px; } diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts new file mode 100644 index 0000000000..92c2d7a3af --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -0,0 +1,757 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./media/tunnelView'; +import * as nls from 'vs/nls'; +import * as dom from 'vs/base/browser/dom'; +import { IViewDescriptor, IEditableData } from 'vs/workbench/common/views'; +import { WorkbenchAsyncDataTree, TreeResourceNavigator2 } from 'vs/platform/list/browser/listService'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { IContextKeyService, IContextKey, RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { ICommandService, ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { Event, Emitter } from 'vs/base/common/event'; +import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { ITreeRenderer, ITreeNode, IAsyncDataSource, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { Disposable, IDisposable, toDisposable, MutableDisposable, dispose } from 'vs/base/common/lifecycle'; +import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet'; +import { ActionBar, ActionViewItem, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; +import { ActionRunner, IAction } from 'vs/base/common/actions'; +import { IMenuService, MenuId, IMenu, MenuRegistry, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { createAndFillInContextMenuActions, createAndFillInActionBarActions, ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IRemoteExplorerService, TunnelModel } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; +import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; +import { once } from 'vs/base/common/functional'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { URI } from 'vs/base/common/uri'; + +class TunnelTreeVirtualDelegate implements IListVirtualDelegate { + getHeight(element: ITunnelItem): number { + return 22; + } + + getTemplateId(element: ITunnelItem): string { + return 'tunnelItemTemplate'; + } +} + +export interface ITunnelViewModel { + onForwardedPortsChanged: Event; + readonly forwarded: TunnelItem[]; + readonly published: TunnelItem[]; + readonly candidates: TunnelItem[]; + readonly groups: ITunnelGroup[]; +} + +export class TunnelViewModel extends Disposable implements ITunnelViewModel { + private _onForwardedPortsChanged: Emitter = new Emitter(); + public onForwardedPortsChanged: Event = this._onForwardedPortsChanged.event; + private model: TunnelModel; + + constructor( + @IRemoteExplorerService remoteExplorerService: IRemoteExplorerService) { + super(); + this.model = remoteExplorerService.tunnelModel; + this._register(this.model.onForwardPort(() => this._onForwardedPortsChanged.fire())); + this._register(this.model.onClosePort(() => this._onForwardedPortsChanged.fire())); + this._register(this.model.onPortName(() => this._onForwardedPortsChanged.fire())); + } + + get groups(): ITunnelGroup[] { + const groups: ITunnelGroup[] = []; + if (this.model.forwarded.size > 0) { + groups.push({ + label: nls.localize('remote.tunnelsView.forwarded', "Forwarded"), + tunnelType: TunnelType.Forwarded, + items: this.forwarded + }); + } + if (this.model.published.size > 0) { + groups.push({ + label: nls.localize('remote.tunnelsView.published', "Published"), + tunnelType: TunnelType.Published, + items: this.published + }); + } + const candidates = this.candidates; + if (this.candidates.length > 0) { + groups.push({ + label: nls.localize('remote.tunnelsView.candidates', "Candidates"), + tunnelType: TunnelType.Candidate, + items: candidates + }); + } + groups.push({ + label: nls.localize('remote.tunnelsView.add', "Add a Port..."), + tunnelType: TunnelType.Add, + }); + return groups; + } + + get forwarded(): TunnelItem[] { + return Array.from(this.model.forwarded.values()).map(tunnel => { + return new TunnelItem(TunnelType.Forwarded, tunnel.remote, tunnel.localAddress, tunnel.closeable, tunnel.name, tunnel.description); + }); + } + + get published(): TunnelItem[] { + return Array.from(this.model.published.values()).map(tunnel => { + return new TunnelItem(TunnelType.Published, tunnel.remote, tunnel.localAddress, false, tunnel.name, tunnel.description); + }); + } + + get candidates(): TunnelItem[] { + const candidates: TunnelItem[] = []; + const values = this.model.candidates.values(); + let iterator = values.next(); + while (!iterator.done) { + if (!this.model.forwarded.has(iterator.value.remote) && !this.model.published.has(iterator.value.remote)) { + candidates.push(new TunnelItem(TunnelType.Candidate, iterator.value.remote, iterator.value.localAddress, false, undefined, iterator.value.description)); + } + iterator = values.next(); + } + return candidates; + } + + dispose() { + super.dispose(); + } +} + +interface ITunnelTemplateData { + elementDisposable: IDisposable; + container: HTMLElement; + iconLabel: IconLabel; + actionBar: ActionBar; +} + +class TunnelTreeRenderer extends Disposable implements ITreeRenderer { + static readonly ITEM_HEIGHT = 22; + static readonly TREE_TEMPLATE_ID = 'tunnelItemTemplate'; + + private _actionRunner: ActionRunner | undefined; + + constructor( + private readonly viewId: string, + @IMenuService private readonly menuService: IMenuService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IContextViewService private readonly contextViewService: IContextViewService, + @IThemeService private readonly themeService: IThemeService, + @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService + ) { + super(); + } + + set actionRunner(actionRunner: ActionRunner) { + this._actionRunner = actionRunner; + } + + get templateId(): string { + return TunnelTreeRenderer.TREE_TEMPLATE_ID; + } + + renderTemplate(container: HTMLElement): ITunnelTemplateData { + dom.addClass(container, 'custom-view-tree-node-item'); + const iconLabel = new IconLabel(container, { supportHighlights: true }); + // dom.addClass(iconLabel.element, 'tunnel-view-label'); + const actionsContainer = dom.append(iconLabel.element, dom.$('.actions')); + const actionBar = new ActionBar(actionsContainer, { + // actionViewItemProvider: undefined // this.actionViewItemProvider + actionViewItemProvider: (action: IAction) => { + if (action instanceof MenuItemAction) { + return this.instantiationService.createInstance(ContextAwareMenuEntryActionViewItem, action); + } + + return undefined; + } + }); + + return { iconLabel, actionBar, container, elementDisposable: Disposable.None }; + } + + private isTunnelItem(item: ITunnelGroup | ITunnelItem): item is ITunnelItem { + return !!((item).remote); + } + + renderElement(element: ITreeNode, index: number, templateData: ITunnelTemplateData): void { + templateData.elementDisposable.dispose(); + const node = element.element; + + // reset + templateData.actionBar.clear(); + let editableData: IEditableData | undefined; + if (this.isTunnelItem(node)) { + editableData = this.remoteExplorerService.getEditableData(node.remote); + if (editableData) { + templateData.iconLabel.element.style.display = 'none'; + this.renderInputBox(templateData.container, editableData); + } else { + templateData.iconLabel.element.style.display = 'flex'; + this.renderTunnel(node, templateData); + } + } else if ((node.tunnelType === TunnelType.Add) && (editableData = this.remoteExplorerService.getEditableData(undefined))) { + templateData.iconLabel.element.style.display = 'none'; + this.renderInputBox(templateData.container, editableData); + } else { + templateData.iconLabel.element.style.display = 'flex'; + templateData.iconLabel.setLabel(node.label); + } + } + + private renderTunnel(node: ITunnelItem, templateData: ITunnelTemplateData) { + templateData.iconLabel.setLabel(node.label, node.description, { title: node.label + ' - ' + node.description, extraClasses: ['tunnel-view-label'] }); + templateData.actionBar.context = node; + const contextKeyService = this.contextKeyService.createScoped(); + contextKeyService.createKey('view', this.viewId); + contextKeyService.createKey('tunnelType', node.tunnelType); + contextKeyService.createKey('tunnelCloseable', node.closeable); + const menu = this.menuService.createMenu(MenuId.TunnelInline, contextKeyService); + this._register(menu); + const actions: IAction[] = []; + this._register(createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, actions)); + if (actions) { + templateData.actionBar.push(actions, { icon: true, label: false }); + if (this._actionRunner) { + templateData.actionBar.actionRunner = this._actionRunner; + } + } + } + + private renderInputBox(container: HTMLElement, editableData: IEditableData): IDisposable { + const value = editableData.startingValue || ''; + const inputBox = new InputBox(container, this.contextViewService, { + ariaLabel: nls.localize('remote.tunnelsView.input', "Press Enter to confirm or Escape to cancel."), + validationOptions: { + validation: (value) => { + const content = editableData.validationMessage(value); + if (!content) { + return null; + } + + return { + content, + formatContent: true, + type: MessageType.ERROR + }; + } + }, + placeholder: editableData.placeholder || '' + }); + const styler = attachInputBoxStyler(inputBox, this.themeService); + + inputBox.value = value; + inputBox.focus(); + + const done = once((success: boolean, finishEditing: boolean) => { + inputBox.element.style.display = 'none'; + const value = inputBox.value; + dispose(toDispose); + if (finishEditing) { + editableData.onFinish(value, success); + } + }); + + const toDispose = [ + inputBox, + dom.addStandardDisposableListener(inputBox.inputElement, dom.EventType.KEY_DOWN, (e: IKeyboardEvent) => { + if (e.equals(KeyCode.Enter)) { + if (inputBox.validate()) { + done(true, true); + } + } else if (e.equals(KeyCode.Escape)) { + done(false, true); + } + }), + dom.addDisposableListener(inputBox.inputElement, dom.EventType.BLUR, () => { + done(inputBox.isInputValid(), true); + }), + styler + ]; + + return toDisposable(() => { + done(false, false); + }); + } + + disposeElement(resource: ITreeNode, index: number, templateData: ITunnelTemplateData): void { + templateData.elementDisposable.dispose(); + } + + disposeTemplate(templateData: ITunnelTemplateData): void { + templateData.actionBar.dispose(); + templateData.elementDisposable.dispose(); + } +} + +class TunnelDataSource implements IAsyncDataSource { + hasChildren(element: ITunnelViewModel | ITunnelItem | ITunnelGroup) { + if (element instanceof TunnelViewModel) { + return true; + } else if (element instanceof TunnelItem) { + return false; + } else if ((element).items) { + return true; + } + return false; + } + + getChildren(element: ITunnelViewModel | ITunnelItem | ITunnelGroup) { + if (element instanceof TunnelViewModel) { + return element.groups; + } else if (element instanceof TunnelItem) { + return []; + } else if ((element).items) { + return (element).items!; + } + return []; + } +} + +enum TunnelType { + Candidate = 'Candidate', + Published = 'Published', + Forwarded = 'Forwarded', + Add = 'Add' +} + +interface ITunnelGroup { + tunnelType: TunnelType; + label: string; + items?: ITunnelItem[]; +} + +interface ITunnelItem { + tunnelType: TunnelType; + remote: number; + localAddress?: string; + name?: string; + closeable?: boolean; + readonly description?: string; + readonly label: string; +} + +class TunnelItem implements ITunnelItem { + constructor( + public tunnelType: TunnelType, + public remote: number, + public localAddress?: string, + public closeable?: boolean, + public name?: string, + private _description?: string, + ) { } + get label(): string { + if (this.name) { + return nls.localize('remote.tunnelsView.forwardedPortLabel0', "{0}", this.name); + } else if (this.localAddress) { + return nls.localize('remote.tunnelsView.forwardedPortLabel2', "{0} to {1}", this.remote, this.localAddress); + } else { + return nls.localize('remote.tunnelsView.forwardedPortLabel3', "{0} not forwarded", this.remote); + } + } + + get description(): string | undefined { + if (this._description) { + return this._description; + } else if (this.name) { + return nls.localize('remote.tunnelsView.forwardedPortDescription0', "{0} to {1}", this.remote, this.localAddress); + } + return undefined; + } +} + +export const TunnelTypeContextKey = new RawContextKey('tunnelType', TunnelType.Add); +export const TunnelCloseableContextKey = new RawContextKey('tunnelCloseable', false); + +export class TunnelPanel extends ViewletPane { + static readonly ID = '~remote.tunnelPanel'; + static readonly TITLE = nls.localize('remote.tunnel', "Tunnels"); + private tree!: WorkbenchAsyncDataTree; + private tunnelTypeContext: IContextKey; + private tunnelCloseableContext: IContextKey; + + private titleActions: IAction[] = []; + private readonly titleActionsDisposable = this._register(new MutableDisposable()); + + constructor( + protected viewModel: ITunnelViewModel, + options: IViewletPaneOptions, + @IKeybindingService protected keybindingService: IKeybindingService, + @IContextMenuService protected contextMenuService: IContextMenuService, + @IContextKeyService protected contextKeyService: IContextKeyService, + @IConfigurationService protected configurationService: IConfigurationService, + @IInstantiationService protected readonly instantiationService: IInstantiationService, + @IOpenerService protected openerService: IOpenerService, + @IQuickInputService protected quickInputService: IQuickInputService, + @ICommandService protected commandService: ICommandService, + @IMenuService private readonly menuService: IMenuService, + @INotificationService private readonly notificationService: INotificationService, + @IContextViewService private readonly contextViewService: IContextViewService, + @IThemeService private readonly themeService: IThemeService, + @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService + ) { + super(options, keybindingService, contextMenuService, configurationService, contextKeyService); + this.tunnelTypeContext = TunnelTypeContextKey.bindTo(contextKeyService); + this.tunnelCloseableContext = TunnelCloseableContextKey.bindTo(contextKeyService); + + const scopedContextKeyService = this._register(this.contextKeyService.createScoped()); + scopedContextKeyService.createKey('view', TunnelPanel.ID); + + const titleMenu = this._register(this.menuService.createMenu(MenuId.TunnelTitle, scopedContextKeyService)); + const updateActions = () => { + this.titleActions = []; + this.titleActionsDisposable.value = createAndFillInActionBarActions(titleMenu, undefined, this.titleActions); + this.updateActions(); + }; + + this._register(titleMenu.onDidChange(updateActions)); + updateActions(); + + this._register(toDisposable(() => { + this.titleActions = []; + })); + + } + + protected renderBody(container: HTMLElement): void { + dom.addClass(container, '.tree-explorer-viewlet-tree-view'); + const treeContainer = document.createElement('div'); + dom.addClass(treeContainer, 'customview-tree'); + dom.addClass(treeContainer, 'file-icon-themable-tree'); + dom.addClass(treeContainer, 'show-file-icons'); + container.appendChild(treeContainer); + const renderer = new TunnelTreeRenderer(TunnelPanel.ID, this.menuService, this.contextKeyService, this.instantiationService, this.contextViewService, this.themeService, this.remoteExplorerService); + this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, + 'RemoteTunnels', + treeContainer, + new TunnelTreeVirtualDelegate(), + [renderer], + new TunnelDataSource(), + { + keyboardSupport: true, + collapseByDefault: (e: ITunnelItem | ITunnelGroup): boolean => { + return false; + }, + keyboardNavigationLabelProvider: { + getKeyboardNavigationLabel: (item: ITunnelItem | ITunnelGroup) => { + return item.label; + } + }, + } + ); + const actionRunner: ActionRunner = new ActionRunner(); + renderer.actionRunner = actionRunner; + + this._register(this.tree.onContextMenu(e => this.onContextMenu(e, actionRunner))); + + this.tree.setInput(this.viewModel); + this._register(this.viewModel.onForwardedPortsChanged(() => { + this.tree.updateChildren(undefined, true); + })); + + const navigator = this._register(new TreeResourceNavigator2(this.tree, { openOnFocus: false, openOnSelection: false })); + + this._register(Event.debounce(navigator.onDidOpenResource, (last, event) => event, 75, true)(e => { + if (e.element && (e.element.tunnelType === TunnelType.Add)) { + this.commandService.executeCommand(ForwardPortAction.ID); + } + })); + + this._register(this.remoteExplorerService.onDidChangeEditable(async e => { + const isEditing = !!this.remoteExplorerService.getEditableData(e); + + if (!isEditing) { + dom.removeClass(treeContainer, 'highlight'); + } + + await this.tree.updateChildren(undefined, false); + + if (isEditing) { + dom.addClass(treeContainer, 'highlight'); + } else { + this.tree.domFocus(); + } + })); + } + + private get contributedContextMenu(): IMenu { + const contributedContextMenu = this.menuService.createMenu(MenuId.TunnelContext, this.tree.contextKeyService); + this._register(contributedContextMenu); + return contributedContextMenu; + } + + getActions(): IAction[] { + return this.titleActions; + } + + private onContextMenu(treeEvent: ITreeContextMenuEvent, actionRunner: ActionRunner): void { + if (!(treeEvent.element instanceof TunnelItem)) { + return; + } + const node: ITunnelItem | null = treeEvent.element; + const event: UIEvent = treeEvent.browserEvent; + + event.preventDefault(); + event.stopPropagation(); + + this.tree!.setFocus([node]); + this.tunnelTypeContext.set(node.tunnelType); + this.tunnelCloseableContext.set(!!node.closeable); + + const actions: IAction[] = []; + this._register(createAndFillInContextMenuActions(this.contributedContextMenu, { shouldForwardArgs: true }, actions, this.contextMenuService)); + + this.contextMenuService.showContextMenu({ + getAnchor: () => treeEvent.anchor, + getActions: () => actions, + getActionViewItem: (action) => { + const keybinding = this.keybindingService.lookupKeybinding(action.id); + if (keybinding) { + return new ActionViewItem(action, action, { label: true, keybinding: keybinding.getLabel() }); + } + return undefined; + }, + onHide: (wasCancelled?: boolean) => { + if (wasCancelled) { + this.tree!.domFocus(); + } + }, + getActionsContext: () => node, + actionRunner + }); + } + + protected layoutBody(height: number, width: number): void { + this.tree.layout(height, width); + } + + getActionViewItem(action: IAction): IActionViewItem | undefined { + return action instanceof MenuItemAction ? new ContextAwareMenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService) : undefined; + } +} + +export class TunnelPanelDescriptor implements IViewDescriptor { + readonly id = TunnelPanel.ID; + readonly name = TunnelPanel.TITLE; + readonly ctorDescriptor: { ctor: any, arguments?: any[] }; + readonly canToggleVisibility = true; + readonly hideByDefault = false; + readonly workspace = true; + readonly group = 'details@0'; + readonly remoteAuthority?: string | string[]; + + constructor(viewModel: ITunnelViewModel, environmentService: IWorkbenchEnvironmentService) { + this.ctorDescriptor = { ctor: TunnelPanel, arguments: [viewModel] }; + this.remoteAuthority = environmentService.configuration.remoteAuthority ? environmentService.configuration.remoteAuthority.split('+')[0] : undefined; + } +} + +namespace NameTunnelAction { + export const ID = 'remote.tunnel.name'; + export const LABEL = nls.localize('remote.tunnel.name', "Rename"); + + export function handler(): ICommandHandler { + return async (accessor, arg) => { + if (arg instanceof TunnelItem) { + const remoteExplorerService = accessor.get(IRemoteExplorerService); + remoteExplorerService.setEditable(arg.remote, { + onFinish: (value, success) => { + if (success) { + remoteExplorerService.tunnelModel.name(arg.remote, value); + } + remoteExplorerService.setEditable(arg.remote, null); + }, + validationMessage: () => null, + placeholder: nls.localize('remote.tunnelsView.namePlaceholder', "Port name"), + startingValue: arg.name + }); + } + return; + }; + } +} + +namespace ForwardPortAction { + export const ID = 'remote.tunnel.forward'; + export const LABEL = nls.localize('remote.tunnel.forward', "Forward Port"); + + export function handler(): ICommandHandler { + return async (accessor, arg) => { + const remoteExplorerService = accessor.get(IRemoteExplorerService); + if (arg instanceof TunnelItem) { + remoteExplorerService.tunnelModel.forward(arg.remote); + } else { + remoteExplorerService.setEditable(undefined, { + onFinish: (value, success) => { + if (success) { + remoteExplorerService.tunnelModel.forward(Number(value)); + } + remoteExplorerService.setEditable(undefined, null); + }, + validationMessage: (value) => { + const asNumber = Number(value); + if (isNaN(asNumber) || (asNumber < 0) || (asNumber > 65535)) { + return nls.localize('remote.tunnelsView.portNumberValid', "Port number is invalid"); + } + return null; + }, + placeholder: nls.localize('remote.tunnelsView.forwardPortPlaceholder', "Port number") + }); + } + }; + } +} + +namespace ClosePortAction { + export const ID = 'remote.tunnel.close'; + export const LABEL = nls.localize('remote.tunnel.close', "Stop Forwarding Port"); + + export function handler(): ICommandHandler { + return async (accessor, arg) => { + if (arg instanceof TunnelItem) { + const remoteExplorerService = accessor.get(IRemoteExplorerService); + await remoteExplorerService.tunnelModel.close(arg.remote); + } + }; + } +} + +namespace OpenPortInBrowserAction { + export const ID = 'remote.tunnel.open'; + export const LABEL = nls.localize('remote.tunnel.open', "Open in Browser"); + + export function handler(): ICommandHandler { + return async (accessor, arg) => { + if (arg instanceof TunnelItem) { + const model = accessor.get(IRemoteExplorerService).tunnelModel; + const openerService = accessor.get(IOpenerService); + const tunnel = model.forwarded.has(arg.remote) ? model.forwarded.get(arg.remote) : model.published.get(arg.remote); + let address: string | undefined; + if (tunnel && tunnel.localAddress && (address = model.address(tunnel.remote))) { + return openerService.open(URI.parse('http://' + address)); + } + return Promise.resolve(); + } + }; + } +} + +namespace CopyAddressAction { + export const ID = 'remote.tunnel.copyAddress'; + export const LABEL = nls.localize('remote.tunnel.copyAddress', "Copy Address"); + + export function handler(): ICommandHandler { + return async (accessor, arg) => { + if (arg instanceof TunnelItem) { + const model = accessor.get(IRemoteExplorerService).tunnelModel; + const clipboard = accessor.get(IClipboardService); + const address = model.address(arg.remote); + if (address) { + await clipboard.writeText(address.toString()); + } + } + }; + } +} + +CommandsRegistry.registerCommand(NameTunnelAction.ID, NameTunnelAction.handler()); +CommandsRegistry.registerCommand(ForwardPortAction.ID, ForwardPortAction.handler()); +CommandsRegistry.registerCommand(ClosePortAction.ID, ClosePortAction.handler()); +CommandsRegistry.registerCommand(OpenPortInBrowserAction.ID, OpenPortInBrowserAction.handler()); +CommandsRegistry.registerCommand(CopyAddressAction.ID, CopyAddressAction.handler()); + +MenuRegistry.appendMenuItem(MenuId.TunnelTitle, ({ + group: 'navigation', + order: 0, + command: { + id: ForwardPortAction.ID, + title: ForwardPortAction.LABEL, + icon: { id: 'codicon/plus' } + } +})); +MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ + group: '0_manage', + order: 0, + command: { + id: CopyAddressAction.ID, + title: CopyAddressAction.LABEL, + }, + when: ContextKeyExpr.or(TunnelTypeContextKey.isEqualTo(TunnelType.Forwarded), TunnelTypeContextKey.isEqualTo(TunnelType.Published)) +})); +MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ + group: '0_manage', + order: 1, + command: { + id: OpenPortInBrowserAction.ID, + title: OpenPortInBrowserAction.LABEL, + }, + when: ContextKeyExpr.or(TunnelTypeContextKey.isEqualTo(TunnelType.Forwarded), TunnelTypeContextKey.isEqualTo(TunnelType.Published)) +})); +MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ + group: '0_manage', + order: 2, + command: { + id: NameTunnelAction.ID, + title: NameTunnelAction.LABEL, + }, + when: TunnelTypeContextKey.isEqualTo(TunnelType.Forwarded) +})); +MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ + group: '0_manage', + order: 1, + command: { + id: ForwardPortAction.ID, + title: ForwardPortAction.LABEL, + }, + when: ContextKeyExpr.or(TunnelTypeContextKey.isEqualTo(TunnelType.Candidate), TunnelTypeContextKey.isEqualTo(TunnelType.Published)) +})); +MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ + group: '0_manage', + order: 3, + command: { + id: ClosePortAction.ID, + title: ClosePortAction.LABEL, + }, + when: TunnelCloseableContextKey +})); + +MenuRegistry.appendMenuItem(MenuId.TunnelInline, ({ + order: 0, + command: { + id: OpenPortInBrowserAction.ID, + title: OpenPortInBrowserAction.LABEL, + icon: { id: 'codicon/globe' } + }, + when: ContextKeyExpr.or(TunnelTypeContextKey.isEqualTo(TunnelType.Forwarded), TunnelTypeContextKey.isEqualTo(TunnelType.Published)) +})); +MenuRegistry.appendMenuItem(MenuId.TunnelInline, ({ + order: 0, + command: { + id: ForwardPortAction.ID, + title: ForwardPortAction.LABEL, + icon: { id: 'codicon/plus' } + }, + when: ContextKeyExpr.or(TunnelTypeContextKey.isEqualTo(TunnelType.Candidate), TunnelTypeContextKey.isEqualTo(TunnelType.Published)) +})); +MenuRegistry.appendMenuItem(MenuId.TunnelInline, ({ + order: 2, + command: { + id: ClosePortAction.ID, + title: ClosePortAction.LABEL, + icon: { id: 'codicon/x' } + }, + when: TunnelCloseableContextKey +})); diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index 15873391af..e69f252480 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -6,7 +6,7 @@ import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { ILabelService } from 'vs/platform/label/common/label'; +import { ILabelService, ResourceLabelFormatting } from 'vs/platform/label/common/label'; import { OperatingSystem, isWeb } from 'vs/base/common/platform'; import { Schemas } from 'vs/base/common/network'; import { IRemoteAgentService, RemoteExtensionLogFileName } from 'vs/workbench/services/remote/common/remoteAgentService'; @@ -34,7 +34,7 @@ export const VIEW_CONTAINER: ViewContainer = Registry.as { if (remoteEnvironment) { + const formatting: ResourceLabelFormatting = { + label: '${path}', + separator: remoteEnvironment.os === OperatingSystem.Windows ? '\\' : '/', + tildify: remoteEnvironment.os !== OperatingSystem.Windows, + normalizeDriveLetter: remoteEnvironment.os === OperatingSystem.Windows, + workspaceSuffix: isWeb ? undefined : Schemas.vscodeRemote + }; this.labelService.registerFormatter({ scheme: Schemas.vscodeRemote, - formatting: { - label: '${path}', - separator: remoteEnvironment.os === OperatingSystem.Windows ? '\\' : '/', - tildify: remoteEnvironment.os !== OperatingSystem.Windows, - normalizeDriveLetter: remoteEnvironment.os === OperatingSystem.Windows, - workspaceSuffix: isWeb ? undefined : Schemas.vscodeRemote - } + formatting + }); + this.labelService.registerFormatter({ + scheme: Schemas.userData, + formatting }); } }); diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index 1bb6ece177..6811573515 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -38,6 +38,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { RemoteConnectionState, Deprecated_RemoteAuthorityContext, RemoteFileDialogContext } from 'vs/workbench/browser/contextkeys'; import { IDownloadService } from 'vs/platform/download/common/download'; import { OpenLocalFileFolderCommand, OpenLocalFileCommand, OpenLocalFolderCommand, SaveLocalFileCommand } from 'vs/workbench/services/dialogs/browser/simpleFileDialog'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; const WINDOW_ACTIONS_COMMAND_ID = 'workbench.action.remote.showMenu'; const CLOSE_REMOTE_COMMAND_ID = 'workbench.action.remote.close'; @@ -95,7 +96,7 @@ export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenc }); // Pending entry until extensions are ready - this.renderWindowIndicator(nls.localize('host.open', "$(sync~spin) Opening Remote..."), undefined, WINDOW_ACTIONS_COMMAND_ID); + this.renderWindowIndicator('$(sync~spin) ' + nls.localize('host.open', "Opening Remote..."), undefined, WINDOW_ACTIONS_COMMAND_ID); this.connectionState = 'initializing'; RemoteConnectionState.bindTo(this.contextKeyService).set(this.connectionState); @@ -365,6 +366,18 @@ workbenchContributionsRegistry.registerWorkbenchContribution(RemoteWindowActiveI workbenchContributionsRegistry.registerWorkbenchContribution(RemoteTelemetryEnablementUpdater, LifecyclePhase.Ready); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteEmptyWorkbenchPresentation, LifecyclePhase.Starting); +const extensionKindSchema: IJSONSchema = { + type: 'string', + enum: [ + 'ui', + 'workspace' + ], + enumDescriptions: [ + nls.localize('ui', "UI extension kind. In a remote window, such extensions are enabled only when available on the local machine."), + nls.localize('workspace', "Workspace extension kind. In a remote window, such extensions are enabled only when available on the remote.") + ], +}; + Registry.as(ConfigurationExtensions.Configuration) .registerConfiguration({ id: 'remote', @@ -376,21 +389,18 @@ Registry.as(ConfigurationExtensions.Configuration) markdownDescription: nls.localize('remote.extensionKind', "Override the kind of an extension. `ui` extensions are installed and run on the local machine while `workspace` extensions are run on the remote. By overriding an extension's default kind using this setting, you specify if that extension should be installed and enabled locally or remotely."), patternProperties: { '([a-z0-9A-Z][a-z0-9\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\-A-Z]*)$': { - type: 'string', - enum: [ - 'ui', - 'workspace' - ], - enumDescriptions: [ - nls.localize('ui', "UI extension kind. In a remote window, such extensions are enabled only when available on the local machine."), - nls.localize('workspace', "Workspace extension kind. In a remote window, such extensions are enabled only when available on the remote.") - ], - default: 'ui' + oneOf: [{ type: 'array', items: extensionKindSchema }, extensionKindSchema], + default: ['ui'], }, }, default: { - 'pub.name': 'ui' + 'pub.name': ['ui'] } + }, + 'remote.downloadExtensionsLocally': { + type: 'boolean', + markdownDescription: nls.localize('remote.downloadExtensionsLocally', "When enabled extensions are downloaded locally and installed on remote."), + default: false } } }); diff --git a/src/vs/workbench/contrib/scm/browser/activity.ts b/src/vs/workbench/contrib/scm/browser/activity.ts index 28d52e6067..4de5e4b9e7 100644 --- a/src/vs/workbench/contrib/scm/browser/activity.ts +++ b/src/vs/workbench/contrib/scm/browser/activity.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { basename } from 'vs/base/common/resources'; +import { basename, relativePath } from 'vs/base/common/resources'; import { IDisposable, dispose, Disposable, DisposableStore, combinedDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { VIEWLET_ID, ISCMService, ISCMRepository } from 'vs/workbench/contrib/scm/common/scm'; @@ -13,7 +13,6 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/workbench/services/statusbar/common/statusbar'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { commonPrefixLength } from 'vs/base/common/strings'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; function getCount(repository: ISCMRepository): number { @@ -51,23 +50,23 @@ export class SCMStatusController implements IWorkbenchContribution { this.onDidAddRepository(repository); } - editorService.onDidActiveEditorChange(this.onDidActiveEditorChange, this, this.disposables); + editorService.onDidActiveEditorChange(this.tryFocusRepositoryBasedOnActiveEditor, this, this.disposables); this.renderActivityCount(); } - private onDidActiveEditorChange(): void { + private tryFocusRepositoryBasedOnActiveEditor(): boolean { if (!this.editorService.activeEditor) { - return; + return false; } const resource = this.editorService.activeEditor.getResource(); - if (!resource || resource.scheme !== 'file') { - return; + if (!resource) { + return false; } let bestRepository: ISCMRepository | null = null; - let bestMatchLength = Number.NEGATIVE_INFINITY; + let bestMatchLength = Number.POSITIVE_INFINITY; for (const repository of this.scmService.repositories) { const root = repository.provider.rootUri; @@ -76,22 +75,24 @@ export class SCMStatusController implements IWorkbenchContribution { continue; } - const rootFSPath = root.fsPath; - const prefixLength = commonPrefixLength(rootFSPath, resource.fsPath); + const path = relativePath(root, resource); - if (prefixLength === rootFSPath.length && prefixLength > bestMatchLength) { + if (path && !/^\.\./.test(path) && path.length < bestMatchLength) { bestRepository = repository; - bestMatchLength = prefixLength; + bestMatchLength = path.length; } } - if (bestRepository) { - this.onDidFocusRepository(bestRepository); + if (!bestRepository) { + return false; } + + this.focusRepository(bestRepository); + return true; } private onDidAddRepository(repository: ISCMRepository): void { - const focusDisposable = repository.onDidFocus(() => this.onDidFocusRepository(repository)); + const focusDisposable = repository.onDidFocus(() => this.focusRepository(repository)); const onDidChange = Event.any(repository.provider.onDidChange, repository.provider.onDidChangeResources); const changeDisposable = onDidChange(() => this.renderActivityCount()); @@ -102,7 +103,7 @@ export class SCMStatusController implements IWorkbenchContribution { this.disposables = this.disposables.filter(d => d !== removeDisposable); if (this.scmService.repositories.length === 0) { - this.onDidFocusRepository(undefined); + this.focusRepository(undefined); } else if (this.focusedRepository === repository) { this.scmService.repositories[0].focus(); } @@ -113,12 +114,18 @@ export class SCMStatusController implements IWorkbenchContribution { const disposable = combinedDisposable(focusDisposable, changeDisposable, removeDisposable); this.disposables.push(disposable); - if (!this.focusedRepository) { - this.onDidFocusRepository(repository); + if (this.focusedRepository) { + return; } + + if (this.tryFocusRepositoryBasedOnActiveEditor()) { + return; + } + + this.focusRepository(repository); } - private onDidFocusRepository(repository: ISCMRepository | undefined): void { + private focusRepository(repository: ISCMRepository | undefined): void { if (this.focusedRepository === repository) { return; } diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index 9a546f4651..c9d711927a 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -24,14 +24,13 @@ import { registerColor } from 'vs/platform/theme/common/colorRegistry'; import { Color, RGBA } from 'vs/base/common/color'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction } from 'vs/editor/browser/editorExtensions'; -import { PeekViewWidget, getOuterEditor } from 'vs/editor/contrib/referenceSearch/peekViewWidget'; +import { PeekViewWidget, getOuterEditor, peekViewBorder, peekViewTitleBackground, peekViewTitleForeground, peekViewTitleInfoForeground } from 'vs/editor/contrib/peekView/peekView'; import { IContextKeyService, IContextKey, ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Position } from 'vs/editor/common/core/position'; import { rot } from 'vs/base/common/numbers'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { peekViewBorder, peekViewTitleBackground, peekViewTitleForeground, peekViewTitleInfoForeground } from 'vs/editor/contrib/referenceSearch/referencesWidget'; import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { IDiffEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions'; import { Action, IAction, ActionRunner } from 'vs/base/common/actions'; @@ -41,7 +40,7 @@ import { basename, isEqualOrParent } from 'vs/base/common/resources'; import { MenuId, IMenuService, IMenu, MenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; import { createAndFillInActionBarActions, ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IChange, IEditorModel, ScrollType, IEditorContribution, IDiffEditorModel } from 'vs/editor/common/editorCommon'; -import { OverviewRulerLane, ITextModel, IModelDecorationOptions } from 'vs/editor/common/model'; +import { OverviewRulerLane, ITextModel, IModelDecorationOptions, MinimapPosition } from 'vs/editor/common/model'; import { sortedDiff, firstIndex } from 'vs/base/common/arrays'; import { IMarginData } from 'vs/editor/browser/controller/mouseTarget'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; @@ -825,6 +824,24 @@ export const editorGutterDeletedBackground = registerColor('editorGutter.deleted hc: new Color(new RGBA(141, 14, 20)) }, nls.localize('editorGutterDeletedBackground', "Editor gutter background color for lines that are deleted.")); +export const minimapGutterModifiedBackground = registerColor('minimapGutter.modifiedBackground', { + dark: new Color(new RGBA(12, 125, 157)), + light: new Color(new RGBA(102, 175, 224)), + hc: new Color(new RGBA(0, 73, 122)) +}, nls.localize('minimapGutterModifiedBackground', "Minimap gutter background color for lines that are modified.")); + +export const minimapGutterAddedBackground = registerColor('minimapGutter.addedBackground', { + dark: new Color(new RGBA(88, 124, 12)), + light: new Color(new RGBA(129, 184, 139)), + hc: new Color(new RGBA(27, 82, 37)) +}, nls.localize('minimapGutterAddedBackground', "Minimap gutter background color for lines that are added.")); + +export const minimapGutterDeletedBackground = registerColor('minimapGutter.deletedBackground', { + dark: new Color(new RGBA(148, 21, 27)), + light: new Color(new RGBA(202, 75, 81)), + hc: new Color(new RGBA(141, 14, 20)) +}, nls.localize('minimapGutterDeletedBackground', "Minimap gutter background color for lines that are deleted.")); + const overviewRulerDefault = new Color(new RGBA(0, 122, 204, 0.6)); export const overviewRulerModifiedForeground = registerColor('editorOverviewRuler.modifiedForeground', { dark: overviewRulerDefault, light: overviewRulerDefault, hc: overviewRulerDefault }, nls.localize('overviewRulerModifiedForeground', 'Overview ruler marker color for modified content.')); export const overviewRulerAddedForeground = registerColor('editorOverviewRuler.addedForeground', { dark: overviewRulerDefault, light: overviewRulerDefault, hc: overviewRulerDefault }, nls.localize('overviewRulerAddedForeground', 'Overview ruler marker color for added content.')); @@ -832,7 +849,7 @@ export const overviewRulerDeletedForeground = registerColor('editorOverviewRuler class DirtyDiffDecorator extends Disposable { - static createDecoration(className: string, foregroundColor: string, options: { gutter: boolean, overview: boolean, isWholeLine: boolean }): ModelDecorationOptions { + static createDecoration(className: string, options: { gutter: boolean, overview: { active: boolean, color: string }, minimap: { active: boolean, color: string }, isWholeLine: boolean }): ModelDecorationOptions { const decorationOptions: IModelDecorationOptions = { isWholeLine: options.isWholeLine, }; @@ -841,13 +858,20 @@ class DirtyDiffDecorator extends Disposable { decorationOptions.linesDecorationsClassName = `dirty-diff-glyph ${className}`; } - if (options.overview) { + if (options.overview.active) { decorationOptions.overviewRuler = { - color: themeColorFromId(foregroundColor), + color: themeColorFromId(options.overview.color), position: OverviewRulerLane.Left }; } + if (options.minimap.active) { + decorationOptions.minimap = { + color: themeColorFromId(options.minimap.color), + position: MinimapPosition.Gutter + }; + } + return ModelDecorationOptions.createDynamic(decorationOptions); } @@ -867,11 +891,26 @@ class DirtyDiffDecorator extends Disposable { const decorations = configurationService.getValue('scm.diffDecorations'); const gutter = decorations === 'all' || decorations === 'gutter'; const overview = decorations === 'all' || decorations === 'overview'; - const options = { gutter, overview, isWholeLine: true }; + const minimap = decorations === 'all' || decorations === 'minimap'; - this.modifiedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-modified', overviewRulerModifiedForeground, options); - this.addedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-added', overviewRulerAddedForeground, options); - this.deletedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-deleted', overviewRulerDeletedForeground, { ...options, isWholeLine: false }); + this.modifiedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-modified', { + gutter, + overview: { active: overview, color: overviewRulerModifiedForeground }, + minimap: { active: minimap, color: minimapGutterModifiedBackground }, + isWholeLine: true + }); + this.addedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-added', { + gutter, + overview: { active: overview, color: overviewRulerAddedForeground }, + minimap: { active: minimap, color: minimapGutterAddedBackground }, + isWholeLine: true + }); + this.deletedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-deleted', { + gutter, + overview: { active: overview, color: overviewRulerDeletedForeground }, + minimap: { active: minimap, color: minimapGutterDeletedBackground }, + isWholeLine: false + }); this._register(model.onDidChange(this.onDidChange, this)); } @@ -1176,9 +1215,15 @@ class DirtyDiffItem { } } +interface IViewState { + readonly width: number; + readonly visibility: 'always' | 'hover'; +} + export class DirtyDiffWorkbenchController extends Disposable implements ext.IWorkbenchContribution, IModelRegistry { private enabled = false; + private viewState: IViewState = { width: 3, visibility: 'always' }; private models: ITextModel[] = []; private items: { [modelId: string]: DirtyDiffItem; } = Object.create(null); private readonly transientDisposables = this._register(new DisposableStore()); @@ -1223,15 +1268,20 @@ export class DirtyDiffWorkbenchController extends Disposable implements ext.IWor width = 3; } - this.stylesheet.innerHTML = `.monaco-editor .dirty-diff-modified,.monaco-editor .dirty-diff-added{border-left-width:${width}px;}`; + this.setViewState({ ...this.viewState, width }); } private onDidChangeDiffVisibiltiyConfiguration(): void { - const visibility = this.configurationService.getValue('scm.diffDecorationsGutterVisibility'); + const visibility = this.configurationService.getValue<'always' | 'hover'>('scm.diffDecorationsGutterVisibility'); + this.setViewState({ ...this.viewState, visibility }); + } + private setViewState(state: IViewState): void { + this.viewState = state; this.stylesheet.innerHTML = ` + .monaco-editor .dirty-diff-modified,.monaco-editor .dirty-diff-added{border-left-width:${state.width}px;} .monaco-editor .dirty-diff-modified, .monaco-editor .dirty-diff-added, .monaco-editor .dirty-diff-deleted { - opacity: ${visibility === 'always' ? 1 : 0}; + opacity: ${state.visibility === 'always' ? 1 : 0}; } `; } diff --git a/src/vs/workbench/contrib/scm/browser/mainPanel.ts b/src/vs/workbench/contrib/scm/browser/mainPane.ts similarity index 93% rename from src/vs/workbench/contrib/scm/browser/mainPanel.ts rename to src/vs/workbench/contrib/scm/browser/mainPane.ts index 4a3f174417..128c0bd4cd 100644 --- a/src/vs/workbench/contrib/scm/browser/mainPanel.ts +++ b/src/vs/workbench/contrib/scm/browser/mainPane.ts @@ -8,7 +8,7 @@ import { localize } from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; import { basename } from 'vs/base/common/resources'; import { IDisposable, dispose, Disposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; -import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet'; import { append, $, toggleClass } from 'vs/base/browser/dom'; import { IListVirtualDelegate, IListRenderer, IListContextMenuEvent, IListEvent } from 'vs/base/browser/ui/list/list'; import { ISCMService, ISCMRepository } from 'vs/workbench/contrib/scm/common/scm'; @@ -29,6 +29,7 @@ import { renderCodicons } from 'vs/base/browser/ui/codiconLabel/codiconLabel'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IViewDescriptor } from 'vs/workbench/common/views'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; export interface ISpliceEvent { index: number; @@ -167,16 +168,16 @@ class ProviderRenderer implements IListRenderer; constructor( protected viewModel: IViewModel, - options: IViewletPanelOptions, + options: IViewletPaneOptions, @IKeybindingService protected keybindingService: IKeybindingService, @IContextMenuService protected contextMenuService: IContextMenuService, @ISCMService protected scmService: ISCMService, @@ -193,9 +194,12 @@ export class MainPanel extends ViewletPanel { const renderer = this.instantiationService.createInstance(ProviderRenderer); const identityProvider = { getId: (r: ISCMRepository) => r.provider.id }; - this.list = this.instantiationService.createInstance(WorkbenchList, `SCM Main`, container, delegate, [renderer], { + this.list = this.instantiationService.createInstance>(WorkbenchList, `SCM Main`, container, delegate, [renderer], { identityProvider, - horizontalScrolling: false + horizontalScrolling: false, + overrideStyles: { + listBackground: SIDE_BAR_BACKGROUND + } }); this._register(renderer.onDidRenderElement(e => this.list.updateWidth(this.viewModel.repositories.indexOf(e)), null)); @@ -311,10 +315,10 @@ export class MainPanel extends ViewletPanel { } } -export class MainPanelDescriptor implements IViewDescriptor { +export class MainPaneDescriptor implements IViewDescriptor { - readonly id = MainPanel.ID; - readonly name = MainPanel.TITLE; + readonly id = MainPane.ID; + readonly name = MainPane.TITLE; readonly ctorDescriptor: { ctor: any, arguments?: any[] }; readonly canToggleVisibility = true; readonly hideByDefault = false; @@ -323,6 +327,6 @@ export class MainPanelDescriptor implements IViewDescriptor { readonly when = ContextKeyExpr.or(ContextKeyExpr.equals('config.scm.alwaysShowProviders', true), ContextKeyExpr.and(ContextKeyExpr.notEquals('scm.providerCount', 0), ContextKeyExpr.notEquals('scm.providerCount', 1))); constructor(viewModel: IViewModel) { - this.ctorDescriptor = { ctor: MainPanel, arguments: [viewModel] }; + this.ctorDescriptor = { ctor: MainPane, arguments: [viewModel] }; } } diff --git a/src/vs/workbench/contrib/scm/browser/media/dirtydiffDecorator.css b/src/vs/workbench/contrib/scm/browser/media/dirtydiffDecorator.css index 28f22fa09e..302281284c 100644 --- a/src/vs/workbench/contrib/scm/browser/media/dirtydiffDecorator.css +++ b/src/vs/workbench/contrib/scm/browser/media/dirtydiffDecorator.css @@ -39,7 +39,7 @@ transition: height 80ms linear; } -.monaco-editor .margin-view-overlays > div:hover > .dirty-diff-glyph:before { +.monaco-editor .margin-view-overlays .dirty-diff-glyph:hover::before { position: absolute; content: ''; height: 100%; @@ -47,7 +47,7 @@ left: -6px; } -.monaco-editor .margin-view-overlays > div:hover > .dirty-diff-deleted:after { +.monaco-editor .margin-view-overlays .dirty-diff-deleted:hover::after { bottom: 0; border-top-width: 0; border-bottom-width: 0; @@ -56,4 +56,4 @@ /* Hide glyph decorations when inside the inline diff editor */ .monaco-editor.modified-in-monaco-diff-editor .margin-view-overlays > div > .dirty-diff-glyph { display: none; -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/scm/browser/media/scm-activity-bar.svg b/src/vs/workbench/contrib/scm/browser/media/scm-activity-bar.svg deleted file mode 100644 index 5092b85732..0000000000 --- a/src/vs/workbench/contrib/scm/browser/media/scm-activity-bar.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css b/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css index caa4fd6cf6..79bd3156f3 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css +++ b/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css @@ -3,10 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .activitybar > .content .monaco-action-bar .action-label.scm { - -webkit-mask: url('scm-activity-bar.svg') no-repeat 50% 50%; -} - .monaco-workbench .viewlet.scm-viewlet .collapsible.header .actions { width: initial; flex: 1; @@ -19,7 +15,7 @@ } .scm-viewlet:not(.empty) .empty-message, -.scm-viewlet.empty .monaco-panel-view { +.scm-viewlet.empty .monaco-pane-view { display: none; } @@ -48,8 +44,15 @@ align-items: center; } +.scm-viewlet .monaco-list-row > .scm-provider > .monaco-action-bar .action-label { + text-overflow: ellipsis; + overflow: hidden; + min-width: 14px; /* minimum size of icons */ +} + .scm-viewlet .monaco-list-row > .scm-provider > .monaco-action-bar .action-label .codicon { font-size: 14px; + vertical-align: sub; } .scm-viewlet .monaco-list-row > .scm-provider > .monaco-action-bar .action-item:last-of-type { @@ -133,6 +136,7 @@ .scm-viewlet .monaco-list .monaco-list-row .resource-group > .actions, .scm-viewlet .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions { display: none; + max-width: fit-content; } .scm-viewlet .monaco-list .monaco-list-row:hover .resource-group > .actions, diff --git a/src/vs/workbench/contrib/scm/browser/repositoryPanel.ts b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts similarity index 95% rename from src/vs/workbench/contrib/scm/browser/repositoryPanel.ts rename to src/vs/workbench/contrib/scm/browser/repositoryPane.ts index 7aa60aff88..c10f83f1c1 100644 --- a/src/vs/workbench/contrib/scm/browser/repositoryPanel.ts +++ b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts @@ -6,9 +6,9 @@ import 'vs/css!./media/scmViewlet'; import { Event, Emitter } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; -import { basename } from 'vs/base/common/resources'; +import { basename, isEqual } from 'vs/base/common/resources'; import { IDisposable, Disposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; -import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet'; import { append, $, addClass, toggleClass, trackFocus, removeClass } from 'vs/base/browser/dom'; import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list'; import { ISCMRepository, ISCMResourceGroup, ISCMResource, InputValidationType } from 'vs/workbench/contrib/scm/common/scm'; @@ -38,7 +38,7 @@ import * as platform from 'vs/base/common/platform'; import { ITreeNode, ITreeFilter, ITreeSorter, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { ResourceTree, IResourceNode } from 'vs/base/common/resourceTree'; import { ISequence, ISplice } from 'vs/base/common/sequence'; -import { ObjectTree, ICompressibleTreeRenderer, ICompressibleKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/tree/objectTree'; +import { ICompressibleTreeRenderer, ICompressibleKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/tree/objectTree'; import { Iterator } from 'vs/base/common/iterator'; import { ICompressedTreeNode, ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; import { URI } from 'vs/base/common/uri'; @@ -47,11 +47,13 @@ import { compareFileNames } from 'vs/base/common/comparers'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { IViewDescriptor } from 'vs/workbench/common/views'; import { localize } from 'vs/nls'; -import { flatten } from 'vs/base/common/arrays'; +import { flatten, find } from 'vs/base/common/arrays'; import { memoize } from 'vs/base/common/decorators'; import { IWorkbenchThemeService, IFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { toResource, SideBySideEditor } from 'vs/workbench/common/editor'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { Hasher } from 'vs/base/common/hash'; type TreeElement = ISCMResourceGroup | IResourceNode | ISCMResource; @@ -428,7 +430,7 @@ class ViewModel { constructor( private groups: ISequence, - private tree: ObjectTree, + private tree: WorkbenchCompressibleObjectTree, private _mode: ViewModelMode, @IEditorService protected editorService: IEditorService, @IConfigurationService protected configurationService: IConfigurationService, @@ -466,18 +468,16 @@ class ViewModel { } private onDidSpliceGroup(item: IGroupItem, { start, deleteCount, toInsert }: ISplice): void { - if (this._mode === ViewModelMode.Tree) { - for (const resource of toInsert) { - item.tree.add(resource.sourceUri, resource); - } - } - const deleted = item.resources.splice(start, deleteCount, ...toInsert); if (this._mode === ViewModelMode.Tree) { for (const resource of deleted) { item.tree.delete(resource.sourceUri); } + + for (const resource of toInsert) { + item.tree.add(resource.sourceUri, resource); + } } this.refresh(item); @@ -536,12 +536,15 @@ class ViewModel { // go backwards from last group for (let i = this.items.length - 1; i >= 0; i--) { - const node = this.items[i].tree.getNode(uri); + const item = this.items[i]; + const resource = this.mode === ViewModelMode.Tree + ? item.tree.getNode(uri)?.element + : find(item.resources, r => isEqual(r.sourceUri, uri)); - if (node && node.element) { - this.tree.reveal(node.element); - this.tree.setSelection([node.element]); - this.tree.setFocus([node.element]); + if (resource) { + this.tree.reveal(resource); + this.tree.setSelection([resource]); + this.tree.setFocus([resource]); return; } } @@ -570,7 +573,7 @@ export class ToggleViewModeAction extends Action { } private onDidChangeMode(mode: ViewModelMode): void { - const iconClass = mode === ViewModelMode.List ? 'codicon-filter' : 'codicon-selection'; + const iconClass = mode === ViewModelMode.List ? 'codicon-list-tree' : 'codicon-list-flat'; this.class = `scm-action toggle-view-mode ${iconClass}`; } } @@ -583,14 +586,14 @@ function convertValidationType(type: InputValidationType): MessageType { } } -export class RepositoryPanel extends ViewletPanel { +export class RepositoryPane extends ViewletPane { private cachedHeight: number | undefined = undefined; private cachedWidth: number | undefined = undefined; private inputBoxContainer!: HTMLElement; private inputBox!: InputBox; private listContainer!: HTMLElement; - private tree!: ObjectTree; + private tree!: WorkbenchCompressibleObjectTree; private viewModel!: ViewModel; private listLabels!: ResourceLabels; private menus: SCMMenus; @@ -600,7 +603,7 @@ export class RepositoryPanel extends ViewletPanel { constructor( readonly repository: ISCMRepository, - options: IViewletPanelOptions, + options: IViewletPaneOptions, @IKeybindingService protected keybindingService: IKeybindingService, @IWorkbenchThemeService protected themeService: IWorkbenchThemeService, @IContextMenuService protected contextMenuService: IContextMenuService, @@ -732,7 +735,7 @@ export class RepositoryPanel extends ViewletPanel { const keyboardNavigationLabelProvider = new SCMTreeKeyboardNavigationLabelProvider(); const identityProvider = new SCMResourceIdentityProvider(); - this.tree = this.instantiationService.createInstance( + this.tree = this.instantiationService.createInstance>( WorkbenchCompressibleObjectTree, 'SCM Tree Repo', this.listContainer, @@ -743,7 +746,10 @@ export class RepositoryPanel extends ViewletPanel { horizontalScrolling: false, filter, sorter, - keyboardNavigationLabelProvider + keyboardNavigationLabelProvider, + overrideStyles: { + listBackground: SIDE_BAR_BACKGROUND + } }); this._register(Event.chain(this.tree.onDidOpen) @@ -958,9 +964,12 @@ export class RepositoryViewDescriptor implements IViewDescriptor { constructor(readonly repository: ISCMRepository, readonly hideByDefault: boolean) { const repoId = repository.provider.rootUri ? repository.provider.rootUri.toString() : `#${RepositoryViewDescriptor.counter++}`; - this.id = `scm:repository:${repository.provider.label}:${repoId}`; + const hasher = new Hasher(); + hasher.hash(repository.provider.label); + hasher.hash(repoId); + this.id = `scm:repository:${hasher.value}`; this.name = repository.provider.rootUri ? basename(repository.provider.rootUri) : repository.provider.label; - this.ctorDescriptor = { ctor: RepositoryPanel, arguments: [repository] }; + this.ctorDescriptor = { ctor: RepositoryPane, arguments: [repository] }; } } diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index bce8fae3ff..84b22a17f2 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -38,13 +38,12 @@ class OpenSCMViewletAction extends ShowViewletAction { Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(DirtyDiffWorkbenchController, LifecyclePhase.Restored); -Registry.as(ViewletExtensions.Viewlets).registerViewlet(new ViewletDescriptor( +Registry.as(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( SCMViewlet, VIEWLET_ID, localize('source control', "Source Control"), - 'scm', - // {{SQL CARBON EDIT}} - 12 + 'codicon-source-control', + 12 // {{SQL CARBON EDIT}} )); Registry.as(WorkbenchExtensions.Workbench) @@ -52,7 +51,7 @@ Registry.as(WorkbenchExtensions.Workbench) // Register Action to Open Viewlet Registry.as(WorkbenchActionExtensions.WorkbenchActions).registerWorkbenchAction( - new SyncActionDescriptor(OpenSCMViewletAction, VIEWLET_ID, localize('toggleSCMViewlet', "Show SCM"), { + SyncActionDescriptor.create(OpenSCMViewletAction, VIEWLET_ID, localize('toggleSCMViewlet', "Show SCM"), { primary: 0, win: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_G }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_G }, @@ -81,7 +80,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis }, 'scm.diffDecorations': { type: 'string', - enum: ['all', 'gutter', 'overview', 'none'], + enum: ['all', 'gutter', 'overview', 'minimap', 'none'], default: 'all', description: localize('diffDecorations', "Controls diff decorations in the editor.") }, @@ -98,7 +97,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis localize('scm.diffDecorationsGutterVisibility.always', "Show the diff decorator in the gutter at all times."), localize('scm.diffDecorationsGutterVisibility.hover', "Show the diff decorator in the gutter only on hover.") ], - description: localize('scm.diffDecorationsGutterVisibility', "Controls the visibilty of the Source Control diff decorator in the gutter."), + description: localize('scm.diffDecorationsGutterVisibility', "Controls the visibility of the Source Control diff decorator in the gutter."), default: 'always' }, 'scm.alwaysShowActions': { diff --git a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts index 781c3ca350..38335eeb02 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts @@ -29,8 +29,8 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IViewsRegistry, Extensions } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; import { nextTick } from 'vs/base/common/process'; -import { RepositoryPanel, RepositoryViewDescriptor } from 'vs/workbench/contrib/scm/browser/repositoryPanel'; -import { MainPanelDescriptor, MainPanel } from 'vs/workbench/contrib/scm/browser/mainPanel'; +import { RepositoryPane, RepositoryViewDescriptor } from 'vs/workbench/contrib/scm/browser/repositoryPane'; +import { MainPaneDescriptor, MainPane } from 'vs/workbench/contrib/scm/browser/mainPane'; export interface ISpliceEvent { index: number; @@ -73,8 +73,8 @@ export class SCMViewlet extends ViewContainerViewlet implements IViewModel { } get visibleRepositories(): ISCMRepository[] { - return this.panels.filter(panel => panel instanceof RepositoryPanel) - .map(panel => (panel as RepositoryPanel).repository); + return this.panes.filter(pane => pane instanceof RepositoryPane) + .map(pane => (pane as RepositoryPane).repository); } get onDidChangeVisibleRepositories(): Event { @@ -107,11 +107,11 @@ export class SCMViewlet extends ViewContainerViewlet implements IViewModel { this.message = $('.empty-message', { tabIndex: 0 }, localize('no open repo', "No source control providers registered.")); const viewsRegistry = Registry.as(Extensions.ViewsRegistry); - viewsRegistry.registerViews([new MainPanelDescriptor(this)], VIEW_CONTAINER); + viewsRegistry.registerViews([new MainPaneDescriptor(this)], VIEW_CONTAINER); this._register(configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('scm.alwaysShowProviders') && configurationService.getValue('scm.alwaysShowProviders')) { - this.viewsModel.setVisible(MainPanel.ID, true); + this.viewsModel.setVisible(MainPane.ID, true); } })); @@ -184,11 +184,11 @@ export class SCMViewlet extends ViewContainerViewlet implements IViewModel { const repository = this.visibleRepositories[0]; if (repository) { - const panel = this.panels - .filter(panel => panel instanceof RepositoryPanel && panel.repository === repository)[0] as RepositoryPanel | undefined; + const pane = this.panes + .filter(pane => pane instanceof RepositoryPane && pane.repository === repository)[0] as RepositoryPane | undefined; - if (panel) { - panel.focus(); + if (pane) { + pane.focus(); } else { super.focus(); } @@ -257,10 +257,10 @@ export class SCMViewlet extends ViewContainerViewlet implements IViewModel { for (const viewDescriptor of toSetInvisible) { if (oneToOne) { - const panel = this.panels.filter(panel => panel.id === viewDescriptor.id)[0]; + const pane = this.panes.filter(pane => pane.id === viewDescriptor.id)[0]; - if (panel) { - size = this.getPanelSize(panel); + if (pane) { + size = this.getPaneSize(pane); } } diff --git a/src/vs/workbench/contrib/search/browser/media/search-activity-bar.svg b/src/vs/workbench/contrib/search/browser/media/search-activity-bar.svg deleted file mode 100644 index a7ea9ab29a..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/search-activity-bar.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/search/browser/media/search.contribution.css b/src/vs/workbench/contrib/search/browser/media/search.contribution.css deleted file mode 100644 index 9f80aa7cd2..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/search.contribution.css +++ /dev/null @@ -1,9 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/* Activity Bar */ -.monaco-workbench .activitybar .monaco-action-bar .action-label.search { - -webkit-mask: url('search-activity-bar.svg') no-repeat 50% 50%; -} \ No newline at end of file diff --git a/src/vs/workbench/contrib/search/browser/media/searchview.css b/src/vs/workbench/contrib/search/browser/media/searchview.css index bdd4ba27c6..f1d4fc1410 100644 --- a/src/vs/workbench/contrib/search/browser/media/searchview.css +++ b/src/vs/workbench/contrib/search/browser/media/searchview.css @@ -47,6 +47,10 @@ height: 24px; /* set initial height before measure */ } +.search-view .monaco-inputbox > .wrapper > textarea.input { + scrollbar-width: none; /* Firefox: hide scrollbar */ +} + .search-view .monaco-inputbox > .wrapper > textarea.input::-webkit-scrollbar { display: none; } @@ -66,7 +70,6 @@ .search-view .search-widget .replace-input { position: relative; display: flex; - display: -webkit-flex; vertical-align: middle; width: auto !important; } @@ -157,6 +160,7 @@ margin-bottom: 0px; padding-bottom: 4px; user-select: text; + -webkit-user-select: text; } .search-view .foldermatch, @@ -298,10 +302,6 @@ background-color: rgba(255, 255, 255, 0.1) !important; } -.vs-dark .search-view .message { - opacity: .5; -} - .vs-dark .search-view .foldermatch, .vs-dark .search-view .filematch { padding: 0; diff --git a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts index 29fa19f8c5..6d278b13c1 100644 --- a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts +++ b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts @@ -16,6 +16,9 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachInputBoxStyler, attachCheckboxStyler } from 'vs/platform/theme/common/styler'; import { ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedHistoryWidget'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; +import { Delayer } from 'vs/base/common/async'; export interface IOptions { placeholder?: string; @@ -23,6 +26,8 @@ export interface IOptions { validation?: IInputValidator; ariaLabel?: string; history?: string[]; + submitOnType?: boolean; + submitOnTypeDelay?: number; } export class PatternInputWidget extends Widget { @@ -39,13 +44,16 @@ export class PatternInputWidget extends Widget { protected inputBox!: HistoryInputBox; private _onSubmit = this._register(new Emitter()); - onSubmit: CommonEvent = this._onSubmit.event; + onSubmit: CommonEvent = this._onSubmit.event; - private _onCancel = this._register(new Emitter()); - onCancel: CommonEvent = this._onCancel.event; + private _onCancel = this._register(new Emitter()); + onCancel: CommonEvent = this._onCancel.event; + + private searchOnTypeDelayer: Delayer; constructor(parent: HTMLElement, private contextViewProvider: IContextViewProvider, options: IOptions = Object.create(null), @IThemeService protected themeService: IThemeService, + @IConfigurationService private configurationService: IConfigurationService, @IContextKeyService private readonly contextKeyService: IContextKeyService ) { super(); @@ -53,6 +61,8 @@ export class PatternInputWidget extends Widget { this.placeholder = options.placeholder || ''; this.ariaLabel = options.ariaLabel || nls.localize('defaultLabel', "input"); + this._register(this.searchOnTypeDelayer = new Delayer(this.searchConfig.searchOnTypeDebouncePeriod)); + this.render(options); parent.appendChild(this.domNode); @@ -139,6 +149,12 @@ export class PatternInputWidget extends Widget { this._register(attachInputBoxStyler(this.inputBox, this.themeService)); this.inputFocusTracker = dom.trackFocus(this.inputBox.inputElement); this.onkeyup(this.inputBox.inputElement, (keyboardEvent) => this.onInputKeyUp(keyboardEvent)); + this._register(this.inputBox.onDidChange(() => { + if (this.searchConfig.searchOnType) { + this._onCancel.fire(); + this.searchOnTypeDelayer.trigger(() => this._onSubmit.fire(true), this.searchConfig.searchOnTypeDebouncePeriod); + } + })); const controls = document.createElement('div'); controls.className = 'controls'; @@ -154,24 +170,32 @@ export class PatternInputWidget extends Widget { private onInputKeyUp(keyboardEvent: IKeyboardEvent) { switch (keyboardEvent.keyCode) { case KeyCode.Enter: - this._onSubmit.fire(false); + this.searchOnTypeDelayer.trigger(() => this._onSubmit.fire(false), 0); return; case KeyCode.Escape: - this._onCancel.fire(false); + this._onCancel.fire(); return; default: return; } } + + private get searchConfig() { + return this.configurationService.getValue('search'); + } } export class ExcludePatternInputWidget extends PatternInputWidget { + private _onChangeIgnoreBoxEmitter = this._register(new Emitter()); + onChangeIgnoreBox = this._onChangeIgnoreBoxEmitter.event; + constructor(parent: HTMLElement, contextViewProvider: IContextViewProvider, options: IOptions = Object.create(null), @IThemeService themeService: IThemeService, + @IConfigurationService configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService ) { - super(parent, contextViewProvider, options, themeService, contextKeyService); + super(parent, contextViewProvider, options, themeService, configurationService, contextKeyService); } private useExcludesAndIgnoreFilesBox!: Checkbox; @@ -200,6 +224,7 @@ export class ExcludePatternInputWidget extends PatternInputWidget { isChecked: true, })); this._register(this.useExcludesAndIgnoreFilesBox.onChange(viaKeyboard => { + this._onChangeIgnoreBoxEmitter.fire(); if (!viaKeyboard) { this.inputBox.focus(); } diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 6845ececa3..4ae03845a4 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -11,7 +11,6 @@ import * as objects from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; import { dirname } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import 'vs/css!./media/search.contribution'; import { registerLanguageCommand } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { getSelectionSearchString } from 'vs/editor/contrib/find/findController'; @@ -42,9 +41,9 @@ import { ExplorerFolderContext, ExplorerRootContext, FilesExplorerFocusCondition import { OpenAnythingHandler } from 'vs/workbench/contrib/search/browser/openAnythingHandler'; import { OpenSymbolHandler } from 'vs/workbench/contrib/search/browser/openSymbolHandler'; import { registerContributions as replaceContributions } from 'vs/workbench/contrib/search/browser/replaceContributions'; -import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, toggleRegexCommand, toggleWholeWordCommand, FindInFilesCommand } from 'vs/workbench/contrib/search/browser/searchActions'; +import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, toggleRegexCommand, toggleWholeWordCommand, FindInFilesCommand, ToggleSearchOnTypeAction, OpenResultsInEditorAction, RerunEditorSearchAction } from 'vs/workbench/contrib/search/browser/searchActions'; import { SearchPanel } from 'vs/workbench/contrib/search/browser/searchPanel'; -import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; +import { SearchView, SearchViewPosition } from 'vs/workbench/contrib/search/browser/searchView'; import { SearchViewlet } from 'vs/workbench/contrib/search/browser/searchViewlet'; import { registerContributions as searchWidgetContributions } from 'vs/workbench/contrib/search/browser/searchWidget'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; @@ -57,6 +56,7 @@ import { ISearchConfiguration, ISearchConfigurationProperties, PANEL_ID, VIEWLET import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ExplorerViewlet } from 'vs/workbench/contrib/files/browser/explorerViewlet'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; registerSingleton(ISearchWorkbenchService, SearchWorkbenchService, true); registerSingleton(ISearchHistoryService, SearchHistoryService, true); @@ -334,7 +334,7 @@ CommandsRegistry.registerCommand({ const RevealInSideBarForSearchResultsCommand: ICommandAction = { id: Constants.RevealInSideBarForSearchResults, - title: nls.localize('revealInSideBar', "Reveal in Explorer") + title: nls.localize('revealInSideBar', "Reveal in Side Bar") }; MenuRegistry.appendMenuItem(MenuId.SearchContext, { @@ -503,15 +503,15 @@ class ShowAllSymbolsAction extends Action { } } -Registry.as(ViewletExtensions.Viewlets).registerViewlet(new ViewletDescriptor( +Registry.as(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( SearchViewlet, VIEWLET_ID, nls.localize('name', "Search"), - 'search', + 'codicon-search', 1 )); -Registry.as(PanelExtensions.Panels).registerPanel(new PanelDescriptor( +Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( SearchPanel, PANEL_ID, nls.localize('name', "Search"), @@ -531,7 +531,7 @@ class RegisterSearchViewContribution implements IWorkbenchContribution { const config = configurationService.getValue(); if (config.search.location === 'panel') { viewsRegistry.deregisterViews(viewsRegistry.getViews(VIEW_CONTAINER), VIEW_CONTAINER); - Registry.as(PanelExtensions.Panels).registerPanel(new PanelDescriptor( + Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( SearchPanel, PANEL_ID, nls.localize('name', "Search"), @@ -543,7 +543,7 @@ class RegisterSearchViewContribution implements IWorkbenchContribution { } } else { Registry.as(PanelExtensions.Panels).deregisterPanel(PANEL_ID); - viewsRegistry.registerViews([{ id: VIEW_ID, name: nls.localize('search', "Search"), ctorDescriptor: { ctor: SearchView }, canToggleVisibility: false }], VIEW_CONTAINER); + viewsRegistry.registerViews([{ id: VIEW_ID, name: nls.localize('search', "Search"), ctorDescriptor: { ctor: SearchView, arguments: [SearchViewPosition.SideBar] }, canToggleVisibility: false }], VIEW_CONTAINER); if (open) { viewletService.openViewlet(VIEWLET_ID); } @@ -565,7 +565,7 @@ const registry = Registry.as(ActionExtensions.Workbenc // Show Search and Find in Files are redundant, but we can't break keybindings by removing one. So it's the same action, same keybinding, registered to different IDs. // Show Search 'when' is redundant but if the two conflict with exactly the same keybinding and 'when' clause, then they can show up as "unbound" - #51780 -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenSearchViewletAction, VIEWLET_ID, OpenSearchViewletAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_F }, Constants.SearchViewVisibleKey.toNegated()), 'View: Show Search', nls.localize('view', "View")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenSearchViewletAction, VIEWLET_ID, OpenSearchViewletAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_F }, Constants.SearchViewVisibleKey.toNegated()), 'View: Show Search', nls.localize('view', "View")); KeybindingsRegistry.registerCommandAndKeybindingRule({ id: Constants.FindInFilesActionId, weight: KeybindingWeight.WorkbenchContrib, @@ -583,10 +583,10 @@ MenuRegistry.appendMenuItem(MenuId.MenubarEditMenu, { order: 1 }); -registry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextSearchResultAction, FocusNextSearchResultAction.ID, FocusNextSearchResultAction.LABEL, { primary: KeyCode.F4 }, ContextKeyExpr.and(Constants.HasSearchResults)), 'Focus Next Search Result', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousSearchResultAction, FocusPreviousSearchResultAction.ID, FocusPreviousSearchResultAction.LABEL, { primary: KeyMod.Shift | KeyCode.F4 }, ContextKeyExpr.and(Constants.HasSearchResults)), 'Focus Previous Search Result', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusNextSearchResultAction, FocusNextSearchResultAction.ID, FocusNextSearchResultAction.LABEL, { primary: KeyCode.F4 }, ContextKeyExpr.and(Constants.HasSearchResults)), 'Focus Next Search Result', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusPreviousSearchResultAction, FocusPreviousSearchResultAction.ID, FocusPreviousSearchResultAction.LABEL, { primary: KeyMod.Shift | KeyCode.F4 }, ContextKeyExpr.and(Constants.HasSearchResults)), 'Focus Previous Search Result', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(ReplaceInFilesAction, ReplaceInFilesAction.ID, ReplaceInFilesAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_H }), 'Replace in Files', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ReplaceInFilesAction, ReplaceInFilesAction.ID, ReplaceInFilesAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_H }), 'Replace in Files', category); MenuRegistry.appendMenuItem(MenuId.MenubarEditMenu, { group: '4_find_global', command: { @@ -617,16 +617,44 @@ KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({ handler: toggleRegexCommand }, ToggleRegexKeybinding)); -registry.registerWorkbenchAction(new SyncActionDescriptor(CollapseDeepestExpandedLevelAction, CollapseDeepestExpandedLevelAction.ID, CollapseDeepestExpandedLevelAction.LABEL), 'Search: Collapse All', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(ShowAllSymbolsAction, ShowAllSymbolsAction.ID, ShowAllSymbolsAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_T }), 'Go to Symbol in Workspace...'); +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: Constants.AddCursorsAtSearchResults, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.FileMatchOrMatchFocusKey), + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_L, + handler: (accessor, args: any) => { + const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); + if (searchView) { + const tree: WorkbenchObjectTree = searchView.getControl(); + searchView.openEditorWithMultiCursor(tree.getFocus()[0]); + } + } +}); -registry.registerWorkbenchAction(new SyncActionDescriptor(RefreshAction, RefreshAction.ID, RefreshAction.LABEL), 'Search: Refresh', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(ClearSearchResultsAction, ClearSearchResultsAction.ID, ClearSearchResultsAction.LABEL), 'Search: Clear Search Results', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(CollapseDeepestExpandedLevelAction, CollapseDeepestExpandedLevelAction.ID, CollapseDeepestExpandedLevelAction.LABEL), 'Search: Collapse All', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowAllSymbolsAction, ShowAllSymbolsAction.ID, ShowAllSymbolsAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_T }), 'Go to Symbol in Workspace...'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleSearchOnTypeAction, ToggleSearchOnTypeAction.ID, ToggleSearchOnTypeAction.LABEL), 'Search: Toggle Search on Type', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(RefreshAction, RefreshAction.ID, RefreshAction.LABEL), 'Search: Refresh', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ClearSearchResultsAction, ClearSearchResultsAction.ID, ClearSearchResultsAction.LABEL), 'Search: Clear Search Results', category); + +registry.registerWorkbenchAction( + SyncActionDescriptor.create(OpenResultsInEditorAction, OpenResultsInEditorAction.ID, OpenResultsInEditorAction.LABEL, + { mac: { primary: KeyMod.CtrlCmd | KeyCode.Enter } }, + ContextKeyExpr.and(Constants.HasSearchResults, Constants.SearchViewFocusedKey, Constants.EnableSearchEditorPreview)), + 'Search: Open Results in Editor', category, + ContextKeyExpr.and(Constants.EnableSearchEditorPreview)); + +registry.registerWorkbenchAction( + SyncActionDescriptor.create(RerunEditorSearchAction, RerunEditorSearchAction.ID, RerunEditorSearchAction.LABEL, + { primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.KEY_R }, + ContextKeyExpr.and(EditorContextKeys.languageId.isEqualTo('search-result'))), + 'Search Editor: Search Again', category, + ContextKeyExpr.and(EditorContextKeys.languageId.isEqualTo('search-result'))); // Register Quick Open Handler Registry.as(QuickOpenExtensions.Quickopen).registerDefaultQuickOpenHandler( - new QuickOpenHandlerDescriptor( + QuickOpenHandlerDescriptor.create( OpenAnythingHandler, OpenAnythingHandler.ID, '', @@ -636,7 +664,7 @@ Registry.as(QuickOpenExtensions.Quickopen).registerDefaultQu ); Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - new QuickOpenHandlerDescriptor( + QuickOpenHandlerDescriptor.create( OpenSymbolHandler, OpenSymbolHandler.ID, ShowAllSymbolsAction.ALL_SYMBOLS_PREFIX, @@ -747,7 +775,7 @@ configurationRegistry.registerConfiguration({ '', '' ], - default: 'auto', + default: 'alwaysExpand', description: nls.localize('search.collapseAllResults', "Controls whether the search results will be collapsed or expanded."), }, 'search.useReplacePreview': { @@ -775,6 +803,21 @@ configurationRegistry.registerConfiguration({ ], default: 'auto', description: nls.localize('search.actionsPosition', "Controls the positioning of the actionbar on rows in the search view.") + }, + 'search.searchOnType': { + type: 'boolean', + default: true, + description: nls.localize('search.searchOnType', "Search all files as you type.") + }, + 'search.searchOnTypeDebouncePeriod': { + type: 'number', + default: 300, + markdownDescription: nls.localize('search.searchOnTypeDebouncePeriod', "When `#search.searchOnType#` is enabled, controls the timeout in milliseconds between a character being typed and the search starting. Has no effect when `search.searchOnType` is disabled.") + }, + 'search.enableSearchEditorPreview': { + type: 'boolean', + default: false, + description: nls.localize('search.enableSearchEditorPreview', "Experimental: When enabled, allows opening workspace search results in an editor.") } } }); diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index 49343231d6..c625a2f167 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -13,7 +13,7 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { ILabelService } from 'vs/platform/label/common/label'; import { ICommandHandler } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { getSelectionKeyboardEvent, WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; @@ -23,12 +23,15 @@ import { FolderMatch, FileMatch, FileMatchOrMatch, FolderMatchWithResource, Matc import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { ISearchConfiguration, VIEWLET_ID, PANEL_ID } from 'vs/workbench/services/search/common/search'; +import { ISearchConfiguration, VIEWLET_ID, PANEL_ID, ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; import { ISearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { SearchViewlet } from 'vs/workbench/contrib/search/browser/searchViewlet'; import { SearchPanel } from 'vs/workbench/contrib/search/browser/searchPanel'; import { ITreeNavigator } from 'vs/base/browser/ui/tree/tree'; +import { createEditorFromSearchResult, refreshActiveEditorSearch } from 'vs/workbench/contrib/search/browser/searchEditor'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; export function isSearchViewFocused(viewletService: IViewletService, panelService: IPanelService): boolean { const searchView = getSearchView(viewletService, panelService); @@ -251,6 +254,30 @@ export class CloseReplaceAction extends Action { } } +// --- Toggle Search On Type + +export class ToggleSearchOnTypeAction extends Action { + + static readonly ID = 'workbench.action.toggleSearchOnType'; + static readonly LABEL = nls.localize('toggleTabs', "Toggle Search on Type"); + + private static readonly searchOnTypeKey = 'search.searchOnType'; + + constructor( + id: string, + label: string, + @IConfigurationService private readonly configurationService: IConfigurationService + ) { + super(id, label); + } + + run(): Promise { + const searchOnType = this.configurationService.getValue(ToggleSearchOnTypeAction.searchOnTypeKey); + return this.configurationService.updateValue(ToggleSearchOnTypeAction.searchOnTypeKey, !searchOnType); + } +} + + export class RefreshAction extends Action { static readonly ID: string = 'search.action.refreshSearchResults'; @@ -265,7 +292,7 @@ export class RefreshAction extends Action { get enabled(): boolean { const searchView = getSearchView(this.viewletService, this.panelService); - return !!searchView && searchView.hasSearchResults(); + return !!searchView && searchView.hasSearchPattern(); } update(): void { @@ -275,7 +302,7 @@ export class RefreshAction extends Action { run(): Promise { const searchView = getSearchView(this.viewletService, this.panelService); if (searchView) { - searchView.onQueryChanged(); + searchView.onQueryChanged(false); } return Promise.resolve(); @@ -394,6 +421,63 @@ export class CancelSearchAction extends Action { } } +export class OpenResultsInEditorAction extends Action { + + static readonly ID: string = Constants.OpenInEditorCommandId; + static readonly LABEL = nls.localize('search.openResultsInEditor', "Open Results in Editor"); + + constructor(id: string, label: string, + @IViewletService private viewletService: IViewletService, + @IPanelService private panelService: IPanelService, + @ILabelService private labelService: ILabelService, + @IEditorService private editorService: IEditorService, + @IConfigurationService private configurationService: IConfigurationService + ) { + super(id, label, 'codicon-go-to-file'); + } + + get enabled(): boolean { + const searchView = getSearchView(this.viewletService, this.panelService); + return !!searchView && searchView.hasSearchResults(); + } + + update() { + this._setEnabled(this.enabled); + } + + async run() { + const searchView = getSearchView(this.viewletService, this.panelService); + if (searchView && this.configurationService.getValue('search').enableSearchEditorPreview) { + await createEditorFromSearchResult(searchView.searchResult, searchView.searchIncludePattern.getValue(), searchView.searchExcludePattern.getValue(), this.labelService, this.editorService); + } + } +} + +export class RerunEditorSearchAction extends Action { + + static readonly ID: string = Constants.RerunEditorSearchCommandId; + static readonly LABEL = nls.localize('search.rerunEditorSearch', "Search Again"); + + constructor(id: string, label: string, + @IInstantiationService private instantiationService: IInstantiationService, + @IEditorService private editorService: IEditorService, + @IConfigurationService private configurationService: IConfigurationService, + @IWorkspaceContextService private contextService: IWorkspaceContextService, + @ILabelService private labelService: ILabelService, + @IProgressService private progressService: IProgressService + ) { + super(id, label); + } + + async run() { + if (this.configurationService.getValue('search').enableSearchEditorPreview) { + await this.progressService.withProgress({ location: ProgressLocation.Window }, + () => refreshActiveEditorSearch(this.editorService, this.instantiationService, this.contextService, this.labelService, this.configurationService)); + } + } +} + + export class FocusNextSearchResultAction extends Action { static readonly ID = 'search.action.focusNextSearchResult'; static readonly LABEL = nls.localize('FocusNextSearchResult.label', "Focus Next Search Result"); diff --git a/src/vs/workbench/contrib/search/browser/searchEditor.ts b/src/vs/workbench/contrib/search/browser/searchEditor.ts new file mode 100644 index 0000000000..62969ab1c1 --- /dev/null +++ b/src/vs/workbench/contrib/search/browser/searchEditor.ts @@ -0,0 +1,266 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Match, searchMatchComparer, FileMatch, SearchResult, SearchModel } from 'vs/workbench/contrib/search/common/searchModel'; +import { repeat } from 'vs/base/common/strings'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { coalesce, flatten } from 'vs/base/common/arrays'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { URI } from 'vs/base/common/uri'; +import { ITextQuery, IPatternInfo, ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; +import * as network from 'vs/base/common/network'; +import { Range } from 'vs/editor/common/core/range'; +import { ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; +import { getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/common/search'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { searchEditorFindMatch, searchEditorFindMatchBorder } from 'vs/platform/theme/common/colorRegistry'; + +// Using \r\n on Windows inserts an extra newline between results. +const lineDelimiter = '\n'; + +const translateRangeLines = + (n: number) => + (range: Range) => + new Range(range.startLineNumber + n, range.startColumn, range.endLineNumber + n, range.endColumn); + +const matchToSearchResultFormat = (match: Match): { line: string, ranges: Range[], lineNumber: string }[] => { + const getLinePrefix = (i: number) => `${match.range().startLineNumber + i}`; + + const fullMatchLines = match.fullPreviewLines(); + const largestPrefixSize = fullMatchLines.reduce((largest, _, i) => Math.max(getLinePrefix(i).length, largest), 0); + + + const results: { line: string, ranges: Range[], lineNumber: string }[] = []; + + fullMatchLines + .forEach((sourceLine, i) => { + const lineNumber = getLinePrefix(i); + const paddingStr = repeat(' ', largestPrefixSize - lineNumber.length); + const prefix = ` ${lineNumber}: ${paddingStr}`; + const prefixOffset = prefix.length; + + const line = (prefix + sourceLine); + + const rangeOnThisLine = ({ start, end }: { start?: number; end?: number; }) => new Range(1, (start ?? 1) + prefixOffset, 1, (end ?? sourceLine.length + 1) + prefixOffset); + + const matchRange = match.range(); + const matchIsSingleLine = matchRange.startLineNumber === matchRange.endLineNumber; + + let lineRange; + if (matchIsSingleLine) { lineRange = (rangeOnThisLine({ start: matchRange.startColumn, end: matchRange.endColumn })); } + else if (i === 0) { lineRange = (rangeOnThisLine({ start: matchRange.startColumn })); } + else if (i === fullMatchLines.length - 1) { lineRange = (rangeOnThisLine({ end: matchRange.endColumn })); } + else { lineRange = (rangeOnThisLine({})); } + + results.push({ lineNumber: lineNumber, line, ranges: [lineRange] }); + }); + + return results; +}; + +type SearchResultSerialization = { text: string[], matchRanges: Range[] }; +function fileMatchToSearchResultFormat(fileMatch: FileMatch, labelFormatter: (x: URI) => string): SearchResultSerialization { + const serializedMatches = flatten(fileMatch.matches() + .sort(searchMatchComparer) + .map(match => matchToSearchResultFormat(match))); + + const uriString = labelFormatter(fileMatch.resource); + let text: string[] = [`${uriString}:`]; + let matchRanges: Range[] = []; + + const targetLineNumberToOffset: Record = {}; + + const seenLines = new Set(); + serializedMatches.forEach(match => { + if (!seenLines.has(match.line)) { + targetLineNumberToOffset[match.lineNumber] = text.length; + seenLines.add(match.line); + text.push(match.line); + } + + matchRanges.push(...match.ranges.map(translateRangeLines(targetLineNumberToOffset[match.lineNumber]))); + }); + + + return { text, matchRanges }; +} + +const flattenSearchResultSerializations = (serializations: SearchResultSerialization[]): SearchResultSerialization => { + let text: string[] = []; + let matchRanges: Range[] = []; + + serializations.forEach(serialized => { + serialized.matchRanges.map(translateRangeLines(text.length)).forEach(range => matchRanges.push(range)); + serialized.text.forEach(line => text.push(line)); + text.push(''); // new line + }); + + return { text, matchRanges }; +}; + +const contentPatternToSearchResultHeader = (pattern: ITextQuery | null, includes: string, excludes: string): string[] => { + if (!pattern) { return []; } + + const removeNullFalseAndUndefined = (a: (T | null | false | undefined)[]) => a.filter(a => a !== false && a !== null && a !== undefined) as T[]; + + const escapeNewlines = (str: string) => str.replace(/\\/g, '\\\\').replace(/\n/g, '\\n'); + + return removeNullFalseAndUndefined([ + `# Query: ${escapeNewlines(pattern.contentPattern.pattern)}`, + + (pattern.contentPattern.isCaseSensitive || pattern.contentPattern.isWordMatch || pattern.contentPattern.isRegExp || pattern.userDisabledExcludesAndIgnoreFiles) + && `# Flags: ${coalesce([ + pattern.contentPattern.isCaseSensitive && 'CaseSensitive', + pattern.contentPattern.isWordMatch && 'WordMatch', + pattern.contentPattern.isRegExp && 'RegExp', + pattern.userDisabledExcludesAndIgnoreFiles && 'IgnoreExcludeSettings' + ]).join(' ')}`, + includes ? `# Including: ${includes}` : undefined, + excludes ? `# Excluding: ${excludes}` : undefined, + '' + ]); +}; + +const searchHeaderToContentPattern = (header: string[]): { pattern: string, flags: { regex: boolean, wholeWord: boolean, caseSensitive: boolean, ignoreExcludes: boolean }, includes: string, excludes: string } => { + const query = { + pattern: '', + flags: { regex: false, caseSensitive: false, ignoreExcludes: false, wholeWord: false }, + includes: '', + excludes: '' + }; + + const unescapeNewlines = (str: string) => str.replace(/\\\\/g, '\\').replace(/\\n/g, '\n'); + const parseYML = /^# ([^:]*): (.*)$/; + for (const line of header) { + const parsed = parseYML.exec(line); + if (!parsed) { continue; } + const [, key, value] = parsed; + switch (key) { + case 'Query': query.pattern = unescapeNewlines(value); break; + case 'Including': query.includes = value; break; + case 'Excluding': query.excludes = value; break; + case 'Flags': { + query.flags = { + regex: value.indexOf('RegExp') !== -1, + caseSensitive: value.indexOf('CaseSensitive') !== -1, + ignoreExcludes: value.indexOf('IgnoreExcludeSettings') !== -1, + wholeWord: value.indexOf('WordMatch') !== -1 + }; + } + } + } + + return query; +}; + +const serializeSearchResultForEditor = (searchResult: SearchResult, rawIncludePattern: string, rawExcludePattern: string, labelFormatter: (x: URI) => string): SearchResultSerialization => { + const header = contentPatternToSearchResultHeader(searchResult.query, rawIncludePattern, rawExcludePattern); + const allResults = + flattenSearchResultSerializations( + flatten(searchResult.folderMatches().sort(searchMatchComparer) + .map(folderMatch => folderMatch.matches().sort(searchMatchComparer) + .map(fileMatch => fileMatchToSearchResultFormat(fileMatch, labelFormatter))))); + + return { matchRanges: allResults.matchRanges.map(translateRangeLines(header.length)), text: header.concat(allResults.text) }; +}; + +export const refreshActiveEditorSearch = + async (editorService: IEditorService, instantiationService: IInstantiationService, contextService: IWorkspaceContextService, labelService: ILabelService, configurationService: IConfigurationService) => { + const model = editorService.activeTextEditorWidget?.getModel(); + if (!model) { return; } + + const textModel = model as ITextModel; + + const header = textModel.getValueInRange(new Range(1, 1, 5, 1)) + .split(lineDelimiter) + .filter(line => line.indexOf('# ') === 0); + + const contentPattern = searchHeaderToContentPattern(header); + + const content: IPatternInfo = { + pattern: contentPattern.pattern, + isRegExp: contentPattern.flags.regex, + isCaseSensitive: contentPattern.flags.caseSensitive, + isWordMatch: contentPattern.flags.wholeWord + }; + + const options: ITextQueryBuilderOptions = { + _reason: 'searchEditor', + extraFileResources: instantiationService.invokeFunction(getOutOfWorkspaceEditorResources), + maxResults: 10000, + disregardIgnoreFiles: contentPattern.flags.ignoreExcludes, + disregardExcludeSettings: contentPattern.flags.ignoreExcludes, + excludePattern: contentPattern.excludes, + includePattern: contentPattern.includes, + previewOptions: { + matchLines: 1, + charsPerLine: 1000 + }, + isSmartCase: configurationService.getValue('search').smartCase, + expandPatterns: true + }; + + const folderResources = contextService.getWorkspace().folders; + + let query: ITextQuery; + try { + const queryBuilder = instantiationService.createInstance(QueryBuilder); + query = queryBuilder.text(content, folderResources.map(folder => folder.uri), options); + } catch (err) { + return; + } + + const searchModel = instantiationService.createInstance(SearchModel); + await searchModel.search(query); + + const labelFormatter = (uri: URI): string => labelService.getUriLabel(uri, { relative: true }); + const results = serializeSearchResultForEditor(searchModel.searchResult, '', '', labelFormatter); + + textModel.setValue(results.text.join(lineDelimiter)); + textModel.deltaDecorations([], results.matchRanges.map(range => ({ range, options: { className: 'searchEditorFindMatch', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }))); + }; + + +export const createEditorFromSearchResult = + async (searchResult: SearchResult, rawIncludePattern: string, rawExcludePattern: string, labelService: ILabelService, editorService: IEditorService) => { + const searchTerm = searchResult.query?.contentPattern.pattern.replace(/[^\w-_. ]/g, '') || 'Search'; + + const labelFormatter = (uri: URI): string => labelService.getUriLabel(uri, { relative: true }); + + const results = serializeSearchResultForEditor(searchResult, rawIncludePattern, rawExcludePattern, labelFormatter); + + let possible = { + contents: results.text.join(lineDelimiter), + mode: 'search-result', + resource: URI.from({ scheme: network.Schemas.untitled, path: searchTerm }) + }; + + let id = 0; + while (editorService.getOpened(possible)) { + possible.resource = possible.resource.with({ path: searchTerm + '-' + ++id }); + } + + const editor = await editorService.openEditor(possible); + const control = editor?.getControl()!; + control.updateOptions({ lineNumbers: 'off' }); + + const model = control.getModel() as ITextModel; + + model.deltaDecorations([], results.matchRanges.map(range => ({ range, options: { className: 'searchEditorFindMatch', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }))); + }; + +// theming +registerThemingParticipant((theme, collector) => { + collector.addRule(`.monaco-editor .searchEditorFindMatch { background-color: ${theme.getColor(searchEditorFindMatch)}; }`); + + const findMatchHighlightBorder = theme.getColor(searchEditorFindMatchBorder); + if (findMatchHighlightBorder) { + collector.addRule(`.monaco-editor .searchEditorFindMatch { border: 1px ${theme.type === 'hc' ? 'dotted' : 'solid'} ${findMatchHighlightBorder}; box-sizing: border-box; }`); + } +}); diff --git a/src/vs/workbench/contrib/search/browser/searchPanel.ts b/src/vs/workbench/contrib/search/browser/searchPanel.ts index c40912b0ae..6960ab3096 100644 --- a/src/vs/workbench/contrib/search/browser/searchPanel.ts +++ b/src/vs/workbench/contrib/search/browser/searchPanel.ts @@ -7,7 +7,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { PANEL_ID } from 'vs/workbench/services/search/common/search'; -import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; +import { SearchView, SearchViewPosition } from 'vs/workbench/contrib/search/browser/searchView'; import { Panel } from 'vs/workbench/browser/panel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { localize } from 'vs/nls'; @@ -25,13 +25,13 @@ export class SearchPanel extends Panel { @IInstantiationService instantiationService: IInstantiationService, ) { super(PANEL_ID, telemetryService, themeService, storageService); - this.searchView = this._register(instantiationService.createInstance(SearchView, { id: PANEL_ID, title: localize('search', "Search") })); + this.searchView = this._register(instantiationService.createInstance(SearchView, SearchViewPosition.Panel, { id: PANEL_ID, title: localize('search', "Search"), actionRunner: this.getActionRunner() })); this._register(this.searchView.onDidChangeTitleArea(() => this.updateTitleArea())); this._register(this.onDidChangeVisibility(visible => this.searchView.setVisible(visible))); } create(parent: HTMLElement): void { - dom.addClasses(parent, 'monaco-panel-view', 'search-panel'); + dom.addClasses(parent, 'monaco-pane-view', 'search-panel'); this.searchView.render(); dom.append(parent, this.searchView.element); this.searchView.setExpanded(true); diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index b445e1ba12..4c63d2f5fb 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -20,7 +20,7 @@ import * as env from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/searchview'; -import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, isCodeEditor, isDiffEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import * as nls from 'vs/nls'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; @@ -36,14 +36,14 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IProgressService, IProgressStep, IProgress } from 'vs/platform/progress/common/progress'; import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchConfigurationProperties, ITextQuery, VIEW_ID, VIEWLET_ID } from 'vs/workbench/services/search/common/search'; import { ISearchHistoryService, ISearchHistoryValues } from 'vs/workbench/contrib/search/common/searchHistoryService'; -import { diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, editorFindMatchHighlight, editorFindMatchHighlightBorder, listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry'; +import { diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, editorFindMatchHighlight, editorFindMatchHighlightBorder, listActiveSelectionForeground, foreground } from 'vs/platform/theme/common/colorRegistry'; import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { OpenFileFolderAction, OpenFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; import { ResourceLabels } from 'vs/workbench/browser/labels'; import { IEditor } from 'vs/workbench/common/editor'; import { ExcludePatternInputWidget, PatternInputWidget } from 'vs/workbench/contrib/search/browser/patternInputWidget'; -import { CancelSearchAction, ClearSearchResultsAction, CollapseDeepestExpandedLevelAction, RefreshAction, IFindInFilesArgs } from 'vs/workbench/contrib/search/browser/searchActions'; +import { CancelSearchAction, ClearSearchResultsAction, CollapseDeepestExpandedLevelAction, RefreshAction, IFindInFilesArgs, OpenResultsInEditorAction, appendKeyBindingLabel } from 'vs/workbench/contrib/search/browser/searchActions'; import { FileMatchRenderer, FolderMatchRenderer, MatchRenderer, SearchAccessibilityProvider, SearchDelegate, SearchDND } from 'vs/workbench/contrib/search/browser/searchResultsView'; import { ISearchWidgetOptions, SearchWidget } from 'vs/workbench/contrib/search/browser/searchWidget'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; @@ -53,14 +53,20 @@ import { getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/co import { FileMatch, FileMatchOrMatch, IChangeEvent, ISearchWorkbenchService, Match, RenderableMatch, searchMatchComparer, SearchModel, SearchResult, FolderMatch, FolderMatchWithResource } from 'vs/workbench/contrib/search/common/searchModel'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IPreferencesService, ISettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { relativePath } from 'vs/base/common/resources'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; -import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Memento, MementoObject } from 'vs/workbench/common/memento'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { MultiCursorSelectionController } from 'vs/editor/contrib/multicursor/multicursor'; +import { Selection } from 'vs/editor/common/core/selection'; +import { SIDE_BAR_BACKGROUND, PANEL_BACKGROUND } from 'vs/workbench/common/theme'; +import { createEditorFromSearchResult } from 'vs/workbench/contrib/search/browser/searchEditor'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { Color, RGBA } from 'vs/base/common/color'; const $ = dom.$; @@ -70,7 +76,13 @@ enum SearchUIState { SlowSearch } -export class SearchView extends ViewletPanel { +export enum SearchViewPosition { + SideBar, + Panel +} + +const SEARCH_CANCELLED_MESSAGE = nls.localize('searchCanceled', "Search was canceled before any results could be found - "); +export class SearchView extends ViewletPane { private static readonly MAX_TEXT_RESULTS = 10000; @@ -98,10 +110,11 @@ export class SearchView extends ViewletPanel { private folderMatchFocused: IContextKey; private matchFocused: IContextKey; private hasSearchResultsKey: IContextKey; + private enableSearchEditorPreview: IContextKey; private state: SearchUIState = SearchUIState.Idle; - private actions: Array = []; + private actions: Array = []; private cancelAction: CancelSearchAction; private refreshAction: RefreshAction; private contextMenu: IMenu | null = null; @@ -128,9 +141,11 @@ export class SearchView extends ViewletPanel { private searchWithoutFolderMessageElement: HTMLElement | undefined; private currentSearchQ = Promise.resolve(); + private addToSearchHistoryDelayer: Delayer; constructor( - options: IViewletPanelOptions, + private position: SearchViewPosition, + options: IViewletPaneOptions, @IFileService private readonly fileService: IFileService, @IEditorService private readonly editorService: IEditorService, @IProgressService private readonly progressService: IProgressService, @@ -143,7 +158,7 @@ export class SearchView extends ViewletPanel { @ISearchWorkbenchService private readonly searchWorkbenchService: ISearchWorkbenchService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IReplaceService private readonly replaceService: IReplaceService, - @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService, @IPreferencesService private readonly preferencesService: IPreferencesService, @IThemeService protected themeService: IThemeService, @ISearchHistoryService private readonly searchHistoryService: ISearchHistoryService, @@ -152,9 +167,10 @@ export class SearchView extends ViewletPanel { @IAccessibilityService private readonly accessibilityService: IAccessibilityService, @IKeybindingService keybindingService: IKeybindingService, @IStorageService storageService: IStorageService, + @ILabelService private readonly labelService: ILabelService, @IOpenerService private readonly openerService: IOpenerService ) { - super({ ...(options as IViewletPanelOptions), id: VIEW_ID, ariaHeaderLabel: nls.localize('searchView', "Search") }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...options, id: VIEW_ID, ariaHeaderLabel: nls.localize('searchView', "Search") }, keybindingService, contextMenuService, configurationService, contextKeyService); this.viewletVisible = Constants.SearchViewVisibleKey.bindTo(contextKeyService); this.viewletFocused = Constants.SearchViewFocusedKey.bindTo(contextKeyService); @@ -169,6 +185,14 @@ export class SearchView extends ViewletPanel { this.folderMatchFocused = Constants.FolderFocusKey.bindTo(contextKeyService); this.matchFocused = Constants.MatchFocusKey.bindTo(this.contextKeyService); this.hasSearchResultsKey = Constants.HasSearchResults.bindTo(this.contextKeyService); + this.enableSearchEditorPreview = Constants.EnableSearchEditorPreview.bindTo(this.contextKeyService); + + this.enableSearchEditorPreview.set(this.searchConfig.enableSearchEditorPreview); + this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('search.previewSearchEditor')) { + this.enableSearchEditorPreview.set(this.searchConfig.enableSearchEditorPreview); + } + }); this.viewModel = this._register(this.searchWorkbenchService.searchModel); this.queryBuilder = this.instantiationService.createInstance(QueryBuilder); @@ -176,16 +200,25 @@ export class SearchView extends ViewletPanel { this.viewletState = this.memento.getMemento(StorageScope.WORKSPACE); this._register(this.fileService.onFileChanges(e => this.onFilesChanged(e))); - this._register(this.untitledEditorService.onDidChangeDirty(e => this.onUntitledDidChangeDirty(e))); + this._register(this.untitledTextEditorService.onDidDisposeModel(e => this.onUntitledDidDispose(e))); this._register(this.contextService.onDidChangeWorkbenchState(() => this.onDidChangeWorkbenchState())); this._register(this.searchHistoryService.onDidClearHistory(() => this.clearHistory())); this.delayedRefresh = this._register(new Delayer(250)); + this.addToSearchHistoryDelayer = this._register(new Delayer(500)); + this.actions = [ this._register(this.instantiationService.createInstance(ClearSearchResultsAction, ClearSearchResultsAction.ID, ClearSearchResultsAction.LABEL)), this._register(this.instantiationService.createInstance(CollapseDeepestExpandedLevelAction, CollapseDeepestExpandedLevelAction.ID, CollapseDeepestExpandedLevelAction.LABEL)) ]; + + if (this.searchConfig.enableSearchEditorPreview) { + this.actions.push( + this._register(this.instantiationService.createInstance(OpenResultsInEditorAction, OpenResultsInEditorAction.ID, OpenResultsInEditorAction.LABEL)) + ); + } + this.refreshAction = this._register(this.instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL)); this.cancelAction = this._register(this.instantiationService.createInstance(CancelSearchAction, CancelSearchAction.ID, CancelSearchAction.LABEL)); } @@ -264,8 +297,8 @@ export class SearchView extends ViewletPanel { this.inputPatternIncludes.setValue(patternIncludes); - this.inputPatternIncludes.onSubmit(() => this.onQueryChanged(true)); - this.inputPatternIncludes.onCancel(() => this.viewModel.cancelSearch()); // Cancel search without focusing the search widget + this.inputPatternIncludes.onSubmit(triggeredOnType => this.onQueryChanged(true, triggeredOnType)); + this.inputPatternIncludes.onCancel(() => this.cancelSearch(false)); this.trackInputBox(this.inputPatternIncludes.inputFocusTracker, this.inputPatternIncludesFocused); // excludes list @@ -280,8 +313,9 @@ export class SearchView extends ViewletPanel { this.inputPatternExcludes.setValue(patternExclusions); this.inputPatternExcludes.setUseExcludesAndIgnoreFiles(useExcludesAndIgnoreFiles); - this.inputPatternExcludes.onSubmit(() => this.onQueryChanged(true)); - this.inputPatternExcludes.onCancel(() => this.viewModel.cancelSearch()); // Cancel search without focusing the search widget + this.inputPatternExcludes.onSubmit(triggeredOnType => this.onQueryChanged(true, triggeredOnType)); + this.inputPatternExcludes.onCancel(() => this.cancelSearch(false)); + this.inputPatternExcludes.onChangeIgnoreBox(() => this.onQueryChanged(true)); this.trackInputBox(this.inputPatternExcludes.inputFocusTracker, this.inputPatternExclusionsFocused); this.messagesElement = dom.append(this.container, $('.messages')); @@ -381,8 +415,8 @@ export class SearchView extends ViewletPanel { this.searchWidget.toggleReplace(true); } - this._register(this.searchWidget.onSearchSubmit(() => this.onQueryChanged())); - this._register(this.searchWidget.onSearchCancel(() => this.cancelSearch())); + this._register(this.searchWidget.onSearchSubmit(triggeredOnType => this.onQueryChanged(false, triggeredOnType))); + this._register(this.searchWidget.onSearchCancel(({ focus }) => this.cancelSearch(focus))); this._register(this.searchWidget.searchInput.onDidOptionChange(() => this.onQueryChanged(true))); this._register(this.searchWidget.onDidHeightChange(() => this.reLayout())); @@ -446,7 +480,7 @@ export class SearchView extends ViewletPanel { } refreshTree(event?: IChangeEvent): void { - const collapseResults = this.configurationService.getValue('search').collapseResults; + const collapseResults = this.searchConfig.collapseResults; if (!event || event.added || event.removed) { // Refresh whole tree this.tree.setChildren(null, this.createResultIterator(collapseResults)); @@ -538,6 +572,7 @@ export class SearchView extends ViewletPanel { progressComplete(); const messageEl = this.clearMessage(); dom.append(messageEl, $('p', undefined, afterReplaceAllMessage)); + this.reLayout(); }, (error) => { progressComplete(); errors.isPromiseCanceledError(error); @@ -646,7 +681,10 @@ export class SearchView extends ViewletPanel { identityProvider, accessibilityProvider: this.instantiationService.createInstance(SearchAccessibilityProvider, this.viewModel), dnd: this.instantiationService.createInstance(SearchDND), - multipleSelectionSupport: false + multipleSelectionSupport: false, + overrideStyles: { + listBackground: this.position === SearchViewPosition.SideBar ? SIDE_BAR_BACKGROUND : PANEL_BACKGROUND + } })); this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); @@ -793,9 +831,9 @@ export class SearchView extends ViewletPanel { if (this.searchWidget.searchInput.getRegex()) { selectedText = strings.escapeRegExpCharacters(selectedText); } - - this.searchWidget.searchInput.setValue(selectedText); + this.searchWidget.setValue(selectedText, true); updatedText = true; + this.onQueryChanged(false); } } @@ -877,7 +915,7 @@ export class SearchView extends ViewletPanel { return; } - const actionsPosition = this.configurationService.getValue('search').actionsPosition; + const actionsPosition = this.searchConfig.actionsPosition; dom.toggleClass(this.getContainer(), SearchView.ACTIONS_RIGHT_CLASS_NAME, actionsPosition === 'right'); dom.toggleClass(this.getContainer(), SearchView.WIDE_CLASS_NAME, this.size.width >= SearchView.WIDE_VIEW_SIZE); @@ -921,9 +959,13 @@ export class SearchView extends ViewletPanel { return !this.viewModel.searchResult.isEmpty(); } + hasSearchPattern(): boolean { + return this.searchWidget && this.searchWidget.searchInput.getValue().length > 0; + } + clearSearchResults(): void { this.viewModel.searchResult.clear(); - this.showEmptyStage(); + this.showEmptyStage(true); if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { this.showSearchWithoutFolderMessage(); } @@ -934,9 +976,9 @@ export class SearchView extends ViewletPanel { aria.status(nls.localize('ariaSearchResultsClearStatus', "The search results have been cleared")); } - cancelSearch(): boolean { + cancelSearch(focus: boolean = true): boolean { if (this.viewModel.cancelSearch()) { - this.searchWidget.focus(); + if (focus) { this.searchWidget.focus(); } return true; } return false; @@ -1146,7 +1188,7 @@ export class SearchView extends ViewletPanel { this.searchWidget.focus(false); } - onQueryChanged(preserveFocus?: boolean): void { + onQueryChanged(preserveFocus: boolean, triggeredOnType = false): void { if (!this.searchWidget.searchInput.inputBox.isInputValid()) { return; } @@ -1160,6 +1202,8 @@ export class SearchView extends ViewletPanel { const useExcludesAndIgnoreFiles = this.inputPatternExcludes.useExcludesAndIgnoreFiles(); if (contentPattern.length === 0) { + this.clearSearchResults(); + this.clearMessage(); return; } @@ -1176,8 +1220,7 @@ export class SearchView extends ViewletPanel { // Need the full match line to correctly calculate replace text, if this is a search/replace with regex group references ($1, $2, ...). // 10000 chars is enough to avoid sending huge amounts of text around, if you do a replace with a longer match, it may or may not resolve the group refs correctly. // https://github.com/Microsoft/vscode/issues/58374 - const charsPerLine = content.isRegExp ? 10000 : - 250; + const charsPerLine = content.isRegExp ? 10000 : 1000; const options: ITextQueryBuilderOptions = { _reason: 'searchView', @@ -1191,7 +1234,7 @@ export class SearchView extends ViewletPanel { matchLines: 1, charsPerLine }, - isSmartCase: this.configurationService.getValue().search.smartCase, + isSmartCase: this.searchConfig.smartCase, expandPatterns: true }; const folderResources = this.contextService.getWorkspace().folders; @@ -1210,7 +1253,7 @@ export class SearchView extends ViewletPanel { } this.validateQuery(query).then(() => { - this.onQueryTriggered(query, options, excludePatternText, includePatternText); + this.onQueryTriggered(query, options, excludePatternText, includePatternText, triggeredOnType); if (!preserveFocus) { this.searchWidget.focus(false); // focus back to input field @@ -1240,21 +1283,21 @@ export class SearchView extends ViewletPanel { }); } - private onQueryTriggered(query: ITextQuery, options: ITextQueryBuilderOptions, excludePatternText: string, includePatternText: string): void { - this.searchWidget.searchInput.onSearchSubmit(); + private onQueryTriggered(query: ITextQuery, options: ITextQueryBuilderOptions, excludePatternText: string, includePatternText: string, triggeredOnType: boolean): void { + this.addToSearchHistoryDelayer.trigger(() => this.searchWidget.searchInput.onSearchSubmit()); this.inputPatternExcludes.onSearchSubmit(); this.inputPatternIncludes.onSearchSubmit(); this.viewModel.cancelSearch(); this.currentSearchQ = this.currentSearchQ - .then(() => this.doSearch(query, options, excludePatternText, includePatternText)) + .then(() => this.doSearch(query, options, excludePatternText, includePatternText, triggeredOnType)) .then(() => undefined, () => undefined); } - private doSearch(query: ITextQuery, options: ITextQueryBuilderOptions, excludePatternText: string, includePatternText: string): Thenable { + private doSearch(query: ITextQuery, options: ITextQueryBuilderOptions, excludePatternText: string, includePatternText: string, triggeredOnType: boolean): Thenable { let progressComplete: () => void; - this.progressService.withProgress({ location: VIEWLET_ID }, _progress => { + this.progressService.withProgress({ location: VIEWLET_ID, delay: triggeredOnType ? 300 : 0 }, _progress => { return new Promise(resolve => progressComplete = resolve); }); @@ -1277,7 +1320,7 @@ export class SearchView extends ViewletPanel { // Do final render, then expand if just 1 file with less than 50 matches this.onSearchResultsChanged(); - const collapseResults = this.configurationService.getValue('search').collapseResults; + const collapseResults = this.searchConfig.collapseResults; if (collapseResults !== 'alwaysCollapse' && this.viewModel.searchResult.matches().length === 1) { const onlyMatch = this.viewModel.searchResult.matches()[0]; if (onlyMatch.count() < 50) { @@ -1303,7 +1346,7 @@ export class SearchView extends ViewletPanel { let message: string; if (!completed) { - message = nls.localize('searchCanceled', "Search was canceled before any results could be found - "); + message = SEARCH_CANCELLED_MESSAGE; } else if (hasIncludes && hasExcludes) { message = nls.localize('noResultsIncludesExcludes', "No results found in '{0}' excluding '{1}' - ", includePatternText, excludePatternText); } else if (hasIncludes) { @@ -1324,7 +1367,7 @@ export class SearchView extends ViewletPanel { const searchAgainLink = dom.append(p, $('a.pointer.prominent', undefined, nls.localize('rerunSearch.message', "Search again"))); this.messageDisposables.push(dom.addDisposableListener(searchAgainLink, dom.EventType.CLICK, (e: MouseEvent) => { dom.EventHelper.stop(e, false); - this.onQueryChanged(); + this.onQueryChanged(false); })); } else if (hasIncludes || hasExcludes) { const searchAgainLink = dom.append(p, $('a.pointer.prominent', { tabindex: 0 }, nls.localize('rerunSearchInAll.message', "Search again in all files"))); @@ -1334,7 +1377,7 @@ export class SearchView extends ViewletPanel { this.inputPatternExcludes.setValue(''); this.inputPatternIncludes.setValue(''); - this.onQueryChanged(); + this.onQueryChanged(false); })); } else { const openSettingsLink = dom.append(p, $('a.pointer.prominent', { tabindex: 0 }, nls.localize('openSettings.message', "Open Settings"))); @@ -1455,7 +1498,23 @@ export class SearchView extends ViewletPanel { resultMsg += nls.localize('useIgnoresAndExcludesDisabled', " - exclude settings and ignore files are disabled"); } - dom.append(messageEl, $('p', undefined, resultMsg)); + if (this.searchConfig.enableSearchEditorPreview) { + dom.append(messageEl, $('span', undefined, resultMsg + ' - ')); + const span = dom.append(messageEl, $('span', undefined)); + const openInEditorLink = dom.append(span, $('a.pointer.prominent', undefined, nls.localize('openInEditor.message', "Open in editor"))); + + openInEditorLink.title = appendKeyBindingLabel( + nls.localize('openInEditor.tooltip', "Copy current search results to an editor"), + this.keybindingService.lookupKeybinding(Constants.OpenInEditorCommandId), this.keybindingService); + + this.messageDisposables.push(dom.addDisposableListener(openInEditorLink, dom.EventType.CLICK, (e: MouseEvent) => { + dom.EventHelper.stop(e, false); + createEditorFromSearchResult(this.searchResult, this.searchIncludePattern.getValue(), this.searchExcludePattern.getValue(), this.labelService, this.editorService); + })); + + } else { + dom.append(messageEl, $('p', undefined, resultMsg)); + } this.reLayout(); } else if (!msgWasHidden) { dom.hide(this.messagesElement); @@ -1499,13 +1558,19 @@ export class SearchView extends ViewletPanel { })); } - private showEmptyStage(): void { + private showEmptyStage(forceHideMessages = false): void { // disable 'result'-actions this.updateActions(); + const showingCancelled = (this.messagesElement.firstChild?.textContent?.indexOf(SEARCH_CANCELLED_MESSAGE) ?? -1) > -1; + // clean up ui // this.replaceService.disposeAllReplacePreviews(); - dom.hide(this.messagesElement); + if (showingCancelled || forceHideMessages || !this.configurationService.getValue().search.searchOnType) { + // when in search to type, don't preemptively hide, as it causes flickering and shifting of the live results + dom.hide(this.messagesElement); + } + dom.show(this.resultsElement); this.currentSelectedFileMatch = undefined; } @@ -1540,6 +1605,38 @@ export class SearchView extends ViewletPanel { }, errors.onUnexpectedError); } + openEditorWithMultiCursor(element: FileMatchOrMatch): Promise { + const resource = element instanceof Match ? element.parent().resource : (element).resource; + return this.editorService.openEditor({ + resource: resource, + options: { + preserveFocus: false, + pinned: true, + revealIfVisible: true + } + }).then(editor => { + if (editor) { + let fileMatch = null; + if (element instanceof FileMatch) { + fileMatch = element; + } + else if (element instanceof Match) { + fileMatch = element.parent(); + } + + if (fileMatch) { + const selections = fileMatch.matches().map(m => new Selection(m.range().startLineNumber, m.range().startColumn, m.range().endLineNumber, m.range().endColumn)); + const codeEditor = getCodeEditor(editor.getControl()); + if (codeEditor) { + let multiCursorController = MultiCursorSelectionController.get(codeEditor); + multiCursorController.selectAllUsingSelections(selections); + } + } + } + this.viewModel.searchResult.rangeHighlightDecorations.removeHighlightRange(); + }, errors.onUnexpectedError); + } + private getSelectionFrom(element: FileMatchOrMatch): any { let match: Match | null = null; if (element instanceof Match) { @@ -1564,18 +1661,16 @@ export class SearchView extends ViewletPanel { return undefined; } - private onUntitledDidChangeDirty(resource: URI): void { + private onUntitledDidDispose(resource: URI): void { if (!this.viewModel) { return; } // remove search results from this resource as it got disposed - if (!this.untitledEditorService.isDirty(resource)) { - const matches = this.viewModel.searchResult.matches(); - for (let i = 0, len = matches.length; i < len; i++) { - if (resource.toString() === matches[i].resource.toString()) { - this.viewModel.searchResult.remove(matches[i]); - } + const matches = this.viewModel.searchResult.matches(); + for (let i = 0, len = matches.length; i < len; i++) { + if (resource.toString() === matches[i].resource.toString()) { + this.viewModel.searchResult.remove(matches[i]); } } } @@ -1600,6 +1695,10 @@ export class SearchView extends ViewletPanel { ]; } + private get searchConfig(): ISearchConfigurationProperties { + return this.configurationService.getValue('search'); + } + private clearHistory(): void { this.searchWidget.clearHistory(); this.inputPatternExcludes.clearHistory(); @@ -1698,4 +1797,10 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { if (outlineSelectionColor) { collector.addRule(`.monaco-workbench .search-view .monaco-list.element-focused .monaco-list-row.focused.selected:not(.highlighted) .action-label:focus { outline-color: ${outlineSelectionColor} }`); } + + const foregroundColor = theme.getColor(foreground); + if (foregroundColor) { + const fgWithOpacity = new Color(new RGBA(foregroundColor.rgba.r, foregroundColor.rgba.g, foregroundColor.rgba.b, 0.5)); + collector.addRule(`.vs-dark .search-view .message { color: ${fgWithOpacity}; }`); + } }); diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 6b40a1b55f..4860263ea8 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -15,7 +15,6 @@ import { Action } from 'vs/base/common/actions'; import { Delayer } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import * as strings from 'vs/base/common/strings'; import { CONTEXT_FIND_WIDGET_NOT_VISIBLE } from 'vs/editor/contrib/find/findModel'; import * as nls from 'vs/nls'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -118,15 +117,15 @@ export class SearchWidget extends Widget { private replaceActive: IContextKey; private replaceActionBar!: ActionBar; private _replaceHistoryDelayer: Delayer; - + private _searchDelayer: Delayer; private ignoreGlobalFindBufferOnNextFocus = false; private previousGlobalFindBufferValue: string | null = null; - private _onSearchSubmit = this._register(new Emitter()); - readonly onSearchSubmit: Event = this._onSearchSubmit.event; + private _onSearchSubmit = this._register(new Emitter()); + readonly onSearchSubmit: Event = this._onSearchSubmit.event; - private _onSearchCancel = this._register(new Emitter()); - readonly onSearchCancel: Event = this._onSearchCancel.event; + private _onSearchCancel = this._register(new Emitter<{ focus: boolean }>()); + readonly onSearchCancel: Event<{ focus: boolean }> = this._onSearchCancel.event; private _onReplaceToggled = this._register(new Emitter()); readonly onReplaceToggled: Event = this._onReplaceToggled.event; @@ -149,6 +148,8 @@ export class SearchWidget extends Widget { private _onDidHeightChange = this._register(new Emitter()); readonly onDidHeightChange: Event = this._onDidHeightChange.event; + private temporarilySkipSearchOnChange = false; + constructor( container: HTMLElement, options: ISearchWidgetOptions, @@ -165,6 +166,7 @@ export class SearchWidget extends Widget { this.searchInputBoxFocused = Constants.SearchInputBoxFocusedKey.bindTo(this.contextKeyService); this.replaceInputBoxFocused = Constants.ReplaceInputBoxFocusedKey.bindTo(this.contextKeyService); this._replaceHistoryDelayer = new Delayer(500); + this._searchDelayer = this._register(new Delayer(this.searchConfiguration.searchOnTypeDebouncePeriod)); this.render(container, options); this.configurationService.onDidChangeConfiguration(e => { @@ -323,9 +325,6 @@ export class SearchWidget extends Widget { this.searchInput.setRegex(!!options.isRegex); this.searchInput.setCaseSensitive(!!options.isCaseSensitive); this.searchInput.setWholeWords(!!options.isWholeWords); - this._register(this.onSearchSubmit(() => { - this.searchInput.inputBox.addToHistory(); - })); this._register(this.searchInput.onCaseSensitiveKeyDown((keyboardEvent: IKeyboardEvent) => this.onCaseSensitiveKeyDown(keyboardEvent))); this._register(this.searchInput.onRegexKeyDown((keyboardEvent: IKeyboardEvent) => this.onRegexKeyDown(keyboardEvent))); this._register(this.searchInput.inputBox.onDidChange(() => this.onSearchInputChanged())); @@ -406,6 +405,11 @@ export class SearchWidget extends Widget { this._onReplaceToggled.fire(); } + setValue(value: string, skipSearchOnChange: boolean) { + this.searchInput.setValue(value); + this.temporarilySkipSearchOnChange = skipSearchOnChange || this.temporarilySkipSearchOnChange; + } + setReplaceAllActionState(enabled: boolean): void { if (this.replaceAllAction.enabled !== enabled) { this.replaceAllAction.enabled = enabled; @@ -438,18 +442,21 @@ export class SearchWidget extends Widget { return { content: e.message }; } - if (strings.regExpContainsBackreference(value)) { - if (!this.searchConfiguration.usePCRE2) { - return { content: nls.localize('regexp.backreferenceValidationFailure', "Backreferences are not supported") }; - } - } - return null; } private onSearchInputChanged(): void { this.searchInput.clearMessage(); this.setReplaceAllActionState(false); + + if (this.searchConfiguration.searchOnType) { + if (this.temporarilySkipSearchOnChange) { + this.temporarilySkipSearchOnChange = false; + } else { + this._onSearchCancel.fire({ focus: false }); + this._searchDelayer.trigger((() => this.submitSearch(true)), this.searchConfiguration.searchOnTypeDebouncePeriod); + } + } } private onSearchInputKeyDown(keyboardEvent: IKeyboardEvent) { @@ -464,7 +471,7 @@ export class SearchWidget extends Widget { } else if (keyboardEvent.equals(KeyCode.Escape)) { - this._onSearchCancel.fire(); + this._onSearchCancel.fire({ focus: true }); keyboardEvent.preventDefault(); } @@ -556,7 +563,7 @@ export class SearchWidget extends Widget { } } - private submitSearch(): void { + private submitSearch(triggeredOnType = false): void { this.searchInput.validate(); if (!this.searchInput.inputBox.isInputValid()) { return; @@ -564,13 +571,10 @@ export class SearchWidget extends Widget { const value = this.searchInput.getValue(); const useGlobalFindBuffer = this.searchConfiguration.globalFindClipboard; - if (value) { - if (useGlobalFindBuffer) { - this.clipboardServce.writeFindText(value); - } - - this._onSearchSubmit.fire(); + if (value && useGlobalFindBuffer) { + this.clipboardServce.writeFindText(value); } + this._onSearchSubmit.fire(triggeredOnType); } dispose(): void { diff --git a/src/vs/workbench/contrib/search/common/constants.ts b/src/vs/workbench/contrib/search/common/constants.ts index cde331349c..9fa0b9d991 100644 --- a/src/vs/workbench/contrib/search/common/constants.ts +++ b/src/vs/workbench/contrib/search/common/constants.ts @@ -15,6 +15,8 @@ export const RemoveActionId = 'search.action.remove'; export const CopyPathCommandId = 'search.action.copyPath'; export const CopyMatchCommandId = 'search.action.copyMatch'; export const CopyAllCommandId = 'search.action.copyAll'; +export const OpenInEditorCommandId = 'search.action.openInEditor'; +export const RerunEditorSearchCommandId = 'search.action.rerunEditorSearch'; export const ClearSearchHistoryCommandId = 'search.action.clearHistory'; export const FocusSearchListCommandID = 'search.action.focusSearchList'; export const ReplaceActionId = 'search.action.replace'; @@ -24,6 +26,7 @@ export const CloseReplaceWidgetActionId = 'closeReplaceInFilesWidget'; export const ToggleCaseSensitiveCommandId = 'toggleSearchCaseSensitive'; export const ToggleWholeWordCommandId = 'toggleSearchWholeWord'; export const ToggleRegexCommandId = 'toggleSearchRegex'; +export const AddCursorsAtSearchResults = 'addCursorsAtSearchResults'; export const RevealInSideBarForSearchResults = 'search.action.revealInSideBar'; export const ToggleSearchViewPositionCommandId = 'search.action.toggleSearchViewPosition'; @@ -37,6 +40,7 @@ export const PatternIncludesFocusedKey = new RawContextKey('patternIncl export const PatternExcludesFocusedKey = new RawContextKey('patternExcludesInputBoxFocus', false); export const ReplaceActiveKey = new RawContextKey('replaceActive', false); export const HasSearchResults = new RawContextKey('hasSearchResult', false); +export const EnableSearchEditorPreview = new RawContextKey('previewSearchEditor', false); export const FirstMatchFocusKey = new RawContextKey('firstMatchFocus', false); export const FileMatchOrMatchFocusKey = new RawContextKey('fileMatchOrMatchFocus', false); // This is actually, Match or File or Folder diff --git a/src/vs/workbench/contrib/search/common/searchModel.ts b/src/vs/workbench/contrib/search/common/searchModel.ts index 45a121caba..8e2507e461 100644 --- a/src/vs/workbench/contrib/search/common/searchModel.ts +++ b/src/vs/workbench/contrib/search/common/searchModel.ts @@ -20,7 +20,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress'; import { ReplacePattern } from 'vs/workbench/services/search/common/replace'; -import { IFileMatch, IPatternInfo, ISearchComplete, ISearchProgressItem, ISearchService, ITextQuery, ITextSearchPreviewOptions, ITextSearchMatch, ITextSearchStats, resultIsMatch, ISearchRange, OneLineRange } from 'vs/workbench/services/search/common/search'; +import { IFileMatch, IPatternInfo, ISearchComplete, ISearchProgressItem, ISearchConfigurationProperties, ISearchService, ITextQuery, ITextSearchPreviewOptions, ITextSearchMatch, ITextSearchStats, resultIsMatch, ISearchRange, OneLineRange } from 'vs/workbench/services/search/common/search'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { overviewRulerFindMatchForeground, minimapFindMatch } from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; @@ -28,6 +28,7 @@ import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; import { editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; import { withNullAsUndefined } from 'vs/base/common/types'; import { memoize } from 'vs/base/common/decorators'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class Match { @@ -638,6 +639,7 @@ export class SearchResult extends Disposable { private _query: ITextQuery | null = null; private _rangeHighlightDecorations: RangeHighlightDecorations; + private disposePastResults: () => void = () => { }; constructor( private _searchModel: SearchModel, @@ -657,8 +659,15 @@ export class SearchResult extends Disposable { } set query(query: ITextQuery | null) { - // When updating the query we could change the roots, so ensure we clean up the old roots first. - this.clear(); + // When updating the query we could change the roots, so keep a reference to them to clean up when we trigger `disposePastResults` + const oldFolderMatches = this.folderMatches(); + new Promise(resolve => this.disposePastResults = resolve) + .then(() => oldFolderMatches.forEach(match => match.clear())) + .then(() => oldFolderMatches.forEach(match => match.dispose())); + + this._rangeHighlightDecorations.removeHighlightRange(); + this._folderMatchesMap = TernarySearchTree.forPaths(); + if (!query) { return; } @@ -714,7 +723,8 @@ export class SearchResult extends Disposable { } }); - this._otherFilesMatch!.add(other, silent); + this._otherFilesMatch?.add(other, silent); + this.disposePastResults(); } clear(): void { @@ -883,6 +893,7 @@ export class SearchResult extends Disposable { } dispose(): void { + this.disposePastResults(); this.disposeMatches(); this._rangeHighlightDecorations.dispose(); super.dispose(); @@ -897,6 +908,8 @@ export class SearchModel extends Disposable { private _replaceString: string | null = null; private _replacePattern: ReplacePattern | null = null; private _preserveCase: boolean = false; + private _startStreamDelay: Promise = Promise.resolve(); + private _resultQueue: IFileMatch[] = []; private readonly _onReplaceTermChanged: Emitter = this._register(new Emitter()); readonly onReplaceTermChanged: Event = this._onReplaceTermChanged.event; @@ -906,6 +919,7 @@ export class SearchModel extends Disposable { constructor( @ISearchService private readonly searchService: ISearchService, @ITelemetryService private readonly telemetryService: ITelemetryService, + @IConfigurationService private readonly configurationService: IConfigurationService, @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); @@ -951,13 +965,25 @@ export class SearchModel extends Disposable { search(query: ITextQuery, onProgress?: (result: ISearchProgressItem) => void): Promise { this.cancelSearch(); + // Exclude Search Editor results unless explicity included + const searchEditorFilenameGlob = `**/*.code-search`; + if (!query.includePattern || !query.includePattern[searchEditorFilenameGlob]) { + query.excludePattern = { ...(query.excludePattern ?? {}), [searchEditorFilenameGlob]: true }; + } + this._searchQuery = query; - this.searchResult.clear(); + if (!this.searchConfig.searchOnType) { + this.searchResult.clear(); + } + this._searchResult.query = this._searchQuery; const progressEmitter = new Emitter(); this._replacePattern = new ReplacePattern(this.replaceString, this._searchQuery.contentPattern); + // In search on type case, delay the streaming of results just a bit, so that we don't flash the only "local results" fast path + this._startStreamDelay = new Promise(resolve => setTimeout(resolve, this.searchConfig.searchOnType ? 100 : 0)); + const tokenSource = this.currentCancelTokenSource = new CancellationTokenSource(); const currentRequest = this.searchService.textSearch(this._searchQuery, this.currentCancelTokenSource.token, p => { progressEmitter.fire(); @@ -1001,6 +1027,9 @@ export class SearchModel extends Disposable { throw new Error('onSearchCompleted must be called after a search is started'); } + this._searchResult.add(this._resultQueue); + this._resultQueue = []; + const options: IPatternInfo = objects.assign({}, this._searchQuery.contentPattern); delete options.pattern; @@ -1019,7 +1048,8 @@ export class SearchModel extends Disposable { "options": { "${inline}": [ "${IPatternInfo}" ] }, "duration": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "type" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, - "scheme" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + "scheme" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "searchOnTypeEnabled" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } } */ this.telemetryService.publicLog('searchResultsShown', { @@ -1028,7 +1058,8 @@ export class SearchModel extends Disposable { options, duration, type: stats && stats.type, - scheme + scheme, + searchOnTypeEnabled: this.searchConfig.searchOnType }); return completed; } @@ -1039,12 +1070,21 @@ export class SearchModel extends Disposable { } } - private onSearchProgress(p: ISearchProgressItem): void { + private async onSearchProgress(p: ISearchProgressItem) { if ((p).resource) { - this._searchResult.add([p], true); + this._resultQueue.push(p); + await this._startStreamDelay; + if (this._resultQueue.length) { + this._searchResult.add(this._resultQueue, true); + this._resultQueue = []; + } } } + private get searchConfig() { + return this.configurationService.getValue('search'); + } + cancelSearch(): boolean { if (this.currentCancelTokenSource) { this.currentCancelTokenSource.cancel(); diff --git a/src/vs/workbench/contrib/search/test/common/searchModel.test.ts b/src/vs/workbench/contrib/search/test/common/searchModel.test.ts index 0950afa2d7..ca8c505a7a 100644 --- a/src/vs/workbench/contrib/search/test/common/searchModel.test.ts +++ b/src/vs/workbench/contrib/search/test/common/searchModel.test.ts @@ -70,6 +70,10 @@ suite('SearchModel', () => { instantiationService.stub(IModelService, stubModelService(instantiationService)); instantiationService.stub(ISearchService, {}); instantiationService.stub(ISearchService, 'textSearch', Promise.resolve({ results: [] })); + + const config = new TestConfigurationService(); + config.setUserConfiguration('search', { searchOnType: true }); + instantiationService.stub(IConfigurationService, config); }); teardown(() => { diff --git a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts index 285072f305..6115924776 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { MarkdownString } from 'vs/base/common/htmlContent'; -import { compare } from 'vs/base/common/strings'; +import { compare, startsWith } from 'vs/base/common/strings'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; @@ -22,14 +22,14 @@ export class SnippetCompletion implements CompletionItem { detail: string; insertText: string; documentation?: MarkdownString; - range: IRange; + range: IRange | { insert: IRange, replace: IRange }; sortText: string; kind: CompletionItemKind; insertTextRules: CompletionItemInsertTextRule; constructor( readonly snippet: Snippet, - range: IRange + range: IRange | { insert: IRange, replace: IRange } ) { this.label = snippet.prefix; this.detail = localize('detail.snippet', "{0} ({1})", snippet.description || snippet.name, snippet.source); @@ -80,7 +80,8 @@ export class SnippetCompletionProvider implements CompletionItemProvider { let suggestions: SnippetCompletion[]; let pos = { lineNumber: position.lineNumber, column: 1 }; let lineOffsets: number[] = []; - let linePrefixLow = model.getLineContent(position.lineNumber).substr(0, position.column - 1).toLowerCase(); + const lineContent = model.getLineContent(position.lineNumber); + const linePrefixLow = lineContent.substr(0, position.column - 1).toLowerCase(); let endsInWhitespace = linePrefixLow.match(/\s$/); while (pos.column < position.column) { @@ -104,13 +105,19 @@ export class SnippetCompletionProvider implements CompletionItemProvider { } } + const lineSuffixLow = lineContent.substr(position.column - 1).toLowerCase(); let availableSnippets = new Set(); snippets.forEach(availableSnippets.add, availableSnippets); suggestions = []; for (let start of lineOffsets) { availableSnippets.forEach(snippet => { if (isPatternInWord(linePrefixLow, start, linePrefixLow.length, snippet.prefixLow, 0, snippet.prefixLow.length)) { - suggestions.push(new SnippetCompletion(snippet, Range.fromPositions(position.delta(0, -(linePrefixLow.length - start)), position))); + const snippetPrefixSubstr = snippet.prefixLow.substr(linePrefixLow.length - start); + const endColumn = startsWith(lineSuffixLow, snippetPrefixSubstr) ? position.column + snippetPrefixSubstr.length : position.column; + const replace = Range.fromPositions(position.delta(0, -(linePrefixLow.length - start)), { lineNumber: position.lineNumber, column: endColumn }); + const insert = replace.setEndPosition(position.lineNumber, position.column); + + suggestions.push(new SnippetCompletion(snippet, { replace, insert })); availableSnippets.delete(snippet); } }); @@ -119,7 +126,8 @@ export class SnippetCompletionProvider implements CompletionItemProvider { // add remaing snippets when the current prefix ends in whitespace or when no // interesting positions have been found availableSnippets.forEach(snippet => { - suggestions.push(new SnippetCompletion(snippet, Range.fromPositions(position))); + const range = Range.fromPositions(position); + suggestions.push(new SnippetCompletion(snippet, { replace: range, insert: range })); }); } diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts index 3eff3b3a0f..098bb6cc13 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { parse as jsonParse } from 'vs/base/common/json'; +import { parse as jsonParse, getNodeType } from 'vs/base/common/json'; import { forEach } from 'vs/base/common/collections'; import { localize } from 'vs/nls'; import { extname, basename } from 'vs/base/common/path'; @@ -14,6 +14,7 @@ import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IdleValue } from 'vs/base/common/async'; +import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; class SnippetBodyInsights { @@ -153,7 +154,8 @@ export class SnippetFile { readonly location: URI, public defaultScopes: string[] | undefined, private readonly _extension: IExtensionDescription | undefined, - private readonly _fileService: IFileService + private readonly _fileService: IFileService, + private readonly _extensionResourceLoaderService: IExtensionResourceLoaderService ) { this.isGlobalSnippets = extname(location.path) === '.code-snippets'; this.isUserSnippets = !this._extension; @@ -199,11 +201,20 @@ export class SnippetFile { } } + private async _load(): Promise { + if (this._extension) { + return this._extensionResourceLoaderService.readExtensionResource(this.location); + } else { + const content = await this._fileService.readFile(this.location); + return content.value.toString(); + } + } + load(): Promise { if (!this._loadPromise) { - this._loadPromise = Promise.resolve(this._fileService.readFile(this.location)).then(content => { - const data = jsonParse(content.value.toString()); - if (typeof data === 'object') { + this._loadPromise = Promise.resolve(this._load()).then(content => { + const data = jsonParse(content); + if (getNodeType(data) === 'object') { forEach(data, entry => { const { key: name, value: scopeOrTemplate } = entry; if (isJsonSerializedSnippet(scopeOrTemplate)) { diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts index 316efe6b2b..768602e844 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts @@ -25,6 +25,7 @@ import { Snippet, SnippetFile, SnippetSource } from 'vs/workbench/contrib/snippe import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { languagesExtPoint } from 'vs/workbench/services/mode/common/workbenchModeService'; import { SnippetCompletionProvider } from './snippetCompletionProvider'; +import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; namespace snippetExt { @@ -139,6 +140,7 @@ class SnippetsService implements ISnippetsService { @IModeService private readonly _modeService: IModeService, @ILogService private readonly _logService: ILogService, @IFileService private readonly _fileService: IFileService, + @IExtensionResourceLoaderService private readonly _extensionResourceLoaderService: IExtensionResourceLoaderService, @ILifecycleService lifecycleService: ILifecycleService, ) { this._pendingWork.push(Promise.resolve(lifecycleService.when(LifecyclePhase.Restored).then(() => { @@ -225,7 +227,7 @@ class SnippetsService implements ISnippetsService { file.defaultScopes = []; } } else { - const file = new SnippetFile(SnippetSource.Extension, validContribution.location, validContribution.language ? [validContribution.language] : undefined, extension.description, this._fileService); + const file = new SnippetFile(SnippetSource.Extension, validContribution.location, validContribution.language ? [validContribution.language] : undefined, extension.description, this._fileService, this._extensionResourceLoaderService); this._files.set(file.location.toString(), file); if (this._environmentService.isExtensionDevelopment) { @@ -318,9 +320,9 @@ class SnippetsService implements ISnippetsService { const key = uri.toString(); if (source === SnippetSource.User && ext === '.json') { const langName = resources.basename(uri).replace(/\.json/, ''); - this._files.set(key, new SnippetFile(source, uri, [langName], undefined, this._fileService)); + this._files.set(key, new SnippetFile(source, uri, [langName], undefined, this._fileService, this._extensionResourceLoaderService)); } else if (ext === '.code-snippets') { - this._files.set(key, new SnippetFile(source, uri, undefined, undefined, this._fileService)); + this._files.set(key, new SnippetFile(source, uri, undefined, undefined, this._fileService, this._extensionResourceLoaderService)); } return { dispose: () => this._files.delete(key) diff --git a/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts index 3a00334a03..5085c9264b 100644 --- a/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts +++ b/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts @@ -11,7 +11,7 @@ suite('Snippets', function () { class TestSnippetFile extends SnippetFile { constructor(filepath: URI, snippets: Snippet[]) { - super(SnippetSource.Extension, filepath, undefined, undefined, undefined!); + super(SnippetSource.Extension, filepath, undefined, undefined, undefined!, undefined!); this.data.push(...snippets); } } diff --git a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts index 96f5776668..970561955f 100644 --- a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts +++ b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts @@ -84,7 +84,7 @@ suite('SnippetsService', function () { assert.equal(result.incomplete, undefined); assert.equal(result.suggestions.length, 1); assert.equal(result.suggestions[0].label, 'bar'); - assert.equal(result.suggestions[0].range.startColumn, 1); + assert.equal((result.suggestions[0].range as any).insert.startColumn, 1); assert.equal(result.suggestions[0].insertText, 'barCodeSnippet'); }); }); @@ -117,10 +117,10 @@ suite('SnippetsService', function () { assert.equal(result.suggestions.length, 2); assert.equal(result.suggestions[0].label, 'bar'); assert.equal(result.suggestions[0].insertText, 's1'); - assert.equal(result.suggestions[0].range.startColumn, 1); + assert.equal((result.suggestions[0].range as any).insert.startColumn, 1); assert.equal(result.suggestions[1].label, 'bar-bar'); assert.equal(result.suggestions[1].insertText, 's2'); - assert.equal(result.suggestions[1].range.startColumn, 1); + assert.equal((result.suggestions[1].range as any).insert.startColumn, 1); }); await provider.provideCompletionItems(model, new Position(1, 5), context)!.then(result => { @@ -128,7 +128,7 @@ suite('SnippetsService', function () { assert.equal(result.suggestions.length, 1); assert.equal(result.suggestions[0].label, 'bar-bar'); assert.equal(result.suggestions[0].insertText, 's2'); - assert.equal(result.suggestions[0].range.startColumn, 1); + assert.equal((result.suggestions[0].range as any).insert.startColumn, 1); }); await provider.provideCompletionItems(model, new Position(1, 6), context)!.then(result => { @@ -136,10 +136,10 @@ suite('SnippetsService', function () { assert.equal(result.suggestions.length, 2); assert.equal(result.suggestions[0].label, 'bar'); assert.equal(result.suggestions[0].insertText, 's1'); - assert.equal(result.suggestions[0].range.startColumn, 5); + assert.equal((result.suggestions[0].range as any).insert.startColumn, 5); assert.equal(result.suggestions[1].label, 'bar-bar'); assert.equal(result.suggestions[1].insertText, 's2'); - assert.equal(result.suggestions[1].range.startColumn, 1); + assert.equal((result.suggestions[1].range as any).insert.startColumn, 1); }); }); @@ -165,14 +165,14 @@ suite('SnippetsService', function () { return provider.provideCompletionItems(model, new Position(1, 4), context)!; }).then(result => { assert.equal(result.suggestions.length, 1); - assert.equal(result.suggestions[0].range.startColumn, 2); + assert.equal((result.suggestions[0].range as any).insert.startColumn, 2); model.dispose(); model = TextModel.createFromString('a { assert.equal(result.suggestions.length, 1); - assert.equal(result.suggestions[0].range.startColumn, 2); + assert.equal((result.suggestions[0].range as any).insert.startColumn, 2); model.dispose(); }); }); @@ -400,13 +400,43 @@ suite('SnippetsService', function () { assert.equal(result.suggestions.length, 1); let [first] = result.suggestions; - assert.equal(first.range.startColumn, 2); + assert.equal((first.range as any).insert.startColumn, 2); model = TextModel.createFromString('1', undefined, modeService.getLanguageIdentifier('fooLang')); result = await provider.provideCompletionItems(model, new Position(1, 2), context)!; assert.equal(result.suggestions.length, 1); [first] = result.suggestions; - assert.equal(first.range.startColumn, 1); + assert.equal((first.range as any).insert.startColumn, 1); + }); + + test('Snippet replace range', async function () { + snippetService = new SimpleSnippetService([new Snippet( + ['fooLang'], + 'notWordTest', + 'not word', + '', + 'not word snippet', + '', + SnippetSource.User + )]); + + const provider = new SnippetCompletionProvider(modeService, snippetService); + + let model = TextModel.createFromString('not wordFoo bar', undefined, modeService.getLanguageIdentifier('fooLang')); + let result = await provider.provideCompletionItems(model, new Position(1, 3), context)!; + + assert.equal(result.suggestions.length, 1); + let [first] = result.suggestions; + assert.equal((first.range as any).insert.endColumn, 3); + assert.equal((first.range as any).replace.endColumn, 9); + + model = TextModel.createFromString('not woFoo bar', undefined, modeService.getLanguageIdentifier('fooLang')); + result = await provider.provideCompletionItems(model, new Position(1, 3), context)!; + + assert.equal(result.suggestions.length, 1); + [first] = result.suggestions; + assert.equal((first.range as any).insert.endColumn, 3); + assert.equal((first.range as any).replace.endColumn, 3); }); }); diff --git a/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts b/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts index 4c72713307..d7f0761298 100644 --- a/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts +++ b/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts @@ -60,10 +60,12 @@ class PartsSplash { if (e.affectsConfiguration('window.titleBarStyle')) { this._didChangeTitleBarStyle = true; this._savePartsSplash(); - } else if (e.affectsConfiguration('workbench.colorTheme') || e.affectsConfiguration('workbench.colorCustomizations')) { - this._savePartsSplash(); } }, this, this._disposables); + + _themeService.onThemeChange(_ => { + this._savePartsSplash(); + }, this, this._disposables); } dispose(): void { @@ -80,6 +82,7 @@ class PartsSplash { sideBarBackground: this._getThemeColor(themes.SIDE_BAR_BACKGROUND), statusBarBackground: this._getThemeColor(themes.STATUS_BAR_BACKGROUND), statusBarNoFolderBackground: this._getThemeColor(themes.STATUS_BAR_NO_FOLDER_BACKGROUND), + windowBorder: this._getThemeColor(themes.WINDOW_ACTIVE_BORDER) ?? this._getThemeColor(themes.WINDOW_INACTIVE_BORDER) }; const layoutInfo = !this._shouldSaveLayoutInfo() ? undefined : { sideBarSide: this._layoutService.getSideBarPosition() === Position.RIGHT ? 'right' : 'left', @@ -88,6 +91,8 @@ class PartsSplash { activityBarWidth: this._layoutService.isVisible(Parts.ACTIVITYBAR_PART) ? getTotalWidth(assertIsDefined(this._layoutService.getContainer(Parts.ACTIVITYBAR_PART))) : 0, sideBarWidth: this._layoutService.isVisible(Parts.SIDEBAR_PART) ? getTotalWidth(assertIsDefined(this._layoutService.getContainer(Parts.SIDEBAR_PART))) : 0, statusBarHeight: this._layoutService.isVisible(Parts.STATUSBAR_PART) ? getTotalHeight(assertIsDefined(this._layoutService.getContainer(Parts.STATUSBAR_PART))) : 0, + windowBorder: this._layoutService.hasWindowBorder(), + windowBorderRadius: this._layoutService.getWindowBorderRadius() }; this._textFileService.write( URI.file(join(this._envService.userDataPath, 'rapid_render.json')), diff --git a/src/vs/workbench/contrib/stats/browser/workspaceStatsService.ts b/src/vs/workbench/contrib/tags/browser/workspaceTagsService.ts similarity index 78% rename from src/vs/workbench/contrib/stats/browser/workspaceStatsService.ts rename to src/vs/workbench/contrib/tags/browser/workspaceTagsService.ts index c9683a14c0..3080b549e9 100644 --- a/src/vs/workbench/contrib/stats/browser/workspaceStatsService.ts +++ b/src/vs/workbench/contrib/tags/browser/workspaceTagsService.ts @@ -6,9 +6,9 @@ import { WorkbenchState, IWorkspace } from 'vs/platform/workspace/common/workspace'; import { URI } from 'vs/base/common/uri'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IWorkspaceStatsService, Tags } from 'vs/workbench/contrib/stats/common/workspaceStats'; +import { IWorkspaceTagsService, Tags } from 'vs/workbench/contrib/tags/common/workspaceTags'; -export class NoOpWorkspaceStatsService implements IWorkspaceStatsService { +export class NoOpWorkspaceTagsService implements IWorkspaceTagsService { _serviceBrand: undefined; @@ -25,4 +25,4 @@ export class NoOpWorkspaceStatsService implements IWorkspaceStatsService { } } -registerSingleton(IWorkspaceStatsService, NoOpWorkspaceStatsService, true); +registerSingleton(IWorkspaceTagsService, NoOpWorkspaceTagsService, true); diff --git a/src/vs/workbench/contrib/stats/common/workspaceStats.ts b/src/vs/workbench/contrib/tags/common/workspaceTags.ts similarity index 88% rename from src/vs/workbench/contrib/stats/common/workspaceStats.ts rename to src/vs/workbench/contrib/tags/common/workspaceTags.ts index d03a5895b2..74749bdd05 100644 --- a/src/vs/workbench/contrib/stats/common/workspaceStats.ts +++ b/src/vs/workbench/contrib/tags/common/workspaceTags.ts @@ -9,9 +9,9 @@ import { URI } from 'vs/base/common/uri'; export type Tags = { [index: string]: boolean | number | string | undefined }; -export const IWorkspaceStatsService = createDecorator('workspaceStatsService'); +export const IWorkspaceTagsService = createDecorator('workspaceTagsService'); -export interface IWorkspaceStatsService { +export interface IWorkspaceTagsService { _serviceBrand: undefined; getTags(): Promise; diff --git a/src/vs/workbench/contrib/stats/electron-browser/stats.contribution.ts b/src/vs/workbench/contrib/tags/electron-browser/tags.contribution.ts similarity index 75% rename from src/vs/workbench/contrib/stats/electron-browser/stats.contribution.ts rename to src/vs/workbench/contrib/tags/electron-browser/tags.contribution.ts index 94a27a0a33..3da7192483 100644 --- a/src/vs/workbench/contrib/stats/electron-browser/stats.contribution.ts +++ b/src/vs/workbench/contrib/tags/electron-browser/tags.contribution.ts @@ -5,8 +5,8 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { WorkspaceStats } from 'vs/workbench/contrib/stats/electron-browser/workspaceStats'; +import { WorkspaceTags } from 'vs/workbench/contrib/tags/electron-browser/workspaceTags'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -// Register Workspace Stats Contribution -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(WorkspaceStats, LifecyclePhase.Eventually); \ No newline at end of file +// Register Workspace Tags Contribution +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(WorkspaceTags, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts b/src/vs/workbench/contrib/tags/electron-browser/workspaceTags.ts similarity index 95% rename from src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts rename to src/vs/workbench/contrib/tags/electron-browser/workspaceTags.ts index f37960a8c8..999761f2c8 100644 --- a/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts +++ b/src/vs/workbench/contrib/tags/electron-browser/workspaceTags.ts @@ -13,7 +13,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { endsWith } from 'vs/base/common/strings'; import { ITextFileService, } from 'vs/workbench/services/textfile/common/textfiles'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; -import { IWorkspaceStatsService, Tags } from 'vs/workbench/contrib/stats/common/workspaceStats'; +import { IWorkspaceTagsService, Tags } from 'vs/workbench/contrib/tags/common/workspaceTags'; import { IWorkspaceInformation } from 'vs/platform/diagnostics/common/diagnostics'; import { IRequestService } from 'vs/platform/request/common/request'; import { isWindows } from 'vs/base/common/platform'; @@ -137,7 +137,7 @@ export function getHashedRemotesFromConfig(text: string, stripEndingDotGit: bool }); } -export class WorkspaceStats implements IWorkbenchContribution { +export class WorkspaceTags implements IWorkbenchContribution { constructor( @IFileService private readonly fileService: IFileService, @@ -146,7 +146,7 @@ export class WorkspaceStats implements IWorkbenchContribution { @IRequestService private readonly requestService: IRequestService, @ITextFileService private readonly textFileService: ITextFileService, @ISharedProcessService private readonly sharedProcessService: ISharedProcessService, - @IWorkspaceStatsService private readonly workspaceStatsService: IWorkspaceStatsService + @IWorkspaceTagsService private readonly workspaceTagsService: IWorkspaceTagsService ) { if (this.telemetryService.isOptedIn) { this.report(); @@ -157,8 +157,8 @@ export class WorkspaceStats implements IWorkbenchContribution { // Windows-only Edition Event this.reportWindowsEdition(); - // Workspace Stats - this.workspaceStatsService.getTags() + // Workspace Tags + this.workspaceTagsService.getTags() .then(tags => this.reportWorkspaceTags(tags), error => onUnexpectedError(error)); // Cloud Stats @@ -192,7 +192,7 @@ export class WorkspaceStats implements IWorkbenchContribution { private async getWorkspaceInformation(): Promise { const workspace = this.contextService.getWorkspace(); const state = this.contextService.getWorkbenchState(); - const telemetryId = this.workspaceStatsService.getTelemetryWorkspaceId(workspace, state); + const telemetryId = this.workspaceTagsService.getTelemetryWorkspaceId(workspace, state); return this.telemetryService.getTelemetryInfo().then(info => { return { id: workspace.id, @@ -243,7 +243,7 @@ export class WorkspaceStats implements IWorkbenchContribution { private reportRemotes(workspaceUris: URI[]): void { Promise.all(workspaceUris.map(workspaceUri => { - return this.workspaceStatsService.getHashedRemotesFromUri(workspaceUri, true); + return this.workspaceTagsService.getHashedRemotesFromUri(workspaceUri, true); })).then(hashedRemotes => { /* __GDPR__ "workspace.hashedRemotes" : { @@ -268,7 +268,7 @@ export class WorkspaceStats implements IWorkbenchContribution { return this.fileService.resolveAll(uris.map(resource => ({ resource }))).then( results => { const names = ([]).concat(...results.map(result => result.success ? (result.stat!.children || []) : [])).map(c => c.name); - const referencesAzure = WorkspaceStats.searchArray(names, /azure/i); + const referencesAzure = WorkspaceTags.searchArray(names, /azure/i); if (referencesAzure) { tags['node'] = true; } diff --git a/src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts b/src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts similarity index 97% rename from src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts rename to src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts index 4f564eef8d..f08d92976f 100644 --- a/src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts +++ b/src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts @@ -19,8 +19,9 @@ import { localize } from 'vs/nls'; import Severity from 'vs/base/common/severity'; import { joinPath } from 'vs/base/common/resources'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IWorkspaceStatsService, Tags } from 'vs/workbench/contrib/stats/common/workspaceStats'; -import { getHashedRemotesFromConfig } from 'vs/workbench/contrib/stats/electron-browser/workspaceStats'; +import { IWorkspaceTagsService, Tags } from 'vs/workbench/contrib/tags/common/workspaceTags'; +import { getHashedRemotesFromConfig } from 'vs/workbench/contrib/tags/electron-browser/workspaceTags'; +import { IProductService } from 'vs/platform/product/common/productService'; const ModulesToLookFor = [ // Packages that suggest a node server @@ -90,7 +91,7 @@ const PyModulesToLookFor = [ 'botframework-connector' ]; -export class WorkspaceStatsService implements IWorkspaceStatsService { +export class WorkspaceTagsService implements IWorkspaceTagsService { _serviceBrand: undefined; private _tags: Tags | undefined; @@ -98,13 +99,14 @@ export class WorkspaceStatsService implements IWorkspaceStatsService { @IFileService private readonly fileService: IFileService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IProductService private readonly productService: IProductService, @IHostService private readonly hostService: IHostService, @INotificationService private readonly notificationService: INotificationService, @IQuickInputService private readonly quickInputService: IQuickInputService, @ITextFileService private readonly textFileService: ITextFileService ) { } - public async getTags(): Promise { + async getTags(): Promise { if (!this._tags) { this._tags = await this.resolveWorkspaceTags(this.environmentService.configuration, rootFiles => this.handleWorkspaceFiles(rootFiles)); } @@ -112,7 +114,7 @@ export class WorkspaceStatsService implements IWorkspaceStatsService { return this._tags; } - public getTelemetryWorkspaceId(workspace: IWorkspace, state: WorkbenchState): string | undefined { + getTelemetryWorkspaceId(workspace: IWorkspace, state: WorkbenchState): string | undefined { function createHash(uri: URI): string { return crypto.createHash('sha1').update(uri.scheme === Schemas.file ? uri.fsPath : uri.toString()).digest('hex'); } @@ -260,7 +262,7 @@ export class WorkspaceStatsService implements IWorkspaceStatsService { tags['workspace.roots'] = isEmpty ? 0 : workspace.folders.length; tags['workspace.empty'] = isEmpty; - const folders = !isEmpty ? workspace.folders.map(folder => folder.uri) : this.environmentService.appQuality !== 'stable' && this.findFolders(configuration); + const folders = !isEmpty ? workspace.folders.map(folder => folder.uri) : this.productService.quality !== 'stable' && this.findFolders(configuration); if (!folders || !folders.length || !this.fileService) { return Promise.resolve(tags); } @@ -503,4 +505,4 @@ export class WorkspaceStatsService implements IWorkspaceStatsService { } } -registerSingleton(IWorkspaceStatsService, WorkspaceStatsService, true); +registerSingleton(IWorkspaceTagsService, WorkspaceTagsService, true); diff --git a/src/vs/workbench/contrib/stats/test/workspaceStats.test.ts b/src/vs/workbench/contrib/tags/test/workspaceTags.test.ts similarity index 98% rename from src/vs/workbench/contrib/stats/test/workspaceStats.test.ts rename to src/vs/workbench/contrib/tags/test/workspaceTags.test.ts index b2960ffc77..202af2773f 100644 --- a/src/vs/workbench/contrib/stats/test/workspaceStats.test.ts +++ b/src/vs/workbench/contrib/tags/test/workspaceTags.test.ts @@ -5,13 +5,13 @@ import * as assert from 'assert'; import * as crypto from 'crypto'; -import { getDomainsOfRemotes, getRemotes, getHashedRemotesFromConfig } from 'vs/workbench/contrib/stats/electron-browser/workspaceStats'; +import { getDomainsOfRemotes, getRemotes, getHashedRemotesFromConfig } from 'vs/workbench/contrib/tags/electron-browser/workspaceTags'; function hash(value: string): string { return crypto.createHash('sha1').update(value.toString()).digest('hex'); } -suite('Telemetry - WorkspaceStats', () => { +suite('Telemetry - WorkspaceTags', () => { const whitelist = [ 'github.com', @@ -163,4 +163,4 @@ suite('Telemetry - WorkspaceStats', () => { fetch = +refs/heads/*:refs/remotes/origin/* `; } -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 3e22c5022a..9ab398a0d0 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -82,12 +82,16 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance const QUICKOPEN_HISTORY_LIMIT_CONFIG = 'task.quickOpen.history'; const QUICKOPEN_DETAIL_CONFIG = 'task.quickOpen.detail'; +const PROBLEM_MATCHER_NEVER_CONFIG = 'task.problemMatchers.neverPrompt'; +const QUICKOPEN_SKIP_CONFIG = 'task.quickOpen.skip'; export namespace ConfigureTaskAction { export const ID = 'workbench.action.tasks.configureTaskRunner'; export const TEXT = nls.localize('ConfigureTaskRunnerAction.label', "Configure Task"); } +type TaskQuickPickEntryType = (IQuickPickItem & { task: Task; }) | (IQuickPickItem & { folder: IWorkspaceFolder; }); + class ProblemReporter implements TaskConfig.IProblemReporter { private _validationStatus: ValidationStatus; @@ -184,6 +188,13 @@ interface TaskQuickPickEntry extends IQuickPickItem { task: Task | undefined | null; } +interface ProblemMatcherDisableMetrics { + type: string; +} +type ProblemMatcherDisableMetricsClassification = { + type: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; +}; + export abstract class AbstractTaskService extends Disposable implements ITaskService { // private static autoDetectTelemetryName: string = 'taskServer.autoDetect'; @@ -286,9 +297,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this.updateSetup(folderSetup); this.updateWorkspaceTasks(); })); - this._register(Event.debounce(this.configurationService.onDidChangeConfiguration, () => { - return; - }, 1000)(() => { + this._register(this.configurationService.onDidChangeConfiguration(() => { if (!this._taskSystem && !this._workspaceTasksPromise) { return; } @@ -690,7 +699,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }); } - public run(task: Task | undefined, options?: ProblemMatcherRunOptions, runSource: TaskRunSource = TaskRunSource.System): Promise { + public run(task: Task | undefined, options?: ProblemMatcherRunOptions, runSource: TaskRunSource = TaskRunSource.System): Promise { if (!task) { throw new TaskError(Severity.Info, nls.localize('TaskServer.noTask', 'Task to execute is undefined'), TaskErrors.TaskNotFound); } @@ -721,14 +730,35 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private isProvideTasksEnabled(): boolean { const settingValue = this.configurationService.getValue('task.autoDetect'); - return settingValue === true; + return settingValue === 'on'; + } + + private isProblemMatcherPromptEnabled(type?: string): boolean { + const settingValue = this.configurationService.getValue(PROBLEM_MATCHER_NEVER_CONFIG); + if (Types.isBoolean(settingValue)) { + return !settingValue; + } + if (type === undefined) { + return true; + } + const settingValueMap: IStringDictionary = settingValue; + return !settingValueMap[type]; + } + + private getTypeForTask(task: Task): string { + let type: string; + if (CustomTask.is(task)) { + let configProperties: TaskConfig.ConfigurationProperties = task._source.config.element; + type = (configProperties).type; + } else { + type = task.getDefinition()!.type; + } + return type; } private shouldAttachProblemMatcher(task: Task): boolean { - const settingValue = this.configurationService.getValue('task.problemMatchers.neverPrompt'); - if (settingValue === true) { - return false; - } else if (task.type && Types.isStringArray(settingValue) && (settingValue.indexOf(task.type) >= 0)) { + const enabled = this.isProblemMatcherPromptEnabled(this.getTypeForTask(task)); + if (enabled === false) { return false; } if (!this.canCustomize(task)) { @@ -745,17 +775,33 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } if (CustomTask.is(task)) { let configProperties: TaskConfig.ConfigurationProperties = task._source.config.element; - const type: string = (configProperties).type; - return configProperties.problemMatcher === undefined && !task.hasDefinedMatchers && (Types.isStringArray(settingValue) && (settingValue.indexOf(type) < 0)); + return configProperties.problemMatcher === undefined && !task.hasDefinedMatchers; } return false; } + private async updateNeverProblemMatcherSetting(type: string): Promise { + this.telemetryService.publicLog2('problemMatcherDisabled', { type }); + const current = this.configurationService.getValue(PROBLEM_MATCHER_NEVER_CONFIG); + if (current === true) { + return; + } + let newValue: IStringDictionary; + if (current !== false) { + newValue = current; + } else { + newValue = Object.create(null); + } + newValue[type] = true; + return this.configurationService.updateValue(PROBLEM_MATCHER_NEVER_CONFIG, newValue, ConfigurationTarget.USER); + } + private attachProblemMatcher(task: ContributedTask | CustomTask): Promise { interface ProblemMatcherPickEntry extends IQuickPickItem { matcher: NamedProblemMatcher | undefined; never?: boolean; learnMore?: boolean; + setting?: string; } let entries: QuickPickInput[] = []; for (let key of ProblemMatcherRegistry.keys()) { @@ -782,14 +828,22 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } }); entries.unshift({ type: 'separator', label: nls.localize('TaskService.associate', 'associate') }); + let taskType: string; + if (CustomTask.is(task)) { + let configProperties: TaskConfig.ConfigurationProperties = task._source.config.element; + taskType = (configProperties).type; + } else { + taskType = task.getDefinition().type; + } entries.unshift( { label: nls.localize('TaskService.attachProblemMatcher.continueWithout', 'Continue without scanning the task output'), matcher: undefined }, - { label: nls.localize('TaskService.attachProblemMatcher.never', 'Never scan the task output'), matcher: undefined, never: true }, + { label: nls.localize('TaskService.attachProblemMatcher.never', 'Never scan the task output for this task'), matcher: undefined, never: true }, + { label: nls.localize('TaskService.attachProblemMatcher.neverType', 'Never scan the task output for {0} tasks', taskType), matcher: undefined, setting: taskType }, { label: nls.localize('TaskService.attachProblemMatcher.learnMoreAbout', 'Learn more about scanning the task output'), matcher: undefined, learnMore: true } ); return this.quickInputService.pick(entries, { placeHolder: nls.localize('selectProblemMatcher', 'Select for which kind of errors and warnings to scan the task output'), - }).then((selected) => { + }).then(async (selected) => { if (selected) { if (selected.learnMore) { this.openDocumentation(); @@ -809,6 +863,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } this.customize(task, properties, true); return newTask; + } else if (selected.setting) { + await this.updateNeverProblemMatcherSetting(selected.setting); + return task; } else { return task; } @@ -1177,7 +1234,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private executeTask(task: Task, resolver: ITaskResolver): Promise { return ProblemMatcherRegistry.onReady().then(() => { - return this.textFileService.saveAll().then((value) => { // make sure all dirty files are saved + return this.editorService.saveAll().then((value) => { // make sure all dirty editors are saved let executeResult = this.getTaskSystem().run(task, resolver); return this.handleExecuteResult(executeResult); }); @@ -1286,7 +1343,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer setTimeout(() => { if (!isDone) { const settings: IPromptChoice = { label: nls.localize('TaskSystem.slowProvider.settings', "Settings"), run: () => this.preferencesService.openSettings(false, undefined) }; - const disableAll: IPromptChoice = { label: nls.localize('TaskSystem.slowProvider.disableAll', "Disable All"), run: () => this.configurationService.updateValue('task.autoDetect', false) }; + const disableAll: IPromptChoice = { label: nls.localize('TaskSystem.slowProvider.disableAll', "Disable All"), run: () => this.configurationService.updateValue('task.autoDetect', 'off') }; const dontShow: IPromptChoice = { label: nls.localize('TaskSystem.slowProvider.dontShow', "Don't warn again for {0} tasks", type), run: () => { if (!Types.isStringArray(settingValue)) { @@ -1299,13 +1356,13 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this.notificationService.prompt(Severity.Warning, nls.localize('TaskSystem.slowProvider', "The {0} task provider is slow. The extension that provides {0} tasks may provide a setting to disable it, or you can disable all tasks providers", type), [settings, disableAll, dontShow]); } - }, 2000); + }, 4000); } }); } private getGroupedTasks(type?: string): Promise { - return Promise.all([this.extensionService.activateByEvent('onCommand:workbench.action.tasks.runTask'), TaskDefinitionRegistry.onReady()]).then(() => { + return Promise.all([this.extensionService.activateByEvent('onCommand:workbench.action.tasks.runTask'), this.extensionService.whenInstalledExtensionsRegistered()]).then(() => { let validTypes: IStringDictionary = Object.create(null); TaskDefinitionRegistry.all().forEach(definition => validTypes[definition.taskType] = true); validTypes['shell'] = true; @@ -1907,7 +1964,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } for (let task of tasks) { let entry: TaskQuickPickEntry = TaskQuickPickEntry(task); - entry.buttons = [{ iconClass: 'quick-open-task-configure', tooltip: nls.localize('configureTask', "Configure Task") }]; + entry.buttons = [{ iconClass: 'codicon-gear', tooltip: nls.localize('configureTask', "Configure Task") }]; if (selectedEntry && (task === selectedEntry.task)) { entries.unshift(selectedEntry); } else { @@ -1941,7 +1998,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer for (let task of tasks) { let key = task.getRecentlyUsedKey(); if (!key || !recentlyUsedTasks.has(key)) { - if (task._source.kind === TaskSourceKind.Workspace) { + if ((task._source.kind === TaskSourceKind.Workspace) || (task._source.kind === TaskSourceKind.User)) { configured.push(task); } else { detected.push(task); @@ -1965,23 +2022,43 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return entries; } - private showQuickPick(tasks: Promise | Task[], placeHolder: string, defaultEntry?: TaskQuickPickEntry, group: boolean = false, sort: boolean = false, selectedEntry?: TaskQuickPickEntry, additionalEntries?: TaskQuickPickEntry[]): Promise { - let _createEntries = (): Promise[]> => { + private async showQuickPick(tasks: Promise | Task[], placeHolder: string, defaultEntry?: TaskQuickPickEntry, group: boolean = false, sort: boolean = false, selectedEntry?: TaskQuickPickEntry, additionalEntries?: TaskQuickPickEntry[]): Promise { + const tokenSource = new CancellationTokenSource(); + const cancellationToken: CancellationToken = tokenSource.token; + let _createEntries = new Promise[]>((resolve) => { if (Array.isArray(tasks)) { - return Promise.resolve(this.createTaskQuickPickEntries(tasks, group, sort, selectedEntry)); + resolve(this.createTaskQuickPickEntries(tasks, group, sort, selectedEntry)); } else { - return tasks.then((tasks) => this.createTaskQuickPickEntries(tasks, group, sort, selectedEntry)); + resolve(tasks.then((tasks) => this.createTaskQuickPickEntries(tasks, group, sort, selectedEntry))); } - }; - return this.quickInputService.pick(_createEntries().then((entries) => { - if ((entries.length === 0) && defaultEntry) { + }); + + const timeout: boolean = await Promise.race([new Promise(async (resolve) => { + await _createEntries; + resolve(false); + }), new Promise((resolve) => { + const timer = setTimeout(() => { + clearTimeout(timer); + resolve(true); + }, 200); + })]); + + if (!timeout && ((await _createEntries).length === 1) && this.configurationService.getValue(QUICKOPEN_SKIP_CONFIG)) { + return ((await _createEntries)[0]); + } + + const pickEntries = _createEntries.then((entries) => { + if ((entries.length === 1) && this.configurationService.getValue(QUICKOPEN_SKIP_CONFIG)) { + tokenSource.cancel(); + } else if ((entries.length === 0) && defaultEntry) { entries.push(defaultEntry); } else if (entries.length > 1 && additionalEntries && additionalEntries.length > 0) { entries.push({ type: 'separator', label: '' }); entries.push(additionalEntries[0]); } return entries; - }), { + }); + return this.quickInputService.pick(pickEntries, { placeHolder, matchOnDescription: true, onDidTriggerItemButton: context => { @@ -1993,6 +2070,18 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this.openConfig(task); } } + }, cancellationToken).then(async (selection) => { + if (cancellationToken.isCancellationRequested) { + // canceled when there's only one task + const task = (await pickEntries)[0]; + if ((task).task) { + selection = task; + } + } + if (!selection) { + return undefined; //{{SQL CARBON EDIT}} strict-null-check + } + return selection; }); } @@ -2075,7 +2164,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } ProblemMatcherRegistry.onReady().then(() => { - return this.textFileService.saveAll().then((value) => { // make sure all dirty files are saved + return this.editorService.saveAll().then((value) => { // make sure all dirty editors are saved let executeResult = this.getTaskSystem().rerun(); if (executeResult) { return this.handleExecuteResult(executeResult); @@ -2374,8 +2463,33 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }); } + private isTaskEntry(value: IQuickPickItem): value is IQuickPickItem & { task: Task } { + let candidate: IQuickPickItem & { task: Task } = value as any; + return candidate && !!candidate.task; + } - private runConfigureTasks(): void { + private configureTask(task: Task) { + if (ContributedTask.is(task)) { + this.customize(task, undefined, true); + } else if (CustomTask.is(task)) { + this.openConfig(task); + } else if (ConfiguringTask.is(task)) { + // Do nothing. + } + } + + private handleSelection(selection: TaskQuickPickEntryType) { + if (!selection) { + return; + } + if (this.isTaskEntry(selection)) { + this.configureTask(selection.task); + } else { + this.openTaskFile(selection.folder.toResource('.vscode/tasks.json')); + } + } + + private async runConfigureTasks(): Promise { if (!this.canRunCommand()) { return undefined; } @@ -2386,21 +2500,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer taskPromise = Promise.resolve(new TaskMap()); } - let configureTask = (task: Task): void => { - if (ContributedTask.is(task)) { - this.customize(task, undefined, true); - } else if (CustomTask.is(task)) { - this.openConfig(task); - } else if (ConfiguringTask.is(task)) { - // Do nothing. - } - }; - - function isTaskEntry(value: IQuickPickItem): value is IQuickPickItem & { task: Task } { - let candidate: IQuickPickItem & { task: Task } = value as any; - return candidate && !!candidate.task; - } - let stats = this.contextService.getWorkspace().folders.map>((folder) => { return this.fileService.resolve(folder.toResource('.vscode/tasks.json')).then(stat => stat, () => undefined); }); @@ -2409,13 +2508,12 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer let openLabel = nls.localize('TaskService.openJsonFile', 'Open tasks.json file'); const tokenSource = new CancellationTokenSource(); const cancellationToken: CancellationToken = tokenSource.token; - type EntryType = (IQuickPickItem & { task: Task; }) | (IQuickPickItem & { folder: IWorkspaceFolder; }); let entries = Promise.all(stats).then((stats) => { return taskPromise.then((taskMap) => { - let entries: QuickPickInput[] = []; + let entries: QuickPickInput[] = []; + let needsCreateOrOpen: boolean = true; if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY) { let tasks = taskMap.all(); - let needsCreateOrOpen: boolean = true; if (tasks.length > 0) { tasks = tasks.sort((a, b) => a._label.localeCompare(b._label)); for (let task of tasks) { @@ -2440,7 +2538,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (tasks.length > 0) { tasks = tasks.slice().sort((a, b) => a._label.localeCompare(b._label)); for (let i = 0; i < tasks.length; i++) { - let entry: EntryType = { label: tasks[i]._label, task: tasks[i], description: folder.name }; + let entry: TaskQuickPickEntryType = { label: tasks[i]._label, task: tasks[i], description: folder.name }; if (i === 0) { entries.push({ type: 'separator', label: folder.name }); } @@ -2448,20 +2546,38 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } else { let label = stats[index] !== undefined ? openLabel : createLabel; - let entry: EntryType = { label, folder: folder }; + let entry: TaskQuickPickEntryType = { label, folder: folder }; entries.push({ type: 'separator', label: folder.name }); entries.push(entry); } index++; } } - if (entries.length === 1) { + if ((entries.length === 1) && !needsCreateOrOpen) { tokenSource.cancel(); } return entries; }); }); + const timeout: boolean = await Promise.race([new Promise(async (resolve) => { + await entries; + resolve(false); + }), new Promise((resolve) => { + const timer = setTimeout(() => { + clearTimeout(timer); + resolve(true); + }, 200); + })]); + + if (!timeout && ((await entries).length === 1) && this.configurationService.getValue(QUICKOPEN_SKIP_CONFIG)) { + const entry: any = ((await entries)[0]); + if (entry.task) { + this.handleSelection(entry); + return; + } + } + this.quickInputService.pick(entries, { placeHolder: nls.localize('TaskService.pickTask', 'Select a task to configure') }, cancellationToken). then(async (selection) => { @@ -2469,17 +2585,10 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer // canceled when there's only one task const task = (await entries)[0]; if ((task).task) { - selection = task; + selection = task; } } - if (!selection) { - return; - } - if (isTaskEntry(selection)) { - configureTask(selection.task); - } else { - this.openTaskFile(selection.folder.toResource('.vscode/tasks.json')); - } + this.handleSelection(selection); }); } diff --git a/src/vs/workbench/contrib/tasks/browser/quickOpen.ts b/src/vs/workbench/contrib/tasks/browser/quickOpen.ts index 00dd2a53da..075a84f5da 100644 --- a/src/vs/workbench/contrib/tasks/browser/quickOpen.ts +++ b/src/vs/workbench/contrib/tasks/browser/quickOpen.ts @@ -173,7 +173,7 @@ class CustomizeTaskAction extends Action { } public updateClass(): void { - this.class = 'quick-open-task-configure'; + this.class = 'codicon-gear'; } public run(element: any): Promise { @@ -235,4 +235,4 @@ export class QuickOpenActionContributor extends ActionBarContributor { } return undefined; } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index 388a4f97db..f74221c1d0 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -3,8 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!../common/media/task.contribution'; - import * as nls from 'vs/nls'; import { QuickOpenHandler } from 'vs/workbench/contrib/tasks/browser/taskQuickOpen'; @@ -47,7 +45,7 @@ const workbenchRegistry = Registry.as(Workbench workbenchRegistry.registerWorkbenchContribution(RunAutomaticTasks, LifecyclePhase.Eventually); const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ManageAutomaticTaskRunning, ManageAutomaticTaskRunning.ID, ManageAutomaticTaskRunning.LABEL), 'Tasks: Manage Automatic Tasks in Folder', tasksCategory); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ManageAutomaticTaskRunning, ManageAutomaticTaskRunning.ID, ManageAutomaticTaskRunning.LABEL), 'Tasks: Manage Automatic Tasks in Folder', tasksCategory); export class TaskStatusBarContributions extends Disposable implements IWorkbenchContribution { private runningTasksStatusItem: IStatusbarEntryAccessor | undefined; @@ -256,7 +254,7 @@ const quickOpenRegistry = (Registry.as(QuickOpenExtensions.Q const tasksPickerContextKey = 'inTasksPicker'; quickOpenRegistry.registerQuickOpenHandler( - new QuickOpenHandlerDescriptor( + QuickOpenHandlerDescriptor.create( QuickOpenHandler, QuickOpenHandler.ID, 'task ', @@ -315,17 +313,22 @@ configurationRegistry.registerConfiguration({ type: 'object', properties: { 'task.problemMatchers.neverPrompt': { - markdownDescription: nls.localize('task.problemMatchers.neverPrompt', "Configures whether to show the problem matcher prompt when running a task. Set to `true` to never promp, or use an array of task types to turn off prompting only for specific task types."), + markdownDescription: nls.localize('task.problemMatchers.neverPrompt', "Configures whether to show the problem matcher prompt when running a task. Set to `true` to never prompt, or use a dictionary of task types to turn off prompting only for specific task types."), 'oneOf': [ { type: 'boolean', markdownDescription: nls.localize('task.problemMatchers.neverPrompt.boolean', 'Sets problem matcher prompting behavior for all tasks.') }, { - type: 'array', - items: { - type: 'string', - markdownDescription: nls.localize('task.problemMatchers.neverPrompt.array', 'An array of task types to never prompt for problem matchers on.') + type: 'object', + patternProperties: { + '.*': { + type: 'boolean' + } + }, + markdownDescription: nls.localize('task.problemMatchers.neverPrompt.array', 'An object containing task type-boolean pairs to never prompt for problem matchers on.'), + default: { + 'shell': true } } ], @@ -333,8 +336,9 @@ configurationRegistry.registerConfiguration({ }, 'task.autoDetect': { markdownDescription: nls.localize('task.autoDetect', "Controls enablement of `provideTasks` for all task provider extension. If the Tasks: Run Task command is slow, disabling auto detect for task providers may help. Individual extensions my provide settings to disabled auto detection."), - type: 'boolean', - default: true + type: 'string', + enum: ['on', 'off'], + default: 'on' }, 'task.slowProviderWarning': { markdownDescription: nls.localize('task.slowProviderWarning', "Configures whether a warning is shown when a provider is slow"), @@ -362,6 +366,11 @@ configurationRegistry.registerConfiguration({ markdownDescription: nls.localize('task.quickOpen.detail', "Controls whether to show the task detail for task that have a detail in the Run Task quick pick."), type: 'boolean', default: true + }, + 'task.quickOpen.skip': { + type: 'boolean', + description: nls.localize('task.quickOpen.skip', "Controls whether the task quick pick is skipped when there is only one task to pick from."), + default: false } } }); diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 39d9a2f945..e59f9b6c64 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -343,7 +343,8 @@ export class TerminalTaskSystem implements ITaskSystem { return Promise.all(promises); } - private async executeTask(task: Task, resolver: ITaskResolver, trigger: string): Promise { + private async executeTask(task: Task, resolver: ITaskResolver, trigger: string, alreadyResolved?: Map): Promise { + alreadyResolved = alreadyResolved ?? new Map(); let promises: Promise[] = []; if (task.configurationProperties.dependsOn) { for (const dependency of task.configurationProperties.dependsOn) { @@ -353,7 +354,7 @@ export class TerminalTaskSystem implements ITaskSystem { let promise = this.activeTasks[key] ? this.activeTasks[key].promise : undefined; if (!promise) { this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.DependsOnStarted, task)); - promise = this.executeTask(dependencyTask, resolver, trigger); + promise = this.executeTask(dependencyTask, resolver, trigger, alreadyResolved); } if (task.configurationProperties.dependsOrder === DependsOrder.sequence) { promise = Promise.resolve(await promise); @@ -378,9 +379,9 @@ export class TerminalTaskSystem implements ITaskSystem { } } if (this.isRerun) { - return this.reexecuteCommand(task, trigger); + return this.reexecuteCommand(task, trigger, alreadyResolved!); } else { - return this.executeCommand(task, trigger); + return this.executeCommand(task, trigger, alreadyResolved!); } }); } else { @@ -395,7 +396,36 @@ export class TerminalTaskSystem implements ITaskSystem { } } - private resolveVariablesFromSet(taskSystemInfo: TaskSystemInfo | undefined, workspaceFolder: IWorkspaceFolder | undefined, task: CustomTask | ContributedTask, variables: Set): Promise { + private resolveAndFindExecutable(workspaceFolder: IWorkspaceFolder | undefined, task: CustomTask | ContributedTask, cwd: string | undefined, envPath: string | undefined): Promise { + return this.findExecutable( + this.configurationResolverService.resolve(workspaceFolder, CommandString.value(task.command.name!)), + cwd ? this.configurationResolverService.resolve(workspaceFolder, cwd) : undefined, + envPath ? envPath.split(path.delimiter).map(p => this.configurationResolverService.resolve(workspaceFolder, p)) : undefined + ); + } + + private findUnresolvedVariables(variables: Set, alreadyResolved: Map): Set { + if (alreadyResolved.size === 0) { + return variables; + } + const unresolved = new Set(); + for (const variable of variables) { + if (!alreadyResolved.has(variable.substring(2, variable.length - 1))) { + unresolved.add(variable); + } + } + return unresolved; + } + + private mergeMaps(mergeInto: Map, mergeFrom: Map) { + for (const entry of mergeFrom) { + if (!mergeInto.has(entry[0])) { + mergeInto.set(entry[0], entry[1]); + } + } + } + + private resolveVariablesFromSet(taskSystemInfo: TaskSystemInfo | undefined, workspaceFolder: IWorkspaceFolder | undefined, task: CustomTask | ContributedTask, variables: Set, alreadyResolved: Map): Promise { let isProcess = task.command && task.command.runtime === RuntimeType.Process; let options = task.command && task.command.options ? task.command.options : undefined; let cwd = options ? options.cwd : undefined; @@ -410,11 +440,11 @@ export class TerminalTaskSystem implements ITaskSystem { } } } - + const unresolved = this.findUnresolvedVariables(variables, alreadyResolved); let resolvedVariables: Promise; if (taskSystemInfo && workspaceFolder) { let resolveSet: ResolveSet = { - variables + variables: unresolved }; if (taskSystemInfo.platform === Platform.Platform.Windows && isProcess) { @@ -426,28 +456,32 @@ export class TerminalTaskSystem implements ITaskSystem { resolveSet.process.path = envPath; } } - resolvedVariables = taskSystemInfo.resolveVariables(workspaceFolder, resolveSet).then(resolved => { - if ((taskSystemInfo.platform !== Platform.Platform.Windows) && isProcess) { - resolved.variables.set(TerminalTaskSystem.ProcessVarName, CommandString.value(task.command.name!)); + resolvedVariables = taskSystemInfo.resolveVariables(workspaceFolder, resolveSet).then(async (resolved) => { + this.mergeMaps(alreadyResolved, resolved.variables); + resolved.variables = new Map(alreadyResolved); + if (isProcess) { + let process = CommandString.value(task.command.name!); + if (taskSystemInfo.platform === Platform.Platform.Windows) { + process = await this.resolveAndFindExecutable(workspaceFolder, task, cwd, envPath); + } + resolved.variables.set(TerminalTaskSystem.ProcessVarName, process); } return Promise.resolve(resolved); }); return resolvedVariables; } else { let variablesArray = new Array(); - variables.forEach(variable => variablesArray.push(variable)); + unresolved.forEach(variable => variablesArray.push(variable)); return new Promise((resolve, reject) => { - this.configurationResolverService.resolveWithInteraction(workspaceFolder, variablesArray, 'tasks').then(async resolvedVariablesMap => { + this.configurationResolverService.resolveWithInteraction(workspaceFolder, variablesArray, 'tasks').then(async (resolvedVariablesMap: Map | undefined) => { if (resolvedVariablesMap) { + this.mergeMaps(alreadyResolved, resolvedVariablesMap); + resolvedVariablesMap = new Map(alreadyResolved); if (isProcess) { let processVarValue: string; if (Platform.isWindows) { - processVarValue = await this.findExecutable( - this.configurationResolverService.resolve(workspaceFolder, CommandString.value(task.command.name!)), - cwd ? this.configurationResolverService.resolve(workspaceFolder, cwd) : undefined, - envPath ? envPath.split(path.delimiter).map(p => this.configurationResolverService.resolve(workspaceFolder, p)) : undefined - ); + processVarValue = await this.resolveAndFindExecutable(workspaceFolder, task, cwd, envPath); } else { processVarValue = this.configurationResolverService.resolve(workspaceFolder, CommandString.value(task.command.name!)); } @@ -467,7 +501,7 @@ export class TerminalTaskSystem implements ITaskSystem { } } - private executeCommand(task: CustomTask | ContributedTask, trigger: string): Promise { + private executeCommand(task: CustomTask | ContributedTask, trigger: string, alreadyResolved: Map): Promise { const taskWorkspaceFolder = task.getWorkspaceFolder(); let workspaceFolder: IWorkspaceFolder | undefined; if (taskWorkspaceFolder) { @@ -480,7 +514,7 @@ export class TerminalTaskSystem implements ITaskSystem { let variables = new Set(); this.collectTaskVariables(variables, task); - const resolvedVariables = this.resolveVariablesFromSet(systemInfo, workspaceFolder, task, variables); + const resolvedVariables = this.resolveVariablesFromSet(systemInfo, workspaceFolder, task, variables, alreadyResolved); return resolvedVariables.then((resolvedVariables) => { const isCustomExecution = (task.command.runtime === RuntimeType.CustomExecution); @@ -497,7 +531,7 @@ export class TerminalTaskSystem implements ITaskSystem { }); } - private reexecuteCommand(task: CustomTask | ContributedTask, trigger: string): Promise { + private reexecuteCommand(task: CustomTask | ContributedTask, trigger: string, alreadyResolved: Map): Promise { const lastTask = this.lastTask; if (!lastTask) { return Promise.reject(new Error('No task previously run')); @@ -515,7 +549,7 @@ export class TerminalTaskSystem implements ITaskSystem { }); if (!hasAllVariables) { - return this.resolveVariablesFromSet(lastTask.getVerifiedTask().systemInfo, lastTask.getVerifiedTask().workspaceFolder, task, variables).then((resolvedVariables) => { + return this.resolveVariablesFromSet(lastTask.getVerifiedTask().systemInfo, lastTask.getVerifiedTask().workspaceFolder, task, variables, alreadyResolved).then((resolvedVariables) => { this.currentTask.resolvedVariables = resolvedVariables; return this.executeInTerminal(task, trigger, new VariableResolver(lastTask.getVerifiedTask().workspaceFolder, lastTask.getVerifiedTask().systemInfo, resolvedVariables.variables, this.configurationResolverService), workspaceFolder!); }, reason => { @@ -1412,6 +1446,14 @@ export class TerminalTaskSystem implements ITaskSystem { } } + private async fileExists(path: string): Promise { + const uri: URI = resources.toLocalResource(URI.from({ scheme: Schemas.file, path: path }), this.environmentService.configuration.remoteAuthority); + if (await this.fileService.exists(uri)) { + return !((await this.fileService.resolve(uri)).isDirectory); + } + return false; + } + private async findExecutable(command: string, cwd?: string, paths?: string[]): Promise { // If we have an absolute path then we take it. if (path.isAbsolute(command)) { @@ -1444,15 +1486,15 @@ export class TerminalTaskSystem implements ITaskSystem { fullPath = path.join(cwd, pathEntry, command); } - if (await this.fileService.exists(resources.toLocalResource(URI.from({ scheme: Schemas.file, path: fullPath }), this.environmentService.configuration.remoteAuthority))) { + if (await this.fileExists(fullPath)) { return fullPath; } let withExtension = fullPath + '.com'; - if (await this.fileService.exists(resources.toLocalResource(URI.from({ scheme: Schemas.file, path: withExtension }), this.environmentService.configuration.remoteAuthority))) { + if (await this.fileExists(withExtension)) { return withExtension; } withExtension = fullPath + '.exe'; - if (await this.fileService.exists(resources.toLocalResource(URI.from({ scheme: Schemas.file, path: withExtension }), this.environmentService.configuration.remoteAuthority))) { + if (await this.fileExists(withExtension)) { return withExtension; } } diff --git a/src/vs/workbench/contrib/tasks/common/jsonSchemaCommon.ts b/src/vs/workbench/contrib/tasks/common/jsonSchemaCommon.ts index 1340ffd64d..37afaaac66 100644 --- a/src/vs/workbench/contrib/tasks/common/jsonSchemaCommon.ts +++ b/src/vs/workbench/contrib/tasks/common/jsonSchemaCommon.ts @@ -44,10 +44,10 @@ const schema: IJSONSchema = { type: 'array', items: { anyOf: [ - Schemas.LegacyProblemMatcher, { type: 'string', - } + }, + Schemas.LegacyProblemMatcher ] } } diff --git a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts index 5af470eb9d..5e9931a3e0 100644 --- a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts +++ b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts @@ -569,7 +569,7 @@ export function updateProblemMatchers() { try { let matcherIds = ProblemMatcherRegistry.keys().map(key => '$' + key); definitions.problemMatcherType2.oneOf![0].enum = matcherIds; - (definitions.problemMatcherType2.oneOf![2].items as IJSONSchema).anyOf![1].enum = matcherIds; + (definitions.problemMatcherType2.oneOf![2].items as IJSONSchema).anyOf![0].enum = matcherIds; } catch (err) { console.log('Installing problem matcher ids failed'); } diff --git a/src/vs/workbench/contrib/tasks/common/media/configure-dark.svg b/src/vs/workbench/contrib/tasks/common/media/configure-dark.svg deleted file mode 100644 index ace01a5ddf..0000000000 --- a/src/vs/workbench/contrib/tasks/common/media/configure-dark.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/vs/workbench/contrib/tasks/common/media/configure-hc.svg b/src/vs/workbench/contrib/tasks/common/media/configure-hc.svg deleted file mode 100644 index bd59cb81f6..0000000000 --- a/src/vs/workbench/contrib/tasks/common/media/configure-hc.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/vs/workbench/contrib/tasks/common/media/configure-light.svg b/src/vs/workbench/contrib/tasks/common/media/configure-light.svg deleted file mode 100644 index 4194780bba..0000000000 --- a/src/vs/workbench/contrib/tasks/common/media/configure-light.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/vs/workbench/contrib/tasks/common/media/task.contribution.css b/src/vs/workbench/contrib/tasks/common/media/task.contribution.css deleted file mode 100644 index faa818475b..0000000000 --- a/src/vs/workbench/contrib/tasks/common/media/task.contribution.css +++ /dev/null @@ -1,16 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-workbench .quick-open-task-configure { - background-image: url('configure-light.svg'); -} - -.vs-dark .monaco-workbench .quick-open-task-configure { - background-image: url('configure-dark.svg'); -} - -.hc-black .monaco-workbench .quick-open-task-configure { - background-image: url('configure-hc.svg'); -} diff --git a/src/vs/workbench/contrib/tasks/common/problemMatcher.ts b/src/vs/workbench/contrib/tasks/common/problemMatcher.ts index a3ced76337..9c2c5953ec 100644 --- a/src/vs/workbench/contrib/tasks/common/problemMatcher.ts +++ b/src/vs/workbench/contrib/tasks/common/problemMatcher.ts @@ -790,6 +790,12 @@ export namespace Config { * the current working directory. This is the default. * - ["relative", "path value"]: the filename is always * treated relative to the given path value. + * - "autodetect": the filename is treated relative to + * the current workspace directory, and if the file + * does not exist, it is treated as absolute. + * - ["autodetect", "path value"]: the filename is treated + * relative to the given path value, and if it does not + * exist, it is treated as absolute. */ fileLocation?: string | string[]; diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts index 52cb5e1364..93e8a60853 100644 --- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts @@ -552,7 +552,7 @@ interface MetaData { } -function _isEmpty(this: void, value: T | undefined, properties: MetaData[] | undefined): boolean { +function _isEmpty(this: void, value: T | undefined, properties: MetaData[] | undefined, allowEmptyArray: boolean = false): boolean { if (value === undefined || value === null || properties === undefined) { return true; } @@ -561,7 +561,7 @@ function _isEmpty(this: void, value: T | undefined, properties: MetaData 0) { + } else if (!Array.isArray(property) || (property.length > 0) || allowEmptyArray) { return false; } } @@ -591,11 +591,11 @@ function _assignProperties(this: void, target: T | undefined, source: T | und return target; } -function _fillProperties(this: void, target: T | undefined, source: T | undefined, properties: MetaData[] | undefined): T | undefined { +function _fillProperties(this: void, target: T | undefined, source: T | undefined, properties: MetaData[] | undefined, allowEmptyArray: boolean = false): T | undefined { if (!source || _isEmpty(source, properties)) { return target; } - if (!target || _isEmpty(target, properties)) { + if (!target || _isEmpty(target, properties, allowEmptyArray)) { return source; } for (let meta of properties!) { @@ -727,7 +727,7 @@ namespace ShellConfiguration { } export function isEmpty(this: void, value: Tasks.ShellConfiguration): boolean { - return _isEmpty(value, properties); + return _isEmpty(value, properties, true); } export function assignProperties(this: void, target: Tasks.ShellConfiguration | undefined, source: Tasks.ShellConfiguration | undefined): Tasks.ShellConfiguration | undefined { @@ -735,7 +735,7 @@ namespace ShellConfiguration { } export function fillProperties(this: void, target: Tasks.ShellConfiguration, source: Tasks.ShellConfiguration): Tasks.ShellConfiguration | undefined { - return _fillProperties(target, source, properties); + return _fillProperties(target, source, properties, true); } export function fillDefaults(this: void, value: Tasks.ShellConfiguration, context: ParseContext): Tasks.ShellConfiguration { diff --git a/src/vs/workbench/contrib/tasks/common/taskService.ts b/src/vs/workbench/contrib/tasks/common/taskService.ts index b585517032..cca9420dd2 100644 --- a/src/vs/workbench/contrib/tasks/common/taskService.ts +++ b/src/vs/workbench/contrib/tasks/common/taskService.ts @@ -58,7 +58,7 @@ export interface ITaskService { configureAction(): Action; build(): Promise; runTest(): Promise; - run(task: Task | undefined, options?: ProblemMatcherRunOptions): Promise; + run(task: Task | undefined, options?: ProblemMatcherRunOptions): Promise; inTerminal(): boolean; isActive(): Promise; getActiveTasks(): Promise; diff --git a/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts b/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts index f47fb1fbdb..8a9756c2ae 100644 --- a/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts @@ -91,7 +91,7 @@ export class ProcessTaskSystem implements ITaskSystem { } public getBusyTasks(): Task[] { - return this.getActiveTasks(); + return []; } public run(task: Task): ITaskExecuteResult { diff --git a/src/vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon.ts b/src/vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon.ts index 9a75ad3a02..8caac7c434 100644 --- a/src/vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon.ts @@ -64,7 +64,15 @@ export class CommandTrackerAddon implements ICommandTracker, ITerminalAddon { } let markerIndex; - if (this._currentMarker === Boundary.Bottom) { + const currentLineY = Math.min(this._getLine(this._terminal, this._currentMarker), this._terminal.buffer.baseY); + const viewportY = this._terminal.buffer.viewportY; + if (!retainSelection && currentLineY !== viewportY) { + // The user has scrolled, find the line based on the current scroll position. This only + // works when not retaining selection + const markersBelowViewport = this._terminal.markers.filter(e => e.line >= viewportY).length; + // -1 will scroll to the top + markerIndex = this._terminal.markers.length - markersBelowViewport - 1; + } else if (this._currentMarker === Boundary.Bottom) { markerIndex = this._terminal.markers.length - 1; } else if (this._currentMarker === Boundary.Top) { markerIndex = -1; @@ -95,7 +103,15 @@ export class CommandTrackerAddon implements ICommandTracker, ITerminalAddon { } let markerIndex; - if (this._currentMarker === Boundary.Bottom) { + const currentLineY = Math.min(this._getLine(this._terminal, this._currentMarker), this._terminal.buffer.baseY); + const viewportY = this._terminal.buffer.viewportY; + if (!retainSelection && currentLineY !== viewportY) { + // The user has scrolled, find the line based on the current scroll position. This only + // works when not retaining selection + const markersAboveViewport = this._terminal.markers.filter(e => e.line <= viewportY).length; + // markers.length will scroll to the bottom + markerIndex = markersAboveViewport; + } else if (this._currentMarker === Boundary.Bottom) { markerIndex = this._terminal.markers.length; } else if (this._currentMarker === Boundary.Top) { markerIndex = 0; diff --git a/src/vs/workbench/contrib/terminal/browser/media/scrollbar.css b/src/vs/workbench/contrib/terminal/browser/media/scrollbar.css index dee3846c0b..b23ca300e2 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/scrollbar.css +++ b/src/vs/workbench/contrib/terminal/browser/media/scrollbar.css @@ -38,4 +38,4 @@ .monaco-workbench .panel.integrated-terminal .xterm .xterm-viewport::-webkit-scrollbar-thumb:window-inactive { background-color: inherit; -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 5ea5fc9e91..8bb86f4138 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -10,6 +10,7 @@ flex-direction: column; background-color: transparent!important; user-select: initial; + -webkit-user-select: initial; position: relative; } @@ -104,6 +105,7 @@ bottom: 0; left: 0; user-select: none; + -webkit-user-select: none; } .monaco-workbench .panel.integrated-terminal .monaco-split-view2.vertical .split-view-view:not(:last-child) .xterm { /* When vertical and NOT the bottom terminal, align to the top instead to prevent the output jumping around erratically */ @@ -149,6 +151,11 @@ cursor: -webkit-image-set(url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAL0lEQVQoz2NgCD3x//9/BhBYBWdhgFVAiVW4JBFKGIa4AqD0//9D3pt4I4tAdAMAHTQ/j5Zom30AAAAASUVORK5CYII=') 1x, url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAz0lEQVRIx2NgYGBY/R8I/vx5eelX3n82IJ9FxGf6tksvf/8FiTMQAcAGQMDvSwu09abffY8QYSAScNk45G198eX//yev73/4///701eh//kZSARckrNBRvz//+8+6ZohwCzjGNjdgQxkAg7B9WADeBjIBqtJCbhRA0YNoIkBSNmaPEMoNmA0FkYNoFKhapJ6FGyAH3nauaSmPfwI0v/3OukVi0CIZ+F25KrtYcx/CTIy0e+rC7R1Z4KMICVTQQ14feVXIbR695u14+Ir4gwAAD49E54wc1kWAAAAAElFTkSuQmCC') 2x) 5 8, text; } +/* Override default xterm style to make !important so it takes precedence over custom mac cursor */ +.xterm.xterm-cursor-pointer { + cursor: pointer!important; +} + .monaco-workbench .quick-open-terminal-configure { background-image: url('configure-light.svg'); } diff --git a/src/vs/workbench/contrib/terminal/browser/media/xterm.css b/src/vs/workbench/contrib/terminal/browser/media/xterm.css index 8e078be9bc..e3db5a7dfe 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/xterm.css +++ b/src/vs/workbench/contrib/terminal/browser/media/xterm.css @@ -43,7 +43,6 @@ font-feature-settings: "liga" 0; position: relative; user-select: none; - -ms-user-select: none; -webkit-user-select: none; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 997ce22b35..c8438e912f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -20,7 +20,7 @@ import * as panel from 'vs/workbench/browser/panel'; import { getQuickNavigateHandler } from 'vs/workbench/browser/parts/quickopen/quickopen'; import { Extensions as QuickOpenExtensions, IQuickOpenRegistry, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; -import { ClearSelectionTerminalAction, ClearTerminalAction, CopyTerminalSelectionAction, CreateNewInActiveWorkspaceTerminalAction, CreateNewTerminalAction, DeleteToLineStartTerminalAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, FindNext, FindPrevious, FocusActiveTerminalAction, FocusNextPaneTerminalAction, FocusNextTerminalAction, FocusPreviousPaneTerminalAction, FocusPreviousTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, KillTerminalAction, MoveToLineEndTerminalAction, MoveToLineStartTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, RenameTerminalAction, ResizePaneDownTerminalAction, ResizePaneLeftTerminalAction, ResizePaneRightTerminalAction, ResizePaneUpTerminalAction, RunActiveFileInTerminalAction, RunSelectedTextInTerminalAction, ScrollDownPageTerminalAction, ScrollDownTerminalAction, ScrollToBottomTerminalAction, ScrollToNextCommandAction, ScrollToPreviousCommandAction, ScrollToTopTerminalAction, ScrollUpPageTerminalAction, ScrollUpTerminalAction, SelectAllTerminalAction, SelectDefaultShellWindowsTerminalAction, SelectToNextCommandAction, SelectToNextLineAction, SelectToPreviousCommandAction, SelectToPreviousLineAction, SendSequenceTerminalCommand, SplitInActiveWorkspaceTerminalAction, SplitTerminalAction, TerminalPasteAction, TERMINAL_PICKER_PREFIX, ToggleCaseSensitiveCommand, ToggleEscapeSequenceLoggingAction, ToggleRegexCommand, ToggleTerminalAction, ToggleWholeWordCommand, NavigationModeFocusPreviousTerminalAction, NavigationModeFocusNextTerminalAction, NavigationModeExitTerminalAction, ManageWorkspaceShellPermissionsTerminalCommand, CreateNewWithCwdTerminalCommand } from 'vs/workbench/contrib/terminal/browser/terminalActions'; +import { ClearSelectionTerminalAction, ClearTerminalAction, CopyTerminalSelectionAction, CreateNewInActiveWorkspaceTerminalAction, CreateNewTerminalAction, DeleteToLineStartTerminalAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, FindNext, FindPrevious, FocusActiveTerminalAction, FocusNextPaneTerminalAction, FocusNextTerminalAction, FocusPreviousPaneTerminalAction, FocusPreviousTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, KillTerminalAction, MoveToLineEndTerminalAction, MoveToLineStartTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, RenameTerminalAction, ResizePaneDownTerminalAction, ResizePaneLeftTerminalAction, ResizePaneRightTerminalAction, ResizePaneUpTerminalAction, RunActiveFileInTerminalAction, RunSelectedTextInTerminalAction, ScrollDownPageTerminalAction, ScrollDownTerminalAction, ScrollToBottomTerminalAction, ScrollToNextCommandAction, ScrollToPreviousCommandAction, ScrollToTopTerminalAction, ScrollUpPageTerminalAction, ScrollUpTerminalAction, SelectAllTerminalAction, SelectDefaultShellWindowsTerminalAction, SelectToNextCommandAction, SelectToNextLineAction, SelectToPreviousCommandAction, SelectToPreviousLineAction, SendSequenceTerminalCommand, SplitInActiveWorkspaceTerminalAction, SplitTerminalAction, TerminalPasteAction, TERMINAL_PICKER_PREFIX, ToggleCaseSensitiveCommand, ToggleEscapeSequenceLoggingAction, ToggleRegexCommand, ToggleTerminalAction, ToggleWholeWordCommand, NavigationModeFocusPreviousTerminalAction, NavigationModeFocusNextTerminalAction, NavigationModeExitTerminalAction, ManageWorkspaceShellPermissionsTerminalCommand, CreateNewWithCwdTerminalCommand, RenameWithArgTerminalCommand } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { TerminalPanel } from 'vs/workbench/contrib/terminal/browser/terminalPanel'; import { TerminalPickerHandler } from 'vs/workbench/contrib/terminal/browser/terminalQuickOpen'; import { KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, TERMINAL_ACTION_CATEGORY, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal'; @@ -35,6 +35,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { registerShellConfiguration } from 'vs/workbench/contrib/terminal/common/terminalShellConfig'; import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { BrowserFeatures } from 'vs/base/browser/canIUse'; registerSingleton(ITerminalService, TerminalService, true); @@ -47,7 +48,7 @@ const quickOpenRegistry = (Registry.as(QuickOpenExtensions.Q const inTerminalsPicker = 'inTerminalPicker'; quickOpenRegistry.registerQuickOpenHandler( - new QuickOpenHandlerDescriptor( + QuickOpenHandlerDescriptor.create( TerminalPickerHandler, TerminalPickerHandler.ID, TERMINAL_PICKER_PREFIX, @@ -168,6 +169,11 @@ configurationRegistry.registerConfiguration({ type: 'number', default: DEFAULT_LINE_HEIGHT }, + 'terminal.integrated.minimumContrastRatio': { + description: nls.localize('terminal.integrated.minimumContrastRatio', "When set the foreground color of each cell will change to try meet the contrast ratio specified. Example values:\n\n- 1: The default, do nothing.\n- 4.5: Minimum for WCAG AA compliance.\n- 7: Minimum for WCAG AAA compliance.\n- 21: White on black or black on white."), + type: 'number', + default: 1 + }, 'terminal.integrated.fontWeight': { type: 'string', enum: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], @@ -208,11 +214,12 @@ configurationRegistry.registerConfiguration({ }, 'terminal.integrated.rendererType': { type: 'string', - enum: ['auto', 'canvas', 'dom'], + enum: ['auto', 'canvas', 'dom', 'experimentalWebgl'], enumDescriptions: [ - nls.localize('terminal.integrated.rendererType.auto', "Let Azure Data Studio guess which renderer to use."),// {{SQL CARBON EDIT}} Change product name to ADS - nls.localize('terminal.integrated.rendererType.canvas', "Use the standard GPU/canvas-based renderer"), - nls.localize('terminal.integrated.rendererType.dom', "Use the fallback DOM-based renderer.") + nls.localize('terminal.integrated.rendererType.auto', "Let Azure Data Studio which renderer to use."), // {{SQL CARBON EDIT}} Change product name to ADS + nls.localize('terminal.integrated.rendererType.canvas', "Use the standard GPU/canvas-based renderer."), + nls.localize('terminal.integrated.rendererType.dom', "Use the fallback DOM-based renderer."), + nls.localize('terminal.integrated.rendererType.experimentalWebgl', "Use the experimental webgl-based renderer. Note that this has some [known issues](https://github.com/xtermjs/xterm.js/issues?q=is%3Aopen+is%3Aissue+label%3Aarea%2Faddon%2Fwebgl) and this will only be enabled for new terminals (not hot swappable like the other renderers).") ], default: 'auto', description: nls.localize('terminal.integrated.rendererType', "Controls how the terminal is rendered.") @@ -326,11 +333,11 @@ configurationRegistry.registerConfiguration({ }); const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenTermAction, QuickOpenTermAction.ID, QuickOpenTermAction.LABEL), 'Terminal: Switch Active Terminal', nls.localize('terminal', "Terminal")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenTermAction, QuickOpenTermAction.ID, QuickOpenTermAction.LABEL), 'Terminal: Switch Active Terminal', nls.localize('terminal', "Terminal")); const actionBarRegistry = Registry.as(ActionBarExtensions.Actionbar); actionBarRegistry.registerActionBarContributor(Scope.VIEWER, QuickOpenActionTermContributor); -(Registry.as(panel.Extensions.Panels)).registerPanel(new panel.PanelDescriptor( +(Registry.as(panel.Extensions.Panels)).registerPanel(panel.PanelDescriptor.create( TerminalPanel, TERMINAL_PANEL_ID, nls.localize('terminal', "Terminal"), @@ -343,28 +350,20 @@ Registry.as(panel.Extensions.Panels).setDefaultPanelId(TERM // On mac cmd+` is reserved to cycle between windows, that's why the keybindings use WinCtrl const category = TERMINAL_ACTION_CATEGORY; const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(KillTerminalAction, KillTerminalAction.ID, KillTerminalAction.LABEL), 'Terminal: Kill the Active Terminal Instance', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(CopyTerminalSelectionAction, CopyTerminalSelectionAction.ID, CopyTerminalSelectionAction.LABEL, { - primary: KeyMod.CtrlCmd | KeyCode.KEY_C, - linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_C } -}, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, KEYBINDING_CONTEXT_TERMINAL_FOCUS)), 'Terminal: Copy Selection', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(CreateNewTerminalAction, CreateNewTerminalAction.ID, CreateNewTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(KillTerminalAction, KillTerminalAction.ID, KillTerminalAction.LABEL), 'Terminal: Kill the Active Terminal Instance', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(CreateNewTerminalAction, CreateNewTerminalAction.ID, CreateNewTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_BACKTICK, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.US_BACKTICK } }), 'Terminal: Create New Integrated Terminal', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ClearSelectionTerminalAction, ClearSelectionTerminalAction.ID, ClearSelectionTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ClearSelectionTerminalAction, ClearSelectionTerminalAction.ID, ClearSelectionTerminalAction.LABEL, { primary: KeyCode.Escape, linux: { primary: KeyCode.Escape } }, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE)), 'Terminal: Clear Selection', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(CreateNewInActiveWorkspaceTerminalAction, CreateNewInActiveWorkspaceTerminalAction.ID, CreateNewInActiveWorkspaceTerminalAction.LABEL), 'Terminal: Create New Integrated Terminal (In Active Workspace)', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusActiveTerminalAction, FocusActiveTerminalAction.ID, FocusActiveTerminalAction.LABEL), 'Terminal: Focus Terminal', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextTerminalAction, FocusNextTerminalAction.ID, FocusNextTerminalAction.LABEL), 'Terminal: Focus Next Terminal', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousTerminalAction, FocusPreviousTerminalAction.ID, FocusPreviousTerminalAction.LABEL), 'Terminal: Focus Previous Terminal', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(TerminalPasteAction, TerminalPasteAction.ID, TerminalPasteAction.LABEL, { - primary: KeyMod.CtrlCmd | KeyCode.KEY_V, - linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_V } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Paste into Active Terminal', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectAllTerminalAction, SelectAllTerminalAction.ID, SelectAllTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(CreateNewInActiveWorkspaceTerminalAction, CreateNewInActiveWorkspaceTerminalAction.ID, CreateNewInActiveWorkspaceTerminalAction.LABEL), 'Terminal: Create New Integrated Terminal (In Active Workspace)', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FocusActiveTerminalAction, FocusActiveTerminalAction.ID, FocusActiveTerminalAction.LABEL), 'Terminal: Focus Terminal', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FocusNextTerminalAction, FocusNextTerminalAction.ID, FocusNextTerminalAction.LABEL), 'Terminal: Focus Next Terminal', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FocusPreviousTerminalAction, FocusPreviousTerminalAction.ID, FocusPreviousTerminalAction.LABEL), 'Terminal: Focus Previous Terminal', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SelectAllTerminalAction, SelectAllTerminalAction.ID, SelectAllTerminalAction.LABEL, { // Don't use ctrl+a by default as that would override the common go to start // of prompt shell binding primary: 0, @@ -373,82 +372,82 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectAllTermina // makes it easier for users to see how it works though. mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_A } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Select All', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(RunSelectedTextInTerminalAction, RunSelectedTextInTerminalAction.ID, RunSelectedTextInTerminalAction.LABEL), 'Terminal: Run Selected Text In Active Terminal', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(RunActiveFileInTerminalAction, RunActiveFileInTerminalAction.ID, RunActiveFileInTerminalAction.LABEL), 'Terminal: Run Active File In Active Terminal', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleTerminalAction, ToggleTerminalAction.ID, ToggleTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(RunSelectedTextInTerminalAction, RunSelectedTextInTerminalAction.ID, RunSelectedTextInTerminalAction.LABEL), 'Terminal: Run Selected Text In Active Terminal', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(RunActiveFileInTerminalAction, RunActiveFileInTerminalAction.ID, RunActiveFileInTerminalAction.LABEL), 'Terminal: Run Active File In Active Terminal', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleTerminalAction, ToggleTerminalAction.ID, ToggleTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_BACKTICK, mac: { primary: KeyMod.WinCtrl | KeyCode.US_BACKTICK } }), 'View: Toggle Integrated Terminal', nls.localize('viewCategory', "View")); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollDownTerminalAction, ScrollDownTerminalAction.ID, ScrollDownTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ScrollDownTerminalAction, ScrollDownTerminalAction.ID, ScrollDownTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageDown, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.DownArrow } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll Down (Line)', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollDownPageTerminalAction, ScrollDownPageTerminalAction.ID, ScrollDownPageTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ScrollDownPageTerminalAction, ScrollDownPageTerminalAction.ID, ScrollDownPageTerminalAction.LABEL, { primary: KeyMod.Shift | KeyCode.PageDown, mac: { primary: KeyCode.PageDown } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll Down (Page)', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollToBottomTerminalAction, ScrollToBottomTerminalAction.ID, ScrollToBottomTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ScrollToBottomTerminalAction, ScrollToBottomTerminalAction.ID, ScrollToBottomTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.End, linux: { primary: KeyMod.Shift | KeyCode.End } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll to Bottom', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollUpTerminalAction, ScrollUpTerminalAction.ID, ScrollUpTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ScrollUpTerminalAction, ScrollUpTerminalAction.ID, ScrollUpTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageUp, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow }, }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll Up (Line)', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollUpPageTerminalAction, ScrollUpPageTerminalAction.ID, ScrollUpPageTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ScrollUpPageTerminalAction, ScrollUpPageTerminalAction.ID, ScrollUpPageTerminalAction.LABEL, { primary: KeyMod.Shift | KeyCode.PageUp, mac: { primary: KeyCode.PageUp } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll Up (Page)', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollToTopTerminalAction, ScrollToTopTerminalAction.ID, ScrollToTopTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ScrollToTopTerminalAction, ScrollToTopTerminalAction.ID, ScrollToTopTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.Home, linux: { primary: KeyMod.Shift | KeyCode.Home } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll to Top', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ClearTerminalAction, ClearTerminalAction.ID, ClearTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ClearTerminalAction, ClearTerminalAction.ID, ClearTerminalAction.LABEL, { primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_K } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KeybindingWeight.WorkbenchContrib + 1), 'Terminal: Clear', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectDefaultShellWindowsTerminalAction, SelectDefaultShellWindowsTerminalAction.ID, SelectDefaultShellWindowsTerminalAction.LABEL), 'Terminal: Select Default Shell', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ManageWorkspaceShellPermissionsTerminalCommand, ManageWorkspaceShellPermissionsTerminalCommand.ID, ManageWorkspaceShellPermissionsTerminalCommand.LABEL), 'Terminal: Manage Workspace Shell Permissions', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(RenameTerminalAction, RenameTerminalAction.ID, RenameTerminalAction.LABEL), 'Terminal: Rename', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusTerminalFindWidgetAction, FocusTerminalFindWidgetAction.ID, FocusTerminalFindWidgetAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SelectDefaultShellWindowsTerminalAction, SelectDefaultShellWindowsTerminalAction.ID, SelectDefaultShellWindowsTerminalAction.LABEL), 'Terminal: Select Default Shell', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ManageWorkspaceShellPermissionsTerminalCommand, ManageWorkspaceShellPermissionsTerminalCommand.ID, ManageWorkspaceShellPermissionsTerminalCommand.LABEL), 'Terminal: Manage Workspace Shell Permissions', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(RenameTerminalAction, RenameTerminalAction.ID, RenameTerminalAction.LABEL), 'Terminal: Rename', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FocusTerminalFindWidgetAction, FocusTerminalFindWidgetAction.ID, FocusTerminalFindWidgetAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_F }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Focus Find Widget', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusTerminalFindWidgetAction, FocusTerminalFindWidgetAction.ID, FocusTerminalFindWidgetAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FocusTerminalFindWidgetAction, FocusTerminalFindWidgetAction.ID, FocusTerminalFindWidgetAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_F }, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Focus Find Widget', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(HideTerminalFindWidgetAction, HideTerminalFindWidgetAction.ID, HideTerminalFindWidgetAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(HideTerminalFindWidgetAction, HideTerminalFindWidgetAction.ID, HideTerminalFindWidgetAction.LABEL, { primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape] }, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE)), 'Terminal: Hide Find Widget', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(DeleteWordLeftTerminalAction, DeleteWordLeftTerminalAction.ID, DeleteWordLeftTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(DeleteWordLeftTerminalAction, DeleteWordLeftTerminalAction.ID, DeleteWordLeftTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.Backspace, mac: { primary: KeyMod.Alt | KeyCode.Backspace } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Delete Word Left', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(DeleteWordRightTerminalAction, DeleteWordRightTerminalAction.ID, DeleteWordRightTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(DeleteWordRightTerminalAction, DeleteWordRightTerminalAction.ID, DeleteWordRightTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.Delete, mac: { primary: KeyMod.Alt | KeyCode.Delete } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Delete Word Right', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(DeleteToLineStartTerminalAction, DeleteToLineStartTerminalAction.ID, DeleteToLineStartTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(DeleteToLineStartTerminalAction, DeleteToLineStartTerminalAction.ID, DeleteToLineStartTerminalAction.LABEL, { primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Delete To Line Start', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(MoveToLineStartTerminalAction, MoveToLineStartTerminalAction.ID, MoveToLineStartTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(MoveToLineStartTerminalAction, MoveToLineStartTerminalAction.ID, MoveToLineStartTerminalAction.LABEL, { primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyCode.LeftArrow } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Move To Line Start', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(MoveToLineEndTerminalAction, MoveToLineEndTerminalAction.ID, MoveToLineEndTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(MoveToLineEndTerminalAction, MoveToLineEndTerminalAction.ID, MoveToLineEndTerminalAction.LABEL, { primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyCode.RightArrow } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Move To Line End', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SplitTerminalAction, SplitTerminalAction.ID, SplitTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SplitTerminalAction, SplitTerminalAction.ID, SplitTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_5, mac: { primary: KeyMod.CtrlCmd | KeyCode.US_BACKSLASH, secondary: [KeyMod.WinCtrl | KeyMod.Shift | KeyCode.KEY_5] } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Split Terminal', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SplitInActiveWorkspaceTerminalAction, SplitInActiveWorkspaceTerminalAction.ID, SplitInActiveWorkspaceTerminalAction.LABEL), 'Terminal: Split Terminal (In Active Workspace)', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousPaneTerminalAction, FocusPreviousPaneTerminalAction.ID, FocusPreviousPaneTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SplitInActiveWorkspaceTerminalAction, SplitInActiveWorkspaceTerminalAction.ID, SplitInActiveWorkspaceTerminalAction.LABEL), 'Terminal: Split Terminal (In Active Workspace)', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FocusPreviousPaneTerminalAction, FocusPreviousPaneTerminalAction.ID, FocusPreviousPaneTerminalAction.LABEL, { primary: KeyMod.Alt | KeyCode.LeftArrow, secondary: [KeyMod.Alt | KeyCode.UpArrow], mac: { @@ -456,7 +455,7 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousPan secondary: [KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.UpArrow] } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Focus Previous Pane', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextPaneTerminalAction, FocusNextPaneTerminalAction.ID, FocusNextPaneTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FocusNextPaneTerminalAction, FocusNextPaneTerminalAction.ID, FocusNextPaneTerminalAction.LABEL, { primary: KeyMod.Alt | KeyCode.RightArrow, secondary: [KeyMod.Alt | KeyCode.DownArrow], mac: { @@ -464,101 +463,116 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextPaneTer secondary: [KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.DownArrow] } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Focus Next Pane', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ResizePaneLeftTerminalAction, ResizePaneLeftTerminalAction.ID, ResizePaneLeftTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ResizePaneLeftTerminalAction, ResizePaneLeftTerminalAction.ID, ResizePaneLeftTerminalAction.LABEL, { primary: 0, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.LeftArrow }, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.LeftArrow } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Resize Pane Left', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ResizePaneRightTerminalAction, ResizePaneRightTerminalAction.ID, ResizePaneRightTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ResizePaneRightTerminalAction, ResizePaneRightTerminalAction.ID, ResizePaneRightTerminalAction.LABEL, { primary: 0, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.RightArrow }, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.RightArrow } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Resize Pane Right', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ResizePaneUpTerminalAction, ResizePaneUpTerminalAction.ID, ResizePaneUpTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ResizePaneUpTerminalAction, ResizePaneUpTerminalAction.ID, ResizePaneUpTerminalAction.LABEL, { primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.UpArrow } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Resize Pane Up', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ResizePaneDownTerminalAction, ResizePaneDownTerminalAction.ID, ResizePaneDownTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ResizePaneDownTerminalAction, ResizePaneDownTerminalAction.ID, ResizePaneDownTerminalAction.LABEL, { primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.DownArrow } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Resize Pane Down', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollToPreviousCommandAction, ScrollToPreviousCommandAction.ID, ScrollToPreviousCommandAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ScrollToPreviousCommandAction, ScrollToPreviousCommandAction.ID, ScrollToPreviousCommandAction.LABEL, { primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyCode.UpArrow } }, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate())), 'Terminal: Scroll To Previous Command', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollToNextCommandAction, ScrollToNextCommandAction.ID, ScrollToNextCommandAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ScrollToNextCommandAction, ScrollToNextCommandAction.ID, ScrollToNextCommandAction.LABEL, { primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyCode.DownArrow } }, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate())), 'Terminal: Scroll To Next Command', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectToPreviousCommandAction, SelectToPreviousCommandAction.ID, SelectToPreviousCommandAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SelectToPreviousCommandAction, SelectToPreviousCommandAction.ID, SelectToPreviousCommandAction.LABEL, { primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Select To Previous Command', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectToNextCommandAction, SelectToNextCommandAction.ID, SelectToNextCommandAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SelectToNextCommandAction, SelectToNextCommandAction.ID, SelectToNextCommandAction.LABEL, { primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.DownArrow } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Select To Next Command', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(NavigationModeExitTerminalAction, NavigationModeExitTerminalAction.ID, NavigationModeExitTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(NavigationModeExitTerminalAction, NavigationModeExitTerminalAction.ID, NavigationModeExitTerminalAction.LABEL, { primary: KeyCode.Escape }, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED)), 'Terminal: Exit Navigation Mode', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(NavigationModeFocusPreviousTerminalAction, NavigationModeFocusPreviousTerminalAction.ID, NavigationModeFocusPreviousTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(NavigationModeFocusPreviousTerminalAction, NavigationModeFocusPreviousTerminalAction.ID, NavigationModeFocusPreviousTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.UpArrow }, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED)), 'Terminal: Focus Previous Line (Navigation Mode)', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(NavigationModeFocusPreviousTerminalAction, NavigationModeFocusPreviousTerminalAction.ID, NavigationModeFocusPreviousTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(NavigationModeFocusPreviousTerminalAction, NavigationModeFocusPreviousTerminalAction.ID, NavigationModeFocusPreviousTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.UpArrow }, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED)), 'Terminal: Focus Previous Line (Navigation Mode)', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(NavigationModeFocusNextTerminalAction, NavigationModeFocusNextTerminalAction.ID, NavigationModeFocusNextTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(NavigationModeFocusNextTerminalAction, NavigationModeFocusNextTerminalAction.ID, NavigationModeFocusNextTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.DownArrow }, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED)), 'Terminal: Focus Next Line (Navigation Mode)', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(NavigationModeFocusNextTerminalAction, NavigationModeFocusNextTerminalAction.ID, NavigationModeFocusNextTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(NavigationModeFocusNextTerminalAction, NavigationModeFocusNextTerminalAction.ID, NavigationModeFocusNextTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.DownArrow }, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED)), 'Terminal: Focus Next Line (Navigation Mode)', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectToPreviousLineAction, SelectToPreviousLineAction.ID, SelectToPreviousLineAction.LABEL), 'Terminal: Select To Previous Line', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectToNextLineAction, SelectToNextLineAction.ID, SelectToNextLineAction.LABEL), 'Terminal: Select To Next Line', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleEscapeSequenceLoggingAction, ToggleEscapeSequenceLoggingAction.ID, ToggleEscapeSequenceLoggingAction.LABEL), 'Terminal: Toggle Escape Sequence Logging', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleRegexCommand, ToggleRegexCommand.ID, ToggleRegexCommand.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SelectToPreviousLineAction, SelectToPreviousLineAction.ID, SelectToPreviousLineAction.LABEL), 'Terminal: Select To Previous Line', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SelectToNextLineAction, SelectToNextLineAction.ID, SelectToNextLineAction.LABEL), 'Terminal: Select To Next Line', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleEscapeSequenceLoggingAction, ToggleEscapeSequenceLoggingAction.ID, ToggleEscapeSequenceLoggingAction.LABEL), 'Terminal: Toggle Escape Sequence Logging', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleRegexCommand, ToggleRegexCommand.ID, ToggleRegexCommand.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_R, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_R } -}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Toggle find using regex'); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleRegexCommand, ToggleRegexCommand.ID_TERMINAL_FOCUS, ToggleRegexCommand.LABEL, { +}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Toggle find using regex', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleRegexCommand, ToggleRegexCommand.ID, ToggleRegexCommand.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_R, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_R } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Toggle find using regex', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleWholeWordCommand, ToggleWholeWordCommand.ID, ToggleWholeWordCommand.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleWholeWordCommand, ToggleWholeWordCommand.ID, ToggleWholeWordCommand.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_W, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_W } -}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Toggle find using whole word'); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleWholeWordCommand, ToggleWholeWordCommand.ID_TERMINAL_FOCUS, ToggleWholeWordCommand.LABEL, { +}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Toggle find using whole word', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleWholeWordCommand, ToggleWholeWordCommand.ID, ToggleWholeWordCommand.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_W, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_W } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Toggle find using whole word', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleCaseSensitiveCommand, ToggleCaseSensitiveCommand.ID, ToggleCaseSensitiveCommand.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleCaseSensitiveCommand, ToggleCaseSensitiveCommand.ID, ToggleCaseSensitiveCommand.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_C, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_C } -}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Toggle find using case sensitive'); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleCaseSensitiveCommand, ToggleCaseSensitiveCommand.ID_TERMINAL_FOCUS, ToggleCaseSensitiveCommand.LABEL, { +}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Toggle find using case sensitive', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleCaseSensitiveCommand, ToggleCaseSensitiveCommand.ID, ToggleCaseSensitiveCommand.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_C, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_C } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Toggle find using case sensitive', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FindNext, FindNext.ID_TERMINAL_FOCUS, FindNext.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FindNext, FindNext.ID, FindNext.LABEL, { primary: KeyCode.F3, mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_G, secondary: [KeyCode.F3] } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Find next', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FindNext, FindNext.ID, FindNext.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FindNext, FindNext.ID, FindNext.LABEL, { primary: KeyCode.F3, secondary: [KeyMod.Shift | KeyCode.Enter], mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_G, secondary: [KeyCode.F3, KeyMod.Shift | KeyCode.Enter] } -}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Find next'); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FindPrevious, FindPrevious.ID_TERMINAL_FOCUS, FindPrevious.LABEL, { +}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Find next', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FindPrevious, FindPrevious.ID, FindPrevious.LABEL, { primary: KeyMod.Shift | KeyCode.F3, mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_G, secondary: [KeyMod.Shift | KeyCode.F3] }, }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Find previous', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FindPrevious, FindPrevious.ID, FindPrevious.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FindPrevious, FindPrevious.ID, FindPrevious.LABEL, { primary: KeyMod.Shift | KeyCode.F3, secondary: [KeyCode.Enter], mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_G, secondary: [KeyMod.Shift | KeyCode.F3, KeyCode.Enter] }, -}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Find previous'); +}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Find previous', category); +// Commands might be affected by Web restrictons +if (BrowserFeatures.clipboard.writeText) { + actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(CopyTerminalSelectionAction, CopyTerminalSelectionAction.ID, CopyTerminalSelectionAction.LABEL, { + primary: KeyMod.CtrlCmd | KeyCode.KEY_C, + win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_C, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_C] }, + linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_C } + }, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, KEYBINDING_CONTEXT_TERMINAL_FOCUS)), 'Terminal: Copy Selection', category); +} +if (BrowserFeatures.clipboard.readText) { + actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(TerminalPasteAction, TerminalPasteAction.ID, TerminalPasteAction.LABEL, { + primary: KeyMod.CtrlCmd | KeyCode.KEY_V, + win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_V, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_V] }, + linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_V } + }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Paste into Active Terminal', category); +} (new SendSequenceTerminalCommand({ id: SendSequenceTerminalCommand.ID, precondition: undefined, @@ -576,6 +590,7 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FindPrevious, Fi }] } })).register(); + (new CreateNewWithCwdTerminalCommand({ id: CreateNewWithCwdTerminalCommand.ID, precondition: undefined, @@ -597,6 +612,28 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FindPrevious, Fi } })).register(); +(new RenameWithArgTerminalCommand({ + id: RenameWithArgTerminalCommand.ID, + precondition: undefined, + description: { + description: RenameWithArgTerminalCommand.LABEL, + args: [{ + name: 'args', + schema: { + type: 'object', + required: ['name'], + properties: { + name: { + description: RenameWithArgTerminalCommand.NAME_ARG_LABEL, + type: 'string', + minLength: 1 + } + } + } + }] + } +})).register(); + setupTerminalCommands(); setupTerminalMenu(); diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 1530c7f0b8..02b13b1e96 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -6,6 +6,7 @@ import { Terminal as XTermTerminal } from 'xterm'; import { WebLinksAddon as XTermWebLinksAddon } from 'xterm-addon-web-links'; import { SearchAddon as XTermSearchAddon } from 'xterm-addon-search'; +import { WebglAddon as XTermWebglAddon } from 'xterm-addon-webgl'; import { IWindowsShellHelper, ITerminalConfigHelper, ITerminalChildProcess, IShellLaunchConfig, IDefaultShellAndArgsRequest, ISpawnExtHostProcessRequest, IStartExtensionTerminalRequest, IAvailableShellsRequest, ITerminalProcessExtHostProxy, ICommandTracker, INavigationMode, TitleEventSource, ITerminalDimensions } from 'vs/workbench/contrib/terminal/common/terminal'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IProcessEnvironment, Platform } from 'vs/base/common/platform'; @@ -31,6 +32,7 @@ export interface ITerminalInstanceService { getXtermConstructor(): Promise; getXtermWebLinksConstructor(): Promise; getXtermSearchConstructor(): Promise; + getXtermWebglConstructor(): Promise; createWindowsShellHelper(shellProcessId: number, instance: ITerminalInstance, xterm: XTermTerminal): IWindowsShellHelper; createTerminalProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean): ITerminalChildProcess; @@ -141,7 +143,7 @@ export interface ITerminalService { * @param path The path to be escaped and formatted. * @returns An escaped version of the path to be execuded in the terminal. */ - preparePathForTerminalAsync(path: string, executable: string | undefined, title: string): Promise; + preparePathForTerminalAsync(path: string, executable: string | undefined, title: string, shellType: TerminalShellType): Promise; extHostReady(remoteAuthority: string): void; requestSpawnExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI | undefined, cols: number, rows: number, isWorkspaceShellAllowed: boolean): void; @@ -167,6 +169,14 @@ export interface ISearchOptions { incremental?: boolean; } +export enum WindowsShellType { + CommandPrompt, + PowerShell, + Wsl, + GitBash +} +export type TerminalShellType = WindowsShellType | undefined; + export interface ITerminalInstance { /** * The ID of the terminal instance, this is an arbitrary number only used to identify the @@ -228,6 +238,8 @@ export interface ITerminalInstance { */ onExit: Event; + readonly exitCode: number | undefined; + processReady: Promise; /** @@ -236,6 +248,11 @@ export interface ITerminalInstance { */ readonly title: string; + /** + * The shell type of the terminal. + */ + readonly shellType: TerminalShellType; + /** * The focus state of the terminal before exiting. */ @@ -431,6 +448,11 @@ export interface ITerminalInstance { */ setTitle(title: string, eventSource: TitleEventSource): void; + /** + * Sets the shell type of the terminal instance. + */ + setShellType(shellType: TerminalShellType): void; + waitForTitle(): Promise; setDimensions(dimensions: ITerminalDimensions): void; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 7a71e2c2c5..8b6044bedb 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -305,17 +305,7 @@ export class CreateNewWithCwdTerminalCommand extends Command { public runCommand(accessor: ServicesAccessor, args: { cwd: string } | undefined): Promise { const terminalService = accessor.get(ITerminalService); - const configurationResolverService = accessor.get(IConfigurationResolverService); - const workspaceContextService = accessor.get(IWorkspaceContextService); - const historyService = accessor.get(IHistoryService); - const activeWorkspaceRootUri = historyService.getLastActiveWorkspaceRoot(Schemas.file); - const lastActiveWorkspaceRoot = activeWorkspaceRootUri ? withNullAsUndefined(workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri)) : undefined; - - let cwd: string | undefined; - if (args && args.cwd) { - cwd = configurationResolverService.resolve(lastActiveWorkspaceRoot, args.cwd); - } - const instance = terminalService.createTerminal({ cwd }); + const instance = terminalService.createTerminal({ cwd: args?.cwd }); if (!instance) { return Promise.resolve(undefined); } @@ -722,7 +712,7 @@ export class RunActiveFileInTerminalAction extends Action { return Promise.resolve(undefined); } - return this.terminalService.preparePathForTerminalAsync(uri.fsPath, instance.shellLaunchConfig.executable, instance.title).then(path => { + return this.terminalService.preparePathForTerminalAsync(uri.fsPath, instance.shellLaunchConfig.executable, instance.title, instance.shellType).then(path => { instance.sendText(path, true); return this.terminalService.showPanel(); }); @@ -1065,6 +1055,31 @@ export class RenameTerminalAction extends Action { }); } } +export class RenameWithArgTerminalCommand extends Command { + public static readonly ID = TERMINAL_COMMAND_ID.RENAME_WITH_ARG; + public static readonly LABEL = nls.localize('workbench.action.terminal.renameWithArg', "Rename the Currently Active Terminal"); + public static readonly NAME_ARG_LABEL = nls.localize('workbench.action.terminal.renameWithArg.name', "The new name for the terminal"); + + public runCommand( + accessor: ServicesAccessor, + args?: { name?: string } + ): void { + const notificationService = accessor.get(INotificationService); + const terminalInstance = accessor.get(ITerminalService).getActiveInstance(); + + if (!terminalInstance) { + notificationService.warn(nls.localize('workbench.action.terminal.renameWithArg.noTerminal', "No active terminal to rename")); + return; + } + + if (!args || !args.name) { + notificationService.warn(nls.localize('workbench.action.terminal.renameWithArg.noName', "No name argument provided")); + return; + } + + terminalInstance.setTitle(args.name, TitleEventSource.Api); + } +} export class FocusTerminalFindWidgetAction extends Action { @@ -1328,7 +1343,6 @@ abstract class ToggleFindOptionCommand extends Action { export class ToggleRegexCommand extends ToggleFindOptionCommand { public static readonly ID = TERMINAL_COMMAND_ID.TOGGLE_FIND_REGEX; - public static readonly ID_TERMINAL_FOCUS = TERMINAL_COMMAND_ID.TOGGLE_FIND_REGEX_TERMINAL_FOCUS; public static readonly LABEL = nls.localize('workbench.action.terminal.toggleFindRegex', "Toggle find using regex"); protected runInner(state: FindReplaceState): void { @@ -1338,7 +1352,6 @@ export class ToggleRegexCommand extends ToggleFindOptionCommand { export class ToggleWholeWordCommand extends ToggleFindOptionCommand { public static readonly ID = TERMINAL_COMMAND_ID.TOGGLE_FIND_WHOLE_WORD; - public static readonly ID_TERMINAL_FOCUS = TERMINAL_COMMAND_ID.TOGGLE_FIND_WHOLE_WORD_TERMINAL_FOCUS; public static readonly LABEL = nls.localize('workbench.action.terminal.toggleFindWholeWord', "Toggle find using whole word"); protected runInner(state: FindReplaceState): void { @@ -1348,7 +1361,6 @@ export class ToggleWholeWordCommand extends ToggleFindOptionCommand { export class ToggleCaseSensitiveCommand extends ToggleFindOptionCommand { public static readonly ID = TERMINAL_COMMAND_ID.TOGGLE_FIND_CASE_SENSITIVE; - public static readonly ID_TERMINAL_FOCUS = TERMINAL_COMMAND_ID.TOGGLE_FIND_CASE_SENSITIVE_TERMINAL_FOCUS; public static readonly LABEL = nls.localize('workbench.action.terminal.toggleFindCaseSensitive', "Toggle find using case sensitive"); protected runInner(state: FindReplaceState): void { @@ -1358,7 +1370,6 @@ export class ToggleCaseSensitiveCommand extends ToggleFindOptionCommand { export class FindNext extends Action { public static readonly ID = TERMINAL_COMMAND_ID.FIND_NEXT; - public static readonly ID_TERMINAL_FOCUS = TERMINAL_COMMAND_ID.FIND_NEXT_TERMINAL_FOCUS; public static readonly LABEL = nls.localize('workbench.action.terminal.findNext', "Find next"); constructor( @@ -1376,7 +1387,6 @@ export class FindNext extends Action { export class FindPrevious extends Action { public static readonly ID = TERMINAL_COMMAND_ID.FIND_PREVIOUS; - public static readonly ID_TERMINAL_FOCUS = TERMINAL_COMMAND_ID.FIND_PREVIOUS_TERMINAL_FOCUS; public static readonly LABEL = nls.localize('workbench.action.terminal.findPrevious', "Find previous"); constructor( diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index a2d7d5d52e..d0634ac8e0 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -31,7 +31,7 @@ import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/term import { TerminalLinkHandler } from 'vs/workbench/contrib/terminal/browser/terminalLinkHandler'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; -import { ITerminalInstanceService, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalInstanceService, ITerminalInstance, TerminalShellType } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalProcessManager } from 'vs/workbench/contrib/terminal/browser/terminalProcessManager'; import { Terminal as XTermTerminal, IBuffer, ITerminalAddon } from 'xterm'; import { SearchAddon, ISearchOptions } from 'xterm-addon-search'; @@ -54,11 +54,11 @@ export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [ TERMINAL_COMMAND_ID.DELETE_WORD_RIGHT, TERMINAL_COMMAND_ID.FIND_WIDGET_FOCUS, TERMINAL_COMMAND_ID.FIND_WIDGET_HIDE, - TERMINAL_COMMAND_ID.FIND_NEXT_TERMINAL_FOCUS, - TERMINAL_COMMAND_ID.FIND_PREVIOUS_TERMINAL_FOCUS, - TERMINAL_COMMAND_ID.TOGGLE_FIND_REGEX_TERMINAL_FOCUS, - TERMINAL_COMMAND_ID.TOGGLE_FIND_WHOLE_WORD_TERMINAL_FOCUS, - TERMINAL_COMMAND_ID.TOGGLE_FIND_CASE_SENSITIVE_TERMINAL_FOCUS, + TERMINAL_COMMAND_ID.FIND_NEXT, + TERMINAL_COMMAND_ID.FIND_PREVIOUS, + TERMINAL_COMMAND_ID.TOGGLE_FIND_REGEX, + TERMINAL_COMMAND_ID.TOGGLE_FIND_WHOLE_WORD, + TERMINAL_COMMAND_ID.TOGGLE_FIND_CASE_SENSITIVE, TERMINAL_COMMAND_ID.FOCUS_NEXT_PANE, TERMINAL_COMMAND_ID.FOCUS_NEXT, TERMINAL_COMMAND_ID.FOCUS_PREVIOUS_PANE, @@ -181,7 +181,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { private _hadFocusOnExit: boolean; private _isVisible: boolean; private _isDisposed: boolean; + private _exitCode: number | undefined; private _skipTerminalCommands: string[]; + private _shellType: TerminalShellType; private _title: string = ''; private _wrapperElement: (HTMLElement & { xterm?: XTermTerminal }) | undefined; private _xterm: XTermTerminal | undefined; @@ -226,10 +228,12 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // TODO: How does this work with detached processes? // TODO: Should this be an event as it can fire twice? public get processReady(): Promise { return this._processManager.ptyProcessReady; } + public get exitCode(): number | undefined { return this._exitCode; } public get title(): string { return this._title; } public get hadFocusOnExit(): boolean { return this._hadFocusOnExit; } public get isTitleSetByProcess(): boolean { return !!this._messageTitleDisposable; } public get shellLaunchConfig(): IShellLaunchConfig { return this._shellLaunchConfig; } + public get shellType(): TerminalShellType { return this._shellType; } public get commandTracker(): CommandTrackerAddon | undefined { return this._commandTrackerAddon; } public get navigationMode(): INavigationMode | undefined { return this._navigationModeAddon; } @@ -305,7 +309,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { }); this.addDisposable(this._configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('terminal.integrated')) { + if (e.affectsConfiguration('terminal.integrated') || e.affectsConfiguration('editor.fastScrollSensitivity') || e.affectsConfiguration('editor.mouseWheelScrollSensitivity')) { this.updateConfig(); // HACK: Trigger another async layout to ensure xterm's CharMeasure is ready to use, // this hack can be removed when https://github.com/xtermjs/xterm.js/issues/702 is @@ -365,12 +369,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // when window.devicePixelRatio changes. const scaledWidthAvailable = dimension.width * window.devicePixelRatio; - let scaledCharWidth: number; - if (this._configHelper.config.rendererType === 'dom') { - scaledCharWidth = font.charWidth * window.devicePixelRatio; - } else { - scaledCharWidth = Math.floor(font.charWidth * window.devicePixelRatio) + font.letterSpacing; - } + const scaledCharWidth = font.charWidth * window.devicePixelRatio + font.letterSpacing; const newCols = Math.max(Math.floor(scaledWidthAvailable / scaledCharWidth), 1); const scaledHeightAvailable = dimension.height * window.devicePixelRatio; @@ -421,7 +420,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { const bottom = parseInt(wrapperElementStyle.bottom!.split('px')[0], 10); const innerWidth = width - marginLeft - marginRight; - const innerHeight = height - bottom; + const innerHeight = height - bottom - 1; TerminalInstance._lastKnownCanvasDimensions = new dom.Dimension(innerWidth, innerHeight); return TerminalInstance._lastKnownCanvasDimensions; @@ -448,7 +447,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { const Terminal = await this._getXtermConstructor(); const font = this._configHelper.getFont(undefined, true); const config = this._configHelper.config; - const fastScrollSensitivity = this._configurationService.getValue('editor.fastScrollSensitivity').fastScrollSensitivity; + const editorOptions = this._configurationService.getValue('editor'); + const xterm = new Terminal({ scrollback: config.scrollback, theme: this._getXtermTheme(), @@ -459,14 +459,16 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { fontSize: font.fontSize, letterSpacing: font.letterSpacing, lineHeight: font.lineHeight, + minimumContrastRatio: config.minimumContrastRatio, bellStyle: config.enableBell ? 'sound' : 'none', macOptionIsMeta: config.macOptionIsMeta, macOptionClickForcesSelection: config.macOptionClickForcesSelection, rightClickSelectsWord: config.rightClickBehavior === 'selectWord', fastScrollModifier: 'alt', - fastScrollSensitivity, - // TODO: Guess whether to use canvas or dom better - rendererType: config.rendererType === 'auto' ? 'canvas' : config.rendererType + fastScrollSensitivity: editorOptions.fastScrollSensitivity, + scrollSensitivity: editorOptions.mouseWheelScrollSensitivity, + rendererType: config.rendererType === 'auto' || config.rendererType === 'experimentalWebgl' ? 'canvas' : config.rendererType, + wordSeparator: ' ()[]{}\',:;"`' }); this._xterm = xterm; this._xtermCore = (xterm as any)._core as XTermCore; @@ -484,7 +486,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._processManager.onProcessData(data => this._onProcessData(data)); this._xterm.onData(data => this._processManager.write(data)); - // TODO: How does the cwd work on detached processes? this.processReady.then(async () => { if (this._linkHandler) { this._linkHandler.processCwd = await this._processManager.getInitialCwd(); @@ -524,9 +525,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { throw new Error('The terminal instance has not been attached to a container yet'); } - if (this._wrapperElement.parentNode) { - this._wrapperElement.parentNode.removeChild(this._wrapperElement); - } + this._wrapperElement.parentNode?.removeChild(this._wrapperElement); this._container = container; this._container.appendChild(this._wrapperElement); } @@ -544,9 +543,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } // The container changed, reattach - if (this._container) { - this._container.removeChild(this._wrapperElement); - } + this._container?.removeChild(this._wrapperElement); this._container = container; this._container.appendChild(this._wrapperElement); } @@ -568,6 +565,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._wrapperElement.appendChild(this._xtermElement); this._container.appendChild(this._wrapperElement); xterm.open(this._xtermElement); + if (this._configHelper.config.rendererType === 'experimentalWebgl') { + this._terminalInstanceService.getXtermWebglConstructor().then(Addon => { + xterm.loadAddon(new Addon()); + }); + } if (!xterm.element || !xterm.textarea) { throw new Error('xterm elements not set after open'); @@ -584,7 +586,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // within commandsToSkipShell const standardKeyboardEvent = new StandardKeyboardEvent(event); const resolveResult = this._keybindingService.softDispatch(standardKeyboardEvent, standardKeyboardEvent.target); - const allowChords = resolveResult && resolveResult.enterChord && this._configHelper.config.allowChords; + // Respect chords if the allowChords setting is set and it's not Escape. Escape is + // handled specially for Zen Mode's Escape, Escape chord, plus it's important in + // terminals generally + const allowChords = resolveResult && resolveResult.enterChord && this._configHelper.config.allowChords && event.key !== 'Escape'; if (allowChords || resolveResult && this._skipTerminalCommands.some(k => k === resolveResult.commandId)) { event.preventDefault(); return false; @@ -594,6 +599,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { if (TabFocus.getTabFocusMode() && event.keyCode === 9) { return false; } + // Always have alt+F4 skip the terminal on Windows and allow it to be handled by the // system if (platform.isWindows && event.altKey && event.key === 'F4' && !event.ctrlKey) { @@ -652,11 +658,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { const widgetManager = new TerminalWidgetManager(this._wrapperElement); this._widgetManager = widgetManager; - this._processManager.onProcessReady(() => { - if (this._linkHandler) { - this._linkHandler.setWidgetManager(widgetManager); - } - }); + this._processManager.onProcessReady(() => this._linkHandler?.setWidgetManager(widgetManager)); const computedStyle = window.getComputedStyle(this._container); const width = parseInt(computedStyle.getPropertyValue('width').replace('px', ''), 10); @@ -751,19 +753,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } public clearSelection(): void { - if (!this._xterm) { - return; - } - this._xterm.clearSelection(); + this._xterm?.clearSelection(); } public selectAll(): void { - if (!this._xterm) { - return; - } // Focus here to ensure the terminal context key is set - this._xterm.focus(); - this._xterm.selectAll(); + this._xterm?.focus(); + this._xterm?.selectAll(); } public findNext(term: string, searchOptions: ISearchOptions): boolean { @@ -924,45 +920,31 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } public scrollDownLine(): void { - if (this._xterm) { - this._xterm.scrollLines(1); - } + this._xterm?.scrollLines(1); } public scrollDownPage(): void { - if (this._xterm) { - this._xterm.scrollPages(1); - } + this._xterm?.scrollPages(1); } public scrollToBottom(): void { - if (this._xterm) { - this._xterm.scrollToBottom(); - } + this._xterm?.scrollToBottom(); } public scrollUpLine(): void { - if (this._xterm) { - this._xterm.scrollLines(-1); - } + this._xterm?.scrollLines(-1); } public scrollUpPage(): void { - if (this._xterm) { - this._xterm.scrollPages(-1); - } + this._xterm?.scrollPages(-1); } public scrollToTop(): void { - if (this._xterm) { - this._xterm.scrollToTop(); - } + this._xterm?.scrollToTop(); } public clear(): void { - if (this._xterm) { - this._xterm.clear(); - } + this._xterm?.clear(); } private _refreshSelectionContextKey() { @@ -1019,12 +1001,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } private _onProcessData(data: string): void { - if (this._widgetManager) { - this._widgetManager.closeMessage(); - } - if (this._xterm) { - this._xterm.write(data); - } + this._widgetManager?.closeMessage(); + this._xterm?.write(data); } /** @@ -1041,6 +1019,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._logService.debug(`Terminal process exit (id: ${this.id}) with code ${exitCode}`); + this._exitCode = exitCode; this._isExiting = true; let exitCodeMessage: string | undefined; @@ -1130,10 +1109,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { public reuseTerminal(shell: IShellLaunchConfig): void { // Unsubscribe any key listener we may have. - if (this._pressAnyKeyToCloseListener) { - this._pressAnyKeyToCloseListener.dispose(); - this._pressAnyKeyToCloseListener = undefined; - } + this._pressAnyKeyToCloseListener?.dispose(); + this._pressAnyKeyToCloseListener = undefined; // Kill and clear up the process, making the process manager ready for a new process this._processManager.dispose(); @@ -1243,11 +1220,18 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._setCommandsToSkipShell(config.commandsToSkipShell); this._setEnableBell(config.enableBell); this._safeSetOption('scrollback', config.scrollback); + this._safeSetOption('minimumContrastRatio', config.minimumContrastRatio); this._safeSetOption('macOptionIsMeta', config.macOptionIsMeta); this._safeSetOption('macOptionClickForcesSelection', config.macOptionClickForcesSelection); this._safeSetOption('rightClickSelectsWord', config.rightClickBehavior === 'selectWord'); - this._safeSetOption('rendererType', config.rendererType === 'auto' ? 'canvas' : config.rendererType); - this._safeSetOption('fastScrollSensitivity', this._configurationService.getValue('editor.fastScrollSensitivity').fastScrollSensitivity); + if (config.rendererType !== 'experimentalWebgl') { + // Never set webgl as it's an addon not a rendererType + this._safeSetOption('rendererType', config.rendererType === 'auto' ? 'canvas' : config.rendererType); + } + + const editorOptions = this._configurationService.getValue('editor'); + this._safeSetOption('fastScrollSensitivity', editorOptions.fastScrollSensitivity); + this._safeSetOption('scrollSensitivity', editorOptions.mouseWheelScrollSensitivity); } public updateAccessibilitySupport(): void { @@ -1256,10 +1240,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._navigationModeAddon = new NavigationModeAddon(this._terminalA11yTreeFocusContextKey); this._xterm!.loadAddon(this._navigationModeAddon); } else { - if (this._navigationModeAddon) { - this._navigationModeAddon.dispose(); - this._navigationModeAddon = undefined; - } + this._navigationModeAddon?.dispose(); + this._navigationModeAddon = undefined; } this._xterm!.setOption('screenReaderMode', isEnabled); } @@ -1375,6 +1357,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._processManager.ptyProcessReady.then(() => this._processManager.setDimensions(cols, rows)); } + public setShellType(shellType: TerminalShellType) { + this._shellType = shellType; + } + public setTitle(title: string | undefined, eventSource: TitleEventSource): void { if (!title) { return; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts index 0ba736bd57..1f4b706ba6 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts @@ -8,6 +8,7 @@ import { IWindowsShellHelper, ITerminalChildProcess, IDefaultShellAndArgsRequest import { Terminal as XTermTerminal } from 'xterm'; import { WebLinksAddon as XTermWebLinksAddon } from 'xterm-addon-web-links'; import { SearchAddon as XTermSearchAddon } from 'xterm-addon-search'; +import { WebglAddon as XTermWebglAddon } from 'xterm-addon-webgl'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { Emitter, Event } from 'vs/base/common/event'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -15,6 +16,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; let Terminal: typeof XTermTerminal; let WebLinksAddon: typeof XTermWebLinksAddon; let SearchAddon: typeof XTermSearchAddon; +let WebglAddon: typeof XTermWebglAddon; export class TerminalInstanceService implements ITerminalInstanceService { public _serviceBrand: undefined; @@ -43,6 +45,13 @@ export class TerminalInstanceService implements ITerminalInstanceService { return SearchAddon; } + public async getXtermWebglConstructor(): Promise { + if (!WebglAddon) { + WebglAddon = (await import('xterm-addon-webgl')).WebglAddon; + } + return WebglAddon; + } + public createWindowsShellHelper(): IWindowsShellHelper { throw new Error('Not implemented'); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts index 752cb6c4a0..7dd1d01571 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts @@ -7,13 +7,13 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/terminalWidgetManager'; +import { TerminalWidgetManager, WidgetVerticalAlignment } from 'vs/workbench/contrib/terminal/browser/terminalWidgetManager'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITerminalProcessManager, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal'; import { ITextEditorSelection } from 'vs/platform/editor/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IFileService } from 'vs/platform/files/common/files'; -import { Terminal, ILinkMatcherOptions } from 'xterm'; +import { Terminal, ILinkMatcherOptions, IViewportRange } from 'xterm'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { posix, win32 } from 'vs/base/common/path'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; @@ -71,7 +71,7 @@ export class TerminalLinkHandler { private _processCwd: string | undefined; private _gitDiffPreImagePattern: RegExp; private _gitDiffPostImagePattern: RegExp; - private readonly _tooltipCallback: (event: MouseEvent, uri: string) => boolean | void; + private readonly _tooltipCallback: (event: MouseEvent, uri: string, location: IViewportRange) => boolean | void; private readonly _leaveCallback: () => void; constructor( @@ -89,15 +89,42 @@ export class TerminalLinkHandler { // Matches '+++ b/src/file1', capturing 'src/file1' in group 1 this._gitDiffPostImagePattern = /^\+\+\+ b\/(\S*)/; - this._tooltipCallback = (e: MouseEvent) => { + this._tooltipCallback = (e: MouseEvent, uri: string, location: IViewportRange) => { if (!this._widgetManager) { return; } + + // Get the row bottom up + let offsetRow = this._xterm.rows - location.start.y; + let verticalAlignment = WidgetVerticalAlignment.Bottom; + + // Show the tooltip on the top of the next row to avoid obscuring the first row + if (location.start.y <= 0) { + offsetRow = this._xterm.rows - 1; + verticalAlignment = WidgetVerticalAlignment.Top; + // The start of the wrapped line is above the viewport, move to start of the line + if (location.start.y < 0) { + location.start.x = 0; + } + } + if (this._configHelper.config.rendererType === 'dom') { - const target = (e.target as HTMLElement); - this._widgetManager.showMessage(target.offsetLeft, target.offsetTop, this._getLinkHoverString()); + const font = this._configHelper.getFont(); + const charWidth = font.charWidth; + const charHeight = font.charHeight; + + const leftPosition = location.start.x * (charWidth! + (font.letterSpacing / window.devicePixelRatio)); + const bottomPosition = offsetRow * (Math.ceil(charHeight! * window.devicePixelRatio) * font.lineHeight) / window.devicePixelRatio; + + this._widgetManager.showMessage(leftPosition, bottomPosition, this._getLinkHoverString(), verticalAlignment); } else { - this._widgetManager.showMessage(e.offsetX, e.offsetY, this._getLinkHoverString()); + const target = (e.target as HTMLElement); + const colWidth = target.offsetWidth / this._xterm.cols; + const rowHeight = target.offsetHeight / this._xterm.rows; + + const leftPosition = location.start.x * colWidth; + const bottomPosition = offsetRow * rowHeight; + this._widgetManager.showMessage(leftPosition, bottomPosition, this._getLinkHoverString(), verticalAlignment); } }; this._leaveCallback = () => { @@ -239,8 +266,7 @@ export class TerminalLinkHandler { } private _handleHypertextLink(url: string): void { - const uri = URI.parse(url); - this._openerService.open(uri, { allowTunneling: !!(this._processManager && this._processManager.remoteAuthority) }); + this._openerService.open(url, { allowTunneling: !!(this._processManager && this._processManager.remoteAuthority) }); } private _isLinkActivationModifierDown(event: MouseEvent): boolean { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts b/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts index 6576483fd7..f4046ada5a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts @@ -15,7 +15,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { TERMINAL_PANEL_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { IThemeService, ITheme, registerThemingParticipant, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { TerminalFindWidget } from 'vs/workbench/contrib/terminal/browser/terminalFindWidget'; -import { editorHoverBackground, editorHoverBorder, editorForeground } from 'vs/platform/theme/common/colorRegistry'; +import { editorHoverBackground, editorHoverBorder, editorHoverForeground } from 'vs/platform/theme/common/colorRegistry'; import { KillTerminalAction, SwitchTerminalAction, SwitchTerminalActionViewItem, CopyTerminalSelectionAction, TerminalPasteAction, ClearTerminalAction, SelectAllTerminalAction, CreateNewTerminalAction, SplitTerminalAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { Panel } from 'vs/workbench/browser/panel'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; @@ -26,6 +26,7 @@ import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notif import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { assertIsDefined } from 'vs/base/common/types'; +import { BrowserFeatures } from 'vs/base/browser/canIUse'; const FIND_FOCUS_CLASS = 'find-focused'; @@ -141,13 +142,22 @@ export class TerminalPanel extends Panel { private _getContextMenuActions(): IAction[] { if (!this._contextMenuActions || !this._copyContextMenuAction) { this._copyContextMenuAction = this._instantiationService.createInstance(CopyTerminalSelectionAction, CopyTerminalSelectionAction.ID, CopyTerminalSelectionAction.SHORT_LABEL); + + const clipboardActions = []; + if (BrowserFeatures.clipboard.writeText) { + clipboardActions.push(this._copyContextMenuAction); + } + if (BrowserFeatures.clipboard.readText) { + clipboardActions.push(this._instantiationService.createInstance(TerminalPasteAction, TerminalPasteAction.ID, TerminalPasteAction.SHORT_LABEL)); + } + + clipboardActions.push(this._instantiationService.createInstance(SelectAllTerminalAction, SelectAllTerminalAction.ID, SelectAllTerminalAction.LABEL)); + this._contextMenuActions = [ this._instantiationService.createInstance(CreateNewTerminalAction, CreateNewTerminalAction.ID, CreateNewTerminalAction.SHORT_LABEL), this._instantiationService.createInstance(SplitTerminalAction, SplitTerminalAction.ID, SplitTerminalAction.SHORT_LABEL), new Separator(), - this._copyContextMenuAction, - this._instantiationService.createInstance(TerminalPasteAction, TerminalPasteAction.ID, TerminalPasteAction.SHORT_LABEL), - this._instantiationService.createInstance(SelectAllTerminalAction, SelectAllTerminalAction.ID, SelectAllTerminalAction.LABEL), + ...clipboardActions, new Separator(), this._instantiationService.createInstance(ClearTerminalAction, ClearTerminalAction.ID, ClearTerminalAction.LABEL), new Separator(), @@ -252,9 +262,9 @@ export class TerminalPanel extends Panel { getActions: () => this._getContextMenuActions(), getActionsContext: () => this._parentDomElement }); - } else { - event.stopImmediatePropagation(); } + event.preventDefault(); + event.stopImmediatePropagation(); this._cancelContextMenu = false; })); this._register(dom.addDisposableListener(document, 'keydown', (event: KeyboardEvent) => { @@ -291,7 +301,7 @@ export class TerminalPanel extends Panel { const terminal = this._terminalService.getActiveInstance(); if (terminal) { - return this._terminalService.preparePathForTerminalAsync(path, terminal.shellLaunchConfig.executable, terminal.title).then(preparedPath => { + return this._terminalService.preparePathForTerminalAsync(path, terminal.shellLaunchConfig.executable, terminal.title, terminal.shellType).then(preparedPath => { terminal.sendText(preparedPath, false); }); } @@ -337,7 +347,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { if (hoverBorder) { collector.addRule(`.monaco-workbench .panel.integrated-terminal .terminal-message-widget { border: 1px solid ${hoverBorder}; }`); } - const hoverForeground = theme.getColor(editorForeground); + const hoverForeground = theme.getColor(editorHoverForeground); if (hoverForeground) { collector.addRule(`.monaco-workbench .panel.integrated-terminal .terminal-message-widget { color: ${hoverForeground}; }`); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts index 36be0aa641..2d18b3c91c 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts @@ -17,8 +17,8 @@ export class TerminalProcessExtHostProxy extends Disposable implements ITerminal private readonly _onProcessData = this._register(new Emitter()); public readonly onProcessData: Event = this._onProcessData.event; - private readonly _onProcessExit = this._register(new Emitter()); - public readonly onProcessExit: Event = this._onProcessExit.event; + private readonly _onProcessExit = this._register(new Emitter()); + public readonly onProcessExit: Event = this._onProcessExit.event; private readonly _onProcessReady = this._register(new Emitter<{ pid: number, cwd: string }>()); public get onProcessReady(): Event<{ pid: number, cwd: string }> { return this._onProcessReady.event; } private readonly _onProcessTitleChanged = this._register(new Emitter()); @@ -87,7 +87,7 @@ export class TerminalProcessExtHostProxy extends Disposable implements ITerminal this._onProcessReady.fire({ pid, cwd }); } - public emitExit(exitCode: number): void { + public emitExit(exitCode: number | undefined): void { this._onProcessExit.fire(exitCode); this.dispose(); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index 8660142f70..21cd00663f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -67,8 +67,8 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce public get onProcessData(): Event { return this._onProcessData.event; } private readonly _onProcessTitle = this._register(new Emitter()); public get onProcessTitle(): Event { return this._onProcessTitle.event; } - private readonly _onProcessExit = this._register(new Emitter()); - public get onProcessExit(): Event { return this._onProcessExit.event; } + private readonly _onProcessExit = this._register(new Emitter()); + public get onProcessExit(): Event { return this._onProcessExit.event; } private readonly _onProcessOverrideDimensions = this._register(new Emitter()); public get onProcessOverrideDimensions(): Event { return this._onProcessOverrideDimensions.event; } private readonly _onProcessOverrideShellLaunchConfig = this._register(new Emitter()); @@ -285,7 +285,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce return Promise.resolve(this._latency); } - private _onExit(exitCode: number): void { + private _onExit(exitCode: number | undefined): void { this._process = null; // If the process is marked as launching then mark the process as killed diff --git a/src/vs/workbench/contrib/terminal/browser/terminalQuickOpen.ts b/src/vs/workbench/contrib/terminal/browser/terminalQuickOpen.ts index 1fc468034b..1a95d03d64 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalQuickOpen.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalQuickOpen.ts @@ -88,7 +88,7 @@ export class TerminalPickerHandler extends QuickOpenHandler { const normalizedSearchValueLowercase = stripWildcards(searchValue).toLowerCase(); const terminalEntries: QuickOpenEntry[] = this.getTerminals(); - terminalEntries.push(new CreateTerminal(nls.localize("workbench.action.terminal.newplus", "$(plus) Create New Integrated Terminal"), this.commandService)); + terminalEntries.push(new CreateTerminal('$(plus) ' + nls.localize("workbench.action.terminal.newplus", "Create New Integrated Terminal"), this.commandService)); const entries = terminalEntries.filter(e => { if (!searchValue) { @@ -113,7 +113,7 @@ export class TerminalPickerHandler extends QuickOpenHandler { } private getTerminals(): TerminalEntry[] { - return this.terminalService.terminalTabs.reduce((terminals, tab, tabIndex) => { + return this.terminalService.terminalTabs.reduce((terminals: TerminalEntry[], tab, tabIndex) => { const terminalsInTab = tab.terminalInstances.map((terminal, terminalIndex) => { const label = `${tabIndex + 1}.${terminalIndex + 1}: ${terminal.title}`; return new TerminalEntry(terminal, label, this.terminalService); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 5b2f93e166..87942fa4c1 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -16,7 +16,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; import { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminalInstance'; -import { ITerminalService, ITerminalInstance, ITerminalTab } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalService, ITerminalInstance, ITerminalTab, TerminalShellType, WindowsShellType } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { IQuickInputService, IQuickPickItem, IPickOptions } from 'vs/platform/quickinput/common/quickInput'; @@ -504,7 +504,7 @@ export class TerminalService implements ITerminalService { }); } - public preparePathForTerminalAsync(originalPath: string, executable: string, title: string): Promise { + public preparePathForTerminalAsync(originalPath: string, executable: string, title: string, shellType: TerminalShellType): Promise { return new Promise(c => { if (!executable) { c(originalPath); @@ -527,18 +527,41 @@ export class TerminalService implements ITerminalService { if (isWindows) { // 17063 is the build number where wsl path was introduced. // Update Windows uriPath to be executed in WSL. - const lowerExecutable = executable.toLowerCase(); - if (this._terminalNativeService.getWindowsBuildNumber() >= 17063 && - (lowerExecutable.indexOf('wsl') !== -1 || (lowerExecutable.indexOf('bash.exe') !== -1 && lowerExecutable.toLowerCase().indexOf('git') === -1))) { - c(this._terminalNativeService.getWslPath(originalPath)); - return; - } else if (hasSpace) { - c('"' + originalPath + '"'); + if (shellType !== undefined) { + if (shellType === WindowsShellType.GitBash) { + c(originalPath.replace(/\\/g, '/')); + return; + } + else if (shellType === WindowsShellType.Wsl) { + if (this._terminalNativeService.getWindowsBuildNumber() >= 17063) { + c(this._terminalNativeService.getWslPath(originalPath)); + } else { + c(originalPath.replace(/\\/g, '/')); + } + return; + } + + if (hasSpace) { + c('"' + originalPath + '"'); + } else { + c(originalPath); + } } else { - c(originalPath); + const lowerExecutable = executable.toLowerCase(); + if (this._terminalNativeService.getWindowsBuildNumber() >= 17063 && + (lowerExecutable.indexOf('wsl') !== -1 || (lowerExecutable.indexOf('bash.exe') !== -1 && lowerExecutable.toLowerCase().indexOf('git') === -1))) { + c(this._terminalNativeService.getWslPath(originalPath)); + return; + } else if (hasSpace) { + c('"' + originalPath + '"'); + } else { + c(originalPath); + } } + return; } + c(escapeNonWindowsPath(originalPath)); }); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalWidgetManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalWidgetManager.ts index 87a7162ed6..e31a851c34 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalWidgetManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalWidgetManager.ts @@ -5,6 +5,11 @@ import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; +export enum WidgetVerticalAlignment { + Bottom, + Top +} + const WIDGET_HEIGHT = 29; export class TerminalWidgetManager implements IDisposable { @@ -43,13 +48,13 @@ export class TerminalWidgetManager implements IDisposable { mutationObserver.observe(this._xtermViewport, { attributes: true, attributeFilter: ['style'] }); } - public showMessage(left: number, top: number, text: string): void { + public showMessage(left: number, y: number, text: string, verticalAlignment: WidgetVerticalAlignment = WidgetVerticalAlignment.Bottom): void { if (!this._container) { return; } dispose(this._messageWidget); this._messageListeners.clear(); - this._messageWidget = new MessageWidget(this._container, left, top, text); + this._messageWidget = new MessageWidget(this._container, left, y, text, verticalAlignment); } public closeMessage(): void { @@ -71,9 +76,10 @@ class MessageWidget { private _domNode: HTMLDivElement; public get left(): number { return this._left; } - public get top(): number { return this._top; } + public get y(): number { return this._y; } public get text(): string { return this._text; } public get domNode(): HTMLElement { return this._domNode; } + public get verticalAlignment(): WidgetVerticalAlignment { return this._verticalAlignment; } public static fadeOut(messageWidget: MessageWidget): IDisposable { let handle: any; @@ -91,13 +97,22 @@ class MessageWidget { constructor( private _container: HTMLElement, private _left: number, - private _top: number, - private _text: string + private _y: number, + private _text: string, + private _verticalAlignment: WidgetVerticalAlignment ) { this._domNode = document.createElement('div'); this._domNode.style.position = 'absolute'; this._domNode.style.left = `${_left}px`; - this._domNode.style.bottom = `${_container.offsetHeight - Math.max(_top, WIDGET_HEIGHT)}px`; + + if (this.verticalAlignment === WidgetVerticalAlignment.Top) { + // Y position is to the top of the widget + this._domNode.style.bottom = `${Math.max(_y, WIDGET_HEIGHT) - WIDGET_HEIGHT}px`; + } else { + // Y position is to the bottom of the widget + this._domNode.style.bottom = `${Math.min(_y, _container.offsetHeight - WIDGET_HEIGHT)}px`; + } + this._domNode.classList.add('terminal-message-widget', 'fadeIn'); this._domNode.textContent = _text; this._container.appendChild(this._domNode); diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index b8138fba75..35a4d55afc 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -87,7 +87,7 @@ export interface ITerminalConfiguration { }; macOptionIsMeta: boolean; macOptionClickForcesSelection: boolean; - rendererType: 'auto' | 'canvas' | 'dom'; + rendererType: 'auto' | 'canvas' | 'dom' | 'experimentalWebgl'; rightClickBehavior: 'default' | 'copyPaste' | 'paste' | 'selectWord'; cursorBlinking: boolean; cursorStyle: string; @@ -95,6 +95,7 @@ export interface ITerminalConfiguration { fontFamily: string; fontWeight: FontWeight; fontWeightBold: FontWeight; + minimumContrastRatio: number; // fontLigatures: boolean; fontSize: number; letterSpacing: number; @@ -113,7 +114,6 @@ export interface ITerminalConfiguration { windows: { [key: string]: string }; }; showExitAlert: boolean; - experimentalBufferImpl: 'JsArray' | 'TypedArray'; splitCwd: 'workspaceRoot' | 'initial' | 'inherited'; windowsEnableConpty: boolean; experimentalRefreshOnResume: boolean; @@ -286,7 +286,7 @@ export interface ITerminalProcessManager extends IDisposable { readonly onBeforeProcessData: Event; readonly onProcessData: Event; readonly onProcessTitle: Event; - readonly onProcessExit: Event; + readonly onProcessExit: Event; readonly onProcessOverrideDimensions: Event; readonly onProcessResolvedShellLaunchConfig: Event; @@ -325,7 +325,7 @@ export interface ITerminalProcessExtHostProxy extends IDisposable { emitData(data: string): void; emitTitle(title: string): void; emitReady(pid: number, cwd: string): void; - emitExit(exitCode: number): void; + emitExit(exitCode: number | undefined): void; emitOverrideDimensions(dimensions: ITerminalDimensions | undefined): void; emitResolvedShellLaunchConfig(shellLaunchConfig: IShellLaunchConfig): void; emitInitialCwd(initialCwd: string): void; @@ -389,7 +389,7 @@ export interface IWindowsShellHelper extends IDisposable { */ export interface ITerminalChildProcess { onProcessData: Event; - onProcessExit: Event; + onProcessExit: Event; onProcessReady: Event<{ pid: number, cwd: string }>; onProcessTitleChanged: Event; onProcessOverrideDimensions?: Event; @@ -412,9 +412,7 @@ export interface ITerminalChildProcess { export const enum TERMINAL_COMMAND_ID { FIND_NEXT = 'workbench.action.terminal.findNext', - FIND_NEXT_TERMINAL_FOCUS = 'workbench.action.terminal.findNextTerminalFocus', FIND_PREVIOUS = 'workbench.action.terminal.findPrevious', - FIND_PREVIOUS_TERMINAL_FOCUS = 'workbench.action.terminal.findPreviousTerminalFocus', TOGGLE = 'workbench.action.terminal.toggleTerminal', KILL = 'workbench.action.terminal.kill', QUICK_KILL = 'workbench.action.terminal.quickKill', @@ -455,6 +453,7 @@ export const enum TERMINAL_COMMAND_ID { CLEAR_SELECTION = 'workbench.action.terminal.clearSelection', MANAGE_WORKSPACE_SHELL_PERMISSIONS = 'workbench.action.terminal.manageWorkspaceShellPermissions', RENAME = 'workbench.action.terminal.rename', + RENAME_WITH_ARG = 'workbench.action.terminal.renameWithArg', FIND_WIDGET_FOCUS = 'workbench.action.terminal.focusFindWidget', FIND_WIDGET_HIDE = 'workbench.action.terminal.hideFindWidget', QUICK_OPEN_TERM = 'workbench.action.quickOpenTerm', @@ -469,9 +468,6 @@ export const enum TERMINAL_COMMAND_ID { TOGGLE_FIND_REGEX = 'workbench.action.terminal.toggleFindRegex', TOGGLE_FIND_WHOLE_WORD = 'workbench.action.terminal.toggleFindWholeWord', TOGGLE_FIND_CASE_SENSITIVE = 'workbench.action.terminal.toggleFindCaseSensitive', - TOGGLE_FIND_REGEX_TERMINAL_FOCUS = 'workbench.action.terminal.toggleFindRegexTerminalFocus', - TOGGLE_FIND_WHOLE_WORD_TERMINAL_FOCUS = 'workbench.action.terminal.toggleFindWholeWordTerminalFocus', - TOGGLE_FIND_CASE_SENSITIVE_TERMINAL_FOCUS = 'workbench.action.terminal.toggleFindCaseSensitiveTerminalFocus', NAVIGATION_MODE_EXIT = 'workbench.action.terminal.navigationModeExit', NAVIGATION_MODE_FOCUS_NEXT = 'workbench.action.terminal.navigationModeFocusNext', NAVIGATION_MODE_FOCUS_PREVIOUS = 'workbench.action.terminal.navigationModeFocusPrevious' diff --git a/src/vs/workbench/contrib/terminal/common/terminalDataBuffering.ts b/src/vs/workbench/contrib/terminal/common/terminalDataBuffering.ts new file mode 100644 index 0000000000..612c56407e --- /dev/null +++ b/src/vs/workbench/contrib/terminal/common/terminalDataBuffering.ts @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { IDisposable } from 'vs/base/common/lifecycle'; + +interface TerminalDataBuffer extends IDisposable { + data: string[]; + timeoutId: any; +} + +export class TerminalDataBufferer implements IDisposable { + private readonly _terminalBufferMap = new Map(); + + dispose() { + for (const buffer of this._terminalBufferMap.values()) { + buffer.dispose(); + } + } + + startBuffering(id: number, event: Event, callback: (id: number, data: string) => void, throttleBy: number = 5): IDisposable { + let disposable: IDisposable; + disposable = event((e: string) => { + let buffer = this._terminalBufferMap.get(id); + if (buffer) { + buffer.data.push(e); + + return; + } + + const timeoutId = setTimeout(() => { + this._terminalBufferMap.delete(id); + callback(id, buffer!.data.join('')); + }, throttleBy); + buffer = { + data: [e], + timeoutId: timeoutId, + dispose: () => { + clearTimeout(timeoutId); + this._terminalBufferMap.delete(id); + disposable.dispose(); + } + }; + this._terminalBufferMap.set(id, buffer); + }); + return disposable; + } + + stopBuffering(id: number) { + const buffer = this._terminalBufferMap.get(id); + if (buffer) { + buffer.dispose(); + } + } +} diff --git a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts index b18b8fd334..70509ef973 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts @@ -184,23 +184,16 @@ export function getCwd( logService?: ILogService ): string { if (shell.cwd) { - return (typeof shell.cwd === 'object') ? shell.cwd.fsPath : shell.cwd; + const unresolved = (typeof shell.cwd === 'object') ? shell.cwd.fsPath : shell.cwd; + const resolved = _resolveCwd(unresolved, lastActiveWorkspace, configurationResolverService); + return resolved || unresolved; } let cwd: string | undefined; if (!shell.ignoreConfigurationCwd && customCwd) { if (configurationResolverService) { - try { - customCwd = configurationResolverService.resolve(lastActiveWorkspace, customCwd); - } catch (e) { - // There was an issue resolving a variable, log the error in the console and - // fallback to the default. - if (logService) { - logService.error('Could not resolve terminal.integrated.cwd', e); - } - customCwd = undefined; - } + customCwd = _resolveCwd(customCwd, lastActiveWorkspace, configurationResolverService, logService); } if (customCwd) { if (path.isAbsolute(customCwd)) { @@ -219,6 +212,18 @@ export function getCwd( return _sanitizeCwd(cwd); } +function _resolveCwd(cwd: string, lastActiveWorkspace: IWorkspaceFolder | undefined, configurationResolverService: IConfigurationResolverService | undefined, logService?: ILogService): string | undefined { + if (configurationResolverService) { + try { + return configurationResolverService.resolve(lastActiveWorkspace, cwd); + } catch (e) { + logService?.error('Could not resolve terminal cwd', e); + return undefined; + } + } + return cwd; +} + function _sanitizeCwd(cwd: string): string { // Make the drive letter uppercase on Windows (see #9448) if (platform.platform === platform.Platform.Windows && cwd && cwd[1] === ':') { diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts index 48b70035d4..16a6353ecc 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts @@ -13,6 +13,7 @@ import { getSystemShell } from 'vs/workbench/contrib/terminal/node/terminal'; import { Terminal as XTermTerminal } from 'xterm'; import { WebLinksAddon as XTermWebLinksAddon } from 'xterm-addon-web-links'; import { SearchAddon as XTermSearchAddon } from 'xterm-addon-search'; +import { WebglAddon as XTermWebglAddon } from 'xterm-addon-webgl'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { getDefaultShell, getDefaultShellArgs } from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; import { StorageScope, IStorageService } from 'vs/platform/storage/common/storage'; @@ -25,6 +26,7 @@ import { ILogService } from 'vs/platform/log/common/log'; let Terminal: typeof XTermTerminal; let WebLinksAddon: typeof XTermWebLinksAddon; let SearchAddon: typeof XTermSearchAddon; +let WebglAddon: typeof XTermWebglAddon; export class TerminalInstanceService implements ITerminalInstanceService { public _serviceBrand: undefined; @@ -61,6 +63,13 @@ export class TerminalInstanceService implements ITerminalInstanceService { return SearchAddon; } + public async getXtermWebglConstructor(): Promise { + if (!WebglAddon) { + WebglAddon = (await import('xterm-addon-webgl')).WebglAddon; + } + return WebglAddon; + } + public createWindowsShellHelper(shellProcessId: number, instance: ITerminalInstance, xterm: XTermTerminal): IWindowsShellHelper { return new WindowsShellHelper(shellProcessId, instance, xterm); } diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeService.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeService.ts index 5fa271ddcc..a0dd76f725 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeService.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeService.ts @@ -69,9 +69,10 @@ export class TerminalNativeService implements ITerminalNativeService { throw new Error('wslpath does not exist on Windows build < 17063'); } return new Promise(c => { - execFile('bash.exe', ['-c', 'echo $(wslpath ' + escapeNonWindowsPath(path) + ')'], {}, (error, stdout, stderr) => { + const proc = execFile('bash.exe', ['-c', `wslpath ${escapeNonWindowsPath(path)}`], {}, (error, stdout, stderr) => { c(escapeNonWindowsPath(stdout.trim())); }); + proc.stdin!.end(); }); } diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalRemote.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalRemote.ts index fab36b0428..2af06aed7b 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalRemote.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalRemote.ts @@ -15,7 +15,7 @@ import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal export function registerRemoteContributions() { const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); - actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(CreateNewLocalTerminalAction, CreateNewLocalTerminalAction.ID, CreateNewLocalTerminalAction.LABEL), 'Terminal: Create New Integrated Terminal (Local)', TERMINAL_ACTION_CATEGORY); + actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(CreateNewLocalTerminalAction, CreateNewLocalTerminalAction.ID, CreateNewLocalTerminalAction.LABEL), 'Terminal: Create New Integrated Terminal (Local)', TERMINAL_ACTION_CATEGORY); } export class CreateNewLocalTerminalAction extends Action { diff --git a/src/vs/workbench/contrib/terminal/electron-browser/windowsShellHelper.ts b/src/vs/workbench/contrib/terminal/electron-browser/windowsShellHelper.ts index df8fc2942a..d0f038a7fa 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/windowsShellHelper.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/windowsShellHelper.ts @@ -9,11 +9,12 @@ import { IWindowsShellHelper, TitleEventSource } from 'vs/workbench/contrib/term import { Terminal as XTermTerminal } from 'xterm'; import * as WindowsProcessTreeType from 'windows-process-tree'; import { Disposable } from 'vs/base/common/lifecycle'; -import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalInstance, TerminalShellType, WindowsShellType } from 'vs/workbench/contrib/terminal/browser/terminal'; const SHELL_EXECUTABLES = [ 'cmd.exe', 'powershell.exe', + 'pwsh.exe', 'bash.exe', 'wsl.exe', 'ubuntu.exe', @@ -81,6 +82,7 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe if (platform.isWindows && this._terminalInstance.isTitleSetByProcess) { this.getShellName().then(title => { if (!this._isDisposed) { + this._terminalInstance.setShellType(this.getShellType(title)); this._terminalInstance.setTitle(title, TitleEventSource.Process); } }); @@ -138,4 +140,26 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe }); return this._currentRequest; } + + public getShellType(executable: string): TerminalShellType { + switch (executable.toLowerCase()) { + case 'cmd.exe': + return WindowsShellType.CommandPrompt; + case 'powershell.exe': + case 'pwsh.exe': + return WindowsShellType.PowerShell; + case 'bash.exe': + return WindowsShellType.GitBash; + case 'wsl.exe': + case 'ubuntu.exe': + case 'ubuntu1804.exe': + case 'kali.exe': + case 'debian.exe': + case 'opensuse-42.exe': + case 'sles-12.exe': + return WindowsShellType.Wsl; + default: + return undefined; + } + } } diff --git a/src/vs/workbench/contrib/terminal/node/terminalProcess.ts b/src/vs/workbench/contrib/terminal/node/terminalProcess.ts index 3eae617061..7af8e2cae0 100644 --- a/src/vs/workbench/contrib/terminal/node/terminalProcess.ts +++ b/src/vs/workbench/contrib/terminal/node/terminalProcess.ts @@ -65,7 +65,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess env, cols, rows, - experimentalUseConpty: useConpty, + useConpty, // This option will force conpty to not redraw the whole viewport on launch conptyInheritCursor: useConpty && !!shellLaunchConfig.initialText }; @@ -74,18 +74,21 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess if (!stat.isDirectory()) { return Promise.reject(SHELL_CWD_INVALID_EXIT_CODE); } + return undefined; }, async err => { if (err && err.code === 'ENOENT') { // So we can include in the error message the specified CWD shellLaunchConfig.cwd = cwd; return Promise.reject(SHELL_CWD_INVALID_EXIT_CODE); } + return undefined; }); const executableVerification = stat(shellLaunchConfig.executable!).then(async stat => { if (!stat.isFile() && !stat.isSymbolicLink()) { return Promise.reject(stat.isDirectory() ? SHELL_PATH_DIRECTORY_EXIT_CODE : SHELL_PATH_INVALID_EXIT_CODE); } + return undefined; }, async (err) => { if (err && err.code === 'ENOENT') { let cwd = shellLaunchConfig.cwd instanceof URI ? shellLaunchConfig.cwd.path : shellLaunchConfig.cwd!; @@ -96,6 +99,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess return Promise.reject(SHELL_PATH_INVALID_EXIT_CODE); } } + return undefined; }); Promise.all([cwdVerification, executableVerification]).then(() => { @@ -260,7 +264,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess return; } this._logService.trace('IPty#pid'); - exec('lsof -p ' + this._ptyProcess.pid + ' | grep cwd', (error, stdout, stderr) => { + exec('lsof -OPl -p ' + this._ptyProcess.pid + ' | grep cwd', (error, stdout, stderr) => { if (stdout !== '') { resolve(stdout.substring(stdout.indexOf('/'), stdout.length - 1)); } diff --git a/src/vs/workbench/contrib/terminal/test/common/terminalDataBuffering.test.ts b/src/vs/workbench/contrib/terminal/test/common/terminalDataBuffering.test.ts new file mode 100644 index 0000000000..7b12303c9a --- /dev/null +++ b/src/vs/workbench/contrib/terminal/test/common/terminalDataBuffering.test.ts @@ -0,0 +1,191 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { Emitter } from 'vs/base/common/event'; +import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/terminalDataBuffering'; + +const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + +suite('Workbench - TerminalDataBufferer', () => { + let bufferer: TerminalDataBufferer; + + setup(async () => { + bufferer = new TerminalDataBufferer(); + }); + + test('start', async () => { + let terminalOnData = new Emitter(); + let counter = 0; + let data: string | undefined; + + bufferer.startBuffering(1, terminalOnData.event, (id, e) => { + counter++; + data = e; + }, 0); + + terminalOnData.fire('1'); + terminalOnData.fire('2'); + terminalOnData.fire('3'); + + await wait(0); + + terminalOnData.fire('4'); + + assert.equal(counter, 1); + assert.equal(data, '123'); + + await wait(0); + + assert.equal(counter, 2); + assert.equal(data, '4'); + }); + + test('start 2', async () => { + let terminal1OnData = new Emitter(); + let terminal1Counter = 0; + let terminal1Data: string | undefined; + + bufferer.startBuffering(1, terminal1OnData.event, (id, e) => { + terminal1Counter++; + terminal1Data = e; + }, 0); + + let terminal2OnData = new Emitter(); + let terminal2Counter = 0; + let terminal2Data: string | undefined; + + bufferer.startBuffering(2, terminal2OnData.event, (id, e) => { + terminal2Counter++; + terminal2Data = e; + }, 0); + + terminal1OnData.fire('1'); + terminal2OnData.fire('4'); + terminal1OnData.fire('2'); + terminal2OnData.fire('5'); + terminal1OnData.fire('3'); + terminal2OnData.fire('6'); + terminal2OnData.fire('7'); + + assert.equal(terminal1Counter, 0); + assert.equal(terminal1Data, undefined); + assert.equal(terminal2Counter, 0); + assert.equal(terminal2Data, undefined); + + await wait(0); + + assert.equal(terminal1Counter, 1); + assert.equal(terminal1Data, '123'); + assert.equal(terminal2Counter, 1); + assert.equal(terminal2Data, '4567'); + }); + + test('stop', async () => { + let terminalOnData = new Emitter(); + let counter = 0; + let data: string | undefined; + + bufferer.startBuffering(1, terminalOnData.event, (id, e) => { + counter++; + data = e; + }, 0); + + terminalOnData.fire('1'); + terminalOnData.fire('2'); + terminalOnData.fire('3'); + + bufferer.stopBuffering(1); + + await wait(0); + + assert.equal(counter, 0); + assert.equal(data, undefined); + }); + + test('start 2 stop 1', async () => { + let terminal1OnData = new Emitter(); + let terminal1Counter = 0; + let terminal1Data: string | undefined; + + bufferer.startBuffering(1, terminal1OnData.event, (id, e) => { + terminal1Counter++; + terminal1Data = e; + }, 0); + + let terminal2OnData = new Emitter(); + let terminal2Counter = 0; + let terminal2Data: string | undefined; + + bufferer.startBuffering(2, terminal2OnData.event, (id, e) => { + terminal2Counter++; + terminal2Data = e; + }, 0); + + + terminal1OnData.fire('1'); + terminal2OnData.fire('4'); + terminal1OnData.fire('2'); + terminal2OnData.fire('5'); + terminal1OnData.fire('3'); + terminal2OnData.fire('6'); + terminal2OnData.fire('7'); + + assert.equal(terminal1Counter, 0); + assert.equal(terminal1Data, undefined); + assert.equal(terminal2Counter, 0); + assert.equal(terminal2Data, undefined); + + bufferer.stopBuffering(1); + await wait(0); + + assert.equal(terminal1Counter, 0); + assert.equal(terminal1Data, undefined); + assert.equal(terminal2Counter, 1); + assert.equal(terminal2Data, '4567'); + }); + + test('dispose', async () => { + let terminal1OnData = new Emitter(); + let terminal1Counter = 0; + let terminal1Data: string | undefined; + + bufferer.startBuffering(1, terminal1OnData.event, (id, e) => { + terminal1Counter++; + terminal1Data = e; + }, 0); + + let terminal2OnData = new Emitter(); + let terminal2Counter = 0; + let terminal2Data: string | undefined; + + bufferer.startBuffering(2, terminal2OnData.event, (id, e) => { + terminal2Counter++; + terminal2Data = e; + }, 0); + + + terminal1OnData.fire('1'); + terminal2OnData.fire('4'); + terminal1OnData.fire('2'); + terminal2OnData.fire('5'); + terminal1OnData.fire('3'); + terminal2OnData.fire('6'); + terminal2OnData.fire('7'); + + assert.equal(terminal1Counter, 0); + assert.equal(terminal1Data, undefined); + assert.equal(terminal2Counter, 0); + assert.equal(terminal2Data, undefined); + + bufferer.dispose(); + await wait(0); + + assert.equal(terminal1Counter, 0); + assert.equal(terminal1Data, undefined); + assert.equal(terminal2Counter, 0); + assert.equal(terminal2Data, undefined); + }); +}); diff --git a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalColorRegistry.test.ts b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalColorRegistry.test.ts index 9ce8be5f70..5a95842d9b 100644 --- a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalColorRegistry.test.ts +++ b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalColorRegistry.test.ts @@ -19,7 +19,10 @@ function getMockTheme(type: ThemeType): ITheme { label: '', type: type, getColor: (colorId: ColorIdentifier): Color | undefined => themingRegistry.resolveDefaultColor(colorId, theme), - defines: () => true + defines: () => true, + getTokenStyleMetadata: () => undefined, + tokenColorMap: [] + }; return theme; } @@ -99,4 +102,4 @@ suite('Workbench - TerminalColorRegistry', () => { '#e5e5e5' ], 'The dark terminal colors should be used when a dark theme is active'); }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts index 01ddc8a8bf..b48c72617d 100644 --- a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts +++ b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts @@ -46,6 +46,9 @@ class MockTerminalInstanceService implements ITerminalInstanceService { getXtermSearchConstructor(): Promise { throw new Error('Method not implemented.'); } + getXtermWebglConstructor(): Promise { + throw new Error('Method not implemented.'); + } createWindowsShellHelper(): any { throw new Error('Method not implemented.'); } diff --git a/src/vs/workbench/contrib/testCustomEditors/browser/testCustomEditors.ts b/src/vs/workbench/contrib/testCustomEditors/browser/testCustomEditors.ts new file mode 100644 index 0000000000..0430a52789 --- /dev/null +++ b/src/vs/workbench/contrib/testCustomEditors/browser/testCustomEditors.ts @@ -0,0 +1,252 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { Action } from 'vs/base/common/actions'; +import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; +import { IEditorInputFactory, EditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, EditorModel, EditorOptions, GroupIdentifier, ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorModel } from 'vs/platform/editor/common/editor'; +import { Dimension, addDisposableListener, EventType } from 'vs/base/browser/dom'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { URI } from 'vs/base/common/uri'; +import { isEqual } from 'vs/base/common/resources'; +import { generateUuid } from 'vs/base/common/uuid'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry'; +import { IWorkingCopy, IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { env } from 'vs/base/common/process'; + +const CUSTOM_SCHEME = 'testCustomEditor'; +const ENABLE = !!env['VSCODE_DEV']; + +class TestCustomEditorsAction extends Action { + + static readonly ID = 'workbench.action.openCustomEditor'; + static readonly LABEL = nls.localize('openCustomEditor', "Test Open Custom Editor"); + + constructor( + id: string, + label: string, + @IEditorService private readonly editorService: IEditorService, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { + super(id, label); + } + + async run(): Promise { + const input = this.instantiationService.createInstance(TestCustomEditorInput, URI.parse(`${CUSTOM_SCHEME}:/${generateUuid()}`)); + await this.editorService.openEditor(input); + + return true; + } +} + +class TestCustomEditor extends BaseEditor { + + static ID = 'testCustomEditor'; + + private textArea: HTMLTextAreaElement | undefined = undefined; + + constructor( + @ITelemetryService telemetryService: ITelemetryService, + @IThemeService themeService: IThemeService, + @IStorageService storageService: IStorageService + ) { + super(TestCustomEditor.ID, telemetryService, themeService, storageService); + } + + updateStyles(): void { + super.updateStyles(); + + if (this.textArea) { + this.textArea.style.backgroundColor = this.getColor(editorBackground)!.toString(); + this.textArea.style.color = this.getColor(editorForeground)!.toString(); + } + } + + protected createEditor(parent: HTMLElement): void { + this.textArea = document.createElement('textarea'); + this.textArea.style.width = '100%'; + this.textArea.style.height = '100%'; + + parent.appendChild(this.textArea); + + addDisposableListener(this.textArea, EventType.CHANGE, e => this.onDidType()); + addDisposableListener(this.textArea, EventType.KEY_UP, e => this.onDidType()); + + this.updateStyles(); + } + + private onDidType(): void { + if (this._input instanceof TestCustomEditorInput) { + this._input.setValue(this.textArea!.value); + } + } + + async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { + await super.setInput(input, options, token); + + const model = await input.resolve(); + if (model instanceof TestCustomEditorModel) { + this.textArea!.value = model.value; + } + } + + clearInput() { + super.clearInput(); + + this.textArea!.value = ''; + } + + focus(): void { + this.textArea!.focus(); + } + + layout(dimension: Dimension): void { } +} + +class TestCustomEditorInput extends EditorInput implements IWorkingCopy { + private model: TestCustomEditorModel | undefined = undefined; + + private dirty = false; + + readonly capabilities = 0; + + constructor(public readonly resource: URI, @IWorkingCopyService workingCopyService: IWorkingCopyService) { + super(); + + this._register(workingCopyService.registerWorkingCopy(this)); + } + + getResource(): URI { + return this.resource; + } + + getTypeId(): string { + return TestCustomEditor.ID; + } + + getName(): string { + return this.resource.toString(); + } + + setValue(value: string) { + if (this.model) { + if (this.model.value === value) { + return; + } + + this.model.value = value; + } + + this.setDirty(value.length > 0); + } + + private setDirty(dirty: boolean) { + if (this.dirty !== dirty) { + this.dirty = dirty; + this._onDidChangeDirty.fire(); + } + } + + isReadonly(): boolean { + return false; + } + + isDirty(): boolean { + return this.dirty; + } + + async save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + this.setDirty(false); + + return true; + } + + async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + this.setDirty(false); + + return true; + } + + async revert(options?: IRevertOptions): Promise { + this.setDirty(false); + + return true; + } + + async resolve(): Promise { + if (!this.model) { + this.model = new TestCustomEditorModel(this.resource); + } + + return this.model; + } + + matches(other: EditorInput) { + return other instanceof TestCustomEditorInput && isEqual(other.resource, this.resource); + } + + dispose(): void { + this.setDirty(false); + + if (this.model) { + this.model.dispose(); + this.model = undefined; + } + + super.dispose(); + } +} + +class TestCustomEditorModel extends EditorModel { + + public value: string = ''; + + constructor(public readonly resource: URI) { + super(); + } +} + +if (ENABLE) { + Registry.as(EditorExtensions.Editors).registerEditor( + EditorDescriptor.create( + TestCustomEditor, + TestCustomEditor.ID, + nls.localize('testCustomEditor', "Test Custom Editor") + ), + [ + new SyncDescriptor(TestCustomEditorInput), + ] + ); + + const registry = Registry.as(Extensions.WorkbenchActions); + + registry.registerWorkbenchAction(SyncActionDescriptor.create(TestCustomEditorsAction, TestCustomEditorsAction.ID, TestCustomEditorsAction.LABEL), 'Test Open Custom Editor'); + + class TestCustomEditorInputFactory implements IEditorInputFactory { + + serialize(editorInput: TestCustomEditorInput): string { + return JSON.stringify({ + resource: editorInput.resource.toString() + }); + } + + deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): TestCustomEditorInput { + return instantiationService.createInstance(TestCustomEditorInput, URI.parse(JSON.parse(serializedEditorInput).resource)); + } + } + + Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(TestCustomEditor.ID, TestCustomEditorInputFactory); +} diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index 5c1835fa95..8f2f38257c 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -60,17 +60,10 @@ export class SelectColorThemeAction extends Action { } selectThemeTimeout = window.setTimeout(() => { selectThemeTimeout = undefined; - - let themeId = theme.id; - if (typeof theme.id === 'undefined') { // 'pick in marketplace' entry - if (applyTheme) { - openExtensionViewlet(this.viewletService, 'category:themes '); - } - themeId = currentTheme.id; - } + const themeId = theme && theme.id !== undefined ? theme.id : currentTheme.id; let target: ConfigurationTarget | undefined = undefined; if (applyTheme) { - let confValue = this.configurationService.inspect(COLOR_THEME_SETTING); + const confValue = this.configurationService.inspect(COLOR_THEME_SETTING); target = typeof confValue.workspace !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; } @@ -83,15 +76,35 @@ export class SelectColorThemeAction extends Action { }, applyTheme ? 0 : 200); }; - const placeHolder = localize('themes.selectTheme', "Select Color Theme (Up/Down Keys to Preview)"); - const autoFocusIndex = firstIndex(picks, p => isItem(p) && p.id === currentTheme.id); - const activeItem: ThemeItem = picks[autoFocusIndex] as ThemeItem; + return new Promise((s, _) => { + let isCompleted = false; - const chooseTheme = (theme: ThemeItem) => selectTheme(theme || currentTheme, true); - const tryTheme = (theme: ThemeItem) => selectTheme(theme, false); - - return this.quickInputService.pick(picks, { placeHolder, activeItem, onDidFocus: tryTheme }) - .then(chooseTheme); + const autoFocusIndex = firstIndex(picks, p => isItem(p) && p.id === currentTheme.id); + const quickpick = this.quickInputService.createQuickPick(); + quickpick.items = picks; + quickpick.placeholder = localize('themes.selectTheme', "Select Color Theme (Up/Down Keys to Preview)"); + quickpick.activeItems = [picks[autoFocusIndex] as ThemeItem]; + quickpick.canSelectMany = false; + quickpick.onDidAccept(_ => { + const theme = quickpick.activeItems[0]; + if (!theme || typeof theme.id === 'undefined') { // 'pick in marketplace' entry + openExtensionViewlet(this.viewletService, `category:themes ${quickpick.value}`); + } else { + selectTheme(theme, true); + } + isCompleted = true; + quickpick.hide(); + s(); + }); + quickpick.onDidChangeActive(themes => selectTheme(themes[0], false)); + quickpick.onDidHide(() => { + if (!isCompleted) { + selectTheme(currentTheme, true); + s(); + } + }); + quickpick.show(); + }); }); } } @@ -133,16 +146,10 @@ class SelectIconThemeAction extends Action { } selectThemeTimeout = window.setTimeout(() => { selectThemeTimeout = undefined; - let themeId = theme.id; - if (typeof theme.id === 'undefined') { // 'pick in marketplace' entry - if (applyTheme) { - openExtensionViewlet(this.viewletService, 'tag:icon-theme '); - } - themeId = currentTheme.id; - } + const themeId = theme && theme.id !== undefined ? theme.id : currentTheme.id; let target: ConfigurationTarget | undefined = undefined; if (applyTheme) { - let confValue = this.configurationService.inspect(ICON_THEME_SETTING); + const confValue = this.configurationService.inspect(ICON_THEME_SETTING); target = typeof confValue.workspace !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; } this.themeService.setFileIconTheme(themeId, target).then(undefined, @@ -154,14 +161,35 @@ class SelectIconThemeAction extends Action { }, applyTheme ? 0 : 200); }; - const placeHolder = localize('themes.selectIconTheme', "Select File Icon Theme"); - const autoFocusIndex = firstIndex(picks, p => isItem(p) && p.id === currentTheme.id); - const activeItem: ThemeItem = picks[autoFocusIndex] as ThemeItem; - const chooseTheme = (theme: ThemeItem) => selectTheme(theme || currentTheme, true); - const tryTheme = (theme: ThemeItem) => selectTheme(theme, false); + return new Promise((s, _) => { + let isCompleted = false; - return this.quickInputService.pick(picks, { placeHolder, activeItem, onDidFocus: tryTheme }) - .then(chooseTheme); + const autoFocusIndex = firstIndex(picks, p => isItem(p) && p.id === currentTheme.id); + const quickpick = this.quickInputService.createQuickPick(); + quickpick.items = picks; + quickpick.placeholder = localize('themes.selectIconTheme', "Select File Icon Theme"); + quickpick.activeItems = [picks[autoFocusIndex] as ThemeItem]; + quickpick.canSelectMany = false; + quickpick.onDidAccept(_ => { + const theme = quickpick.activeItems[0]; + if (!theme || typeof theme.id === 'undefined') { // 'pick in marketplace' entry + openExtensionViewlet(this.viewletService, `tag:icon-theme ${quickpick.value}`); + } else { + selectTheme(theme, true); + } + isCompleted = true; + quickpick.hide(); + s(); + }); + quickpick.onDidChangeActive(themes => selectTheme(themes[0], false)); + quickpick.onDidHide(() => { + if (!isCompleted) { + selectTheme(currentTheme, true); + s(); + } + }); + quickpick.show(); + }); }); } } @@ -259,16 +287,16 @@ class GenerateColorThemeAction extends Action { const category = localize('preferences', "Preferences"); -const colorThemeDescriptor = new SyncActionDescriptor(SelectColorThemeAction, SelectColorThemeAction.ID, SelectColorThemeAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_T) }); +const colorThemeDescriptor = SyncActionDescriptor.create(SelectColorThemeAction, SelectColorThemeAction.ID, SelectColorThemeAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_T) }); Registry.as(Extensions.WorkbenchActions).registerWorkbenchAction(colorThemeDescriptor, 'Preferences: Color Theme', category); -const iconThemeDescriptor = new SyncActionDescriptor(SelectIconThemeAction, SelectIconThemeAction.ID, SelectIconThemeAction.LABEL); +const iconThemeDescriptor = SyncActionDescriptor.create(SelectIconThemeAction, SelectIconThemeAction.ID, SelectIconThemeAction.LABEL); Registry.as(Extensions.WorkbenchActions).registerWorkbenchAction(iconThemeDescriptor, 'Preferences: File Icon Theme', category); const developerCategory = localize('developer', "Developer"); -const generateColorThemeDescriptor = new SyncActionDescriptor(GenerateColorThemeAction, GenerateColorThemeAction.ID, GenerateColorThemeAction.LABEL); +const generateColorThemeDescriptor = SyncActionDescriptor.create(GenerateColorThemeAction, GenerateColorThemeAction.ID, GenerateColorThemeAction.LABEL); Registry.as(Extensions.WorkbenchActions).registerWorkbenchAction(generateColorThemeDescriptor, 'Developer: Generate Color Theme From Current Settings', developerCategory); MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { diff --git a/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts b/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts index 335d062c85..3ec872a9eb 100644 --- a/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts +++ b/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts @@ -216,6 +216,9 @@ class Snapper { public captureSyntaxTokens(fileName: string, content: string): Promise { const modeId = this.modeService.getModeIdByFilepathOrFirstLine(URI.file(fileName)); return this.textMateService.createGrammar(modeId!).then((grammar) => { + if (!grammar) { + return []; + } let lines = content.split(/\r\n|\r|\n/); let result = this._tokenize(grammar, lines); diff --git a/src/vs/workbench/contrib/update/browser/media/markdown.css b/src/vs/workbench/contrib/update/browser/media/markdown.css deleted file mode 100644 index 5fbef60ceb..0000000000 --- a/src/vs/workbench/contrib/update/browser/media/markdown.css +++ /dev/null @@ -1,130 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -body { - padding: 10px 20px; - line-height: 22px; -} - -img { - max-width: 100%; - max-height: 100%; -} - -a { - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - -a:focus, -input:focus, -select:focus, -textarea:focus { - outline: 1px solid -webkit-focus-ring-color; - outline-offset: -1px; -} - -hr { - border: 0; - height: 2px; - border-bottom: 2px solid; -} - -h1 { - padding-bottom: 0.3em; - line-height: 1.2; - border-bottom-width: 1px; - border-bottom-style: solid; -} - -h1, h2, h3 { - font-weight: normal; -} - - -table { - border-collapse: collapse; -} - -table > thead > tr > th { - text-align: left; - border-bottom: 1px solid; -} - -table > thead > tr > th, -table > thead > tr > td, -table > tbody > tr > th, -table > tbody > tr > td { - padding: 5px 10px; -} - -table > tbody > tr + tr > td { - border-top-width: 1px; - border-top-style: solid; -} - -blockquote { - margin: 0 7px 0 5px; - padding: 0 16px 0 10px; - border-left-width: 5px; - border-left-style: solid; -} - -code { - font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback"; - font-size: 14px; - line-height: 19px; -} - -code > div { - padding: 16px; - border-radius: 3px; - overflow: auto; -} - -.monaco-tokenized-source { - white-space: pre; -} - -/** Theming */ - -.vscode-light code > div { - background-color: rgba(220, 220, 220, 0.4); -} - -.vscode-dark code > div { - background-color: rgba(10, 10, 10, 0.4); -} - -.vscode-high-contrast code > div { - background-color: rgb(0, 0, 0); -} - -.vscode-high-contrast h1 { - border-color: rgb(0, 0, 0); -} - -.vscode-light table > thead > tr > th { - border-color: rgba(0, 0, 0, 0.69); -} - -.vscode-dark table > thead > tr > th { - border-color: rgba(255, 255, 255, 0.69); -} - -.vscode-light h1, -.vscode-light hr, -.vscode-light table > tbody > tr + tr > td { - border-color: rgba(0, 0, 0, 0.18); -} - -.vscode-dark h1, -.vscode-dark hr, -.vscode-dark table > tbody > tr + tr > td { - border-color: rgba(255, 255, 255, 0.18); -} diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index 2b7ab73aa4..c5756e5596 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -81,13 +81,11 @@ export class ReleaseNotesManager { { tryRestoreScrollPosition: true, enableFindWidget: true, - localResourceRoots: [ - URI.parse(require.toUrl('./media')) - ] + localResourceRoots: [] }, undefined); - this._currentReleaseNotes.webview.onDidClickLink(uri => this.onDidClickLink(uri)); + this._currentReleaseNotes.webview.onDidClickLink(uri => this.onDidClickLink(URI.parse(uri))); this._currentReleaseNotes.onDispose(() => { this._currentReleaseNotes = undefined; }); const iconPath = URI.parse(require.toUrl('./media/code-icon.svg')); @@ -178,18 +176,146 @@ export class ReleaseNotesManager { } private async renderBody(text: string) { + const nonce = generateUuid(); const content = await renderMarkdownDocument(text, this._extensionService, this._modeService); const colorMap = TokenizationRegistry.getColorMap(); const css = colorMap ? generateTokensCSSForColorMap(colorMap) : ''; - const styleSheetPath = require.toUrl('./media/markdown.css').replace('file://', 'vscode-resource://'); return ` - - - + + ${content} `; diff --git a/src/vs/workbench/contrib/update/browser/update.contribution.ts b/src/vs/workbench/contrib/update/browser/update.contribution.ts index c44d7a68d6..abfec72a52 100644 --- a/src/vs/workbench/contrib/update/browser/update.contribution.ts +++ b/src/vs/workbench/contrib/update/browser/update.contribution.ts @@ -4,18 +4,38 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/platform/update/common/update.config.contribution'; +import { localize } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { ShowCurrentReleaseNotesAction, ProductContribution, UpdateContribution } from 'vs/workbench/contrib/update/browser/update'; +import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { ShowCurrentReleaseNotesAction, ProductContribution, UpdateContribution, CheckForVSCodeUpdateAction, CONTEXT_UPDATE_STATE } from 'vs/workbench/contrib/update/browser/update'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import product from 'vs/platform/product/common/product'; +import { StateType } from 'vs/platform/update/common/update'; const workbench = Registry.as(WorkbenchExtensions.Workbench); workbench.registerWorkbenchContribution(ProductContribution, LifecyclePhase.Restored); workbench.registerWorkbenchContribution(UpdateContribution, LifecyclePhase.Restored); +const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); + // Editor -Registry.as(ActionExtensions.WorkbenchActions) - .registerWorkbenchAction(new SyncActionDescriptor(ShowCurrentReleaseNotesAction, ShowCurrentReleaseNotesAction.ID, ShowCurrentReleaseNotesAction.LABEL), 'Show Release Notes'); +actionRegistry + .registerWorkbenchAction(SyncActionDescriptor.create(ShowCurrentReleaseNotesAction, ShowCurrentReleaseNotesAction.ID, ShowCurrentReleaseNotesAction.LABEL), `${product.nameShort}: Show Release Notes`, product.nameShort); + +actionRegistry + .registerWorkbenchAction(SyncActionDescriptor.create(CheckForVSCodeUpdateAction, CheckForVSCodeUpdateAction.ID, CheckForVSCodeUpdateAction.LABEL), `${product.nameShort}: Check for Update`, product.nameShort, CONTEXT_UPDATE_STATE.isEqualTo(StateType.Idle)); + +// Menu +if (ShowCurrentReleaseNotesAction.AVAILABE) { + MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '1_welcome', + command: { + id: ShowCurrentReleaseNotesAction.ID, + title: localize({ key: 'miReleaseNotes', comment: ['&& denotes a mnemonic'] }, "&&Release Notes") + }, + order: 4 + }); +} diff --git a/src/vs/workbench/contrib/update/browser/update.ts b/src/vs/workbench/contrib/update/browser/update.ts index 7b364f3999..3445523f9a 100644 --- a/src/vs/workbench/contrib/update/browser/update.ts +++ b/src/vs/workbench/contrib/update/browser/update.ts @@ -9,7 +9,7 @@ import { Action } from 'vs/base/common/actions'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IActivityService, NumberBadge, IBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity'; -import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -26,15 +26,12 @@ import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/cont import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { FalseContext } from 'vs/platform/contextkey/common/contextkeys'; -import { ShowCurrentReleaseNotesActionId } from 'vs/workbench/contrib/update/common/update'; +import { ShowCurrentReleaseNotesActionId, CheckForVSCodeUpdateActionId } from 'vs/workbench/contrib/update/common/update'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IProductService } from 'vs/platform/product/common/productService'; +import product from 'vs/platform/product/common/product'; -// TODO@Joao layer breaker -// tslint:disable-next-line: layering -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; - -const CONTEXT_UPDATE_STATE = new RawContextKey('updateState', StateType.Uninitialized); +export const CONTEXT_UPDATE_STATE = new RawContextKey('updateState', StateType.Idle); /*let releaseNotesManager: ReleaseNotesManager | undefined = undefined; {{SQL CARBON EDIT}} comment out for no unused @@ -104,6 +101,7 @@ export class ShowCurrentReleaseNotesAction extends AbstractShowReleaseNotesActio static readonly ID = ShowCurrentReleaseNotesActionId; static readonly LABEL = nls.localize('showReleaseNotes', "Show Release Notes"); + static readonly AVAILABE = !!product.releaseNotesUrl; constructor( id = ShowCurrentReleaseNotesAction.ID, @@ -138,7 +136,7 @@ export class ProductContribution implements IWorkbenchContribution { // was there an update? if so, open release notes const releaseNotesUrl = productService.releaseNotesUrl; - if (shouldShowReleaseNotes && !environmentService.skipReleaseNotes && releaseNotesUrl && lastVersion && productService.version !== lastVersion && productService.quality === 'stable') { // {{SQL CARBON EDIT}} Only show release notes for stable build + if (shouldShowReleaseNotes && !environmentService.args['skip-release-notes'] && releaseNotesUrl && lastVersion && productService.version !== lastVersion && productService.quality === 'stable') { // {{SQL CARBON EDIT}} Only show release notes for stable build /* // {{SQL CARBON EDIT}} Prompt user to open release notes in browser until we can get ADS release notes from the web showReleaseNotes(instantiationService, productService.version) .then(undefined, () => { @@ -173,8 +171,6 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu private readonly badgeDisposable = this._register(new MutableDisposable()); private updateStateContextKey: IContextKey; - private context = `window:${this.electronEnvironmentService ? this.electronEnvironmentService.windowId : 'any'}`; - constructor( @IStorageService private readonly storageService: IStorageService, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -184,7 +180,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu @IActivityService private readonly activityService: IActivityService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IProductService private readonly productService: IProductService, - @optional(IElectronEnvironmentService) private readonly electronEnvironmentService: IElectronEnvironmentService + @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService ) { super(); this.state = updateService.state; @@ -220,7 +216,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu case StateType.Idle: if (state.error) { this.onError(state.error); - } else if (this.state.type === StateType.CheckingForUpdates && this.state.context === this.context) { + } else if (this.state.type === StateType.CheckingForUpdates && this.state.context === this.workbenchEnvironmentService.configuration.sessionId) { this.onUpdateNotAvailable(); } break; @@ -244,18 +240,20 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu let badge: IBadge | undefined = undefined; let clazz: string | undefined; + let priority: number | undefined = undefined; if (state.type === StateType.AvailableForDownload || state.type === StateType.Downloaded || state.type === StateType.Ready) { badge = new NumberBadge(1, () => nls.localize('updateIsReady', "New {0} update available.", this.productService.nameLong)); // {{SQL CARBON EDIT}} change to namelong } else if (state.type === StateType.CheckingForUpdates || state.type === StateType.Downloading || state.type === StateType.Updating) { badge = new ProgressBadge(() => nls.localize('updateIsReady', "New {0} update available.", this.productService.nameLong)); // {{SQL CARBON EDIT}} change to namelong clazz = 'progress-badge'; + priority = 1; } this.badgeDisposable.clear(); if (badge) { - this.badgeDisposable.value = this.activityService.showActivity(GLOBAL_ACTIVITY_ID, badge, clazz); + this.badgeDisposable.value = this.activityService.showActivity(GLOBAL_ACTIVITY_ID, badge, clazz, priority); } this.state = state; @@ -403,7 +401,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu } private registerGlobalActivityActions(): void { - CommandsRegistry.registerCommand('update.check', () => this.updateService.checkForUpdates(this.context)); + CommandsRegistry.registerCommand('update.check', () => this.updateService.checkForUpdates(this.workbenchEnvironmentService.configuration.sessionId)); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '6_update', command: { @@ -477,3 +475,23 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu }); } } + +export class CheckForVSCodeUpdateAction extends Action { + + static readonly ID = CheckForVSCodeUpdateActionId; + static LABEL = nls.localize('checkForUpdates', "Check for Updates..."); + + constructor( + id: string, + label: string, + @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, + @IUpdateService private readonly updateService: IUpdateService, + ) { + super(id, label, undefined, true); + } + + run(): Promise { + return this.updateService.checkForUpdates(this.workbenchEnvironmentService.configuration.sessionId); + } +} + diff --git a/src/vs/workbench/contrib/update/common/update.ts b/src/vs/workbench/contrib/update/common/update.ts index 898424cfa9..9a06a77072 100644 --- a/src/vs/workbench/contrib/update/common/update.ts +++ b/src/vs/workbench/contrib/update/common/update.ts @@ -3,4 +3,5 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export const ShowCurrentReleaseNotesActionId = 'update.showCurrentReleaseNotes'; \ No newline at end of file +export const ShowCurrentReleaseNotesActionId = 'update.showCurrentReleaseNotes'; +export const CheckForVSCodeUpdateActionId = 'update.checkForVSCodeUpdate'; \ No newline at end of file diff --git a/src/vs/workbench/contrib/url/common/trustedDomains.ts b/src/vs/workbench/contrib/url/common/trustedDomains.ts index 1cb1b7d428..6873083464 100644 --- a/src/vs/workbench/contrib/url/common/trustedDomains.ts +++ b/src/vs/workbench/contrib/url/common/trustedDomains.ts @@ -76,6 +76,7 @@ export async function configureOpenerTrustedDomainsHandler( return trustedDomains; } if (pickedResult.id && trustedDomains.indexOf(pickedResult.id) === -1) { + storageService.remove('http.linkProtectionTrustedDomainsContent', StorageScope.GLOBAL); storageService.store( 'http.linkProtectionTrustedDomains', JSON.stringify([...trustedDomains, pickedResult.id]), diff --git a/src/vs/workbench/contrib/url/common/trustedDomainsFileSystemProvider.ts b/src/vs/workbench/contrib/url/common/trustedDomainsFileSystemProvider.ts index 56e39a6137..3d619239d6 100644 --- a/src/vs/workbench/contrib/url/common/trustedDomainsFileSystemProvider.ts +++ b/src/vs/workbench/contrib/url/common/trustedDomainsFileSystemProvider.ts @@ -7,17 +7,7 @@ import { Event } from 'vs/base/common/event'; import { parse } from 'vs/base/common/json'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { - FileDeleteOptions, - FileOverwriteOptions, - FileSystemProviderCapabilities, - FileType, - FileWriteOptions, - IFileService, - IFileSystemProvider, - IStat, - IWatchOptions -} from 'vs/platform/files/common/files'; +import { FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileType, FileWriteOptions, IFileService, IStat, IWatchOptions, IFileSystemProviderWithFileReadWriteCapability } from 'vs/platform/files/common/files'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { VSBuffer } from 'vs/base/common/buffer'; @@ -36,6 +26,8 @@ const TRUSTED_DOMAINS_STAT: IStat = { const CONFIG_HELP_TEXT_PRE = `// Links matching one or more entries in the list below can be opened without link protection. // The following examples show what entries can look like: // - "https://microsoft.com": Matches this specific domain using https +// - "https://microsoft.com/foo": Matches https://microsoft.com/foo and https://microsoft.com/foo/bar, +// but not https://microsoft.com/foobar or https://microsoft.com/bar // - "https://*.microsoft.com": Match all domains ending in "microsoft.com" using https // - "microsoft.com": Match this specific domain using either http or https // - "*.microsoft.com": Match all domains ending in "microsoft.com" using either http or https @@ -75,7 +67,7 @@ function computeTrustedDomainContent(defaultTrustedDomains: string[], trustedDom return content; } -export class TrustedDomainsFileSystemProvider implements IFileSystemProvider, IWorkbenchContribution { +export class TrustedDomainsFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapability, IWorkbenchContribution { readonly capabilities = FileSystemProviderCapabilities.FileReadWrite; readonly onDidChangeCapabilities = Event.None; @@ -115,13 +107,13 @@ export class TrustedDomainsFileSystemProvider implements IFileSystemProvider, IW writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise { try { - const trustedDomainsContent = content.toString(); + const trustedDomainsContent = VSBuffer.wrap(content).toString(); const trustedDomains = parse(trustedDomainsContent); this.storageService.store('http.linkProtectionTrustedDomainsContent', trustedDomainsContent, StorageScope.GLOBAL); this.storageService.store( 'http.linkProtectionTrustedDomains', - JSON.stringify(trustedDomains), + JSON.stringify(trustedDomains) || '', StorageScope.GLOBAL ); } catch (err) { } diff --git a/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts b/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts index f774c2a940..eb5f25f99b 100644 --- a/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts +++ b/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts @@ -5,17 +5,21 @@ import { Schemas } from 'vs/base/common/network'; import Severity from 'vs/base/common/severity'; -import { equalsIgnoreCase } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener'; import { IProductService } from 'vs/platform/product/common/productService'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { configureOpenerTrustedDomainsHandler, readTrustedDomains } from 'vs/workbench/contrib/url/common/trustedDomains'; +import { + configureOpenerTrustedDomainsHandler, + readTrustedDomains +} from 'vs/workbench/contrib/url/common/trustedDomains'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; + export class OpenerValidatorContributions implements IWorkbenchContribution { constructor( @@ -24,18 +28,22 @@ export class OpenerValidatorContributions implements IWorkbenchContribution { @IDialogService private readonly _dialogService: IDialogService, @IProductService private readonly _productService: IProductService, @IQuickInputService private readonly _quickInputService: IQuickInputService, - @IEditorService private readonly _editorService: IEditorService + @IEditorService private readonly _editorService: IEditorService, + @IClipboardService private readonly _clipboardService: IClipboardService ) { this._openerService.registerValidator({ shouldOpen: r => this.validateLink(r) }); } - async validateLink(resource: URI): Promise { - const { scheme, authority } = resource; - - if (!equalsIgnoreCase(scheme, Schemas.http) && !equalsIgnoreCase(scheme, Schemas.https)) { + async validateLink(resource: URI | string): Promise { + if (!matchesScheme(resource, Schemas.http) && !matchesScheme(resource, Schemas.https)) { return true; } + if (typeof resource === 'string') { + resource = URI.parse(resource); + } + const { scheme, authority, path, query, fragment } = resource; + const domainToOpen = `${scheme}://${authority}`; const { defaultTrustedDomains, trustedDomains } = readTrustedDomains(this._storageService, this._productService); const allTrustedDomains = [...defaultTrustedDomains, ...trustedDomains]; @@ -43,21 +51,38 @@ export class OpenerValidatorContributions implements IWorkbenchContribution { if (isURLDomainTrusted(resource, allTrustedDomains)) { return true; } else { + let formattedLink = `${scheme}://${authority}${path}`; + + const linkTail = `${query ? '?' + query : ''}${fragment ? '#' + fragment : ''}`; + + + const remainingLength = Math.max(0, 60 - formattedLink.length); + const linkTailLengthToKeep = Math.min(Math.max(5, remainingLength), linkTail.length); + + if (linkTailLengthToKeep === linkTail.length) { + formattedLink += linkTail; + } else { + // keep the first char ? or # + // add ... and keep the tail end as much as possible + formattedLink += linkTail.charAt(0) + '...' + linkTail.substring(linkTail.length - linkTailLengthToKeep + 1); + } + const { choice } = await this._dialogService.show( Severity.Info, localize( 'openExternalLinkAt', - 'Do you want {0} to open the external website?\n{1}', - this._productService.nameShort, - resource.toString(true) + 'Do you want {0} to open the external website?', + this._productService.nameShort ), [ - localize('openLink', 'Open Link'), + localize('open', 'Open'), + localize('copy', 'Copy'), localize('cancel', 'Cancel'), localize('configureTrustedDomains', 'Configure Trusted Domains') ], { - cancelId: 1 + detail: formattedLink, + cancelId: 2 } ); @@ -65,8 +90,12 @@ export class OpenerValidatorContributions implements IWorkbenchContribution { if (choice === 0) { return true; } + // Copy Link + else if (choice === 1) { + this._clipboardService.writeText(resource.toString(true)); + } // Configure Trusted Domains - else if (choice === 2) { + else if (choice === 3) { const pickedDomains = await configureOpenerTrustedDomainsHandler( trustedDomains, domainToOpen, @@ -132,10 +161,15 @@ export function isURLDomainTrusted(url: URI, trustedDomains: string[]) { } if (url.authority === parsedTrustedDomain.authority) { - return true; + if (pathMatches(url.path, parsedTrustedDomain.path)) { + return true; + } else { + continue; + } } if (trustedDomains[i].indexOf('*') !== -1) { + let reversedAuthoritySegments = url.authority.split('.').reverse(); const reversedTrustedDomainAuthoritySegments = parsedTrustedDomain.authority.split('.').reverse(); @@ -146,11 +180,11 @@ export function isURLDomainTrusted(url: URI, trustedDomains: string[]) { reversedAuthoritySegments = reversedAuthoritySegments.slice(0, reversedTrustedDomainAuthoritySegments.length); } - if ( - reversedAuthoritySegments.every((val, i) => { - return reversedTrustedDomainAuthoritySegments[i] === '*' || val === reversedTrustedDomainAuthoritySegments[i]; - }) - ) { + const authorityMatches = reversedAuthoritySegments.every((val, i) => { + return reversedTrustedDomainAuthoritySegments[i] === '*' || val === reversedTrustedDomainAuthoritySegments[i]; + }); + + if (authorityMatches && pathMatches(url.path, parsedTrustedDomain.path)) { return true; } } @@ -158,3 +192,19 @@ export function isURLDomainTrusted(url: URI, trustedDomains: string[]) { return false; } + +function pathMatches(open: string, rule: string) { + if (rule === '/') { + return true; + } + + const openSegments = open.split('/'); + const ruleSegments = rule.split('/'); + for (let i = 0; i < ruleSegments.length; i++) { + if (ruleSegments[i] !== openSegments[i]) { + return false; + } + } + + return true; +} diff --git a/src/vs/workbench/contrib/url/common/url.contribution.ts b/src/vs/workbench/contrib/url/common/url.contribution.ts index 550feb0395..ef0bea94c5 100644 --- a/src/vs/workbench/contrib/url/common/url.contribution.ts +++ b/src/vs/workbench/contrib/url/common/url.contribution.ts @@ -41,7 +41,7 @@ export class OpenUrlAction extends Action { } Registry.as(ActionExtensions.WorkbenchActions).registerWorkbenchAction( - new SyncActionDescriptor(OpenUrlAction, OpenUrlAction.ID, OpenUrlAction.LABEL), + SyncActionDescriptor.create(OpenUrlAction, OpenUrlAction.ID, OpenUrlAction.LABEL), 'Open URL', localize('developer', 'Developer') ); @@ -54,7 +54,10 @@ CommandsRegistry.registerCommand(manageTrustedDomainSettingsCommand); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: manageTrustedDomainSettingsCommand.id, - title: manageTrustedDomainSettingsCommand.description.description + title: { + value: manageTrustedDomainSettingsCommand.description.description, + original: 'Manage Trusted Domains' + } } }); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataAutoSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataAutoSync.ts new file mode 100644 index 0000000000..2b7c856d77 --- /dev/null +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataAutoSync.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IUserDataSyncService, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IAuthTokenService } from 'vs/platform/auth/common/auth'; +import { Event } from 'vs/base/common/event'; +import { UserDataAutoSync as BaseUserDataAutoSync } from 'vs/platform/userDataSync/common/userDataAutoSync'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { UserDataSyncTrigger } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; + +export class UserDataAutoSync extends BaseUserDataAutoSync { + + constructor( + @IUserDataSyncService userDataSyncService: IUserDataSyncService, + @IConfigurationService configurationService: IConfigurationService, + @IUserDataSyncLogService logService: IUserDataSyncLogService, + @IAuthTokenService authTokenService: IAuthTokenService, + @IInstantiationService instantiationService: IInstantiationService, + @IHostService hostService: IHostService, + ) { + super(configurationService, userDataSyncService, logService, authTokenService); + + // Sync immediately if there is a local change. + this._register(Event.debounce(Event.any( + userDataSyncService.onDidChangeLocal, + instantiationService.createInstance(UserDataSyncTrigger).onDidTriggerSync, + hostService.onDidChangeFocus + ), () => undefined, 500)(() => this.sync(false))); + } + +} diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts index a5e1edba21..2c9482fb8c 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts @@ -3,42 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { registerConfiguration } from 'vs/platform/userDataSync/common/userDataSync'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { isWeb } from 'vs/base/common/platform'; -import { UserDataAutoSync } from 'vs/platform/userDataSync/common/userDataSyncService'; -import { IProductService } from 'vs/platform/product/common/productService'; import { UserDataSyncWorkbenchContribution } from 'vs/workbench/contrib/userDataSync/browser/userDataSync'; -class UserDataSyncConfigurationContribution implements IWorkbenchContribution { - - constructor( - @IProductService productService: IProductService - ) { - if (productService.settingsSyncStoreUrl) { - registerConfiguration(); - } - } -} - -class UserDataAutoSyncContribution extends Disposable implements IWorkbenchContribution { - - constructor( - @IInstantiationService instantiationService: IInstantiationService - ) { - super(); - if (isWeb) { - instantiationService.createInstance(UserDataAutoSync); - } - } -} - - const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchRegistry.registerWorkbenchContribution(UserDataSyncConfigurationContribution, LifecyclePhase.Starting); -workbenchRegistry.registerWorkbenchContribution(UserDataSyncWorkbenchContribution, LifecyclePhase.Restored); -workbenchRegistry.registerWorkbenchContribution(UserDataAutoSyncContribution, LifecyclePhase.Restored); +workbenchRegistry.registerWorkbenchContribution(UserDataSyncWorkbenchContribution, LifecyclePhase.Ready); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 0a77ca5b0e..9fef4396e2 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IUserDataSyncService, SyncStatus, SyncSource, CONTEXT_SYNC_STATE } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, SyncStatus, SyncSource, CONTEXT_SYNC_STATE, IUserDataSyncStore, registerConfiguration, getUserDataSyncStore } from 'vs/platform/userDataSync/common/userDataSync'; import { localize } from 'vs/nls'; -import { Disposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, MutableDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { MenuRegistry, MenuId, IMenuItem } from 'vs/platform/actions/common/actions'; @@ -25,15 +25,24 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { isEqual } from 'vs/base/common/resources'; import { IEditorInput } from 'vs/workbench/common/editor'; import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { FalseContext } from 'vs/platform/contextkey/common/contextkeys'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { isWeb } from 'vs/base/common/platform'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { UserDataAutoSync } from 'vs/workbench/contrib/userDataSync/browser/userDataAutoSync'; +import { UserDataSyncTrigger } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger'; import { timeout } from 'vs/base/common/async'; -const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey('authTokenStatus', AuthTokenStatus.Inactive); +const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey('authTokenStatus', AuthTokenStatus.Initializing); const SYNC_PUSH_LIGHT_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/userDataSync/browser/media/check-light.svg`)); const SYNC_PUSH_DARK_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/userDataSync/browser/media/check-dark.svg`)); export class UserDataSyncWorkbenchContribution extends Disposable implements IWorkbenchContribution { + private static readonly ENABLEMENT_SETTING = 'sync.enable'; + + private readonly userDataSyncStore: IUserDataSyncStore | undefined; private readonly syncStatusContext: IContextKey; private readonly authTokenContext: IContextKey; private readonly badgeDisposable = this._register(new MutableDisposable()); @@ -51,29 +60,43 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo @ITextFileService private readonly textFileService: ITextFileService, @IHistoryService private readonly historyService: IHistoryService, @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, + @IDialogService private readonly dialogService: IDialogService, @IQuickInputService private readonly quickInputService: IQuickInputService, + @IInstantiationService instantiationService: IInstantiationService, ) { super(); + this.userDataSyncStore = getUserDataSyncStore(configurationService); this.syncStatusContext = CONTEXT_SYNC_STATE.bindTo(contextKeyService); this.authTokenContext = CONTEXT_AUTH_TOKEN_STATE.bindTo(contextKeyService); - this.onDidChangeAuthTokenStatus(this.authTokenService.status); - this.onDidChangeSyncStatus(this.userDataSyncService.status); - this._register(Event.debounce(authTokenService.onDidChangeStatus, () => undefined, 500)(() => this.onDidChangeAuthTokenStatus(this.authTokenService.status))); - this._register(Event.debounce(userDataSyncService.onDidChangeStatus, () => undefined, 500)(() => this.onDidChangeSyncStatus(this.userDataSyncService.status))); - this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('configurationSync.enable'))(() => this.updateBadge())); - this.registerActions(); + if (this.userDataSyncStore) { + registerConfiguration(); + this.onDidChangeAuthTokenStatus(this.authTokenService.status); + this.onDidChangeSyncStatus(this.userDataSyncService.status); + this._register(Event.debounce(authTokenService.onDidChangeStatus, () => undefined, 500)(() => this.onDidChangeAuthTokenStatus(this.authTokenService.status))); + this._register(Event.debounce(userDataSyncService.onDidChangeStatus, () => undefined, 500)(() => this.onDidChangeSyncStatus(this.userDataSyncService.status))); + this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING))(() => this.onDidChangeEnablement())); + this.registerActions(); - timeout(2000).then(() => { - if (this.authTokenService.status === AuthTokenStatus.Inactive && configurationService.getValue('configurationSync.enable')) { - this.showSignInNotification(); + if (isWeb) { + this._register(instantiationService.createInstance(UserDataAutoSync)); + } else { + this._register(instantiationService.createInstance(UserDataSyncTrigger).onDidTriggerSync(() => this.triggerSync())); } - }); + } + } + + private triggerSync(): void { + if (this.configurationService.getValue('sync.enable') + && this.userDataSyncService.status !== SyncStatus.Uninitialized + && this.authTokenService.status === AuthTokenStatus.SignedIn) { + this.userDataSyncService.sync(); + } } private onDidChangeAuthTokenStatus(status: AuthTokenStatus) { this.authTokenContext.set(status); - if (status === AuthTokenStatus.Active) { + if (status === AuthTokenStatus.SignedIn) { this.signInNotificationDisposable.clear(); } this.updateBadge(); @@ -82,7 +105,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private onDidChangeSyncStatus(status: SyncStatus) { this.syncStatusContext.set(status); - this.updateBadge(); + if (status === SyncStatus.Syncing) { + // Show syncing progress if takes more than 1s. + timeout(1000).then(() => this.updateBadge()); + } else { + this.updateBadge(); + } if (this.userDataSyncService.status === SyncStatus.HasConflicts) { if (!this.conflictsWarningDisposable.value) { @@ -105,47 +133,124 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } + private onDidChangeEnablement() { + this.updateBadge(); + const enabled = this.configurationService.getValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING); + if (enabled) { + if (this.authTokenService.status === AuthTokenStatus.SignedOut) { + const handle = this.notificationService.prompt(Severity.Info, localize('ask to sign in', "Please sign in with your {0} account to sync configuration across all your machines", this.userDataSyncStore!.account), + [ + { + label: localize('Sign in', "Sign in"), + run: () => this.signIn() + } + ]); + this.signInNotificationDisposable.value = toDisposable(() => handle.close()); + handle.onDidClose(() => this.signInNotificationDisposable.clear()); + } + } else { + this.signInNotificationDisposable.clear(); + } + } + private updateBadge(): void { this.badgeDisposable.clear(); let badge: IBadge | undefined = undefined; let clazz: string | undefined; + let priority: number | undefined = undefined; - if (this.authTokenService.status === AuthTokenStatus.Inactive && this.configurationService.getValue('configurationSync.enable')) { - badge = new NumberBadge(1, () => localize('sign in', "Sign in...")); + if (this.userDataSyncService.status !== SyncStatus.Uninitialized && this.configurationService.getValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING) && this.authTokenService.status === AuthTokenStatus.SignedOut) { + badge = new NumberBadge(1, () => localize('sign in', "Sync: Sign in...")); + } else if (this.authTokenService.status === AuthTokenStatus.SigningIn) { + badge = new ProgressBadge(() => localize('signing in', "Signin in...")); + clazz = 'progress-badge'; + priority = 1; } else if (this.userDataSyncService.status === SyncStatus.HasConflicts) { badge = new NumberBadge(1, () => localize('resolve conflicts', "Resolve Conflicts")); } else if (this.userDataSyncService.status === SyncStatus.Syncing) { badge = new ProgressBadge(() => localize('syncing', "Synchronizing User Configuration...")); clazz = 'progress-badge'; + priority = 1; } if (badge) { - this.badgeDisposable.value = this.activityService.showActivity(GLOBAL_ACTIVITY_ID, badge, clazz); + this.badgeDisposable.value = this.activityService.showActivity(GLOBAL_ACTIVITY_ID, badge, clazz, priority); } } - private showSignInNotification(): void { - const handle = this.notificationService.prompt(Severity.Info, localize('show sign in', "Please sign in to Settings Sync service to start syncing configuration."), - [ - { - label: localize('sign in', "Sign in..."), - run: () => this.signIn() + private async turnOn(): Promise { + if (this.authTokenService.status === AuthTokenStatus.SignedOut) { + const result = await this.dialogService.confirm({ + type: 'info', + message: localize('sign in to account', "Sign in to {0}", this.userDataSyncStore!.name), + detail: localize('ask to sign in', "Please sign in with your {0} account to sync configuration across all your machines", this.userDataSyncStore!.account), + primaryButton: localize('Sign in', "Sign in") + }); + if (!result.confirmed) { + return; + } + await this.signIn(); + } + await this.configureSyncOptions(); + await this.configurationService.updateValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING, true); + this.notificationService.info(localize('Sync Started', "Sync Started.")); + } + + private async configureSyncOptions(): Promise { + return new Promise((c, e) => { + const disposables: DisposableStore = new DisposableStore(); + const quickPick = this.quickInputService.createQuickPick(); + disposables.add(quickPick); + quickPick.title = localize('configure sync title', "Sync: Configure"); + quickPick.placeholder = localize('select configurations to sync', "Choose what to sync"); + quickPick.canSelectMany = true; + quickPick.ignoreFocusOut = true; + const items = [{ + id: 'sync.enableSettings', + label: localize('user settings', "User Settings") + }, { + id: 'sync.enableKeybindings', + label: localize('user keybindings', "User Keybindings") + }, { + id: 'sync.enableExtensions', + label: localize('extensions', "Extensions") + }]; + quickPick.items = items; + quickPick.selectedItems = items.filter(item => this.configurationService.getValue(item.id)); + disposables.add(quickPick.onDidAccept(() => { + for (const item of items) { + const wasEnabled = this.configurationService.getValue(item.id); + const isEnabled = !!quickPick.selectedItems.filter(selected => selected.id === item.id)[0]; + if (wasEnabled !== isEnabled) { + this.configurationService.updateValue(item.id!, isEnabled); + } } - ]); - this.signInNotificationDisposable.value = toDisposable(() => handle.close()); - handle.onDidClose(() => this.signInNotificationDisposable.clear()); + quickPick.hide(); + })); + disposables.add(quickPick.onDidHide(() => { + disposables.dispose(); + c(); + })); + quickPick.show(); + }); + } + + private async turnOff(): Promise { + await this.configurationService.updateValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING, false); } private async signIn(): Promise { - const token = await this.quickInputService.input({ placeHolder: localize('enter token', "Please provide the auth bearer token"), ignoreFocusLost: true, }); - if (token) { - await this.authTokenService.updateToken(token); + try { + await this.authTokenService.login(); + } catch (e) { + this.notificationService.error(e); + throw e; } } private async signOut(): Promise { - await this.authTokenService.deleteToken(); + await this.authTokenService.logout(); } private async continueSync(): Promise { @@ -169,13 +274,14 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } private getPreviewEditorInput(): IEditorInput | undefined { - return this.editorService.editors.filter(input => isEqual(input.getResource(), this.workbenchEnvironmentService.settingsSyncPreviewResource))[0]; + return this.editorService.editors.filter(input => isEqual(input.getResource(), this.workbenchEnvironmentService.settingsSyncPreviewResource) || isEqual(input.getResource(), this.workbenchEnvironmentService.keybindingsSyncPreviewResource))[0]; } private async handleConflicts(): Promise { - if (this.userDataSyncService.conflictsSource === SyncSource.Settings) { + const conflictsResource = this.getConflictsResource(); + if (conflictsResource) { const resourceInput = { - resource: this.workbenchEnvironmentService.settingsSyncPreviewResource, + resource: conflictsResource, options: { preserveFocus: false, pinned: false, @@ -197,80 +303,109 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } + private getConflictsResource(): URI | null { + if (this.userDataSyncService.conflictsSource === SyncSource.Settings) { + return this.workbenchEnvironmentService.settingsSyncPreviewResource; + } + if (this.userDataSyncService.conflictsSource === SyncSource.Keybindings) { + return this.workbenchEnvironmentService.keybindingsSyncPreviewResource; + } + return null; + } + private registerActions(): void { - const signInMenuItem: IMenuItem = { - group: '5_sync', - command: { - id: 'workbench.userData.actions.login', - title: localize('sign in', "Sign in...") - }, - when: ContextKeyExpr.and(CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.Inactive), ContextKeyExpr.has('config.configurationSync.enable')), - }; - CommandsRegistry.registerCommand(signInMenuItem.command.id, () => this.signIn()); - MenuRegistry.appendMenuItem(MenuId.GlobalActivity, signInMenuItem); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, signInMenuItem); - - const signOutMenuItem: IMenuItem = { - command: { - id: 'workbench.userData.actions.logout', - title: localize('sign out', "Sign Out") - }, - when: ContextKeyExpr.and(CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.Active)), - }; - CommandsRegistry.registerCommand(signOutMenuItem.command.id, () => this.signOut()); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, signOutMenuItem); - const startSyncMenuItem: IMenuItem = { group: '5_sync', command: { id: 'workbench.userData.actions.syncStart', - title: localize('start sync', "Configuration Sync: Turn On") + title: localize('start sync', "Sync: Turn On") }, - when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.not('config.configurationSync.enable')), + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.not(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`), CONTEXT_AUTH_TOKEN_STATE.notEqualsTo(AuthTokenStatus.SigningIn)), }; - CommandsRegistry.registerCommand(startSyncMenuItem.command.id, () => this.configurationService.updateValue('configurationSync.enable', true)); + CommandsRegistry.registerCommand(startSyncMenuItem.command.id, () => this.turnOn()); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, startSyncMenuItem); MenuRegistry.appendMenuItem(MenuId.CommandPalette, startSyncMenuItem); - const stopSyncMenuItem: IMenuItem = { + const signInCommandId = 'workbench.userData.actions.signin'; + const signInWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.has(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`), CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.SignedOut)); + CommandsRegistry.registerCommand(signInCommandId, () => this.signIn()); + MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '5_sync', command: { - id: 'workbench.userData.actions.stopSync', - title: localize('stop sync', "Configuration Sync: Turn Off") + id: signInCommandId, + title: localize('global activity sign in', "Sync: Sign in... (1)") }, - when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.has('config.configurationSync.enable')), - }; - CommandsRegistry.registerCommand(stopSyncMenuItem.command.id, () => this.configurationService.updateValue('configurationSync.enable', false)); - MenuRegistry.appendMenuItem(MenuId.GlobalActivity, stopSyncMenuItem); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, stopSyncMenuItem); + when: signInWhenContext, + }); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: signInCommandId, + title: localize('sign in', "Sync: Sign in...") + }, + when: signInWhenContext, + }); - const resolveConflictsMenuItem: IMenuItem = { + const signingInCommandId = 'workbench.userData.actions.signingin'; + CommandsRegistry.registerCommand(signingInCommandId, () => null); + MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '5_sync', command: { - id: 'sync.resolveConflicts', - title: localize('resolveConflicts', "Configuration Sync: Resolve Conflicts"), + id: signingInCommandId, + title: localize('signinig in', "Sync: Signing in..."), + precondition: FalseContext }, - when: CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts), + when: CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.SigningIn) + }); + + const stopSyncCommand = { + id: 'workbench.userData.actions.stopSync', + title: localize('stop sync', "Sync: Turn Off") }; - CommandsRegistry.registerCommand(resolveConflictsMenuItem.command.id, () => this.handleConflicts()); - MenuRegistry.appendMenuItem(MenuId.GlobalActivity, resolveConflictsMenuItem); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, resolveConflictsMenuItem); + CommandsRegistry.registerCommand(stopSyncCommand.id, () => this.turnOff()); + MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { + group: '5_sync', + command: stopSyncCommand, + when: ContextKeyExpr.and(ContextKeyExpr.has(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`), CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.SignedIn), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.HasConflicts)) + }); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: stopSyncCommand, + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.has(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`)), + }); + + const resolveConflictsCommandId = 'workbench.userData.actions.resolveConflicts'; + const resolveConflictsWhenContext = CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts); + CommandsRegistry.registerCommand(resolveConflictsCommandId, () => this.handleConflicts()); + MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { + group: '5_sync', + command: { + id: resolveConflictsCommandId, + title: localize('resolveConflicts_global', "Sync: Resolve Conflicts (1)"), + }, + when: resolveConflictsWhenContext, + }); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: resolveConflictsCommandId, + title: localize('resolveConflicts', "Sync: Resolve Conflicts"), + }, + when: resolveConflictsWhenContext, + }); const continueSyncCommandId = 'workbench.userData.actions.continueSync'; CommandsRegistry.registerCommand(continueSyncCommandId, () => this.continueSync()); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: continueSyncCommandId, - title: localize('continue sync', "Configuration Sync: Continue") + title: localize('continue sync', "Sync: Continue") }, when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts)), }); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: continueSyncCommandId, - title: localize('continue sync', "Configuration Sync: Continue"), - iconLocation: { + title: localize('continue sync', "Sync: Continue"), + icon: { light: SYNC_PUSH_LIGHT_ICON_URI, dark: SYNC_PUSH_DARK_ICON_URI } @@ -279,5 +414,29 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo order: 1, when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts), ResourceContextKey.Resource.isEqualTo(this.workbenchEnvironmentService.settingsSyncPreviewResource.toString())), }); + MenuRegistry.appendMenuItem(MenuId.EditorTitle, { + command: { + id: continueSyncCommandId, + title: localize('continue sync', "Sync: Continue"), + icon: { + light: SYNC_PUSH_LIGHT_ICON_URI, + dark: SYNC_PUSH_DARK_ICON_URI + } + }, + group: 'navigation', + order: 1, + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts), ResourceContextKey.Resource.isEqualTo(this.workbenchEnvironmentService.keybindingsSyncPreviewResource.toString())), + }); + + const signOutMenuItem: IMenuItem = { + group: '5_sync', + command: { + id: 'workbench.userData.actions.signout', + title: localize('sign out', "Sign Out") + }, + when: ContextKeyExpr.and(CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.SignedIn)), + }; + CommandsRegistry.registerCommand(signOutMenuItem.command.id, () => this.signOut()); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, signOutMenuItem); } } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts new file mode 100644 index 0000000000..84c358ae7e --- /dev/null +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts @@ -0,0 +1,61 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event, Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { SettingsEditor2Input, KeybindingsEditorInput, PreferencesEditorInput } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; +import { isEqual } from 'vs/base/common/resources'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IEditorInput } from 'vs/workbench/common/editor'; +import { IViewlet } from 'vs/workbench/common/viewlet'; + +export class UserDataSyncTrigger extends Disposable { + + private readonly _onDidTriggerSync: Emitter = this._register(new Emitter()); + readonly onDidTriggerSync: Event = this._onDidTriggerSync.event; + + constructor( + @IEditorService editorService: IEditorService, + @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, + @IViewletService viewletService: IViewletService, + ) { + super(); + this._register(Event.debounce(Event.any( + Event.filter(editorService.onDidActiveEditorChange, () => this.isUserDataEditorInput(editorService.activeEditor)), + Event.filter(viewletService.onDidViewletOpen, viewlet => this.isUserDataViewlet(viewlet)) + ), () => undefined, 500)(() => this._onDidTriggerSync.fire())); + } + + private isUserDataViewlet(viewlet: IViewlet): boolean { + return viewlet.getId() === VIEWLET_ID; + } + + private isUserDataEditorInput(editorInput: IEditorInput | undefined): boolean { + if (!editorInput) { + return false; + } + if (editorInput instanceof SettingsEditor2Input) { + return true; + } + if (editorInput instanceof PreferencesEditorInput) { + return true; + } + if (editorInput instanceof KeybindingsEditorInput) { + return true; + } + const resource = editorInput.getResource(); + if (isEqual(resource, this.workbenchEnvironmentService.settingsResource)) { + return true; + } + if (isEqual(resource, this.workbenchEnvironmentService.keybindingsResource)) { + return true; + } + return false; + } +} + diff --git a/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts index 6ffa857b30..7521673750 100644 --- a/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts @@ -4,19 +4,22 @@ *--------------------------------------------------------------------------------------------*/ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { ISettingsMergeService } from 'vs/platform/userDataSync/common/userDataSync'; +import { ISettingsMergeService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { SettingsMergeChannel } from 'vs/platform/userDataSync/common/settingsSyncIpc'; +import { UserDataSycnUtilServiceChannel } from 'vs/platform/userDataSync/common/keybindingsSyncIpc'; class UserDataSyncServicesContribution implements IWorkbenchContribution { constructor( @ISettingsMergeService settingsMergeService: ISettingsMergeService, + @IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService, @ISharedProcessService sharedProcessService: ISharedProcessService, ) { sharedProcessService.registerChannel('settingsMerge', new SettingsMergeChannel(settingsMergeService)); + sharedProcessService.registerChannel('userDataSyncUtil', new UserDataSycnUtilServiceChannel(userDataSyncUtilService)); } } diff --git a/src/vs/workbench/contrib/watermark/browser/watermark.ts b/src/vs/workbench/contrib/watermark/browser/watermark.ts index 5f00c6693a..d1755926ca 100644 --- a/src/vs/workbench/contrib/watermark/browser/watermark.ts +++ b/src/vs/workbench/contrib/watermark/browser/watermark.ts @@ -29,6 +29,7 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IDimension } from 'vs/platform/layout/browser/layoutService'; // import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { assertIsDefined } from 'vs/base/common/types'; +import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; // {{SQL CARBON EDIT}} import { NewNotebookAction } from 'sql/workbench/contrib/notebook/browser/notebookActions'; @@ -196,9 +197,7 @@ Registry.as(WorkbenchExtensions.Workbench) Registry.as(ConfigurationExtensions.Configuration) .registerConfiguration({ - 'id': 'workbench', - 'order': 7, - 'title': nls.localize('workbenchConfigurationTitle', "Workbench"), + ...workbenchConfigurationNodeBase, 'properties': { 'workbench.tips.enabled': { 'type': 'boolean', diff --git a/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts b/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts index 45e296b7a1..82d33c6011 100644 --- a/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts @@ -10,7 +10,6 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { WebviewExtensionDescription, WebviewOptions, WebviewContentOptions } from 'vs/workbench/contrib/webview/browser/webview'; -import { URI } from 'vs/base/common/uri'; import { areWebviewInputOptionsEqual } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/common/themeing'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -50,8 +49,8 @@ export abstract class BaseWebview extends Disposable { private _element: T | undefined; protected get element(): T | undefined { return this._element; } - private _focused: boolean; - protected get focused(): boolean { return this._focused; } + private _focused: boolean | undefined; + protected get focused(): boolean { return !!this._focused; } private readonly _ready: Promise; @@ -94,7 +93,7 @@ export abstract class BaseWebview extends Disposable { })); this._register(this.on(WebviewMessageChannels.didClickLink, (uri: string) => { - this._onDidClickLink.fire(URI.parse(uri)); + this._onDidClickLink.fire(uri); })); this._register(this.on(WebviewMessageChannels.onmessage, (data: any) => { @@ -145,7 +144,7 @@ export abstract class BaseWebview extends Disposable { private readonly _onMissingCsp = this._register(new Emitter()); public readonly onMissingCsp = this._onMissingCsp.event; - private readonly _onDidClickLink = this._register(new Emitter()); + private readonly _onDidClickLink = this._register(new Emitter()); public readonly onDidClickLink = this._onDidClickLink.event; private readonly _onMessage = this._register(new Emitter()); diff --git a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts index 260b800e13..a4edd1ac7a 100644 --- a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts +++ b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts @@ -6,7 +6,6 @@ import { memoize } from 'vs/base/common/decorators'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IWebviewService, Webview, WebviewContentOptions, WebviewEditorOverlay, WebviewElement, WebviewOptions, WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; @@ -99,7 +98,7 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd if (this._options.tryRestoreScrollPosition) { webview.initialScrollProgress = this._initialScrollProgress; } - this._webview.value.mountTo(this.container); + webview.mountTo(this.container); // Forward events from inner webview to outer listeners this._webviewEvents.clear(); @@ -143,7 +142,7 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd } public get options(): WebviewOptions { return this._options; } - public set options(value: WebviewOptions) { this._options = value; } + public set options(value: WebviewOptions) { this._options = { customClasses: this._options.customClasses, ...value }; } public get contentOptions(): WebviewContentOptions { return this._contentOptions; } public set contentOptions(value: WebviewContentOptions) { @@ -160,8 +159,8 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd private readonly _onDidFocus = this._register(new Emitter()); public readonly onDidFocus: Event = this._onDidFocus.event; - private readonly _onDidClickLink = this._register(new Emitter()); - public readonly onDidClickLink: Event = this._onDidClickLink.event; + private readonly _onDidClickLink = this._register(new Emitter()); + public readonly onDidClickLink: Event = this._onDidClickLink.event; private readonly _onDidScroll = this._register(new Emitter<{ scrollYPercentage: number; }>()); public readonly onDidScroll: Event<{ scrollYPercentage: number; }> = this._onDidScroll.event; diff --git a/src/vs/workbench/contrib/webview/browser/pre/fake.html b/src/vs/workbench/contrib/webview/browser/pre/fake.html index e69de29bb2..726a69397f 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/fake.html +++ b/src/vs/workbench/contrib/webview/browser/pre/fake.html @@ -0,0 +1,11 @@ + + + + + + + Fake + + + + diff --git a/src/vs/workbench/contrib/webview/browser/pre/index.html b/src/vs/workbench/contrib/webview/browser/pre/index.html index e301f5ea90..d142be649a 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/index.html +++ b/src/vs/workbench/contrib/webview/browser/pre/index.html @@ -3,7 +3,11 @@ - + + + + Virtual Document diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index 63541521d7..97d54a7614 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -91,6 +91,24 @@ border-color: var(--vscode-textBlockQuote-border); } + kbd { + color: var(--vscode-editor-foreground); + border-radius: 3px; + vertical-align: middle; + padding: 1px 3px; + + background-color: hsla(0,0%,50%,.17); + border: 1px solid rgba(71,71,71,.4); + border-bottom-color: rgba(88,88,88,.4); + box-shadow: inset 0 -1px 0 rgba(88,88,88,.4); + } + .vscode-light kbd { + background-color: hsla(0,0%,87%,.5); + border: 1px solid hsla(0,0%,80%,.7); + border-bottom-color: hsla(0,0%,73%,.7); + box-shadow: inset 0 -1px 0 hsla(0,0%,73%,.7); + } + ::-webkit-scrollbar { width: 10px; height: 10px; @@ -213,7 +231,8 @@ /** * @param {MouseEvent} event */ - const handleAuxClick = (event) => { + const handleAuxClick = + (event) => { // Prevent middle clicks opening a broken link in the browser if (!event.view || !event.view.document) { return; @@ -308,7 +327,12 @@ } else { // Rewrite vscode-resource in csp if (data.endpoint) { - csp.setAttribute('content', csp.getAttribute('content').replace(/vscode-resource:/g, data.endpoint)); + try { + const endpointUrl = new URL(data.endpoint); + csp.setAttribute('content', csp.getAttribute('content').replace(/vscode-resource:(?=(\s|;|$))/g, endpointUrl.origin)); + } catch (e) { + console.error('Could not rewrite csp'); + } } } diff --git a/src/vs/workbench/contrib/webview/browser/webview.contribution.ts b/src/vs/workbench/contrib/webview/browser/webview.contribution.ts index e1e810fbe5..41305eb738 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.contribution.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.contribution.ts @@ -21,7 +21,7 @@ import { WebviewEditor } from '../browser/webviewEditor'; import { WebviewInput } from '../browser/webviewEditorInput'; import { IWebviewWorkbenchService, WebviewEditorService } from './webviewWorkbenchService'; -(Registry.as(EditorExtensions.Editors)).registerEditor(new EditorDescriptor( +(Registry.as(EditorExtensions.Editors)).registerEditor(EditorDescriptor.create( WebviewEditor, WebviewEditor.ID, localize('webview.editor.label', "webview editor")), @@ -85,6 +85,6 @@ function registerWebViewCommands(editorId: string): void { registerWebViewCommands(WebviewEditor.ID); actionRegistry.registerWorkbenchAction( - new SyncActionDescriptor(ReloadWebviewAction, ReloadWebviewAction.ID, ReloadWebviewAction.LABEL), + SyncActionDescriptor.create(ReloadWebviewAction, ReloadWebviewAction.ID, ReloadWebviewAction.LABEL), 'Reload Webviews', webviewDeveloperCategory); diff --git a/src/vs/workbench/contrib/webview/browser/webview.ts b/src/vs/workbench/contrib/webview/browser/webview.ts index 10db2b70ac..4af73dd163 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.ts @@ -19,6 +19,9 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' export const KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE = new RawContextKey('webviewFindWidgetVisible', false); export const KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED = new RawContextKey('webviewFindWidgetFocused', false); +export const webviewHasOwnEditFunctionsContextKey = 'webviewHasOwnEditFunctions'; +export const webviewHasOwnEditFunctionsContext = new RawContextKey(webviewHasOwnEditFunctionsContextKey, false); + export const IWebviewService = createDecorator('webviewService'); /** @@ -68,7 +71,7 @@ export interface Webview extends IDisposable { state: string | undefined; readonly onDidFocus: Event; - readonly onDidClickLink: Event; + readonly onDidClickLink: Event; readonly onDidScroll: Event<{ scrollYPercentage: number }>; readonly onDidUpdateState: Event; readonly onMessage: Event; diff --git a/src/vs/workbench/contrib/webview/browser/webviewCommands.ts b/src/vs/workbench/contrib/webview/browser/webviewCommands.ts index 1c7f26efa7..2018c13aab 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewCommands.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewCommands.ts @@ -13,44 +13,32 @@ import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEdito export class ShowWebViewEditorFindWidgetCommand extends Command { public static readonly ID = 'editor.action.webvieweditor.showFind'; - public runCommand(accessor: ServicesAccessor, args: any): void { - const webViewEditor = getActiveWebviewEditor(accessor); - if (webViewEditor) { - webViewEditor.showFind(); - } + public runCommand(accessor: ServicesAccessor): void { + getActiveWebviewEditor(accessor)?.showFind(); } } export class HideWebViewEditorFindCommand extends Command { public static readonly ID = 'editor.action.webvieweditor.hideFind'; - public runCommand(accessor: ServicesAccessor, args: any): void { - const webViewEditor = getActiveWebviewEditor(accessor); - if (webViewEditor) { - webViewEditor.hideFind(); - } + public runCommand(accessor: ServicesAccessor): void { + getActiveWebviewEditor(accessor)?.hideFind(); } } export class WebViewEditorFindNextCommand extends Command { public static readonly ID = 'editor.action.webvieweditor.findNext'; - public runCommand(accessor: ServicesAccessor, args: any): void { - const webViewEditor = getActiveWebviewEditor(accessor); - if (webViewEditor) { - webViewEditor.find(false); - } + public runCommand(accessor: ServicesAccessor): void { + getActiveWebviewEditor(accessor)?.find(false); } } export class WebViewEditorFindPreviousCommand extends Command { public static readonly ID = 'editor.action.webvieweditor.findPrevious'; - public runCommand(accessor: ServicesAccessor, args: any): void { - const webViewEditor = getActiveWebviewEditor(accessor); - if (webViewEditor) { - webViewEditor.find(true); - } + public runCommand(accessor: ServicesAccessor): void { + getActiveWebviewEditor(accessor)?.find(true); } } export class ReloadWebviewAction extends Action { @@ -79,8 +67,8 @@ export class ReloadWebviewAction extends Action { } } -function getActiveWebviewEditor(accessor: ServicesAccessor): WebviewEditor | null { +export function getActiveWebviewEditor(accessor: ServicesAccessor): WebviewEditor | undefined { const editorService = accessor.get(IEditorService); - const activeControl = editorService.activeControl as WebviewEditor; - return activeControl.isWebviewEditor ? activeControl : null; + const activeControl = editorService.activeControl as WebviewEditor | undefined; + return activeControl?.isWebviewEditor ? activeControl : undefined; } diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts index 515a3474df..52c2e4d214 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts @@ -6,17 +6,19 @@ import * as dom from 'vs/base/browser/dom'; import { memoize } from 'vs/base/common/decorators'; import { Lazy } from 'vs/base/common/lazy'; -import { UnownedDisposable as Unowned } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IEditorModel } from 'vs/platform/editor/common/editor'; +import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { EditorInput, EditorModel, GroupIdentifier, IEditorInput, Verbosity } from 'vs/workbench/common/editor'; import { WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview'; +import { Emitter } from 'vs/base/common/event'; const WebviewPanelResourceScheme = 'webview-panel'; class WebviewIconsManager { private readonly _icons = new Map(); + @memoize private get _styleElement(): HTMLStyleElement { const element = dom.createStyleSheet(); @@ -26,7 +28,8 @@ class WebviewIconsManager { public setIcons( webviewId: string, - iconPath: { light: URI, dark: URI } | undefined + iconPath: { light: URI, dark: URI } | undefined, + lifecycleService: ILifecycleService, ) { if (iconPath) { this._icons.set(webviewId, iconPath); @@ -34,21 +37,27 @@ class WebviewIconsManager { this._icons.delete(webviewId); } - this.updateStyleSheet(); + this.updateStyleSheet(lifecycleService); } - private updateStyleSheet() { - const cssRules: string[] = []; - this._icons.forEach((value, key) => { - const webviewSelector = `.show-file-icons .webview-${key}-name-file-icon::before`; - if (URI.isUri(value)) { - cssRules.push(`${webviewSelector} { content: ""; background-image: ${dom.asCSSUrl(value)}; }`); - } else { - cssRules.push(`.vs ${webviewSelector} { content: ""; background-image: ${dom.asCSSUrl(value.light)}; }`); - cssRules.push(`.vs-dark ${webviewSelector} { content: ""; background-image: ${dom.asCSSUrl(value.dark)}; }`); - } - }); - this._styleElement.innerHTML = cssRules.join('\n'); + private async updateStyleSheet(lifecycleService: ILifecycleService, ) { + await lifecycleService.when(LifecyclePhase.Starting); + + try { + const cssRules: string[] = []; + this._icons.forEach((value, key) => { + const webviewSelector = `.show-file-icons .webview-${key}-name-file-icon::before`; + if (URI.isUri(value)) { + cssRules.push(`${webviewSelector} { content: ""; background-image: ${dom.asCSSUrl(value)}; }`); + } else { + cssRules.push(`.vs ${webviewSelector} { content: ""; background-image: ${dom.asCSSUrl(value.light)}; }`); + cssRules.push(`.vs-dark ${webviewSelector} { content: ""; background-image: ${dom.asCSSUrl(value.dark)}; }`); + } + }); + this._styleElement.innerHTML = cssRules.join('\n'); + } catch { + // noop + } } } @@ -61,19 +70,31 @@ export class WebviewInput extends EditorInput { private _name: string; private _iconPath?: { light: URI, dark: URI }; private _group?: GroupIdentifier; + private readonly _webview: Lazy; + private _didSomeoneTakeMyWebview = false; + + private readonly _onDisposeWebview = this._register(new Emitter()); + readonly onDisposeWebview = this._onDisposeWebview.event; constructor( public readonly id: string, public readonly viewType: string, name: string, - webview: Lazy> + webview: Lazy, + @ILifecycleService private readonly lifecycleService: ILifecycleService, ) { super(); - this._name = name; + this._webview = webview; + } - this._webview = webview.map(value => this._register(value.acquire())); // The input owns this webview + dispose() { + if (!this._didSomeoneTakeMyWebview) { + this._webview?.rawValue?.dispose(); + this._onDisposeWebview.fire(); + } + super.dispose(); } public getTypeId(): string { @@ -91,7 +112,7 @@ export class WebviewInput extends EditorInput { return this._name; } - public getTitle(_verbosity?: Verbosity) { + public getTitle(_verbosity?: Verbosity): string { return this.getName(); } @@ -109,7 +130,7 @@ export class WebviewInput extends EditorInput { } public get extension() { - return this._webview.getValue().extension; + return this.webview.extension; } public get iconPath() { @@ -118,7 +139,7 @@ export class WebviewInput extends EditorInput { public set iconPath(value: { light: URI, dark: URI } | undefined) { this._iconPath = value; - WebviewInput.iconsManager.setIcons(this.id, value); + WebviewInput.iconsManager.setIcons(this.id, value, this.lifecycleService); } public matches(other: IEditorInput): boolean { @@ -140,4 +161,12 @@ export class WebviewInput extends EditorInput { public supportsSplitEditor() { return false; } + + protected takeOwnershipOfWebview(): WebviewEditorOverlay | undefined { + if (this._didSomeoneTakeMyWebview) { + return undefined; + } + this._didSomeoneTakeMyWebview = true; + return this.webview; + } } diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index 155c3fb23d..32e0db6012 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -12,7 +12,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; import { Webview, WebviewContentOptions, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewPortMappingManager } from 'vs/workbench/contrib/webview/common/portMapping'; -import { loadLocalResource } from 'vs/workbench/contrib/webview/common/resourceLoader'; +import { loadLocalResource, WebviewResourceResponse } from 'vs/workbench/contrib/webview/common/resourceLoader'; import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/common/themeing'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { BaseWebview, WebviewMessageChannels } from 'vs/workbench/contrib/webview/browser/baseWebviewElement'; @@ -60,7 +60,7 @@ export class IFrameWebview extends BaseWebview implements Web protected createElement(options: WebviewOptions) { const element = document.createElement('iframe'); - element.className = `webview ${options.customClasses}`; + element.className = `webview ${options.customClasses || ''}`; element.sandbox.add('allow-scripts', 'allow-same-origin'); element.setAttribute('src', `${this.externalEndpoint}/index.html?id=${this.id}`); element.style.border = 'none'; @@ -108,7 +108,6 @@ export class IFrameWebview extends BaseWebview implements Web } focus(): void { - console.log('focus'); if (this.element) { this._send('focus'); } @@ -131,7 +130,7 @@ export class IFrameWebview extends BaseWebview implements Web const result = await loadLocalResource(uri, this.fileService, this.extension ? this.extension.location : undefined, () => (this.content.options.localResourceRoots || [])); - if (result.type === 'success') { + if (result.type === WebviewResourceResponse.Type.Success) { return this._send('did-load-resource', { status: 200, path: requestPath, diff --git a/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts b/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts index 5bfae2a712..b76646bf40 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts @@ -5,19 +5,20 @@ import { equals } from 'vs/base/common/arrays'; import { memoize } from 'vs/base/common/decorators'; -import { IDisposable, toDisposable, UnownedDisposable } from 'vs/base/common/lifecycle'; +import { Lazy } from 'vs/base/common/lazy'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { values } from 'vs/base/common/map'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { EditorActivation, IEditorModel } from 'vs/platform/editor/common/editor'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { GroupIdentifier } from 'vs/workbench/common/editor'; -import { IWebviewService, WebviewContentOptions, WebviewEditorOverlay, WebviewOptions, WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview'; +import { IWebviewService, WebviewContentOptions, WebviewEditorOverlay, WebviewExtensionDescription, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; import { IEditorGroup, IEditorGroupsService } 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'; -import { Lazy } from 'vs/base/common/lazy'; export const IWebviewWorkbenchService = createDecorator('webviewEditorService'); @@ -107,10 +108,11 @@ export class LazilyResolvedWebviewEditorInput extends WebviewInput { id: string, viewType: string, name: string, - webview: Lazy>, + webview: Lazy, @IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService, + @ILifecycleService lifeCycleService: ILifecycleService, ) { - super(id, viewType, name, webview); + super(id, viewType, name, webview, lifeCycleService); } @memoize @@ -145,8 +147,9 @@ export class WebviewEditorService implements IWebviewWorkbenchService { private readonly _revivalPool = new RevivalPool(); constructor( - @IEditorService private readonly _editorService: IEditorService, @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, + @IEditorService private readonly _editorService: IEditorService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, @IWebviewService private readonly _webviewService: IWebviewService, ) { } @@ -158,8 +161,8 @@ export class WebviewEditorService implements IWebviewWorkbenchService { options: WebviewInputOptions, extension: WebviewExtensionDescription | undefined, ): WebviewInput { - const webview = new Lazy(() => new UnownedDisposable(this.createWebiew(id, extension, options))); - const webviewInput = new WebviewInput(id, viewType, title, webview); + const webview = new Lazy(() => this.createWebiew(id, extension, options)); + const webviewInput = this._instantiationService.createInstance(WebviewInput, id, viewType, title, webview); this._editorService.openEditor(webviewInput, { pinned: true, preserveFocus: showOptions.preserveFocus, @@ -203,10 +206,10 @@ export class WebviewEditorService implements IWebviewWorkbenchService { const webview = new Lazy(() => { const webview = this.createWebiew(id, extension, options); webview.state = state; - return new UnownedDisposable(webview); + return webview; }); - const webviewInput = new LazilyResolvedWebviewEditorInput(id, viewType, title, webview, this); + const webviewInput = this._instantiationService.createInstance(LazilyResolvedWebviewEditorInput, id, viewType, title, webview); webviewInput.iconPath = iconPath; if (typeof group === 'number') { diff --git a/src/vs/workbench/contrib/webview/common/resourceLoader.ts b/src/vs/workbench/contrib/webview/common/resourceLoader.ts index cb607a3f13..a7a7a28ef2 100644 --- a/src/vs/workbench/contrib/webview/common/resourceLoader.ts +++ b/src/vs/workbench/contrib/webview/common/resourceLoader.ts @@ -10,34 +10,39 @@ import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { getWebviewContentMimeType } from 'vs/workbench/contrib/webview/common/mimeTypes'; +import { isUNC } from 'vs/base/common/extpath'; export const WebviewResourceScheme = 'vscode-resource'; -class Success { - readonly type = 'success'; +export namespace WebviewResourceResponse { + export enum Type { Success, Failed, AccessDenied } + + export class Success { + readonly type = Type.Success; + + constructor( + public readonly data: VSBuffer, + public readonly mimeType: string + ) { } + } + + export const Failed = { type: Type.Failed } as const; + export const AccessDenied = { type: Type.AccessDenied } as const; + + export type Response = Success | typeof Failed | typeof AccessDenied; - constructor( - public readonly data: VSBuffer, - public readonly mimeType: string - ) { } } - -const Failed = new class { readonly type = 'failed'; }; -const AccessDenied = new class { readonly type = 'access-denied'; }; - -type LocalResourceResponse = Success | typeof Failed | typeof AccessDenied; - async function resolveContent( fileService: IFileService, resource: URI, mime: string -): Promise { +): Promise { try { const contents = await fileService.readFile(resource); - return new Success(contents.value, mime); + return new WebviewResourceResponse.Success(contents.value, mime); } catch (err) { console.log(err); - return Failed; + return WebviewResourceResponse.Failed; } } @@ -46,7 +51,7 @@ export async function loadLocalResource( fileService: IFileService, extensionLocation: URI | undefined, getRoots: () => ReadonlyArray -): Promise { +): Promise { const normalizedPath = normalizeRequestPath(requestUri); for (const root of getRoots()) { @@ -69,7 +74,7 @@ export async function loadLocalResource( } } - return AccessDenied; + return WebviewResourceResponse.AccessDenied; } function normalizeRequestPath(requestUri: URI) { @@ -79,7 +84,10 @@ function normalizeRequestPath(requestUri: URI) { // Modern vscode-resources uris put the scheme of the requested resource as the authority if (requestUri.authority) { - return URI.parse(requestUri.authority + ':' + requestUri.path); + return URI.parse(`${requestUri.authority}:${encodeURIComponent(requestUri.path).replace(/%2F/g, '/')}`).with({ + query: requestUri.query, + fragment: requestUri.fragment + }); } // Old style vscode-resource uris lose the scheme of the resource which means they are unable to @@ -88,6 +96,13 @@ function normalizeRequestPath(requestUri: URI) { } function containsResource(root: URI, resource: URI): boolean { - const rootPath = root.fsPath + (endsWith(root.fsPath, sep) ? '' : sep); + let rootPath = root.fsPath + (endsWith(root.fsPath, sep) ? '' : sep); + let resourceFsPath = resource.fsPath; + + if (isUNC(root.fsPath) && isUNC(resource.fsPath)) { + rootPath = rootPath.toLowerCase(); + resourceFsPath = resourceFsPath.toLowerCase(); + } + return startsWith(resource.fsPath, rootPath); } diff --git a/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts b/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts index f726efbd00..e53e97b0d7 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts @@ -3,17 +3,14 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { isMacintosh } from 'vs/base/common/platform'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; -import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEditor'; import { IWebviewService, webviewDeveloperCategory } from 'vs/workbench/contrib/webview/browser/webview'; +import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEditor'; import * as webviewCommands from 'vs/workbench/contrib/webview/electron-browser/webviewCommands'; import { ElectronWebviewService } from 'vs/workbench/contrib/webview/electron-browser/webviewService'; @@ -22,70 +19,22 @@ registerSingleton(IWebviewService, ElectronWebviewService, true); const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); actionRegistry.registerWorkbenchAction( - new SyncActionDescriptor(webviewCommands.OpenWebviewDeveloperToolsAction, webviewCommands.OpenWebviewDeveloperToolsAction.ID, webviewCommands.OpenWebviewDeveloperToolsAction.LABEL), + SyncActionDescriptor.create(webviewCommands.OpenWebviewDeveloperToolsAction, webviewCommands.OpenWebviewDeveloperToolsAction.ID, webviewCommands.OpenWebviewDeveloperToolsAction.LABEL), webviewCommands.OpenWebviewDeveloperToolsAction.ALIAS, webviewDeveloperCategory); function registerWebViewCommands(editorId: string): void { - const contextKeyExpr = ContextKeyExpr.and(ContextKeyExpr.equals('activeEditor', editorId), ContextKeyExpr.not('editorFocus') /* https://github.com/Microsoft/vscode/issues/58668 */); + const contextKeyExpr = ContextKeyExpr.and(ContextKeyExpr.equals('activeEditor', editorId), ContextKeyExpr.not('editorFocus') /* https://github.com/Microsoft/vscode/issues/58668 */)!; - (new webviewCommands.SelectAllWebviewEditorCommand({ - id: webviewCommands.SelectAllWebviewEditorCommand.ID, - precondition: ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)), - kbOpts: { - primary: KeyMod.CtrlCmd | KeyCode.KEY_A, - weight: KeybindingWeight.EditorContrib - } - })).register(); + new webviewCommands.SelectAllWebviewEditorCommand(contextKeyExpr).register(); // These commands are only needed on MacOS where we have to disable the menu bar commands if (isMacintosh) { - (new webviewCommands.CopyWebviewEditorCommand({ - id: webviewCommands.CopyWebviewEditorCommand.ID, - precondition: ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)), - kbOpts: { - primary: KeyMod.CtrlCmd | KeyCode.KEY_C, - weight: KeybindingWeight.EditorContrib - } - })).register(); - - (new webviewCommands.PasteWebviewEditorCommand({ - id: webviewCommands.PasteWebviewEditorCommand.ID, - precondition: ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)), - kbOpts: { - primary: KeyMod.CtrlCmd | KeyCode.KEY_V, - weight: KeybindingWeight.EditorContrib - } - })).register(); - - (new webviewCommands.CutWebviewEditorCommand({ - id: webviewCommands.CutWebviewEditorCommand.ID, - precondition: ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)), - kbOpts: { - primary: KeyMod.CtrlCmd | KeyCode.KEY_X, - weight: KeybindingWeight.EditorContrib - } - })).register(); - - (new webviewCommands.UndoWebviewEditorCommand({ - id: webviewCommands.UndoWebviewEditorCommand.ID, - precondition: ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)), - kbOpts: { - primary: KeyMod.CtrlCmd | KeyCode.KEY_Z, - weight: KeybindingWeight.EditorContrib - } - })).register(); - - (new webviewCommands.RedoWebviewEditorCommand({ - id: webviewCommands.RedoWebviewEditorCommand.ID, - precondition: ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)), - kbOpts: { - primary: KeyMod.CtrlCmd | KeyCode.KEY_Y, - secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z], - mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z }, - weight: KeybindingWeight.EditorContrib - } - })).register(); + new webviewCommands.CopyWebviewEditorCommand(contextKeyExpr).register(); + new webviewCommands.PasteWebviewEditorCommand(contextKeyExpr).register(); + new webviewCommands.CutWebviewEditorCommand(contextKeyExpr).register(); + new webviewCommands.UndoWebviewEditorCommand(contextKeyExpr).register(); + new webviewCommands.RedoWebviewEditorCommand(contextKeyExpr).register(); } } diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts index 7cfb3cd277..950c94e99f 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts @@ -3,14 +3,17 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Action } from 'vs/base/common/actions'; -import * as nls from 'vs/nls'; -import { Command, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEditor'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { ElectronWebviewBasedWebview } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; -import { WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewTag } from 'electron'; +import { Action } from 'vs/base/common/actions'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { Command, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import * as nls from 'vs/nls'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { WebviewEditorOverlay, webviewHasOwnEditFunctionsContextKey } from 'vs/workbench/contrib/webview/browser/webview'; +import { getActiveWebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewCommands'; +import { ElectronWebviewBasedWebview } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; export class OpenWebviewDeveloperToolsAction extends Action { static readonly ID = 'workbench.action.webview.openDeveloperTools'; @@ -37,6 +40,17 @@ export class OpenWebviewDeveloperToolsAction extends Action { export class SelectAllWebviewEditorCommand extends Command { public static readonly ID = 'editor.action.webvieweditor.selectAll'; + constructor(contextKeyExpr: ContextKeyExpr) { + super({ + id: SelectAllWebviewEditorCommand.ID, + precondition: ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)), + kbOpts: { + primary: KeyMod.CtrlCmd | KeyCode.KEY_A, + weight: KeybindingWeight.EditorContrib + } + }); + } + public runCommand(accessor: ServicesAccessor, args: any): void { withActiveWebviewBasedWebview(accessor, webview => webview.selectAll()); } @@ -45,6 +59,17 @@ export class SelectAllWebviewEditorCommand extends Command { export class CopyWebviewEditorCommand extends Command { public static readonly ID = 'editor.action.webvieweditor.copy'; + constructor(contextKeyExpr: ContextKeyExpr) { + super({ + id: CopyWebviewEditorCommand.ID, + precondition: ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)), + kbOpts: { + primary: KeyMod.CtrlCmd | KeyCode.KEY_C, + weight: KeybindingWeight.EditorContrib + } + }); + } + public runCommand(accessor: ServicesAccessor, _args: any): void { withActiveWebviewBasedWebview(accessor, webview => webview.copy()); } @@ -53,6 +78,17 @@ export class CopyWebviewEditorCommand extends Command { export class PasteWebviewEditorCommand extends Command { public static readonly ID = 'editor.action.webvieweditor.paste'; + constructor(contextKeyExpr: ContextKeyExpr) { + super({ + id: PasteWebviewEditorCommand.ID, + precondition: ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)), + kbOpts: { + primary: KeyMod.CtrlCmd | KeyCode.KEY_V, + weight: KeybindingWeight.EditorContrib + } + }); + } + public runCommand(accessor: ServicesAccessor, _args: any): void { withActiveWebviewBasedWebview(accessor, webview => webview.paste()); } @@ -61,6 +97,17 @@ export class PasteWebviewEditorCommand extends Command { export class CutWebviewEditorCommand extends Command { public static readonly ID = 'editor.action.webvieweditor.cut'; + constructor(contextKeyExpr: ContextKeyExpr) { + super({ + id: CutWebviewEditorCommand.ID, + precondition: ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)), + kbOpts: { + primary: KeyMod.CtrlCmd | KeyCode.KEY_X, + weight: KeybindingWeight.EditorContrib + } + }); + } + public runCommand(accessor: ServicesAccessor, _args: any): void { withActiveWebviewBasedWebview(accessor, webview => webview.cut()); } @@ -69,6 +116,17 @@ export class CutWebviewEditorCommand extends Command { export class UndoWebviewEditorCommand extends Command { public static readonly ID = 'editor.action.webvieweditor.undo'; + constructor(contextKeyExpr: ContextKeyExpr) { + super({ + id: UndoWebviewEditorCommand.ID, + precondition: ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey), ContextKeyExpr.not(webviewHasOwnEditFunctionsContextKey)), + kbOpts: { + primary: KeyMod.CtrlCmd | KeyCode.KEY_Z, + weight: KeybindingWeight.EditorContrib + } + }); + } + public runCommand(accessor: ServicesAccessor, args: any): void { withActiveWebviewBasedWebview(accessor, webview => webview.undo()); } @@ -77,17 +135,24 @@ export class UndoWebviewEditorCommand extends Command { export class RedoWebviewEditorCommand extends Command { public static readonly ID = 'editor.action.webvieweditor.redo'; + constructor(contextKeyExpr: ContextKeyExpr) { + super({ + id: RedoWebviewEditorCommand.ID, + precondition: ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey), ContextKeyExpr.not(webviewHasOwnEditFunctionsContextKey)), + kbOpts: { + primary: KeyMod.CtrlCmd | KeyCode.KEY_Y, + secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z], + mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z }, + weight: KeybindingWeight.EditorContrib + } + }); + } + public runCommand(accessor: ServicesAccessor, args: any): void { withActiveWebviewBasedWebview(accessor, webview => webview.redo()); } } -function getActiveWebviewEditor(accessor: ServicesAccessor): WebviewEditor | undefined { - const editorService = accessor.get(IEditorService); - const activeControl = editorService.activeControl as WebviewEditor; - return activeControl.isWebviewEditor ? activeControl : undefined; -} - function withActiveWebviewBasedWebview(accessor: ServicesAccessor, f: (webview: ElectronWebviewBasedWebview) => void): void { const webViewEditor = getActiveWebviewEditor(accessor); if (webViewEditor) { diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index b3963111b9..6d8ae3892b 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -287,6 +287,8 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme this._register(addDisposableListener(this.element!, 'found-in-page', e => { this._hasFindResult.fire(e.result.matches > 0); })); + + this.styledFindWidget(); } } @@ -294,7 +296,7 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme const element = document.createElement('webview'); element.setAttribute('partition', `webview${Date.now()}`); element.setAttribute('webpreferences', 'contextIsolation=yes'); - element.className = `webview ${options.customClasses}`; + element.className = `webview ${options.customClasses || ''}`; element.style.flex = '0 1'; element.style.width = '0'; @@ -341,10 +343,11 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme protected style(): void { super.style(); + this.styledFindWidget(); + } - if (this._webviewFindWidget) { - this._webviewFindWidget.updateTheme(this._webviewThemeDataProvider.getTheme()); - } + private styledFindWidget() { + this._webviewFindWidget?.updateTheme(this._webviewThemeDataProvider.getTheme()); } private readonly _hasFindResult = this._register(new Emitter()); diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewProtocols.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewProtocols.ts index 810c3da9d4..fa8d568edc 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewProtocols.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewProtocols.ts @@ -5,7 +5,7 @@ import * as electron from 'electron'; import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; -import { loadLocalResource } from 'vs/workbench/contrib/webview/common/resourceLoader'; +import { loadLocalResource, WebviewResourceResponse } from 'vs/workbench/contrib/webview/common/resourceLoader'; export function registerFileProtocol( contents: electron.WebContents, @@ -17,13 +17,13 @@ export function registerFileProtocol( contents.session.protocol.registerBufferProtocol(protocol, async (request, callback: any) => { try { const result = await loadLocalResource(URI.parse(request.url), fileService, extensionLocation, getRoots); - if (result.type === 'success') { + if (result.type === WebviewResourceResponse.Type.Success) { return callback({ data: Buffer.from(result.data.buffer), mimeType: result.mimeType }); } - if (result.type === 'access-denied') { + if (result.type === WebviewResourceResponse.Type.AccessDenied) { console.error('Webview: Cannot load resource outside of protocol root'); return callback({ error: -10 /* ACCESS_DENIED: https://cs.chromium.org/chromium/src/net/base/net_error_list.h */ }); } diff --git a/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts b/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts index 16c9634e08..c12eb963d4 100644 --- a/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts +++ b/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts @@ -207,6 +207,7 @@ class WelcomeOverlay extends Disposable { dom.addClass(workbench, 'blur-background'); this._overlayVisible.set(true); this.updateProblemsKey(); + this.updateActivityBarKeys(); this._overlay.focus(); } } @@ -227,6 +228,25 @@ class WelcomeOverlay extends Disposable { } } + private updateActivityBarKeys() { + const ids = ['explorer', 'search', 'git', 'debug', 'extensions']; + const activityBar = document.querySelector('.activitybar .composite-bar'); + if (activityBar instanceof HTMLElement) { + const target = activityBar.getBoundingClientRect(); + const bounds = this._overlay.getBoundingClientRect(); + for (let i = 0; i < ids.length; i++) { + const key = this._overlay.querySelector(`.key.${ids[i]}`) as HTMLElement; + const top = target.top - bounds.top + 50 * i + 13; + key.style.top = top + 'px'; + } + } else { + for (let i = 0; i < ids.length; i++) { + const key = this._overlay.querySelector(`.key.${ids[i]}`) as HTMLElement; + key.style.top = ''; + } + } + } + public hide() { if (this._overlay.style.display !== 'none') { this._overlay.style.display = 'none'; @@ -237,8 +257,11 @@ class WelcomeOverlay extends Disposable { } } -// {SQL CARBON EDIT} -// remove Interface Overview command registrations +/*Registry.as(Extensions.WorkbenchActions) + .registerWorkbenchAction(SyncActionDescriptor.create(WelcomeOverlayAction, WelcomeOverlayAction.ID, WelcomeOverlayAction.LABEL), 'Help: User Interface Overview', localize('help', "Help")); {{SQL CARBON EDIT}} remove interface overview + +Registry.as(Extensions.WorkbenchActions) + .registerWorkbenchAction(SyncActionDescriptor.create(HideWelcomeOverlayAction, HideWelcomeOverlayAction.ID, HideWelcomeOverlayAction.LABEL, { primary: KeyCode.Escape }, OVERLAY_VISIBLE), 'Help: Hide Interface Overview', localize('help', "Help"));*/ // theming diff --git a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.contribution.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.contribution.ts index a0b7f4ee6d..10d152bfdd 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.contribution.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.contribution.ts @@ -12,12 +12,11 @@ import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; Registry.as(ConfigurationExtensions.Configuration) .registerConfiguration({ - 'id': 'workbench', - 'order': 7, - 'title': localize('workbenchConfigurationTitle', "Workbench"), + ...workbenchConfigurationNodeBase, 'properties': { 'workbench.startupEditor': { 'scope': ConfigurationScope.APPLICATION, // Make sure repositories cannot trigger opening a README for tracking. @@ -40,7 +39,7 @@ Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(WelcomePageContribution, LifecyclePhase.Restored); Registry.as(ActionExtensions.WorkbenchActions) - .registerWorkbenchAction(new SyncActionDescriptor(WelcomePageAction, WelcomePageAction.ID, WelcomePageAction.LABEL), 'Help: Welcome', localize('help', "Help")); + .registerWorkbenchAction(SyncActionDescriptor.create(WelcomePageAction, WelcomePageAction.ID, WelcomePageAction.LABEL), 'Help: Welcome', localize('help', "Help")); Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputFactory(WelcomeInputFactory.ID, WelcomeInputFactory); diff --git a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts index 3efd686778..760195d8de 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts @@ -44,6 +44,7 @@ import { IRecentlyOpened, isRecentWorkspace, IRecentWorkspace, IRecentFolder, is import { CancellationToken } from 'vs/base/common/cancellation'; import 'sql/workbench/contrib/welcome/page/browser/az_data_welcome_page'; // {{SQL CARBON EDIT}} import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IProductService } from 'vs/platform/product/common/productService'; const configurationKey = 'workbench.startupEditor'; const oldConfigurationKey = 'workbench.welcome.enabled'; @@ -264,7 +265,9 @@ class WelcomePage extends Disposable { @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @ILifecycleService lifecycleService: ILifecycleService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IHostService private readonly hostService: IHostService + @IHostService private readonly hostService: IHostService, + @IProductService private readonly productService: IProductService, + ) { super(); this._register(lifecycleService.onShutdown(() => this.dispose())); @@ -300,6 +303,11 @@ class WelcomePage extends Disposable { this.configurationService.updateValue(configurationKey, showOnStartup.checked ? 'welcomePage' : 'newUntitledFile', ConfigurationTarget.USER); }); + const prodName = container.querySelector('.welcomePage .title .caption') as HTMLElement; + if (prodName) { + prodName.innerHTML = this.productService.nameLong; + } + recentlyOpened.then(({ workspaces }) => { // Filter out the current workspace workspaces = workspaces.filter(recent => !this.contextService.isCurrentWorkspace(isRecentWorkspace(recent) ? recent.workspace : recent.folderUri)); diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution.ts index 7000c73ef2..8036730058 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution.ts @@ -20,7 +20,7 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; Registry.as(EditorExtensions.Editors) - .registerEditor(new EditorDescriptor( + .registerEditor(EditorDescriptor.create( WalkThroughPart, WalkThroughPart.ID, localize('walkThrough.editor.label', "Interactive Playground"), @@ -29,7 +29,7 @@ Registry.as(EditorExtensions.Editors) Registry.as(Extensions.WorkbenchActions) .registerWorkbenchAction( - new SyncActionDescriptor(EditorWalkThroughAction, EditorWalkThroughAction.ID, EditorWalkThroughAction.LABEL), + SyncActionDescriptor.create(EditorWalkThroughAction, EditorWalkThroughAction.ID, EditorWalkThroughAction.LABEL), 'Help: Interactive Playground', localize('help', "Help")); Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(EditorWalkThroughInputFactory.ID, EditorWalkThroughInputFactory); diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.css b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.css index 967e230cdd..cfaa3188a4 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.css +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.css @@ -8,6 +8,7 @@ padding: 10px 20px; line-height: 22px; user-select: initial; + -webkit-user-select: initial; } .monaco-workbench .part.editor > .content .walkThroughContent img { diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts index b1e2127956..fc58876023 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts @@ -5,6 +5,7 @@ import 'vs/css!./walkThroughPart'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; +import { EventType as TouchEventType, GestureEvent, Gesture } from 'vs/base/browser/touch'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import * as strings from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; @@ -37,6 +38,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { Dimension, size } from 'vs/base/browser/dom'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { domEvent } from 'vs/base/browser/event'; export const WALK_THROUGH_FOCUS = new RawContextKey('interactivePlaygroundFocus', false); @@ -58,11 +60,11 @@ export class WalkThroughPart extends BaseEditor { private readonly disposables = new DisposableStore(); private contentDisposables: IDisposable[] = []; - private content: HTMLDivElement; - private scrollbar: DomScrollableElement; + private content!: HTMLDivElement; + private scrollbar!: DomScrollableElement; private editorFocus: IContextKey; - private lastFocus: HTMLElement; - private size: Dimension; + private lastFocus: HTMLElement | undefined; + private size: Dimension | undefined; private editorMemento: IEditorMemento; constructor( @@ -112,6 +114,14 @@ export class WalkThroughPart extends BaseEditor { } } + private onTouchChange(event: GestureEvent) { + event.preventDefault(); + event.stopPropagation(); + + const scrollPosition = this.scrollbar.getScrollPosition(); + this.scrollbar.setScrollPosition({ scrollTop: scrollPosition.scrollTop - event.translationY }); + } + private addEventListener(element: E, type: K, listener: (this: E, ev: HTMLElementEventMap[K]) => any, useCapture?: boolean): IDisposable; private addEventListener(element: E, type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): IDisposable; private addEventListener(element: E, type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): IDisposable { @@ -396,6 +406,8 @@ export class WalkThroughPart extends BaseEditor { this.scrollbar.scanDomNode(); this.loadTextEditorViewState(input); this.updatedScrollPosition(); + this.contentDisposables.push(Gesture.addTarget(innerContent)); + this.contentDisposables.push(domEvent(innerContent, TouchEventType.Change)(this.onTouchChange, this, this.disposables)); }); } diff --git a/src/vs/workbench/electron-browser/desktop.contribution.ts b/src/vs/workbench/electron-browser/desktop.contribution.ts index 6c4199cba0..4b913b299b 100644 --- a/src/vs/workbench/electron-browser/desktop.contribution.ts +++ b/src/vs/workbench/electron-browser/desktop.contribution.ts @@ -21,6 +21,7 @@ import { IsMacContext, HasMacNativeTabsContext } from 'vs/workbench/browser/cont import { NoEditorsVisibleContext, SingleEditorGroupsContext } from 'vs/workbench/common/editor'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; +import product from 'vs/platform/product/common/product'; import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; // {{SQL CARBON EDIT}} add import @@ -28,20 +29,20 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten (function registerActions(): void { const registry = Registry.as(Extensions.WorkbenchActions); - // Actions: View - (function registerViewActions(): void { + // Actions: Zoom + (function registerZoomActions(): void { const viewCategory = nls.localize('view', "View"); - registry.registerWorkbenchAction(new SyncActionDescriptor(ZoomInAction, ZoomInAction.ID, ZoomInAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_EQUAL, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_EQUAL, KeyMod.CtrlCmd | KeyCode.NUMPAD_ADD] }), 'View: Zoom In', viewCategory); - registry.registerWorkbenchAction(new SyncActionDescriptor(ZoomOutAction, ZoomOutAction.ID, ZoomOutAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_MINUS, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_MINUS, KeyMod.CtrlCmd | KeyCode.NUMPAD_SUBTRACT], linux: { primary: KeyMod.CtrlCmd | KeyCode.US_MINUS, secondary: [KeyMod.CtrlCmd | KeyCode.NUMPAD_SUBTRACT] } }), 'View: Zoom Out', viewCategory); - registry.registerWorkbenchAction(new SyncActionDescriptor(ZoomResetAction, ZoomResetAction.ID, ZoomResetAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.NUMPAD_0 }), 'View: Reset Zoom', viewCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(ZoomInAction, ZoomInAction.ID, ZoomInAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_EQUAL, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_EQUAL, KeyMod.CtrlCmd | KeyCode.NUMPAD_ADD] }), 'View: Zoom In', viewCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(ZoomOutAction, ZoomOutAction.ID, ZoomOutAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_MINUS, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_MINUS, KeyMod.CtrlCmd | KeyCode.NUMPAD_SUBTRACT], linux: { primary: KeyMod.CtrlCmd | KeyCode.US_MINUS, secondary: [KeyMod.CtrlCmd | KeyCode.NUMPAD_SUBTRACT] } }), 'View: Zoom Out', viewCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(ZoomResetAction, ZoomResetAction.ID, ZoomResetAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.NUMPAD_0 }), 'View: Reset Zoom', viewCategory); })(); // Actions: Window (function registerWindowActions(): void { - registry.registerWorkbenchAction(new SyncActionDescriptor(CloseCurrentWindowAction, CloseCurrentWindowAction.ID, CloseCurrentWindowAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_W }), 'Close Window'); - registry.registerWorkbenchAction(new SyncActionDescriptor(SwitchWindow, SwitchWindow.ID, SwitchWindow.LABEL, { primary: 0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_W } }), 'Switch Window...'); - registry.registerWorkbenchAction(new SyncActionDescriptor(QuickSwitchWindow, QuickSwitchWindow.ID, QuickSwitchWindow.LABEL), 'Quick Switch Window...'); + registry.registerWorkbenchAction(SyncActionDescriptor.create(CloseCurrentWindowAction, CloseCurrentWindowAction.ID, CloseCurrentWindowAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_W }), 'Close Window'); + registry.registerWorkbenchAction(SyncActionDescriptor.create(SwitchWindow, SwitchWindow.ID, SwitchWindow.LABEL, { primary: 0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_W } }), 'Switch Window...'); + registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickSwitchWindow, QuickSwitchWindow.ID, QuickSwitchWindow.LABEL), 'Quick Switch Window...'); KeybindingsRegistry.registerCommandAndKeybindingRule({ id: CloseCurrentWindowAction.ID, // close the window when the last editor is closed by reusing the same keybinding @@ -91,10 +92,9 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten // Actions: Developer (function registerDeveloperActions(): void { const developerCategory = nls.localize('developer', "Developer"); - registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleSharedProcessAction, ToggleSharedProcessAction.ID, ToggleSharedProcessAction.LABEL), 'Developer: Toggle Shared Process', developerCategory); - registry.registerWorkbenchAction(new SyncActionDescriptor(ConfigureRuntimeArgumentsAction, ConfigureRuntimeArgumentsAction.ID, ConfigureRuntimeArgumentsAction.LABEL), 'Developer: Configure Runtime Arguments', developerCategory); - registry.registerWorkbenchAction(new SyncActionDescriptor(ReloadWindowWithExtensionsDisabledAction, ReloadWindowWithExtensionsDisabledAction.ID, ReloadWindowWithExtensionsDisabledAction.LABEL), 'Developer: Reload With Extensions Disabled', developerCategory); - registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleDevToolsAction, ToggleDevToolsAction.ID, ToggleDevToolsAction.LABEL), 'Developer: Toggle Developer Tools', developerCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleSharedProcessAction, ToggleSharedProcessAction.ID, ToggleSharedProcessAction.LABEL), 'Developer: Toggle Shared Process', developerCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(ReloadWindowWithExtensionsDisabledAction, ReloadWindowWithExtensionsDisabledAction.ID, ReloadWindowWithExtensionsDisabledAction.LABEL), 'Developer: Reload With Extensions Disabled', developerCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleDevToolsAction, ToggleDevToolsAction.ID, ToggleDevToolsAction.LABEL), 'Developer: Toggle Developer Tools', developerCategory); KeybindingsRegistry.registerKeybindingRule({ id: ToggleDevToolsAction.ID, @@ -104,6 +104,12 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_I } }); })(); + + // Actions: Runtime Arguments + (function registerRuntimeArgumentsAction(): void { + const preferencesCategory = nls.localize('preferences', "Preferences"); + registry.registerWorkbenchAction(SyncActionDescriptor.create(ConfigureRuntimeArgumentsAction, ConfigureRuntimeArgumentsAction.ID, ConfigureRuntimeArgumentsAction.LABEL), 'Preferences: Configure Runtime Arguments', preferencesCategory); + })(); })(); // Menu @@ -164,14 +170,16 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten order: 3 }); - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '3_feedback', - command: { - id: 'workbench.action.openIssueReporter', - title: nls.localize({ key: 'miReportIssue', comment: ['&& denotes a mnemonic', 'Translate this to "Report Issue in English" in all languages please!'] }, "Report &&Issue") - }, - order: 3 - }); + if (!!product.reportIssueUrl) { + MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '3_feedback', + command: { + id: 'workbench.action.openIssueReporter', + title: nls.localize({ key: 'miReportIssue', comment: ['&& denotes a mnemonic', 'Translate this to "Report Issue in English" in all languages please!'] }, "Report &&Issue") + }, + order: 3 + }); + } // Tools MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { diff --git a/src/vs/workbench/electron-browser/desktop.main.ts b/src/vs/workbench/electron-browser/desktop.main.ts index e03cb33c74..cdd5c24de9 100644 --- a/src/vs/workbench/electron-browser/desktop.main.ts +++ b/src/vs/workbench/electron-browser/desktop.main.ts @@ -15,7 +15,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; -import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; +import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { stat } from 'vs/base/node/pfs'; @@ -43,7 +43,7 @@ import { FileService } from 'vs/platform/files/common/fileService'; import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { REMOTE_FILE_SYSTEM_CHANNEL_NAME, RemoteExtensionsFileSystemProvider } from 'vs/platform/remote/common/remoteAgentFileSystemChannel'; +import { REMOTE_FILE_SYSTEM_CHANNEL_NAME, RemoteFileSystemProvider } from 'vs/platform/remote/common/remoteAgentFileSystemChannel'; import { ConfigurationCache } from 'vs/workbench/services/configuration/node/configurationCache'; import { SpdLogService } from 'vs/platform/log/node/spdlogService'; import { SignService } from 'vs/platform/sign/node/signService'; @@ -56,12 +56,12 @@ import { ElectronEnvironmentService, IElectronEnvironmentService } from 'vs/work class DesktopMain extends Disposable { - private readonly environmentService: WorkbenchEnvironmentService; + private readonly environmentService: NativeWorkbenchEnvironmentService; constructor(private configuration: IWindowConfiguration) { super(); - this.environmentService = new WorkbenchEnvironmentService(configuration, configuration.execPath, configuration.windowId); + this.environmentService = new NativeWorkbenchEnvironmentService(configuration, configuration.execPath, configuration.windowId); this.init(); } @@ -217,7 +217,7 @@ class DesktopMain extends Disposable { const connection = remoteAgentService.getConnection(); if (connection) { const channel = connection.getChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME); - const remoteFileSystemProvider = this._register(new RemoteExtensionsFileSystemProvider(channel, remoteAgentService.getEnvironment())); + const remoteFileSystemProvider = this._register(new RemoteFileSystemProvider(channel, remoteAgentService.getEnvironment())); fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider); } diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index d72cf86bc6..0ee0e07b44 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -11,7 +11,7 @@ import * as DOM from 'vs/base/browser/dom'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IAction } from 'vs/base/common/actions'; import { IFileService } from 'vs/platform/files/common/files'; -import { toResource, IUntitledResourceInput, SideBySideEditor, pathsToEditors } from 'vs/workbench/common/editor'; +import { toResource, IUntitledTextResourceInput, SideBySideEditor, pathsToEditors } from 'vs/workbench/common/editor'; import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWindowSettings, IOpenFileRequest, IWindowsConfiguration, IAddFoldersRequest, IRunActionInWindowRequest, IRunKeybindingInWindowRequest, getTitleBarStyle } from 'vs/platform/windows/common/windows'; @@ -21,7 +21,7 @@ import * as browser from 'vs/base/browser/browser'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IResourceInput } from 'vs/platform/editor/common/editor'; import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; -import { ipcRenderer as ipc, webFrame, crashReporter, Event } from 'electron'; +import { ipcRenderer as ipc, webFrame, crashReporter, Event as IpcEvent } from 'electron'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction, SubmenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -60,6 +60,9 @@ import { ITunnelService, extractLocalHostUriMetaDataForPortMapping } from 'vs/pl import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; +import { IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { Event } from 'vs/base/common/event'; export class ElectronWindow extends Disposable { @@ -67,14 +70,16 @@ export class ElectronWindow extends Disposable { private readonly touchBarDisposables = this._register(new DisposableStore()); private lastInstalledTouchedBar: ICommandAction[][] | undefined; - private customTitleContextMenuDisposable = this._register(new DisposableStore()); + private readonly customTitleContextMenuDisposable = this._register(new DisposableStore()); private previousConfiguredZoomLevel: number | undefined; - private addFoldersScheduler: RunOnceScheduler; - private pendingFoldersToAdd: URI[]; + private readonly addFoldersScheduler = this._register(new RunOnceScheduler(() => this.doAddFolders(), 100)); + private pendingFoldersToAdd: URI[] = []; - private closeEmptyWindowScheduler: RunOnceScheduler = this._register(new RunOnceScheduler(() => this.onAllEditorsClosed(), 50)); + private readonly closeEmptyWindowScheduler: RunOnceScheduler = this._register(new RunOnceScheduler(() => this.onAllEditorsClosed(), 50)); + + private isDocumentedEdited = false; constructor( @IEditorService private readonly editorService: EditorServiceImpl, @@ -99,13 +104,12 @@ export class ElectronWindow extends Disposable { @IElectronService private readonly electronService: IElectronService, @ITunnelService private readonly tunnelService: ITunnelService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, - @IElectronEnvironmentService private readonly electronEnvironmentService: IElectronEnvironmentService + @IElectronEnvironmentService private readonly electronEnvironmentService: IElectronEnvironmentService, + @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService ) { super(); - this.pendingFoldersToAdd = []; - this.addFoldersScheduler = this._register(new RunOnceScheduler(() => this.doAddFolders(), 100)); - this.registerListeners(); this.create(); } @@ -123,7 +127,7 @@ export class ElectronWindow extends Disposable { }); // Support runAction event - ipc.on('vscode:runAction', async (event: Event, request: IRunActionInWindowRequest) => { + ipc.on('vscode:runAction', async (event: IpcEvent, request: IRunActionInWindowRequest) => { const args: unknown[] = request.args || []; // If we run an action from the touchbar, we fill in the currently active resource @@ -154,27 +158,27 @@ export class ElectronWindow extends Disposable { }); // Support runKeybinding event - ipc.on('vscode:runKeybinding', (event: Event, request: IRunKeybindingInWindowRequest) => { + ipc.on('vscode:runKeybinding', (event: IpcEvent, request: IRunKeybindingInWindowRequest) => { if (document.activeElement) { this.keybindingService.dispatchByUserSettingsLabel(request.userSettingsLabel, document.activeElement); } }); // Error reporting from main - ipc.on('vscode:reportError', (event: Event, error: string) => { + ipc.on('vscode:reportError', (event: IpcEvent, error: string) => { if (error) { errors.onUnexpectedError(JSON.parse(error)); } }); // Support openFiles event for existing and new files - ipc.on('vscode:openFiles', (event: Event, request: IOpenFileRequest) => this.onOpenFiles(request)); + ipc.on('vscode:openFiles', (event: IpcEvent, request: IOpenFileRequest) => this.onOpenFiles(request)); // Support addFolders event if we have a workspace opened - ipc.on('vscode:addFolders', (event: Event, request: IAddFoldersRequest) => this.onAddFoldersRequest(request)); + ipc.on('vscode:addFolders', (event: IpcEvent, request: IAddFoldersRequest) => this.onAddFoldersRequest(request)); // Message support - ipc.on('vscode:showInfoMessage', (event: Event, message: string) => { + ipc.on('vscode:showInfoMessage', (event: IpcEvent, message: string) => { this.notificationService.info(message); }); @@ -212,7 +216,7 @@ export class ElectronWindow extends Disposable { }); // keyboard layout changed event - ipc.on('vscode:accessibilitySupportChanged', (event: Event, accessibilitySupportEnabled: boolean) => { + ipc.on('vscode:accessibilitySupportChanged', (event: IpcEvent, accessibilitySupportEnabled: boolean) => { this.accessibilityService.setAccessibilitySupport(accessibilitySupportEnabled ? AccessibilitySupport.Enabled : AccessibilitySupport.Disabled); }); @@ -261,6 +265,34 @@ export class ElectronWindow extends Disposable { this.electronService.handleTitleDoubleClick(); })); } + + // Document edited (macOS only): indicate for dirty working copies + if (isMacintosh) { + this._register(this.workingCopyService.onDidChangeDirty(workingCopy => { + const gotDirty = workingCopy.isDirty(); + if (gotDirty && !!(workingCopy.capabilities & WorkingCopyCapabilities.AutoSave) && this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) { + return; // do not indicate dirty of working copies that are auto saved after short delay + } + + if ((!this.isDocumentedEdited && gotDirty) || (this.isDocumentedEdited && !gotDirty)) { + const hasDirtyFiles = this.workingCopyService.hasDirty; + this.isDocumentedEdited = hasDirtyFiles; + + this.electronService.setDocumentEdited(hasDirtyFiles); + } + })); + } + + this._register(Event.any( + Event.map(Event.filter(this.electronService.onWindowMaximize, id => id === this.electronEnvironmentService.windowId), () => true), + Event.map(Event.filter(this.electronService.onWindowUnmaximize, id => id === this.electronEnvironmentService.windowId), () => false) + )(e => this.onDidChangeMaximized(e))); + + this.onDidChangeMaximized(this.environmentService.configuration.maximized ?? false); + } + + private onDidChangeMaximized(maximized: boolean): void { + this.layoutService.updateWindowMaximizedState(maximized); } private onDidVisibleEditorsChange(): void { @@ -398,29 +430,23 @@ export class ElectronWindow extends Disposable { throw new Error('Prevented call to window.open(). Use IOpenerService instead!'); }; - // Handle internal open() calls - this.openerService.registerOpener({ - open: async (resource: URI, options?: OpenOptions): Promise => { - - // If either the caller wants to open externally or the - // scheme is one where we prefer to open externally - // we handle this resource by delegating the opening to - // the main process to prevent window focus issues. - if (this.shouldOpenExternal(resource, options)) { - const { resolved } = await this.openerService.resolveExternalUri(resource, options); - const success = await this.electronService.openExternal(encodeURI(resolved.toString(true))); - if (!success && resolved.scheme === Schemas.file) { + // Handle external open() calls + this.openerService.setExternalOpener({ + openExternal: async (href: string) => { + const success = await this.electronService.openExternal(href); + if (!success) { + const fileCandidate = URI.parse(href); + if (fileCandidate.scheme === Schemas.file) { // if opening failed, and this is a file, we can still try to reveal it - await this.electronService.showItemInFolder(resolved.fsPath); + await this.electronService.showItemInFolder(fileCandidate.fsPath); } - - return true; } - return false; // not handled by us + return true; } }); + // Register external URI resolver this.openerService.registerExternalUriResolver({ resolveExternalUri: async (uri: URI, options?: OpenOptions) => { if (options?.allowTunneling) { @@ -440,12 +466,6 @@ export class ElectronWindow extends Disposable { }); } - private shouldOpenExternal(resource: URI, options?: OpenOptions) { - const scheme = resource.scheme.toLowerCase(); - const preferOpenExternal = (scheme === Schemas.mailto || scheme === Schemas.http || scheme === Schemas.https); - return options?.openExternal || preferOpenExternal; - } - private updateTouchbarMenu(): void { if (!isMacintosh) { return; // macOS only @@ -628,7 +648,7 @@ export class ElectronWindow extends Disposable { }); } - private async openResources(resources: Array, diffMode: boolean): Promise { + private async openResources(resources: Array, diffMode: boolean): Promise { await this.lifecycleService.when(LifecyclePhase.Ready); // In diffMode we open 2 resources as diff diff --git a/src/vs/workbench/services/accessibility/node/accessibilityService.ts b/src/vs/workbench/services/accessibility/node/accessibilityService.ts index 2d3b3fba1e..8f449ecb16 100644 --- a/src/vs/workbench/services/accessibility/node/accessibilityService.ts +++ b/src/vs/workbench/services/accessibility/node/accessibilityService.ts @@ -24,14 +24,16 @@ export class AccessibilityService extends AbstractAccessibilityService implement _serviceBrand: undefined; private _accessibilitySupport = AccessibilitySupport.Unknown; + private didSendTelemetry = false; constructor( - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @IContextKeyService readonly contextKeyService: IContextKeyService, - @IConfigurationService readonly configurationService: IConfigurationService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IContextKeyService contextKeyService: IContextKeyService, + @IConfigurationService configurationService: IConfigurationService, @ITelemetryService private readonly _telemetryService: ITelemetryService ) { super(contextKeyService, configurationService); + this.setAccessibilitySupport(environmentService.configuration.accessibilitySupport ? AccessibilitySupport.Enabled : AccessibilitySupport.Disabled); } alwaysUnderlineAccessKeys(): Promise { @@ -61,17 +63,13 @@ export class AccessibilityService extends AbstractAccessibilityService implement this._accessibilitySupport = accessibilitySupport; this._onDidChangeAccessibilitySupport.fire(); - if (accessibilitySupport === AccessibilitySupport.Enabled) { + if (!this.didSendTelemetry && accessibilitySupport === AccessibilitySupport.Enabled) { this._telemetryService.publicLog2('accessibility', { enabled: true }); + this.didSendTelemetry = true; } } getAccessibilitySupport(): AccessibilitySupport { - if (this._accessibilitySupport === AccessibilitySupport.Unknown) { - const config = this.environmentService.configuration; - this._accessibilitySupport = config?.accessibilitySupport ? AccessibilitySupport.Enabled : AccessibilitySupport.Disabled; - } - return this._accessibilitySupport; } } diff --git a/src/vs/platform/auth/common/authTokenService.ts b/src/vs/workbench/services/authToken/browser/authTokenService.ts similarity index 51% rename from src/vs/platform/auth/common/authTokenService.ts rename to src/vs/workbench/services/authToken/browser/authTokenService.ts index 23c2c3565f..3622da7a0d 100644 --- a/src/vs/platform/auth/common/authTokenService.ts +++ b/src/vs/workbench/services/authToken/browser/authTokenService.ts @@ -3,12 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth'; import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { URI } from 'vs/base/common/uri'; const SERVICE_NAME = 'VS Code'; const ACCOUNT = 'MyAccount'; @@ -16,55 +17,51 @@ const ACCOUNT = 'MyAccount'; export class AuthTokenService extends Disposable implements IAuthTokenService { _serviceBrand: undefined; - private _status: AuthTokenStatus = AuthTokenStatus.Disabled; + private _status: AuthTokenStatus = AuthTokenStatus.Initializing; get status(): AuthTokenStatus { return this._status; } private _onDidChangeStatus: Emitter = this._register(new Emitter()); readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; + readonly _onDidGetCallback: Emitter = this._register(new Emitter()); + constructor( @ICredentialsService private readonly credentialsService: ICredentialsService, - @IProductService productService: IProductService, - @IConfigurationService configurationService: IConfigurationService, + @IQuickInputService private readonly quickInputService: IQuickInputService ) { super(); - if (productService.settingsSyncStoreUrl && configurationService.getValue('configurationSync.enableAuth')) { - this._status = AuthTokenStatus.Inactive; - this.getToken().then(token => { - if (token) { - this.setStatus(AuthTokenStatus.Active); - } - }); - } + this.getToken().then(token => { + if (token) { + this.setStatus(AuthTokenStatus.SignedIn); + } else { + this.setStatus(AuthTokenStatus.SignedOut); + } + }); } - getToken(): Promise { - if (this.status === AuthTokenStatus.Disabled) { - throw new Error('Not enabled'); + async getToken(): Promise { + const token = await this.credentialsService.getPassword(SERVICE_NAME, ACCOUNT); + if (token) { + return token; } - return this.credentialsService.getPassword(SERVICE_NAME, ACCOUNT); + + return undefined; // {{SQL CARBON EDIT}} strict-null-check } - async updateToken(token: string): Promise { - if (this.status === AuthTokenStatus.Disabled) { - throw new Error('Not enabled'); + async login(): Promise { + const token = await this.quickInputService.input({ placeHolder: localize('enter token', "Please provide the auth bearer token"), ignoreFocusLost: true, }); + if (token) { + await this.credentialsService.setPassword(SERVICE_NAME, ACCOUNT, token); + this.setStatus(AuthTokenStatus.SignedIn); } - await this.credentialsService.setPassword(SERVICE_NAME, ACCOUNT, token); - this.setStatus(AuthTokenStatus.Active); } async refreshToken(): Promise { - if (this.status === AuthTokenStatus.Disabled) { - throw new Error('Not enabled'); - } - await this.deleteToken(); + await this.logout(); } - async deleteToken(): Promise { - if (this.status === AuthTokenStatus.Disabled) { - throw new Error('Not enabled'); - } + async logout(): Promise { await this.credentialsService.deletePassword(SERVICE_NAME, ACCOUNT); - this.setStatus(AuthTokenStatus.Inactive); + this.setStatus(AuthTokenStatus.SignedOut); } private setStatus(status: AuthTokenStatus): void { @@ -75,4 +72,3 @@ export class AuthTokenService extends Disposable implements IAuthTokenService { } } - diff --git a/src/vs/workbench/services/authToken/electron-browser/authTokenService.ts b/src/vs/workbench/services/authToken/electron-browser/authTokenService.ts index c9537db934..4719b29741 100644 --- a/src/vs/workbench/services/authToken/electron-browser/authTokenService.ts +++ b/src/vs/workbench/services/authToken/electron-browser/authTokenService.ts @@ -9,6 +9,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth'; +import { URI } from 'vs/base/common/uri'; export class AuthTokenService extends Disposable implements IAuthTokenService { @@ -16,41 +17,43 @@ export class AuthTokenService extends Disposable implements IAuthTokenService { private readonly channel: IChannel; - private _status: AuthTokenStatus = AuthTokenStatus.Disabled; + private _status: AuthTokenStatus = AuthTokenStatus.Initializing; get status(): AuthTokenStatus { return this._status; } private _onDidChangeStatus: Emitter = this._register(new Emitter()); readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; + readonly _onDidGetCallback: Emitter = this._register(new Emitter()); + constructor( - @ISharedProcessService sharedProcessService: ISharedProcessService + @ISharedProcessService sharedProcessService: ISharedProcessService, ) { super(); this.channel = sharedProcessService.getChannel('authToken'); - this.channel.call('_getInitialStatus').then(status => { - this.updateStatus(status); - this._register(this.channel.listen('onDidChangeStatus')(status => this.updateStatus(status))); - }); + this._register(this.channel.listen('onDidChangeStatus')(status => this.updateStatus(status))); + this.channel.call('_getInitialStatus').then(status => this.updateStatus(status)); } getToken(): Promise { return this.channel.call('getToken'); } - updateToken(token: string): Promise { - return this.channel.call('updateToken', [token]); + login(): Promise { + return this.channel.call('login'); } refreshToken(): Promise { return this.channel.call('getToken'); } - deleteToken(): Promise { - return this.channel.call('deleteToken'); + logout(): Promise { + return this.channel.call('logout'); } private async updateStatus(status: AuthTokenStatus): Promise { - this._status = status; - this._onDidChangeStatus.fire(status); + if (status !== AuthTokenStatus.Initializing) { + this._status = status; + this._onDidChangeStatus.fire(status); + } } } diff --git a/src/vs/workbench/services/backup/common/backup.ts b/src/vs/workbench/services/backup/common/backup.ts index e07605e486..9a8948149c 100644 --- a/src/vs/workbench/services/backup/common/backup.ts +++ b/src/vs/workbench/services/backup/common/backup.ts @@ -6,8 +6,6 @@ import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ITextBufferFactory, ITextSnapshot } from 'vs/editor/common/model'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { joinPath, relativePath } from 'vs/base/common/resources'; export const IBackupFileService = createDecorator('backupFileService'); @@ -88,7 +86,3 @@ export interface IBackupFileService { */ discardAllWorkspaceBackups(): Promise; } - -export function toBackupWorkspaceResource(backupWorkspacePath: string, environmentService: IEnvironmentService): URI { - return joinPath(environmentService.userRoamingDataHome, relativePath(URI.file(environmentService.userDataPath), URI.file(backupWorkspacePath))!); -} diff --git a/src/vs/workbench/services/backup/common/backupFileService.ts b/src/vs/workbench/services/backup/common/backupFileService.ts index 26a46a7e4d..c33c52589c 100644 --- a/src/vs/workbench/services/backup/common/backupFileService.ts +++ b/src/vs/workbench/services/backup/common/backupFileService.ts @@ -429,13 +429,13 @@ export class InMemoryBackupFileService implements IBackupFileService { return Promise.resolve(); } - resolveBackupContent(backupResource: URI): Promise> { + async resolveBackupContent(backupResource: URI): Promise> { const snapshot = this.backups.get(backupResource.toString()); if (snapshot) { - return Promise.resolve({ value: createTextBufferFactoryFromSnapshot(snapshot) }); + return { value: createTextBufferFactoryFromSnapshot(snapshot) }; } - return Promise.reject('Unexpected backup resource to resolve'); + throw new Error('Unexpected backup resource to resolve'); } getWorkspaceFileBackups(): Promise { diff --git a/src/vs/workbench/services/backup/electron-browser/backup.ts b/src/vs/workbench/services/backup/electron-browser/backup.ts new file mode 100644 index 0000000000..3c3b9887ee --- /dev/null +++ b/src/vs/workbench/services/backup/electron-browser/backup.ts @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { joinPath, relativePath } from 'vs/base/common/resources'; + +export function toBackupWorkspaceResource(backupWorkspacePath: string, environmentService: IEnvironmentService): URI { + return joinPath(environmentService.userRoamingDataHome, relativePath(URI.file(environmentService.userDataPath), URI.file(backupWorkspacePath))!); +} diff --git a/src/vs/workbench/services/backup/test/node/backupFileService.test.ts b/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts similarity index 99% rename from src/vs/workbench/services/backup/test/node/backupFileService.test.ts rename to src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts index 294945d5d3..b52720a9e5 100644 --- a/src/vs/workbench/services/backup/test/node/backupFileService.test.ts +++ b/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts @@ -20,7 +20,7 @@ import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { FileService } from 'vs/platform/files/common/fileService'; import { NullLogService } from 'vs/platform/log/common/log'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; -import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; +import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; import { IFileService } from 'vs/platform/files/common/files'; @@ -46,7 +46,7 @@ const fooBackupPath = path.join(workspaceBackupPath, 'file', hashPath(fooFile)); const barBackupPath = path.join(workspaceBackupPath, 'file', hashPath(barFile)); const untitledBackupPath = path.join(workspaceBackupPath, 'untitled', hashPath(untitledFile)); -class TestBackupEnvironmentService extends WorkbenchEnvironmentService { +class TestBackupEnvironmentService extends NativeWorkbenchEnvironmentService { constructor(backupPath: string) { super({ ...parseArgs(process.argv, OPTIONS), ...{ backupPath, 'user-data-dir': userdataDir } } as IWindowConfiguration, process.execPath, 0); diff --git a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts index c132e63e7a..c7b6adeeec 100644 --- a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts +++ b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts @@ -226,17 +226,18 @@ class BulkEditModel implements IDisposable { } } -export type Edit = ResourceFileEdit | ResourceTextEdit; +type Edit = ResourceFileEdit | ResourceTextEdit; -export class BulkEdit { +class BulkEdit { - private _edits: Edit[] = []; - private _editor: ICodeEditor | undefined; - private _progress: IProgress; + private readonly _edits: Edit[] = []; + private readonly _editor: ICodeEditor | undefined; + private readonly _progress: IProgress; constructor( editor: ICodeEditor | undefined, progress: IProgress | undefined, + edits: Edit[], @ILogService private readonly _logService: ILogService, @ITextModelService private readonly _textModelService: ITextModelService, @IFileService private readonly _fileService: IFileService, @@ -246,14 +247,7 @@ export class BulkEdit { ) { this._editor = editor; this._progress = progress || emptyProgress; - } - - add(edits: Edit[] | Edit): void { - if (Array.isArray(edits)) { - this._edits.push(...edits); - } else { - this._edits.push(edits); - } + this._edits = edits; } ariaMessage(): string { @@ -419,8 +413,11 @@ export class BulkEditService implements IBulkEditService { // If the code editor is readonly still allow bulk edits to be applied #68549 codeEditor = undefined; } - const bulkEdit = new BulkEdit(codeEditor, options.progress, this._logService, this._textModelService, this._fileService, this._textFileService, this._labelService, this._configurationService); - bulkEdit.add(edits); + const bulkEdit = new BulkEdit( + codeEditor, options.progress, edits, + this._logService, this._textModelService, this._fileService, this._textFileService, this._labelService, this._configurationService + ); + return bulkEdit.perform().then(() => { return { ariaSummary: bulkEdit.ariaMessage() }; diff --git a/src/vs/workbench/services/clipboard/browser/clipboardService.ts b/src/vs/workbench/services/clipboard/browser/clipboardService.ts index b30f50f6ba..9b2fd14606 100644 --- a/src/vs/workbench/services/clipboard/browser/clipboardService.ts +++ b/src/vs/workbench/services/clipboard/browser/clipboardService.ts @@ -18,7 +18,28 @@ export class BrowserClipboardService implements IClipboardService { return; // TODO@sbatten } - return navigator.clipboard.writeText(text); + if (navigator.clipboard && navigator.clipboard.writeText) { + return navigator.clipboard.writeText(text); + } else { + const activeElement = document.activeElement; + const newTextarea = document.createElement('textarea'); + newTextarea.className = 'clipboard-copy'; + newTextarea.style.visibility = 'false'; + newTextarea.style.height = '1px'; + newTextarea.style.width = '1px'; + newTextarea.setAttribute('aria-hidden', 'true'); + newTextarea.style.position = 'absolute'; + newTextarea.style.top = '-1000'; + newTextarea.style.left = '-1000'; + document.body.appendChild(newTextarea); + newTextarea.value = text; + newTextarea.focus(); + newTextarea.select(); + document.execCommand('copy'); + activeElement.focus(); + document.body.removeChild(newTextarea); + } + return; } async readText(type?: string): Promise { diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index e133b052db..136ab47c72 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -33,7 +33,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic public _serviceBrand: undefined; - private workspace: Workspace; + private workspace!: Workspace; private completeWorkspaceBarrier: Barrier; private readonly configurationCache: IConfigurationCache; private _configuration: Configuration; diff --git a/src/vs/workbench/services/configuration/common/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts index 41281c2513..54a4b39860 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -158,7 +158,7 @@ export class ConfigurationEditingService { writeConfiguration(target: EditableConfigurationTarget, value: IConfigurationValue, options: IConfigurationEditingOptions = {}): Promise { const operation = this.getConfigurationEditOperation(target, value, options.scopes || {}); return Promise.resolve(this.queue.queue(() => this.doWriteConfiguration(operation, options) // queue up writes to prevent race conditions - .then(() => null, + .then(() => { }, error => { if (!options.donotNotifyError) { this.onError(error, operation, options.scopes); @@ -409,7 +409,7 @@ export class ConfigurationEditingService { return false; } const parseErrors: json.ParseError[] = []; - json.parse(model.getValue(), parseErrors); + json.parse(model.getValue(), parseErrors, { allowTrailingComma: true, allowEmptyContent: true }); return parseErrors.length > 0; } @@ -436,7 +436,7 @@ export class ConfigurationEditingService { } if (target === EditableConfigurationTarget.WORKSPACE) { - if (!operation.workspaceStandAloneConfigurationKey) { + if (!operation.workspaceStandAloneConfigurationKey && !OVERRIDE_PROPERTY_PATTERN.test(operation.key)) { const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); if (configurationProperties[operation.key].scope === ConfigurationScope.APPLICATION) { return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_APPLICATION, target, operation); @@ -452,7 +452,7 @@ export class ConfigurationEditingService { return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_TARGET, target, operation); } - if (!operation.workspaceStandAloneConfigurationKey) { + if (!operation.workspaceStandAloneConfigurationKey && !OVERRIDE_PROPERTY_PATTERN.test(operation.key)) { const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); if (configurationProperties[operation.key].scope !== ConfigurationScope.RESOURCE) { return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_CONFIGURATION, target, operation); diff --git a/src/vs/workbench/services/configuration/common/jsonEditingService.ts b/src/vs/workbench/services/configuration/common/jsonEditingService.ts index ad91a72bb7..08a52db254 100644 --- a/src/vs/workbench/services/configuration/common/jsonEditingService.ts +++ b/src/vs/workbench/services/configuration/common/jsonEditingService.ts @@ -98,7 +98,7 @@ export class JSONEditingService implements IJSONEditingService { private hasParseErrors(model: ITextModel): boolean { const parseErrors: json.ParseError[] = []; - json.parse(model.getValue(), parseErrors); + json.parse(model.getValue(), parseErrors, { allowTrailingComma: true, allowEmptyContent: true }); return parseErrors.length > 0; } diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts index 3cf078487c..211c2b95bb 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts @@ -18,7 +18,7 @@ import * as uuid from 'vs/base/common/uuid'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; import { ConfigurationEditingService, ConfigurationEditingError, ConfigurationEditingErrorCode, EditableConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditingService'; -import { WORKSPACE_STANDALONE_CONFIGURATIONS } from 'vs/workbench/services/configuration/common/configuration'; +import { WORKSPACE_STANDALONE_CONFIGURATIONS, FOLDER_SETTINGS_PATH } from 'vs/workbench/services/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; @@ -39,11 +39,11 @@ import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemPro import { IFileService } from 'vs/platform/files/common/files'; import { ConfigurationCache } from 'vs/workbench/services/configuration/node/configurationCache'; import { KeybindingsEditingService, IKeybindingEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; -import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; +import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; -class TestEnvironmentService extends WorkbenchEnvironmentService { +class TestEnvironmentService extends NativeWorkbenchEnvironmentService { constructor(private _appSettingsHome: URI) { super(parseArgs(process.argv, OPTIONS) as IWindowConfiguration, process.execPath, 0); @@ -236,6 +236,41 @@ suite('ConfigurationEditingService', () => { }); }); + test('write overridable settings to user settings', () => { + const key = '[language]'; + const value = { 'configurationEditing.service.testSetting': 'overridden value' }; + return testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key, value }) + .then(() => { + const contents = fs.readFileSync(globalSettingsFile).toString('utf8'); + const parsed = json.parse(contents); + assert.deepEqual(parsed[key], value); + }); + }); + + test('write overridable settings to workspace settings', () => { + const key = '[language]'; + const value = { 'configurationEditing.service.testSetting': 'overridden value' }; + return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key, value }) + .then(() => { + const target = path.join(workspaceDir, FOLDER_SETTINGS_PATH); + const contents = fs.readFileSync(target).toString('utf8'); + const parsed = json.parse(contents); + assert.deepEqual(parsed[key], value); + }); + }); + + test('write overridable settings to workspace folder settings', () => { + const key = '[language]'; + const value = { 'configurationEditing.service.testSetting': 'overridden value' }; + const folderSettingsFile = path.join(workspaceDir, FOLDER_SETTINGS_PATH); + return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE_FOLDER, { key, value }, { scopes: { resource: URI.file(folderSettingsFile) } }) + .then(() => { + const contents = fs.readFileSync(folderSettingsFile).toString('utf8'); + const parsed = json.parse(contents); + assert.deepEqual(parsed[key], value); + }); + }); + test('write workspace standalone setting - empty file', () => { return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'tasks.service.testSetting', value: 'value' }) .then(() => { diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts index 7528c24e9b..cb428f846f 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts @@ -45,10 +45,10 @@ import { IConfigurationCache } from 'vs/workbench/services/configuration/common/ import { SignService } from 'vs/platform/sign/browser/signService'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; import { IKeybindingEditingService, KeybindingsEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; -import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; +import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -class TestEnvironmentService extends WorkbenchEnvironmentService { +class TestEnvironmentService extends NativeWorkbenchEnvironmentService { constructor(private _appSettingsHome: URI) { super(parseArgs(process.argv, OPTIONS) as IWindowConfiguration, process.execPath, 0); diff --git a/src/vs/workbench/services/configurationResolver/common/variableResolver.ts b/src/vs/workbench/services/configurationResolver/common/variableResolver.ts index 0fef61e5c9..a54f09b52a 100644 --- a/src/vs/workbench/services/configurationResolver/common/variableResolver.ts +++ b/src/vs/workbench/services/configurationResolver/common/variableResolver.ts @@ -32,17 +32,24 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe _serviceBrand: undefined; + private _context: IVariableResolveContext; + private _envVariables?: IProcessEnvironment; protected _contributedVariables: Map Promise> = new Map(); - constructor( - private _context: IVariableResolveContext, - private _envVariables: IProcessEnvironment - ) { - if (isWindows && _envVariables) { - this._envVariables = Object.create(null); - Object.keys(_envVariables).forEach(key => { - this._envVariables[key.toLowerCase()] = _envVariables[key]; - }); + + constructor(_context: IVariableResolveContext, _envVariables?: IProcessEnvironment) { + this._context = _context; + if (_envVariables) { + if (isWindows) { + // windows env variables are case insensitive + const ev: IProcessEnvironment = Object.create(null); + this._envVariables = ev; + Object.keys(_envVariables).forEach(key => { + ev[key.toLowerCase()] = _envVariables[key]; + }); + } else { + this._envVariables = _envVariables; + } } } @@ -180,14 +187,13 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe case 'env': if (argument) { - if (isWindows) { - argument = argument.toLowerCase(); + if (this._envVariables) { + const env = this._envVariables[isWindows ? argument.toLowerCase() : argument]; + if (types.isString(env)) { + return env; + } } - const env = this._envVariables[argument]; - if (types.isString(env)) { - return env; - } - // For `env` we should do the same as a normal shell does - evaluates missing envs to an empty string #46436 + // For `env` we should do the same as a normal shell does - evaluates undefined envs to an empty string #46436 return ''; } throw new Error(localize('missingEnvVarName', "'{0}' can not be resolved because no environment variable name is given.", match)); diff --git a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts index 86347e2a10..5193ff073a 100644 --- a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts +++ b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts @@ -19,7 +19,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import * as Types from 'vs/base/common/types'; import { EditorType } from 'vs/editor/common/editorCommon'; import { Selection } from 'vs/editor/common/core/selection'; -import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; +import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -642,7 +642,7 @@ class MockInputsConfigurationService extends TestConfigurationService { } } -class MockWorkbenchEnvironmentService extends WorkbenchEnvironmentService { +class MockWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService { constructor(env: platform.IProcessEnvironment) { super({ userEnv: env } as IWindowConfiguration, process.execPath, 0); diff --git a/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts b/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts index 20cc3c44d1..a3498fee95 100644 --- a/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts +++ b/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts @@ -91,18 +91,25 @@ class NativeContextMenuService extends Disposable implements IContextMenuService const anchor = delegate.getAnchor(); let x: number, y: number; + let zoom = webFrame.getZoomFactor(); if (dom.isHTMLElement(anchor)) { let elementPosition = dom.getDomNodePagePosition(anchor); x = elementPosition.left; y = elementPosition.top + elementPosition.height; + + // Shift macOS menus by a few pixels below elements + // to account for extra padding on top of native menu + // https://github.com/microsoft/vscode/issues/84231 + if (isMacintosh) { + y += 4 / zoom; + } } else { const pos: { x: number; y: number; } = anchor; x = pos.x + 1; /* prevent first item from being selected automatically under mouse */ y = pos.y; } - let zoom = webFrame.getZoomFactor(); x *= zoom; y *= zoom; diff --git a/src/vs/workbench/services/decorations/browser/decorations.ts b/src/vs/workbench/services/decorations/browser/decorations.ts index c5fd3b8b1a..b90138e739 100644 --- a/src/vs/workbench/services/decorations/browser/decorations.ts +++ b/src/vs/workbench/services/decorations/browser/decorations.ts @@ -20,7 +20,7 @@ export interface IDecorationData { readonly bubble?: boolean; } -export interface IDecoration { +export interface IDecoration extends IDisposable { readonly tooltip: string; readonly labelClassName: string; readonly badgeClassName: string; diff --git a/src/vs/workbench/services/decorations/browser/decorationsService.ts b/src/vs/workbench/services/decorations/browser/decorationsService.ts index 7cf9130eb4..6f937d8209 100644 --- a/src/vs/workbench/services/decorations/browser/decorationsService.ts +++ b/src/vs/workbench/services/decorations/browser/decorationsService.ts @@ -12,14 +12,13 @@ import { isThenable } from 'vs/base/common/async'; import { LinkedList } from 'vs/base/common/linkedList'; import { createStyleSheet, createCSSRule, removeCSSRulesContainingSelector } from 'vs/base/browser/dom'; import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; -import { IdGenerator } from 'vs/base/common/idGenerator'; -import { Iterator } from 'vs/base/common/iterator'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { localize } from 'vs/nls'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; +import { hash } from 'vs/base/common/hash'; class DecorationRule { @@ -32,18 +31,29 @@ class DecorationRule { } } - private static readonly _classNames = new IdGenerator('monaco-decorations-style-'); + private static readonly _classNamesPrefix = 'monaco-decoration'; readonly data: IDecorationData | IDecorationData[]; readonly itemColorClassName: string; readonly itemBadgeClassName: string; readonly bubbleBadgeClassName: string; - constructor(data: IDecorationData | IDecorationData[]) { + private _refCounter: number = 0; + + constructor(data: IDecorationData | IDecorationData[], key: string) { this.data = data; - this.itemColorClassName = DecorationRule._classNames.nextId(); - this.itemBadgeClassName = DecorationRule._classNames.nextId(); - this.bubbleBadgeClassName = DecorationRule._classNames.nextId(); + const suffix = hash(key).toString(36); + this.itemColorClassName = `${DecorationRule._classNamesPrefix}-itemColor-${suffix}`; + this.itemBadgeClassName = `${DecorationRule._classNamesPrefix}-itemBadge-${suffix}`; + this.bubbleBadgeClassName = `${DecorationRule._classNamesPrefix}-bubbleBadge-${suffix}`; + } + + acquire(): void { + this._refCounter += 1; + } + + release(): boolean { + return --this._refCounter === 0; } appendCSSRules(element: HTMLStyleElement, theme: ITheme): void { @@ -89,12 +99,6 @@ class DecorationRule { removeCSSRulesContainingSelector(this.itemBadgeClassName, element); removeCSSRulesContainingSelector(this.bubbleBadgeClassName, element); } - - isUnused(): boolean { - return !document.querySelector(`.${this.itemColorClassName}`) - && !document.querySelector(`.${this.itemBadgeClassName}`) - && !document.querySelector(`.${this.bubbleBadgeClassName}`); - } } class DecorationStyles { @@ -124,11 +128,13 @@ class DecorationStyles { if (!rule) { // new css rule - rule = new DecorationRule(data); + rule = new DecorationRule(data, key); this._decorationRules.set(key, rule); rule.appendCSSRules(this._styleElement, this._themeService.getTheme()); } + rule.acquire(); + let labelClassName = rule.itemColorClassName; let badgeClassName = rule.itemBadgeClassName; let tooltip = data.filter(d => !isFalsyOrWhitespace(d.tooltip)).map(d => d.tooltip).join(' • '); @@ -142,7 +148,14 @@ class DecorationStyles { return { labelClassName, badgeClassName, - tooltip + tooltip, + dispose: () => { + if (rule && rule.release()) { + this._decorationRules.delete(key); + rule.removeCSSRules(this._styleElement); + rule = undefined; + } + } }; } @@ -152,34 +165,6 @@ class DecorationStyles { rule.appendCSSRules(this._styleElement, this._themeService.getTheme()); }); } - - cleanUp(iter: Iterator): void { - // remove every rule for which no more - // decoration (data) is kept. this isn't cheap - let usedDecorations = new Set(); - for (let e = iter.next(); !e.done; e = iter.next()) { - e.value.data.forEach((value, key) => { - if (value && !(value instanceof DecorationDataRequest)) { - usedDecorations.add(DecorationRule.keyOf(value)); - } - }); - } - this._decorationRules.forEach((value, index) => { - const { data } = value; - if (value.isUnused()) { - let remove: boolean = false; - if (Array.isArray(data)) { - remove = data.every(data => !usedDecorations.has(DecorationRule.keyOf(data))); - } else if (!usedDecorations.has(DecorationRule.keyOf(data))) { - remove = true; - } - if (remove) { - value.removeCSSRules(this._styleElement); - this._decorationRules.delete(index); - } - } - }); - } } class FileDecorationChangeEvent implements IResourceDecorationChangeEvent { @@ -345,15 +330,6 @@ export class DecorationsService implements IDecorationsService { @ILogService private readonly _logService: ILogService, ) { this._decorationStyles = new DecorationStyles(themeService); - - // every so many events we check if there are - // css styles that we don't need anymore - let count = 0; - this.onDidChangeDecorations(() => { - if (++count % 17 === 0) { - this._decorationStyles.cleanUp(this._data.iterator()); - } - }); } dispose(): void { diff --git a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts index c6fdb83548..1fc05900fe 100644 --- a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { IWindowOpenable } from 'vs/platform/windows/common/windows'; -import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, FileFilter } from 'vs/platform/dialogs/common/dialogs'; +import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, FileFilter, IFileDialogService, IDialogService, ConfirmResult, getConfirmMessage } from 'vs/platform/dialogs/common/dialogs'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -14,14 +14,15 @@ import { Schemas } from 'vs/base/common/network'; import * as resources from 'vs/base/common/resources'; import { IInstantiationService, } from 'vs/platform/instantiation/common/instantiation'; import { SimpleFileDialog } from 'vs/workbench/services/dialogs/browser/simpleFileDialog'; -import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; +import { WORKSPACE_EXTENSION, isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IFileService } from 'vs/platform/files/common/files'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import Severity from 'vs/base/common/severity'; -export abstract class AbstractFileDialogService { +export abstract class AbstractFileDialogService implements IFileDialogService { _serviceBrand: undefined; @@ -34,6 +35,7 @@ export abstract class AbstractFileDialogService { @IConfigurationService protected readonly configurationService: IConfigurationService, @IFileService protected readonly fileService: IFileService, @IOpenerService protected readonly openerService: IOpenerService, + @IDialogService private readonly dialogService: IDialogService ) { } defaultFilePath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined { @@ -78,6 +80,40 @@ export abstract class AbstractFileDialogService { return this.defaultFilePath(schemeFilter); } + async showSaveConfirm(fileNamesOrResources: (string | URI)[]): Promise { + if (this.environmentService.isExtensionDevelopment) { + return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests) + } + + if (fileNamesOrResources.length === 0) { + return ConfirmResult.DONT_SAVE; + } + + let message: string; + if (fileNamesOrResources.length === 1) { + message = nls.localize('saveChangesMessage', "Do you want to save the changes you made to {0}?", typeof fileNamesOrResources[0] === 'string' ? fileNamesOrResources[0] : resources.basename(fileNamesOrResources[0])); + } else { + message = getConfirmMessage(nls.localize('saveChangesMessages', "Do you want to save the changes to the following {0} files?", fileNamesOrResources.length), fileNamesOrResources); + } + + const buttons: string[] = [ + fileNamesOrResources.length > 1 ? nls.localize({ key: 'saveAll', comment: ['&& denotes a mnemonic'] }, "&&Save All") : nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save"), + nls.localize({ key: 'dontSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save"), + nls.localize('cancel', "Cancel") + ]; + + const { choice } = await this.dialogService.show(Severity.Warning, message, buttons, { + cancelId: 2, + detail: nls.localize('saveChangesDetail', "Your changes will be lost if you don't save them.") + }); + + switch (choice) { + case 0: return ConfirmResult.SAVE; + case 1: return ConfirmResult.DONT_SAVE; + default: return ConfirmResult.CANCEL; + } + } + protected abstract addFileSchemaIfNeeded(schema: string): string[]; protected async pickFileFolderAndOpenSimplified(schema: string, options: IPickAndOpenOptions, preferNewWindow: boolean): Promise { @@ -179,8 +215,12 @@ export abstract class AbstractFileDialogService { protected getFileSystemSchema(options: { availableFileSystems?: readonly string[], defaultUri?: URI }): string { return options.availableFileSystems && options.availableFileSystems[0] || this.getSchemeFilterForWindow(); } -} -function isUntitledWorkspace(path: URI, environmentService: IWorkbenchEnvironmentService): boolean { - return resources.isEqualOrParent(path, environmentService.untitledWorkspacesHome); + abstract pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise; + abstract pickFileAndOpen(options: IPickAndOpenOptions): Promise; + abstract pickFolderAndOpen(options: IPickAndOpenOptions): Promise; + abstract pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise; + abstract pickFileToSave(options: ISaveDialogOptions): Promise; + abstract showSaveDialog(options: ISaveDialogOptions): Promise; + abstract showOpenDialog(options: IOpenDialogOptions): Promise; } diff --git a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts index efbc4ea0a5..6cb3d59bc9 100644 --- a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts @@ -32,10 +32,9 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { createCancelablePromise, CancelablePromise } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ICommandHandler } from 'vs/platform/commands/common/commands'; -import { ITextFileService, ISaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { toResource } from 'vs/workbench/common/editor'; import { normalizeDriveLetter } from 'vs/base/common/labels'; +import { SaveReason } from 'vs/workbench/common/editor'; export namespace OpenLocalFileCommand { export const ID = 'workbench.action.files.openLocalFile'; @@ -53,13 +52,12 @@ export namespace SaveLocalFileCommand { export const LABEL = nls.localize('saveLocalFile', "Save Local File..."); export function handler(): ICommandHandler { return accessor => { - const textFileService = accessor.get(ITextFileService); const editorService = accessor.get(IEditorService); - let resource: URI | undefined = toResource(editorService.activeEditor); - const options: ISaveOptions = { force: true, availableFileSystems: [Schemas.file] }; - if (resource) { - return textFileService.saveAs(resource, undefined, options); + const activeControl = editorService.activeControl; + if (activeControl) { + return editorService.save({ groupId: activeControl.group.id, editor: activeControl.input }, { saveAs: true, availableFileSystems: [Schemas.file], reason: SaveReason.EXPLICIT }); } + return Promise.resolve(undefined); }; } @@ -262,6 +260,7 @@ export class SimpleFileDialog { this.filePickBox = this.quickInputService.createQuickPick(); this.busy = true; this.filePickBox.matchOnLabel = false; + this.filePickBox.sortByLabel = false; this.filePickBox.autoFocusOnList = false; this.filePickBox.ignoreFocusOut = true; this.filePickBox.ok = true; @@ -373,28 +372,7 @@ export class SimpleFileDialog { }); this.filePickBox.onDidChangeValue(async value => { - try { - // onDidChangeValue can also be triggered by the auto complete, so if it looks like the auto complete, don't do anything - if (this.isValueChangeFromUser()) { - // If the user has just entered more bad path, don't change anything - if (!equalsIgnoreCase(value, this.constructFullUserPath()) && !this.isBadSubpath(value)) { - this.filePickBox.validationMessage = undefined; - const filePickBoxUri = this.filePickBoxValue(); - let updated: UpdateResult = UpdateResult.NotUpdated; - if (!resources.isEqual(this.currentFolder, filePickBoxUri, true)) { - updated = await this.tryUpdateItems(value, filePickBoxUri); - } - if (updated === UpdateResult.NotUpdated) { - this.setActiveItems(value); - } - } else { - this.filePickBox.activeItems = []; - this.userEnteredPathSegment = ''; - } - } - } catch { - // Since any text can be entered in the input box, there is potential for error causing input. If this happens, do nothing. - } + return this.handleValueChange(value); }); this.filePickBox.onDidHide(() => { this.hidden = true; @@ -415,6 +393,31 @@ export class SimpleFileDialog { }); } + private async handleValueChange(value: string) { + try { + // onDidChangeValue can also be triggered by the auto complete, so if it looks like the auto complete, don't do anything + if (this.isValueChangeFromUser()) { + // If the user has just entered more bad path, don't change anything + if (!equalsIgnoreCase(value, this.constructFullUserPath()) && !this.isBadSubpath(value)) { + this.filePickBox.validationMessage = undefined; + const filePickBoxUri = this.filePickBoxValue(); + let updated: UpdateResult = UpdateResult.NotUpdated; + if (!resources.isEqual(this.currentFolder, filePickBoxUri, true)) { + updated = await this.tryUpdateItems(value, filePickBoxUri); + } + if (updated === UpdateResult.NotUpdated) { + this.setActiveItems(value); + } + } else { + this.filePickBox.activeItems = []; + this.userEnteredPathSegment = ''; + } + } + } catch { + // Since any text can be entered in the input box, there is potential for error causing input. If this happens, do nothing. + } + } + private isBadSubpath(value: string) { return this.badPath && (value.length > this.badPath.length) && equalsIgnoreCase(value.substring(0, this.badPath.length), this.badPath); } @@ -514,6 +517,16 @@ export class SimpleFileDialog { return undefined; } + private root(value: URI) { + let lastDir = value; + let dir = resources.dirname(value); + while (!resources.isEqual(lastDir, dir)) { + lastDir = dir; + dir = resources.dirname(dir); + } + return dir; + } + private async tryUpdateItems(value: string, valueUri: URI): Promise { if ((value.length > 0) && ((value[value.length - 1] === '~') || (value[0] === '~'))) { let newDir = this.userHome; @@ -522,6 +535,11 @@ export class SimpleFileDialog { } await this.updateItems(newDir, true); return UpdateResult.Updated; + } else if (value === '\\') { + valueUri = this.root(this.currentFolder); + value = this.pathFromUri(valueUri); + await this.updateItems(valueUri, true); + return UpdateResult.Updated; } else if (!resources.isEqual(this.currentFolder, valueUri, true) && (this.endsWithSlash(value) || (!resources.isEqual(this.currentFolder, resources.dirname(valueUri), true) && resources.isEqualOrParent(this.currentFolder, resources.dirname(valueUri), true)))) { let stat: IFileStat | undefined; try { @@ -535,7 +553,7 @@ export class SimpleFileDialog { } else if (this.endsWithSlash(value)) { // The input box contains a path that doesn't exist on the system. this.filePickBox.validationMessage = nls.localize('remoteFileDialog.badPath', 'The path does not exist.'); - // Save this bad path. It can take too long to to a stat on every user entered character, but once a user enters a bad path they are likely + // Save this bad path. It can take too long to a stat on every user entered character, but once a user enters a bad path they are likely // to keep typing more bad path. We can compare against this bad path and see if the user entered path starts with it. this.badPath = value; return UpdateResult.InvalidPath; @@ -602,7 +620,7 @@ export class SimpleFileDialog { this.activeItem = quickPickItem; if (force) { // clear any selected text - this.insertText(this.userEnteredPathSegment, ''); + document.execCommand('insertText', false, ''); } return false; } else if (!force && (itemBasename.length >= startingBasename.length) && equalsIgnoreCase(itemBasename.substr(0, startingBasename.length), startingBasename)) { @@ -631,14 +649,19 @@ export class SimpleFileDialog { private insertText(wholeValue: string, insertText: string) { if (this.filePickBox.inputHasFocus()) { document.execCommand('insertText', false, insertText); + if (this.filePickBox.value !== wholeValue) { + this.filePickBox.value = wholeValue; + this.handleValueChange(wholeValue); + } } else { this.filePickBox.value = wholeValue; + this.handleValueChange(wholeValue); } } private addPostfix(uri: URI): URI { let result = uri; - if (this.requiresTrailing && this.options.filters && this.options.filters.length > 0) { + if (this.requiresTrailing && this.options.filters && this.options.filters.length > 0 && !resources.hasTrailingPathSeparator(uri)) { // Make sure that the suffix is added. If the user deleted it, we automatically add it here let hasExt: boolean = false; const currentExt = resources.extname(uri).substr(1); diff --git a/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts index 3b07328a29..55cfe83ed4 100644 --- a/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts @@ -6,7 +6,7 @@ import { SaveDialogOptions, OpenDialogOptions } from 'electron'; import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService, IDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -33,8 +33,11 @@ export class FileDialogService extends AbstractFileDialogService implements IFil @IConfigurationService configurationService: IConfigurationService, @IFileService fileService: IFileService, @IOpenerService openerService: IOpenerService, - @IElectronService private readonly electronService: IElectronService - ) { super(hostService, contextService, historyService, environmentService, instantiationService, configurationService, fileService, openerService); } + @IElectronService private readonly electronService: IElectronService, + @IDialogService dialogService: IDialogService + ) { + super(hostService, contextService, historyService, environmentService, instantiationService, configurationService, fileService, openerService, dialogService); + } private toNativeOpenDialogOptions(options: IPickAndOpenOptions): INativeOpenDialogOptions { return { @@ -180,6 +183,16 @@ export class FileDialogService extends AbstractFileDialogService implements IFil // Don't allow untitled schema through. return schema === Schemas.untitled ? [Schemas.file] : (schema !== Schemas.file ? [schema, Schemas.file] : [schema]); } + + async showSaveConfirm(fileNamesOrResources: (string | URI)[]): Promise { + if (this.environmentService.isExtensionDevelopment) { + if (!this.environmentService.args['extension-development-confirm-save']) { + return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests) + } + } + + return super.showSaveConfirm(fileNamesOrResources); + } } registerSingleton(IFileDialogService, FileDialogService, true); diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index ccdc691cb4..13ec07e693 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -5,12 +5,11 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IResourceInput, ITextEditorOptions, IEditorOptions, EditorActivation } from 'vs/platform/editor/common/editor'; -import { IEditorInput, IEditor, GroupIdentifier, IFileEditorInput, IUntitledResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, IFileInputFactory, EditorInput, SideBySideEditorInput, IEditorInputWithOptions, isEditorInputWithOptions, EditorOptions, TextEditorOptions, IEditorIdentifier, IEditorCloseEvent, ITextEditor, ITextDiffEditor, ITextSideBySideEditor, toResource, SideBySideEditor } from 'vs/workbench/common/editor'; +import { IEditorInput, IEditor, GroupIdentifier, IFileEditorInput, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, IFileInputFactory, EditorInput, SideBySideEditorInput, IEditorInputWithOptions, isEditorInputWithOptions, EditorOptions, TextEditorOptions, IEditorIdentifier, IEditorCloseEvent, ITextEditor, ITextDiffEditor, ITextSideBySideEditor, toResource, SideBySideEditor, IRevertOptions } from 'vs/workbench/common/editor'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; -import { DataUriEditorInput } from 'vs/workbench/common/editor/dataUriEditorInput'; import { Registry } from 'vs/platform/registry/common/platform'; import { ResourceMap } from 'vs/base/common/map'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IFileService } from 'vs/platform/files/common/files'; import { Schemas } from 'vs/base/common/network'; import { Event, Emitter } from 'vs/base/common/event'; @@ -18,8 +17,8 @@ import { URI } from 'vs/base/common/uri'; import { basename, isEqual } from 'vs/base/common/resources'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { localize } from 'vs/nls'; -import { IEditorGroupsService, IEditorGroup, GroupsOrder, IEditorReplacement, GroupChangeKind, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IResourceEditor, SIDE_GROUP, IResourceEditorReplacement, IOpenEditorOverrideHandler, IVisibleEditor, IEditorService, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorGroupsService, IEditorGroup, GroupsOrder, IEditorReplacement, GroupChangeKind, preferredSideBySideGroupDirection, EditorsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IResourceEditor, SIDE_GROUP, IResourceEditorReplacement, IOpenEditorOverrideHandler, IVisibleEditor, IEditorService, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE, ISaveEditorsOptions, ISaveAllEditorsOptions, IRevertAllEditorsOptions, IBaseSaveRevertAllEditorOptions } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Disposable, IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { coalesce } from 'vs/base/common/arrays'; @@ -29,40 +28,40 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { withNullAsUndefined } from 'vs/base/common/types'; -type CachedEditorInput = ResourceEditorInput | IFileEditorInput | DataUriEditorInput; +type CachedEditorInput = ResourceEditorInput | IFileEditorInput; type OpenInEditorGroup = IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE; export class EditorService extends Disposable implements EditorServiceImpl { _serviceBrand: undefined; - private static CACHE: ResourceMap = new ResourceMap(); + private static CACHE = new ResourceMap(); //#region events - private readonly _onDidActiveEditorChange: Emitter = this._register(new Emitter()); - readonly onDidActiveEditorChange: Event = this._onDidActiveEditorChange.event; + private readonly _onDidActiveEditorChange = this._register(new Emitter()); + readonly onDidActiveEditorChange = this._onDidActiveEditorChange.event; - private readonly _onDidVisibleEditorsChange: Emitter = this._register(new Emitter()); - readonly onDidVisibleEditorsChange: Event = this._onDidVisibleEditorsChange.event; + private readonly _onDidVisibleEditorsChange = this._register(new Emitter()); + readonly onDidVisibleEditorsChange = this._onDidVisibleEditorsChange.event; - private readonly _onDidCloseEditor: Emitter = this._register(new Emitter()); - readonly onDidCloseEditor: Event = this._onDidCloseEditor.event; + private readonly _onDidCloseEditor = this._register(new Emitter()); + readonly onDidCloseEditor = this._onDidCloseEditor.event; - private readonly _onDidOpenEditorFail: Emitter = this._register(new Emitter()); - readonly onDidOpenEditorFail: Event = this._onDidOpenEditorFail.event; + private readonly _onDidOpenEditorFail = this._register(new Emitter()); + readonly onDidOpenEditorFail = this._onDidOpenEditorFail.event; //#endregion private fileInputFactory: IFileInputFactory; private openEditorHandlers: IOpenEditorOverrideHandler[] = []; - private lastActiveEditor: IEditorInput | null = null; - private lastActiveGroupId: GroupIdentifier | null = null; + private lastActiveEditor: IEditorInput | undefined = undefined; + private lastActiveGroupId: GroupIdentifier | undefined = undefined; constructor( @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, - @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService, @IInstantiationService private readonly instantiationService: IInstantiationService, @ILabelService private readonly labelService: ILabelService, @IFileService private readonly fileService: IFileService, @@ -76,6 +75,8 @@ export class EditorService extends Disposable implements EditorServiceImpl { } private registerListeners(): void { + + // Editor & group changes this.editorGroupService.whenRestored.then(() => this.onEditorsRestored()); this.editorGroupService.onDidActiveGroupChange(group => this.handleActiveEditorChange(group)); this.editorGroupService.onDidAddGroup(group => this.registerGroupListeners(group as IEditorGroupView)); @@ -88,7 +89,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { // Fire initial set of editor events if there is an active editor if (this.activeEditor) { - this.doEmitActiveEditorChangeEvent(); + this.doHandleActiveEditorChangeEvent(); this._onDidVisibleEditorsChange.fire(); } } @@ -106,15 +107,17 @@ export class EditorService extends Disposable implements EditorServiceImpl { return; // ignore if the editor actually did not change } - this.doEmitActiveEditorChangeEvent(); + this.doHandleActiveEditorChangeEvent(); } - private doEmitActiveEditorChangeEvent(): void { + private doHandleActiveEditorChangeEvent(): void { + + // Remember as last active const activeGroup = this.editorGroupService.activeGroup; - this.lastActiveGroupId = activeGroup.id; - this.lastActiveEditor = activeGroup.activeEditor; + this.lastActiveEditor = withNullAsUndefined(activeGroup.activeEditor); + // Fire event to outside parties this._onDidActiveEditorChange.fire(); } @@ -221,7 +224,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#region openEditor() openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: OpenInEditorGroup): Promise; - openEditor(editor: IResourceInput | IUntitledResourceInput, group?: OpenInEditorGroup): Promise; + openEditor(editor: IResourceInput | IUntitledTextResourceInput, group?: OpenInEditorGroup): Promise; openEditor(editor: IResourceDiffInput, group?: OpenInEditorGroup): Promise; openEditor(editor: IResourceSideBySideInput, group?: OpenInEditorGroup): Promise; async openEditor(editor: IEditorInput | IResourceEditor, optionsOrGroup?: IEditorOptions | ITextEditorOptions | OpenInEditorGroup, group?: OpenInEditorGroup): Promise { @@ -362,7 +365,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { return neighbourGroup; } - private toOptions(options?: IEditorOptions | EditorOptions): EditorOptions { + private toOptions(options?: IEditorOptions | ITextEditorOptions | EditorOptions): EditorOptions { if (!options || options instanceof EditorOptions) { return options as EditorOptions; } @@ -424,7 +427,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#region isOpen() - isOpen(editor: IEditorInput | IResourceInput | IUntitledResourceInput): boolean { + isOpen(editor: IEditorInput | IResourceInput | IUntitledTextResourceInput): boolean { return !!this.doGetOpened(editor); } @@ -432,13 +435,13 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#region getOpend() - getOpened(editor: IResourceInput | IUntitledResourceInput): IEditorInput | undefined { + getOpened(editor: IResourceInput | IUntitledTextResourceInput): IEditorInput | undefined { return this.doGetOpened(editor); } - private doGetOpened(editor: IEditorInput | IResourceInput | IUntitledResourceInput): IEditorInput | undefined { + private doGetOpened(editor: IEditorInput | IResourceInput | IUntitledTextResourceInput): IEditorInput | undefined { if (!(editor instanceof EditorInput)) { - const resourceInput = editor as IResourceInput | IUntitledResourceInput; + const resourceInput = editor as IResourceInput | IUntitledTextResourceInput; if (!resourceInput.resource) { return undefined; // we need a resource at least } @@ -462,7 +465,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { continue; // need a resource to compare with } - const resourceInput = editor as IResourceInput | IUntitledResourceInput; + const resourceInput = editor as IResourceInput | IUntitledTextResourceInput; if (resourceInput.resource && isEqual(resource, resourceInput.resource)) { return editorInGroup; } @@ -484,17 +487,20 @@ export class EditorService extends Disposable implements EditorServiceImpl { editors.forEach(replaceEditorArg => { if (replaceEditorArg.editor instanceof EditorInput) { - typedEditors.push(replaceEditorArg as IEditorReplacement); - } else { - const editor = replaceEditorArg.editor as IResourceEditor; - const replacement = replaceEditorArg.replacement as IResourceEditor; - const typedEditor = this.createInput(editor); - const typedReplacement = this.createInput(replacement); + const replacementArg = replaceEditorArg as IEditorReplacement; typedEditors.push({ - editor: typedEditor, - replacement: typedReplacement, - options: this.toOptions(replacement.options) + editor: replacementArg.editor, + replacement: replacementArg.replacement, + options: this.toOptions(replacementArg.options) + }); + } else { + const replacementArg = replaceEditorArg as IResourceEditorReplacement; + + typedEditors.push({ + editor: this.createInput(replacementArg.editor), + replacement: this.createInput(replacementArg.replacement), + options: this.toOptions(replacementArg.replacement.options) }); } }); @@ -568,17 +574,17 @@ export class EditorService extends Disposable implements EditorServiceImpl { } // Untitled file support - const untitledInput = input as IUntitledResourceInput; + const untitledInput = input as IUntitledTextResourceInput; if (untitledInput.forceUntitled || !untitledInput.resource || (untitledInput.resource && untitledInput.resource.scheme === Schemas.untitled)) { - return this.untitledEditorService.createOrGet(untitledInput.resource, untitledInput.mode, untitledInput.contents, untitledInput.encoding); + return this.untitledTextEditorService.createOrGet(untitledInput.resource, untitledInput.mode, untitledInput.contents, untitledInput.encoding); } // Resource Editor Support const resourceInput = input as IResourceInput; if (resourceInput.resource instanceof URI) { let label = resourceInput.label; - if (!label && resourceInput.resource.scheme !== Schemas.data) { - label = basename(resourceInput.resource); // derive the label from the path (but not for data URIs) + if (!label) { + label = basename(resourceInput.resource); // derive the label from the path } return this.createOrGet(resourceInput.resource, this.instantiationService, label, resourceInput.description, resourceInput.encoding, resourceInput.mode, resourceInput.forceFile) as EditorInput; @@ -602,7 +608,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { if (mode) { input.setPreferredMode(mode); } - } else if (!(input instanceof DataUriEditorInput)) { + } else { if (encoding) { input.setPreferredEncoding(encoding); } @@ -621,11 +627,6 @@ export class EditorService extends Disposable implements EditorServiceImpl { input = this.fileInputFactory.createFileInput(resource, encoding, mode, instantiationService); } - // Data URI - else if (resource.scheme === Schemas.data) { - input = instantiationService.createInstance(DataUriEditorInput, label, description, resource); - } - // Resource else { input = instantiationService.createInstance(ResourceEditorInput, label, description, resource, mode); @@ -644,8 +645,8 @@ export class EditorService extends Disposable implements EditorServiceImpl { return undefined; } - // Do not try to extract any paths from simple untitled editors - if (res.scheme === Schemas.untitled && !this.untitledEditorService.hasAssociatedFilePath(res)) { + // Do not try to extract any paths from simple untitled text editors + if (res.scheme === Schemas.untitled && !this.untitledTextEditorService.hasAssociatedFilePath(res)) { return input.getName(); } @@ -654,6 +655,101 @@ export class EditorService extends Disposable implements EditorServiceImpl { } //#endregion + + //#region save/revert + + async save(editors: IEditorIdentifier | IEditorIdentifier[], options?: ISaveEditorsOptions): Promise { + + // Convert to array + if (!Array.isArray(editors)) { + editors = [editors]; + } + + // Split editors up into a bucket that is saved in parallel + // and sequentially. Unless "Save As", all non-untitled editors + // can be saved in parallel to speed up the operation. Remaining + // editors are potentially bringing up some UI and thus run + // sequentially. + const editorsToSaveParallel: IEditorIdentifier[] = []; + const editorsToSaveAsSequentially: IEditorIdentifier[] = []; + if (options?.saveAs) { + editorsToSaveAsSequentially.push(...editors); + } else { + for (const { groupId, editor } of editors) { + if (editor.isUntitled()) { + editorsToSaveAsSequentially.push({ groupId, editor }); + } else { + editorsToSaveParallel.push({ groupId, editor }); + } + } + } + + // Editors to save in parallel + await Promise.all(editorsToSaveParallel.map(({ groupId, editor }) => { + + // Use save as a hint to pin the editor + this.editorGroupService.getGroup(groupId)?.pinEditor(editor); + + // Save + return editor.save(groupId, options); + })); + + // Editors to save sequentially + for (const { groupId, editor } of editorsToSaveAsSequentially) { + if (editor.isDisposed()) { + continue; // might have been disposed from from the save already + } + + const result = options?.saveAs ? await editor.saveAs(groupId, options) : await editor.save(groupId, options); + if (!result) { + return false; // failed or cancelled, abort + } + } + + return true; + } + + saveAll(options?: ISaveAllEditorsOptions): Promise { + return this.save(this.getAllDirtyEditors(options), options); + } + + async revert(editors: IEditorIdentifier | IEditorIdentifier[], options?: IRevertOptions): Promise { + + // Convert to array + if (!Array.isArray(editors)) { + editors = [editors]; + } + + const result = await Promise.all(editors.map(async ({ groupId, editor }) => { + + // Use revert as a hint to pin the editor + this.editorGroupService.getGroup(groupId)?.pinEditor(editor); + + return editor.revert(options); + })); + + return result.every(success => !!success); + } + + async revertAll(options?: IRevertAllEditorsOptions): Promise { + return this.revert(this.getAllDirtyEditors(options), options); + } + + private getAllDirtyEditors(options?: IBaseSaveRevertAllEditorOptions): IEditorIdentifier[] { + const editors: IEditorIdentifier[] = []; + + for (const group of this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE)) { + for (const editor of group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)) { + if (editor.isDirty() && (!editor.isUntitled() || !!options?.includeUntitled)) { + editors.push({ groupId: group.id, editor }); + } + } + } + + return editors; + } + + //#endregion } export interface IEditorOpenHandler { @@ -674,7 +770,7 @@ export class DelegatingEditorService extends EditorService { constructor( @IEditorGroupsService editorGroupService: IEditorGroupsService, - @IUntitledEditorService untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService untitledTextEditorService: IUntitledTextEditorService, @IInstantiationService instantiationService: IInstantiationService, @ILabelService labelService: ILabelService, @IFileService fileService: IFileService, @@ -682,7 +778,7 @@ export class DelegatingEditorService extends EditorService { ) { super( editorGroupService, - untitledEditorService, + untitledTextEditorService, instantiationService, labelService, fileService, diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index 0c7a55a61e..f643470938 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -427,11 +427,6 @@ export interface IEditorGroup { */ readonly editors: ReadonlyArray; - /** - * Returns the editor at a specific index of the group. - */ - getEditor(index: number): IEditorInput | undefined; - /** * Get all editors that are currently opened in the group optionally * sorted by being most recent active. Will sort by sequential appearance @@ -439,6 +434,11 @@ export interface IEditorGroup { */ getEditors(order?: EditorsOrder): ReadonlyArray; + /** + * Returns the editor at a specific index of the group. + */ + getEditorByIndex(index: number): IEditorInput | undefined; + /** * Returns the index of the editor in the group or -1 if not opened. */ diff --git a/src/vs/workbench/services/editor/common/editorService.ts b/src/vs/workbench/services/editor/common/editorService.ts index 3d49efcd32..b0802af3b5 100644 --- a/src/vs/workbench/services/editor/common/editorService.ts +++ b/src/vs/workbench/services/editor/common/editorService.ts @@ -5,7 +5,7 @@ import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IResourceInput, IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; -import { IEditorInput, IEditor, GroupIdentifier, IEditorInputWithOptions, IUntitledResourceInput, IResourceDiffInput, IResourceSideBySideInput, ITextEditor, ITextDiffEditor, ITextSideBySideEditor } from 'vs/workbench/common/editor'; +import { IEditorInput, IEditor, GroupIdentifier, IEditorInputWithOptions, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, ITextEditor, ITextDiffEditor, ITextSideBySideEditor, IEditorIdentifier, ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; import { Event } from 'vs/base/common/event'; import { IEditor as ICodeEditor } from 'vs/editor/common/editorCommon'; import { IEditorGroup, IEditorReplacement } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -13,7 +13,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; export const IEditorService = createDecorator('editorService'); -export type IResourceEditor = IResourceInput | IUntitledResourceInput | IResourceDiffInput | IResourceSideBySideInput; +export type IResourceEditor = IResourceInput | IUntitledTextResourceInput | IResourceDiffInput | IResourceSideBySideInput; export interface IResourceEditorReplacement { editor: IResourceEditor; @@ -44,6 +44,26 @@ export interface IVisibleEditor extends IEditor { group: IEditorGroup; } +export interface ISaveEditorsOptions extends ISaveOptions { + + /** + * If true, will ask for a location of the editor to save to. + */ + saveAs?: boolean; +} + +export interface IBaseSaveRevertAllEditorOptions { + + /** + * Wether to include untitled editors as well. + */ + includeUntitled?: boolean; +} + +export interface ISaveAllEditorsOptions extends ISaveEditorsOptions, IBaseSaveRevertAllEditorOptions { } + +export interface IRevertAllEditorsOptions extends IRevertOptions, IBaseSaveRevertAllEditorOptions { } + export interface IEditorService { _serviceBrand: undefined; @@ -121,7 +141,7 @@ export interface IEditorService { * opened to be active. */ openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IResourceInput | IUntitledResourceInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IResourceInput | IUntitledTextResourceInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; openEditor(editor: IResourceDiffInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; openEditor(editor: IResourceSideBySideInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; @@ -158,7 +178,7 @@ export interface IEditorService { * * @param group optional to specify a group to check for the editor being opened */ - isOpen(editor: IEditorInput | IResourceInput | IUntitledResourceInput, group?: IEditorGroup | GroupIdentifier): boolean; + isOpen(editor: IEditorInput | IResourceInput | IUntitledTextResourceInput, group?: IEditorGroup | GroupIdentifier): boolean; /** * Get the actual opened editor input in any or a specific editor group based on the resource. @@ -167,7 +187,7 @@ export interface IEditorService { * * @param group optional to specify a group to check for the editor */ - getOpened(editor: IResourceInput | IUntitledResourceInput, group?: IEditorGroup | GroupIdentifier): IEditorInput | undefined; + getOpened(editor: IResourceInput | IUntitledTextResourceInput, group?: IEditorGroup | GroupIdentifier): IEditorInput | undefined; /** * Allows to override the opening of editors by installing a handler that will @@ -184,5 +204,25 @@ export interface IEditorService { /** * Converts a lightweight input to a workbench editor input. */ - createInput(input: IResourceEditor): IEditorInput | null; + createInput(input: IResourceEditor): IEditorInput; + + /** + * Save the provided list of editors. + */ + save(editors: IEditorIdentifier | IEditorIdentifier[], options?: ISaveEditorsOptions): Promise; + + /** + * Save all editors. + */ + saveAll(options?: ISaveAllEditorsOptions): Promise; + + /** + * Reverts the provided list of editors. + */ + revert(editors: IEditorIdentifier | IEditorIdentifier[], options?: IRevertOptions): Promise; + + /** + * Reverts all editors. + */ + revertAll(options?: IRevertAllEditorsOptions): Promise; } diff --git a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts index b7deef6b4c..990c642d2d 100644 --- a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts @@ -78,7 +78,7 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite } (Registry.as(EditorExtensions.EditorInputFactories)).registerEditorInputFactory('testEditorInputForGroupsService', TestEditorInputFactory); - (Registry.as(Extensions.Editors)).registerEditor(new EditorDescriptor(TestEditorControl, 'MyTestEditorForGroupsService', 'My Test File Editor'), [new SyncDescriptor(TestEditorInput)]); + (Registry.as(Extensions.Editors)).registerEditor(EditorDescriptor.create(TestEditorControl, 'MyTestEditorForGroupsService', 'My Test File Editor'), [new SyncDescriptor(TestEditorInput)]); } registerTestEditorInput(); @@ -440,8 +440,8 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite assert.equal(editorWillOpenCounter, 2); assert.equal(editorDidOpenCounter, 2); assert.equal(activeEditorChangeCounter, 1); - assert.equal(group.getEditor(0), input); - assert.equal(group.getEditor(1), inputInactive); + assert.equal(group.getEditorByIndex(0), input); + assert.equal(group.getEditorByIndex(1), inputInactive); assert.equal(group.getIndexOfEditor(input), 0); assert.equal(group.getIndexOfEditor(inputInactive), 1); @@ -491,8 +491,8 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); assert.equal(group.count, 2); - assert.equal(group.getEditor(0), input); - assert.equal(group.getEditor(1), inputInactive); + assert.equal(group.getEditorByIndex(0), input); + assert.equal(group.getEditorByIndex(1), inputInactive); await group.closeEditors([input, inputInactive]); assert.equal(group.isEmpty, true); @@ -510,13 +510,13 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite await group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]); assert.equal(group.count, 3); - assert.equal(group.getEditor(0), input1); - assert.equal(group.getEditor(1), input2); - assert.equal(group.getEditor(2), input3); + assert.equal(group.getEditorByIndex(0), input1); + assert.equal(group.getEditorByIndex(1), input2); + assert.equal(group.getEditorByIndex(2), input3); await group.closeEditors({ except: input2 }); assert.equal(group.count, 1); - assert.equal(group.getEditor(0), input2); + assert.equal(group.getEditorByIndex(0), input2); part.dispose(); }); @@ -531,9 +531,9 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite await group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]); assert.equal(group.count, 3); - assert.equal(group.getEditor(0), input1); - assert.equal(group.getEditor(1), input2); - assert.equal(group.getEditor(2), input3); + assert.equal(group.getEditorByIndex(0), input1); + assert.equal(group.getEditorByIndex(1), input2); + assert.equal(group.getEditorByIndex(2), input3); await group.closeEditors({ savedOnly: true }); assert.equal(group.count, 0); @@ -551,14 +551,14 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite await group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]); assert.equal(group.count, 3); - assert.equal(group.getEditor(0), input1); - assert.equal(group.getEditor(1), input2); - assert.equal(group.getEditor(2), input3); + assert.equal(group.getEditorByIndex(0), input1); + assert.equal(group.getEditorByIndex(1), input2); + assert.equal(group.getEditorByIndex(2), input3); await group.closeEditors({ direction: CloseDirection.RIGHT, except: input2 }); assert.equal(group.count, 2); - assert.equal(group.getEditor(0), input1); - assert.equal(group.getEditor(1), input2); + assert.equal(group.getEditorByIndex(0), input1); + assert.equal(group.getEditorByIndex(1), input2); part.dispose(); }); @@ -573,14 +573,14 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite await group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]); assert.equal(group.count, 3); - assert.equal(group.getEditor(0), input1); - assert.equal(group.getEditor(1), input2); - assert.equal(group.getEditor(2), input3); + assert.equal(group.getEditorByIndex(0), input1); + assert.equal(group.getEditorByIndex(1), input2); + assert.equal(group.getEditorByIndex(2), input3); await group.closeEditors({ direction: CloseDirection.LEFT, except: input2 }); assert.equal(group.count, 2); - assert.equal(group.getEditor(0), input2); - assert.equal(group.getEditor(1), input3); + assert.equal(group.getEditorByIndex(0), input2); + assert.equal(group.getEditorByIndex(1), input3); part.dispose(); }); @@ -594,8 +594,8 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); assert.equal(group.count, 2); - assert.equal(group.getEditor(0), input); - assert.equal(group.getEditor(1), inputInactive); + assert.equal(group.getEditorByIndex(0), input); + assert.equal(group.getEditorByIndex(1), inputInactive); await group.closeAllEditors(); assert.equal(group.isEmpty, true); @@ -620,12 +620,12 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); assert.equal(group.count, 2); - assert.equal(group.getEditor(0), input); - assert.equal(group.getEditor(1), inputInactive); + assert.equal(group.getEditorByIndex(0), input); + assert.equal(group.getEditorByIndex(1), inputInactive); group.moveEditor(inputInactive, group, { index: 0 }); assert.equal(editorMoveCounter, 1); - assert.equal(group.getEditor(0), inputInactive); - assert.equal(group.getEditor(1), input); + assert.equal(group.getEditorByIndex(0), inputInactive); + assert.equal(group.getEditorByIndex(1), input); editorGroupChangeListener.dispose(); part.dispose(); }); @@ -642,13 +642,13 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); assert.equal(group.count, 2); - assert.equal(group.getEditor(0), input); - assert.equal(group.getEditor(1), inputInactive); + assert.equal(group.getEditorByIndex(0), input); + assert.equal(group.getEditorByIndex(1), inputInactive); group.moveEditor(inputInactive, rightGroup, { index: 0 }); assert.equal(group.count, 1); - assert.equal(group.getEditor(0), input); + assert.equal(group.getEditorByIndex(0), input); assert.equal(rightGroup.count, 1); - assert.equal(rightGroup.getEditor(0), inputInactive); + assert.equal(rightGroup.getEditorByIndex(0), inputInactive); part.dispose(); }); @@ -664,14 +664,14 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); assert.equal(group.count, 2); - assert.equal(group.getEditor(0), input); - assert.equal(group.getEditor(1), inputInactive); + assert.equal(group.getEditorByIndex(0), input); + assert.equal(group.getEditorByIndex(1), inputInactive); group.copyEditor(inputInactive, rightGroup, { index: 0 }); assert.equal(group.count, 2); - assert.equal(group.getEditor(0), input); - assert.equal(group.getEditor(1), inputInactive); + assert.equal(group.getEditorByIndex(0), input); + assert.equal(group.getEditorByIndex(1), inputInactive); assert.equal(rightGroup.count, 1); - assert.equal(rightGroup.getEditor(0), inputInactive); + assert.equal(rightGroup.getEditorByIndex(0), inputInactive); part.dispose(); }); @@ -685,11 +685,11 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite await group.openEditor(input); assert.equal(group.count, 1); - assert.equal(group.getEditor(0), input); + assert.equal(group.getEditorByIndex(0), input); await group.replaceEditors([{ editor: input, replacement: inputInactive }]); assert.equal(group.count, 1); - assert.equal(group.getEditor(0), inputInactive); + assert.equal(group.getEditorByIndex(0), inputInactive); part.dispose(); }); diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index 44b3ed0d1d..5891845799 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { IEditorModel, EditorActivation } from 'vs/platform/editor/common/editor'; import { URI } from 'vs/base/common/uri'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { EditorInput, EditorOptions, IFileEditorInput, IEditorInput } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, IFileEditorInput, IEditorInput, GroupIdentifier, ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; import { workbenchInstantiationService, TestStorageService } from 'vs/workbench/test/workbenchTestServices'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; @@ -22,7 +22,7 @@ import { IEditorRegistry, EditorDescriptor, Extensions } from 'vs/workbench/brow import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; -import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; import { CancellationToken } from 'vs/base/common/cancellation'; import { timeout } from 'vs/base/common/async'; @@ -30,7 +30,7 @@ import { toResource } from 'vs/base/test/common/utils'; import { IFileService } from 'vs/platform/files/common/files'; import { Disposable } from 'vs/base/common/lifecycle'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; -import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel'; +import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; import { NullFileSystemProvider } from 'vs/platform/files/test/common/nullFileSystemProvider'; export class TestEditorControl extends BaseEditor { @@ -50,11 +50,15 @@ export class TestEditorControl extends BaseEditor { export class TestEditorInput extends EditorInput implements IFileEditorInput { public gotDisposed = false; + public gotSaved = false; + public gotSavedAs = false; + public gotReverted = false; + public dirty = false; private fails = false; constructor(private resource: URI) { super(); } getTypeId() { return 'testEditorInputForEditorService'; } - resolve(): Promise { return !this.fails ? Promise.resolve(null) : Promise.reject(new Error('fails')); } + resolve(): Promise { return !this.fails ? Promise.resolve(null) : Promise.reject(new Error('fails')); } matches(other: TestEditorInput): boolean { return other && other.resource && this.resource.toString() === other.resource.toString() && other instanceof TestEditorInput; } setEncoding(encoding: string) { } getEncoding() { return undefined; } @@ -66,6 +70,26 @@ export class TestEditorInput extends EditorInput implements IFileEditorInput { setFailToOpen(): void { this.fails = true; } + save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + this.gotSaved = true; + return Promise.resolve(true); + } + saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + this.gotSavedAs = true; + return Promise.resolve(true); + } + revert(options?: IRevertOptions): Promise { + this.gotReverted = true; + this.gotSaved = false; + this.gotSavedAs = false; + return Promise.resolve(true); + } + isDirty(): boolean { + return this.dirty; + } + isReadonly(): boolean { + return false; + } dispose(): void { super.dispose(); this.gotDisposed = true; @@ -83,7 +107,7 @@ class FileServiceProvider extends Disposable { suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite function registerTestEditorInput(): void { - Registry.as(Extensions.Editors).registerEditor(new EditorDescriptor(TestEditorControl, 'MyTestEditorForEditorService', 'My Test Editor For Next Editor Service'), [new SyncDescriptor(TestEditorInput)]); + Registry.as(Extensions.Editors).registerEditor(EditorDescriptor.create(TestEditorControl, 'MyTestEditorForEditorService', 'My Test Editor For Next Editor Service'), [new SyncDescriptor(TestEditorInput)]); } registerTestEditorInput(); @@ -270,36 +294,36 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite // Untyped Input (untitled) input = service.createInput({ options: { selection: { startLineNumber: 1, startColumn: 1 } } }); - assert(input instanceof UntitledEditorInput); + assert(input instanceof UntitledTextEditorInput); // Untyped Input (untitled with contents) input = service.createInput({ contents: 'Hello Untitled', options: { selection: { startLineNumber: 1, startColumn: 1 } } }); - assert(input instanceof UntitledEditorInput); - let model = await input.resolve() as UntitledEditorModel; + assert(input instanceof UntitledTextEditorInput); + let model = await input.resolve() as UntitledTextEditorModel; assert.equal(model.textEditorModel!.getValue(), 'Hello Untitled'); // Untyped Input (untitled with mode) input = service.createInput({ mode, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); - assert(input instanceof UntitledEditorInput); - model = await input.resolve() as UntitledEditorModel; + assert(input instanceof UntitledTextEditorInput); + model = await input.resolve() as UntitledTextEditorModel; assert.equal(model.getMode(), mode); // Untyped Input (untitled with file path) input = service.createInput({ resource: URI.file('/some/path.txt'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); - assert(input instanceof UntitledEditorInput); - assert.ok((input as UntitledEditorInput).hasAssociatedFilePath); + assert(input instanceof UntitledTextEditorInput); + assert.ok((input as UntitledTextEditorInput).hasAssociatedFilePath); // Untyped Input (untitled with untitled resource) input = service.createInput({ resource: URI.parse('untitled://Untitled-1'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); - assert(input instanceof UntitledEditorInput); - assert.ok(!(input as UntitledEditorInput).hasAssociatedFilePath); + assert(input instanceof UntitledTextEditorInput); + assert.ok(!(input as UntitledTextEditorInput).hasAssociatedFilePath); // Untyped Input (untitled with custom resource) const provider = instantiationService.createInstance(FileServiceProvider, 'untitled-custom'); input = service.createInput({ resource: URI.parse('untitled-custom://some/path'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); - assert(input instanceof UntitledEditorInput); - assert.ok((input as UntitledEditorInput).hasAssociatedFilePath); + assert(input instanceof UntitledTextEditorInput); + assert.ok((input as UntitledTextEditorInput).hasAssociatedFilePath); provider.dispose(); @@ -686,4 +710,45 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite let failingEditor = await service.openEditor(failingInput); assert.ok(!failingEditor); }); + + test('save, saveAll, revertAll', async function () { + const partInstantiator = workbenchInstantiationService(); + + const part = partInstantiator.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + const testInstantiationService = partInstantiator.createChild(new ServiceCollection([IEditorGroupsService, part])); + + const service: IEditorService = testInstantiationService.createInstance(EditorService); + + const input1 = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource1-openside')); + input1.dirty = true; + const input2 = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-openside')); + input2.dirty = true; + + const rootGroup = part.activeGroup; + + await part.whenRestored; + + await service.openEditor(input1, { pinned: true }); + await service.openEditor(input2, { pinned: true }); + + await service.save({ groupId: rootGroup.id, editor: input1 }); + assert.equal(input1.gotSaved, true); + + await service.save({ groupId: rootGroup.id, editor: input1 }, { saveAs: true }); + assert.equal(input1.gotSavedAs, true); + + await service.revertAll(); + assert.equal(input1.gotReverted, true); + + await service.saveAll(); + assert.equal(input1.gotSaved, true); + assert.equal(input2.gotSaved, true); + + await service.saveAll({ saveAs: true }); + assert.equal(input1.gotSavedAs, true); + assert.equal(input2.gotSavedAs, true); + }); }); diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index 25e3da30f3..28edfcc91b 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -9,7 +9,7 @@ import { IProcessEnvironment } from 'vs/base/common/platform'; import { joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; -import { BACKUPS, IDebugParams, IExtensionHostDebugParams } from 'vs/platform/environment/common/environment'; +import { BACKUPS, IExtensionHostDebugParams } from 'vs/platform/environment/common/environment'; import { LogLevel } from 'vs/platform/log/common/log'; import { IPath, IPathsToWaitFor, IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; @@ -17,104 +17,294 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; import product from 'vs/platform/product/common/product'; import { serializableToMap } from 'vs/base/common/map'; +import { memoize } from 'vs/base/common/decorators'; +// TODO@ben remove properties that are node/electron only export class BrowserWindowConfiguration implements IWindowConfiguration { + constructor( + private readonly options: IBrowserWorkbenchEnvironmentConstructionOptions, + private readonly payload: Map | undefined, + private readonly environment: IWorkbenchEnvironmentService + ) { } + + //#region PROPERLY CONFIGURED IN DESKTOP + WEB + + @memoize + get sessionId(): string { return generateUuid(); } + + @memoize + get remoteAuthority(): string | undefined { return this.options.remoteAuthority; } + + @memoize + get connectionToken(): string | undefined { return this.options.connectionToken || this.getCookieValue('vscode-tkn'); } + + @memoize + get backupWorkspaceResource(): URI { return joinPath(this.environment.backupHome, this.options.workspaceId); } + + @memoize + get filesToOpenOrCreate(): IPath[] | undefined { + if (this.payload) { + const fileToOpen = this.payload.get('openFile'); + if (fileToOpen) { + return [{ fileUri: URI.parse(fileToOpen) }]; + } + } + + return undefined; + } + + // Currently unsupported in web + get filesToDiff(): IPath[] | undefined { return undefined; } + + //#endregion + + + //#region TODO MOVE TO NODE LAYER + _!: any[]; - machineId!: string; windowId!: number; - logLevel!: LogLevel; - mainPid!: number; + logLevel!: LogLevel; + appRoot!: string; execPath!: string; - isInitialStartup?: boolean; - - userEnv!: IProcessEnvironment; + backupPath?: string; nodeCachedDataDir?: string; - backupPath?: string; - backupWorkspaceResource?: URI; + userEnv!: IProcessEnvironment; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; - remoteAuthority?: string; - connectionToken?: string; - zoomLevel?: number; fullscreen?: boolean; maximized?: boolean; highContrast?: boolean; - frameless?: boolean; accessibilitySupport?: boolean; partsSplashPath?: string; - perfStartTime?: number; - perfAppReady?: number; - perfWindowLoadTime?: number; + isInitialStartup?: boolean; perfEntries!: ExportData; - filesToOpenOrCreate?: IPath[]; - filesToDiff?: IPath[]; filesToWait?: IPathsToWaitFor; - termProgram?: string; + + //#endregion + + private getCookieValue(name: string): string | undefined { + const m = document.cookie.match('(^|[^;]+)\\s*' + name + '\\s*=\\s*([^;]+)'); // See https://stackoverflow.com/a/25490531 + + return m ? m.pop() : undefined; + } } -interface IBrowserWorkbenchEnvironemntConstructionOptions extends IWorkbenchConstructionOptions { +interface IBrowserWorkbenchEnvironmentConstructionOptions extends IWorkbenchConstructionOptions { workspaceId: string; logsPath: URI; } +interface IExtensionHostDebugEnvironment { + params: IExtensionHostDebugParams; + isExtensionDevelopment: boolean; + extensionDevelopmentLocationURI: URI[]; + extensionTestsLocationURI?: URI; +} + export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironmentService { _serviceBrand: undefined; - readonly configuration: IWindowConfiguration = new BrowserWindowConfiguration(); + //#region PROPERLY CONFIGURED IN DESKTOP + WEB - constructor(readonly options: IBrowserWorkbenchEnvironemntConstructionOptions) { - this.args = { _: [] }; - this.logsPath = options.logsPath.path; - this.logFile = joinPath(options.logsPath, 'window.log'); - this.appRoot = '/web/'; - this.appNameLong = 'Azure Data Studio - Web'; // {{SQL CARBON EDIT}} vscode to ads + @memoize + get isBuilt(): boolean { return !!product.commit; } - this.configuration.remoteAuthority = options.remoteAuthority; - this.configuration.machineId = generateUuid(); - this.userRoamingDataHome = URI.file('/User').with({ scheme: Schemas.userData }); - this.settingsResource = joinPath(this.userRoamingDataHome, 'settings.json'); - this.settingsSyncPreviewResource = joinPath(this.userRoamingDataHome, '.settings.json'); - this.userDataSyncLogResource = joinPath(options.logsPath, 'userDataSync.log'); - this.keybindingsResource = joinPath(this.userRoamingDataHome, 'keybindings.json'); - this.keyboardLayoutResource = joinPath(this.userRoamingDataHome, 'keyboardLayout.json'); - this.argvResource = joinPath(this.userRoamingDataHome, 'argv.json'); - this.backupHome = joinPath(this.userRoamingDataHome, BACKUPS); - this.untitledWorkspacesHome = joinPath(this.userRoamingDataHome, 'Workspaces'); - this.configuration.backupWorkspaceResource = joinPath(this.backupHome, options.workspaceId); - this.configuration.connectionToken = options.connectionToken || getCookieValue('vscode-tkn'); + @memoize + get logsPath(): string { return this.options.logsPath.path; } - this.debugExtensionHost = { - port: null, - break: false + @memoize + get logFile(): URI { return joinPath(this.options.logsPath, 'window.log'); } + + @memoize + get userRoamingDataHome(): URI { return URI.file('/User').with({ scheme: Schemas.userData }); } + + @memoize + get settingsResource(): URI { return joinPath(this.userRoamingDataHome, 'settings.json'); } + + @memoize + get settingsSyncPreviewResource(): URI { return joinPath(this.userRoamingDataHome, '.settings.json'); } + + @memoize + get keybindingsSyncPreviewResource(): URI { return joinPath(this.userRoamingDataHome, '.keybindings.json'); } + + @memoize + get userDataSyncLogResource(): URI { return joinPath(this.options.logsPath, 'userDataSync.log'); } + + @memoize + get keybindingsResource(): URI { return joinPath(this.userRoamingDataHome, 'keybindings.json'); } + + @memoize + get keyboardLayoutResource(): URI { return joinPath(this.userRoamingDataHome, 'keyboardLayout.json'); } + + @memoize + get backupHome(): URI { return joinPath(this.userRoamingDataHome, BACKUPS); } + + @memoize + get untitledWorkspacesHome(): URI { return joinPath(this.userRoamingDataHome, 'Workspaces'); } + + private _extensionHostDebugEnvironment: IExtensionHostDebugEnvironment | undefined = undefined; + get debugExtensionHost(): IExtensionHostDebugParams { + if (!this._extensionHostDebugEnvironment) { + this._extensionHostDebugEnvironment = this.resolveExtensionHostDebugEnvironment(); + } + + return this._extensionHostDebugEnvironment.params; + } + + get isExtensionDevelopment(): boolean { + if (!this._extensionHostDebugEnvironment) { + this._extensionHostDebugEnvironment = this.resolveExtensionHostDebugEnvironment(); + } + + return this._extensionHostDebugEnvironment.isExtensionDevelopment; + } + + get extensionDevelopmentLocationURI(): URI[] { + if (!this._extensionHostDebugEnvironment) { + this._extensionHostDebugEnvironment = this.resolveExtensionHostDebugEnvironment(); + } + + return this._extensionHostDebugEnvironment.extensionDevelopmentLocationURI; + } + + get extensionTestsLocationURI(): URI | undefined { + if (!this._extensionHostDebugEnvironment) { + this._extensionHostDebugEnvironment = this.resolveExtensionHostDebugEnvironment(); + } + + return this._extensionHostDebugEnvironment.extensionTestsLocationURI; + } + + @memoize + get webviewExternalEndpoint(): string { + // TODO: get fallback from product.json + return (this.options.webviewEndpoint || 'https://{{uuid}}.vscode-webview-test.com/{{commit}}').replace('{{commit}}', product.commit || '0d728c31ebdf03869d2687d9be0b017667c9ff37'); + } + + @memoize + get webviewResourceRoot(): string { + return `${this.webviewExternalEndpoint}/vscode-resource/{{resource}}`; + } + + @memoize + get webviewCspSource(): string { + return this.webviewExternalEndpoint.replace('{{uuid}}', '*'); + } + + // Currently not configurable in web + get disableExtensions() { return false; } + get extensionsPath(): string | undefined { return undefined; } + get verbose(): boolean { return false; } + get disableUpdates(): boolean { return false; } + get logExtensionHostCommunication(): boolean { return false; } + + //#endregion + + + //#region TODO MOVE TO NODE LAYER + + private _configuration: IWindowConfiguration | undefined = undefined; + get configuration(): IWindowConfiguration { + if (!this._configuration) { + this._configuration = new BrowserWindowConfiguration(this.options, this.payload, this); + } + + return this._configuration; + } + + args = { _: [] }; + + wait!: boolean; + status!: boolean; + log?: string; + + mainIPCHandle!: string; + sharedIPCHandle!: string; + + nodeCachedDataDir?: string; + + argvResource!: URI; + + disableCrashReporter!: boolean; + + driverHandle?: string; + driverVerbose!: boolean; + + installSourcePath!: string; + + builtinExtensionsPath!: string; + + globalStorageHome!: string; + workspaceStorageHome!: string; + + backupWorkspacesPath!: string; + + machineSettingsHome!: URI; + machineSettingsResource!: URI; + + userHome!: string; + userDataPath!: string; + appRoot!: string; + appSettingsHome!: URI; + execPath!: string; + cliPath!: string; + + //#endregion + + + //#region TODO ENABLE IN WEB + + galleryMachineIdResource?: URI; + + //#endregion + + private payload: Map | undefined; + + constructor(readonly options: IBrowserWorkbenchEnvironmentConstructionOptions) { + if (options.workspaceProvider && Array.isArray(options.workspaceProvider.payload)) { + this.payload = serializableToMap(options.workspaceProvider.payload); + } + } + + private resolveExtensionHostDebugEnvironment(): IExtensionHostDebugEnvironment { + const extensionHostDebugEnvironment: IExtensionHostDebugEnvironment = { + params: { + port: null, + break: false + }, + isExtensionDevelopment: false, + extensionDevelopmentLocationURI: [] }; // Fill in selected extra environmental properties - if (options.workspaceProvider && Array.isArray(options.workspaceProvider.payload)) { - const environment = serializableToMap(options.workspaceProvider.payload); - for (const [key, value] of environment) { + if (this.payload) { + for (const [key, value] of this.payload) { switch (key) { case 'extensionDevelopmentPath': - this.extensionDevelopmentLocationURI = [URI.parse(value)]; - this.isExtensionDevelopment = true; + extensionHostDebugEnvironment.extensionDevelopmentLocationURI = [URI.parse(value)]; + extensionHostDebugEnvironment.isExtensionDevelopment = true; + break; + case 'extensionTestsPath': + extensionHostDebugEnvironment.extensionTestsLocationURI = URI.parse(value); break; case 'debugId': - this.debugExtensionHost.debugId = value; + extensionHostDebugEnvironment.params.debugId = value; break; case 'inspect-brk-extensions': - this.debugExtensionHost.port = parseInt(value); - this.debugExtensionHost.break = false; + extensionHostDebugEnvironment.params.port = parseInt(value); + extensionHostDebugEnvironment.params.break = true; break; } } @@ -133,96 +323,23 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment const edp = map.get('extensionDevelopmentPath'); if (edp) { - this.extensionDevelopmentLocationURI = [URI.parse(edp)]; - this.isExtensionDevelopment = true; + extensionHostDebugEnvironment.extensionDevelopmentLocationURI = [URI.parse(edp)]; + extensionHostDebugEnvironment.isExtensionDevelopment = true; } const di = map.get('debugId'); if (di) { - this.debugExtensionHost.debugId = di; + extensionHostDebugEnvironment.params.debugId = di; } const ibe = map.get('inspect-brk-extensions'); if (ibe) { - this.debugExtensionHost.port = parseInt(ibe); - this.debugExtensionHost.break = false; + extensionHostDebugEnvironment.params.port = parseInt(ibe); + extensionHostDebugEnvironment.params.break = false; } } } - } - untitledWorkspacesHome: URI; - extensionTestsLocationURI?: URI; - args: any; - execPath!: string; - cliPath!: string; - appRoot: string; - userHome!: string; - userDataPath!: string; - appNameLong: string; - appQuality?: string; - appSettingsHome!: URI; - userRoamingDataHome: URI; - settingsResource: URI; - keybindingsResource: URI; - keyboardLayoutResource: URI; - argvResource: URI; - settingsSyncPreviewResource: URI; - userDataSyncLogResource: URI; - machineSettingsHome!: URI; - machineSettingsResource!: URI; - globalStorageHome!: string; - workspaceStorageHome!: string; - backupHome: URI; - backupWorkspacesPath!: string; - workspacesHome!: string; - isExtensionDevelopment!: boolean; - disableExtensions!: boolean | string[]; - builtinExtensionsPath!: string; - extensionsPath?: string; - extensionDevelopmentLocationURI?: URI[]; - extensionTestsPath?: string; - debugExtensionHost: IExtensionHostDebugParams; - debugSearch!: IDebugParams; - logExtensionHostCommunication!: boolean; - isBuilt!: boolean; - wait!: boolean; - status!: boolean; - log?: string; - logsPath: string; - verbose!: boolean; - skipReleaseNotes!: boolean; - mainIPCHandle!: string; - sharedIPCHandle!: string; - nodeCachedDataDir?: string; - installSourcePath!: string; - disableUpdates!: boolean; - disableCrashReporter!: boolean; - driverHandle?: string; - driverVerbose!: boolean; - galleryMachineIdResource?: URI; - readonly logFile: URI; - - get webviewExternalEndpoint(): string { - // TODO: get fallback from product.json - return (this.options.webviewEndpoint || 'https://{{uuid}}.vscode-webview-test.com/{{commit}}') - .replace('{{commit}}', product.commit || 'c58aaab8a1cc22a7139b761166a0d4f37d41e998'); - } - - get webviewResourceRoot(): string { - return `${this.webviewExternalEndpoint}/vscode-resource/{{resource}}`; - } - - get webviewCspSource(): string { - return this.webviewExternalEndpoint - .replace('{{uuid}}', '*'); + return extensionHostDebugEnvironment; } } - -/** - * See https://stackoverflow.com/a/25490531 - */ -function getCookieValue(name: string): string | undefined { - const m = document.cookie.match('(^|[^;]+)\\s*' + name + '\\s*=\\s*([^;]+)'); - return m ? m.pop() : undefined; -} diff --git a/src/vs/workbench/services/environment/common/environmentService.ts b/src/vs/workbench/services/environment/common/environmentService.ts index 47ea9e2f40..6511378831 100644 --- a/src/vs/workbench/services/environment/common/environmentService.ts +++ b/src/vs/workbench/services/environment/common/environmentService.ts @@ -5,7 +5,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; -import { IEnvironmentService, IDebugParams } from 'vs/platform/environment/common/environment'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; import { URI } from 'vs/base/common/uri'; @@ -20,14 +20,8 @@ export interface IWorkbenchEnvironmentService extends IEnvironmentService { readonly options?: IWorkbenchConstructionOptions; readonly logFile: URI; - readonly logExtensionHostCommunication: boolean; - - readonly debugSearch: IDebugParams; readonly webviewExternalEndpoint: string; readonly webviewResourceRoot: string; readonly webviewCspSource: string; - - readonly skipReleaseNotes: boolean | undefined; - } diff --git a/src/vs/workbench/services/environment/node/environmentService.ts b/src/vs/workbench/services/environment/electron-browser/environmentService.ts similarity index 65% rename from src/vs/workbench/services/environment/node/environmentService.ts rename to src/vs/workbench/services/environment/electron-browser/environmentService.ts index 334d9896a7..022c35fd22 100644 --- a/src/vs/workbench/services/environment/node/environmentService.ts +++ b/src/vs/workbench/services/environment/electron-browser/environmentService.ts @@ -3,28 +3,38 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { EnvironmentService, parseSearchPort } from 'vs/platform/environment/node/environmentService'; +import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { memoize } from 'vs/base/common/decorators'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; -import { toBackupWorkspaceResource } from 'vs/workbench/services/backup/common/backup'; +import { toBackupWorkspaceResource } from 'vs/workbench/services/backup/electron-browser/backup'; import { join } from 'vs/base/common/path'; -import { IDebugParams } from 'vs/platform/environment/common/environment'; import product from 'vs/platform/product/common/product'; -export class WorkbenchEnvironmentService extends EnvironmentService implements IWorkbenchEnvironmentService { +export class NativeWorkbenchEnvironmentService extends EnvironmentService implements IWorkbenchEnvironmentService { _serviceBrand: undefined; + @memoize get webviewExternalEndpoint(): string { const baseEndpoint = 'https://{{uuid}}.vscode-webview-test.com/{{commit}}'; - return baseEndpoint.replace('{{commit}}', product.commit || 'c58aaab8a1cc22a7139b761166a0d4f37d41e998'); + + return baseEndpoint.replace('{{commit}}', product.commit || '0d728c31ebdf03869d2687d9be0b017667c9ff37'); } - readonly webviewResourceRoot = 'vscode-resource://{{resource}}'; - readonly webviewCspSource = 'vscode-resource:'; + @memoize + get webviewResourceRoot(): string { return 'vscode-resource://{{resource}}'; } + + @memoize + get webviewCspSource(): string { return 'vscode-resource:'; } + + @memoize + get userRoamingDataHome(): URI { return this.appSettingsHome.with({ scheme: Schemas.userData }); } + + @memoize + get logFile(): URI { return URI.file(join(this.logsPath, `renderer${this.windowId}.log`)); } constructor( readonly configuration: IWindowConfiguration, @@ -35,17 +45,4 @@ export class WorkbenchEnvironmentService extends EnvironmentService implements I this.configuration.backupWorkspaceResource = this.configuration.backupPath ? toBackupWorkspaceResource(this.configuration.backupPath, this) : undefined; } - - get skipReleaseNotes(): boolean { return !!this.args['skip-release-notes']; } - - @memoize - get userRoamingDataHome(): URI { return this.appSettingsHome.with({ scheme: Schemas.userData }); } - - @memoize - get logFile(): URI { return URI.file(join(this.logsPath, `renderer${this.windowId}.log`)); } - - get logExtensionHostCommunication(): boolean { return !!this.args.logExtensionHostCommunication; } - - @memoize - get debugSearch(): IDebugParams { return parseSearchPort(this.args, this.isBuilt); } } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts index 12739940e7..e60e8844fd 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts @@ -15,7 +15,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { isUndefinedOrNull } from 'vs/base/common/types'; import { ExtensionType, IExtension } from 'vs/platform/extensions/common/extensions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; +import { getExtensionKind } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -146,12 +146,21 @@ export class ExtensionEnablementService extends Disposable implements IExtension } private _isDisabledByExtensionKind(extension: IExtension): boolean { - if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) { - if (!isUIExtension(extension.manifest, this.productService, this.configurationService)) { - // workspace extensions must run on the remote, but UI extensions can run on either side - const server = this.extensionManagementServerService.remoteExtensionManagementServer; - return this.extensionManagementServerService.getExtensionManagementServer(extension.location) !== server; + if (this.extensionManagementServerService.remoteExtensionManagementServer) { + const server = this.extensionManagementServerService.getExtensionManagementServer(extension.location); + for (const extensionKind of getExtensionKind(extension.manifest, this.productService, this.configurationService)) { + if (extensionKind === 'ui') { + if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.localExtensionManagementServer === server) { + return false; + } + } + if (extensionKind === 'workspace') { + if (server === this.extensionManagementServerService.remoteExtensionManagementServer) { + return false; + } + } } + return true; } return false; } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index 6d23174711..e877fb59c1 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -5,7 +5,7 @@ import { Event, EventMultiplexer } from 'vs/base/common/event'; import { - IExtensionManagementService, ILocalExtension, IGalleryExtension, InstallExtensionEvent, DidInstallExtensionEvent, IExtensionIdentifier, DidUninstallExtensionEvent, IReportedExtension, IGalleryMetadata, IExtensionGalleryService + IExtensionManagementService, ILocalExtension, IGalleryExtension, InstallExtensionEvent, DidInstallExtensionEvent, IExtensionIdentifier, DidUninstallExtensionEvent, IReportedExtension, IGalleryMetadata, IExtensionGalleryService, INSTALL_ERROR_NOT_SUPPORTED } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionManagementServer, IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionType, isLanguagePackExtension, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; @@ -15,7 +15,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { CancellationToken } from 'vs/base/common/cancellation'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { localize } from 'vs/nls'; -import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; +import { prefersExecuteOnUI, canExecuteOnWorkspace } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { IProductService } from 'vs/platform/product/common/productService'; import { Schemas } from 'vs/base/common/network'; import { IDownloadService } from 'vs/platform/download/common/download'; @@ -93,7 +93,7 @@ export class ExtensionManagementService extends Disposable implements IExtension private async uninstallInServer(extension: ILocalExtension, server: IExtensionManagementServer, force?: boolean): Promise { if (server === this.extensionManagementServerService.localExtensionManagementServer) { const installedExtensions = await this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.getInstalled(ExtensionType.User); - const dependentNonUIExtensions = installedExtensions.filter(i => !isUIExtension(i.manifest, this.productService, this.configurationService) + const dependentNonUIExtensions = installedExtensions.filter(i => !prefersExecuteOnUI(i.manifest, this.productService, this.configurationService) && i.manifest.extensionDependencies && i.manifest.extensionDependencies.some(id => areSameExtensions({ id }, extension.identifier))); if (dependentNonUIExtensions.length) { return Promise.reject(new Error(this.getDependentsErrorMessage(extension, dependentNonUIExtensions))); @@ -152,7 +152,7 @@ export class ExtensionManagementService extends Disposable implements IExtension const [local] = await Promise.all(this.servers.map(server => this.installVSIX(vsix, server))); return local; } - if (isUIExtension(manifest, this.productService, this.configurationService)) { + if (prefersExecuteOnUI(manifest, this.productService, this.configurationService)) { // Install only on local server return this.installVSIX(vsix, this.extensionManagementServerService.localExtensionManagementServer); } @@ -190,7 +190,7 @@ export class ExtensionManagementService extends Disposable implements IExtension // Install on both servers return Promise.all(this.servers.map(server => server.extensionManagementService.installFromGallery(gallery))).then(([local]) => local); } - if (isUIExtension(manifest, this.productService, this.configurationService)) { + if (prefersExecuteOnUI(manifest, this.productService, this.configurationService)) { // Install only on local server return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(gallery); } @@ -204,6 +204,15 @@ export class ExtensionManagementService extends Disposable implements IExtension return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(gallery); } if (this.extensionManagementServerService.remoteExtensionManagementServer) { + const manifest = await this.extensionGalleryService.getManifest(gallery, CancellationToken.None); + if (!manifest) { + return Promise.reject(localize('Manifest is not found', "Installing Extension {0} failed: Manifest is not found.", gallery.displayName || gallery.name)); + } + if (!isLanguagePackExtension(manifest) && !canExecuteOnWorkspace(manifest, this.productService, this.configurationService)) { + const error = new Error(localize('cannot be installed', "Cannot install '{0}' extension since it cannot be enabled in the remote server.", gallery.displayName || gallery.name)); + error.name = INSTALL_ERROR_NOT_SUPPORTED; + return Promise.reject(error); + } return this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.installFromGallery(gallery); } return Promise.reject('No Servers to Install'); diff --git a/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts b/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts index 2f79c9193c..fbdc4a3e81 100644 --- a/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts +++ b/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; import { IExtensionManagementService, DidUninstallExtensionEvent, ILocalExtension, DidInstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IExtensionEnablementService, EnablementState, IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionEnablementService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { Emitter } from 'vs/base/common/event'; @@ -39,13 +39,15 @@ function storageService(instantiationService: TestInstantiationService): IStorag export class TestExtensionEnablementService extends ExtensionEnablementService { constructor(instantiationService: TestInstantiationService) { + const extensionManagementService = instantiationService.get(IExtensionManagementService) || instantiationService.stub(IExtensionManagementService, { onDidInstallExtension: new Emitter().event, onDidUninstallExtension: new Emitter().event } as IExtensionManagementService); + const extensionManagementServerService = instantiationService.get(IExtensionManagementServerService) || instantiationService.stub(IExtensionManagementServerService, { localExtensionManagementServer: { extensionManagementService } }); super( storageService(instantiationService), instantiationService.get(IWorkspaceContextService), instantiationService.get(IWorkbenchEnvironmentService) || instantiationService.stub(IWorkbenchEnvironmentService, { configuration: Object.create(null) } as IWorkbenchEnvironmentService), - instantiationService.get(IExtensionManagementService) || instantiationService.stub(IExtensionManagementService, - { onDidInstallExtension: new Emitter().event, onDidUninstallExtension: new Emitter().event } as IExtensionManagementService), - instantiationService.get(IConfigurationService), instantiationService.get(IExtensionManagementServerService), + extensionManagementService, + instantiationService.get(IConfigurationService), + extensionManagementServerService, productService ); } @@ -403,15 +405,23 @@ suite('ExtensionEnablementService Test', () => { test('test local workspace extension is disabled by kind', async () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); - const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); testObject = new TestExtensionEnablementService(instantiationService); assert.ok(!testObject.isEnabled(localWorkspaceExtension)); assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); }); + test('test local workspace + ui extension is enabled by kind', async () => { + instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['workspace', 'ui'] }, { location: URI.file(`pub.a`) }); + testObject = new TestExtensionEnablementService(instantiationService); + assert.ok(testObject.isEnabled(localWorkspaceExtension)); + assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); + }); + test('test local ui extension is not disabled by kind', async () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); - const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`) }); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`) }); testObject = new TestExtensionEnablementService(instantiationService); assert.ok(testObject.isEnabled(localWorkspaceExtension)); assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); @@ -419,29 +429,45 @@ suite('ExtensionEnablementService Test', () => { test('test canChangeEnablement return false when the local workspace extension is disabled by kind', () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); - const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); testObject = new TestExtensionEnablementService(instantiationService); assert.equal(testObject.canChangeEnablement(localWorkspaceExtension), false); }); test('test canChangeEnablement return true for local ui extension', () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); - const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`) }); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`) }); testObject = new TestExtensionEnablementService(instantiationService); assert.equal(testObject.canChangeEnablement(localWorkspaceExtension), true); }); test('test remote ui extension is disabled by kind', async () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); - const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + testObject = new TestExtensionEnablementService(instantiationService); + assert.ok(!testObject.isEnabled(localWorkspaceExtension)); + assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); + }); + + test('test remote ui+workspace extension is disabled by kind', async () => { + instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['ui', 'workspace'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); testObject = new TestExtensionEnablementService(instantiationService); assert.ok(testObject.isEnabled(localWorkspaceExtension)); assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); }); + test('test remote ui extension is disabled by kind when there is no local server', async () => { + instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(null, anExtensionManagementServer('vscode-remote', instantiationService))); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + testObject = new TestExtensionEnablementService(instantiationService); + assert.ok(!testObject.isEnabled(localWorkspaceExtension)); + assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); + }); + test('test remote workspace extension is not disabled by kind', async () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); - const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); testObject = new TestExtensionEnablementService(instantiationService); assert.ok(testObject.isEnabled(localWorkspaceExtension)); assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); @@ -449,31 +475,35 @@ suite('ExtensionEnablementService Test', () => { test('test canChangeEnablement return false when the remote ui extension is disabled by kind', () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); - const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); testObject = new TestExtensionEnablementService(instantiationService); - assert.equal(testObject.canChangeEnablement(localWorkspaceExtension), true); + assert.equal(testObject.canChangeEnablement(localWorkspaceExtension), false); }); test('test canChangeEnablement return true for remote workspace extension', () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); - const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); testObject = new TestExtensionEnablementService(instantiationService); assert.equal(testObject.canChangeEnablement(localWorkspaceExtension), true); }); }); +function anExtensionManagementServer(authority: string, instantiationService: TestInstantiationService): IExtensionManagementServer { + return { + authority, + label: authority, + extensionManagementService: instantiationService.get(IExtensionManagementService) + }; +} + function aMultiExtensionManagementServerService(instantiationService: TestInstantiationService): IExtensionManagementServerService { - const localExtensionManagementServer = { - authority: 'vscode-local', - label: 'local', - extensionManagementService: instantiationService.get(IExtensionManagementService) - }; - const remoteExtensionManagementServer = { - authority: 'vscode-remote', - label: 'remote', - extensionManagementService: instantiationService.get(IExtensionManagementService) - }; + const localExtensionManagementServer = anExtensionManagementServer('vscode-local', instantiationService); + const remoteExtensionManagementServer = anExtensionManagementServer('vscode-remote', instantiationService); + return anExtensionManagementServerService(localExtensionManagementServer, remoteExtensionManagementServer); +} + +function anExtensionManagementServerService(localExtensionManagementServer: IExtensionManagementServer | null, remoteExtensionManagementServer: IExtensionManagementServer | null): IExtensionManagementServerService { return { _serviceBrand: undefined, localExtensionManagementServer, diff --git a/src/vs/workbench/services/extensionResourceLoader/browser/extensionResourceLoaderService.ts b/src/vs/workbench/services/extensionResourceLoader/browser/extensionResourceLoaderService.ts new file mode 100644 index 0000000000..770910dcbe --- /dev/null +++ b/src/vs/workbench/services/extensionResourceLoader/browser/extensionResourceLoaderService.ts @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; +import * as dom from 'vs/base/browser/dom'; +import { Schemas } from 'vs/base/common/network'; + +class ExtensionResourceLoaderService implements IExtensionResourceLoaderService { + + _serviceBrand: undefined; + + constructor( + @IFileService private readonly _fileService: IFileService + ) { } + + async readExtensionResource(uri: URI): Promise { + uri = dom.asDomUri(uri); + + if (uri.scheme !== Schemas.http && uri.scheme !== Schemas.https) { + const result = await this._fileService.readFile(uri); + return result.value.toString(); + } + + const response = await fetch(uri.toString(true)); + if (response.status !== 200) { + throw new Error(response.statusText); + } + return response.text(); + + } +} + +registerSingleton(IExtensionResourceLoaderService, ExtensionResourceLoaderService); diff --git a/src/vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader.ts b/src/vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader.ts new file mode 100644 index 0000000000..a646f7b447 --- /dev/null +++ b/src/vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader.ts @@ -0,0 +1,21 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IExtensionResourceLoaderService = createDecorator('extensionResourceLoaderService'); + +/** + * A service useful for reading resources from within extensions. + */ +export interface IExtensionResourceLoaderService { + _serviceBrand: undefined; + + /** + * Read a certain resource within an extension. + */ + readExtensionResource(uri: URI): Promise; +} diff --git a/src/vs/workbench/services/extensionResourceLoader/electron-browser/extensionResourceLoaderService.ts b/src/vs/workbench/services/extensionResourceLoader/electron-browser/extensionResourceLoaderService.ts new file mode 100644 index 0000000000..9e52d65f57 --- /dev/null +++ b/src/vs/workbench/services/extensionResourceLoader/electron-browser/extensionResourceLoaderService.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; + +export class ExtensionResourceLoaderService implements IExtensionResourceLoaderService { + + _serviceBrand: undefined; + + constructor( + @IFileService private readonly _fileService: IFileService + ) { } + + async readExtensionResource(uri: URI): Promise { + const result = await this._fileService.readFile(uri); + return result.value.toString(); + } +} + +registerSingleton(IExtensionResourceLoaderService, ExtensionResourceLoaderService); diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index bc43981dbd..14b0a5c31a 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -20,7 +20,7 @@ import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEn import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { WebWorkerExtensionHostStarter } from 'vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter'; import { URI } from 'vs/base/common/uri'; -import { isWebExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; +import { canExecuteOnWeb } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { FetchFileSystemProvider } from 'vs/workbench/services/extensions/browser/webWorkerFileSystemProvider'; @@ -85,14 +85,14 @@ export class ExtensionService extends AbstractExtensionService implements IExten protected _createExtensionHosts(_isInitialStart: boolean, initialActivationEvents: string[]): ExtensionHostProcessManager[] { const result: ExtensionHostProcessManager[] = []; - const webExtensions = this.getExtensions().then(extensions => extensions.filter(ext => isWebExtension(ext, this._configService))); + const webExtensions = this.getExtensions().then(extensions => extensions.filter(ext => canExecuteOnWeb(ext, this._productService, this._configService))); const webHostProcessWorker = this._instantiationService.createInstance(WebWorkerExtensionHostStarter, true, webExtensions, URI.file(this._environmentService.logsPath).with({ scheme: this._environmentService.logFile.scheme })); const webHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, false, webHostProcessWorker, null, initialActivationEvents); result.push(webHostProcessManager); const remoteAgentConnection = this._remoteAgentService.getConnection(); if (remoteAgentConnection) { - const remoteExtensions = this.getExtensions().then(extensions => extensions.filter(ext => !isWebExtension(ext, this._configService))); + const remoteExtensions = this.getExtensions().then(extensions => extensions.filter(ext => !canExecuteOnWeb(ext, this._productService, this._configService))); const remoteExtHostProcessWorker = this._instantiationService.createInstance(RemoteExtensionHostClient, remoteExtensions, this._createProvider(remoteAgentConnection.remoteAuthority), this._remoteAgentService.socketFactory); const remoteExtHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, false, remoteExtHostProcessWorker, remoteAgentConnection.remoteAuthority, initialActivationEvents); result.push(remoteExtHostProcessManager); @@ -111,7 +111,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten let result: DeltaExtensionsResult; // local: only enabled and web'ish extension - localExtensions = localExtensions.filter(ext => this._isEnabled(ext) && isWebExtension(ext, this._configService)); + localExtensions = localExtensions!.filter(ext => this._isEnabled(ext) && canExecuteOnWeb(ext, this._productService, this._configService)); this._checkEnableProposedApi(localExtensions); if (!remoteEnv) { @@ -119,7 +119,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten } else { // remote: only enabled and none-web'ish extension - remoteEnv.extensions = remoteEnv.extensions.filter(extension => this._isEnabled(extension) && !isWebExtension(extension, this._configService)); + remoteEnv.extensions = remoteEnv.extensions.filter(extension => this._isEnabled(extension) && !canExecuteOnWeb(extension, this._productService, this._configService)); this._checkEnableProposedApi(remoteEnv.extensions); // in case of overlap, the remote wins diff --git a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts index 96d5768491..c6b6e84232 100644 --- a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts @@ -428,4 +428,4 @@ export class ManageAuthorizedExtensionURIsAction extends Action { } const actionRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ManageAuthorizedExtensionURIsAction, ManageAuthorizedExtensionURIsAction.ID, ManageAuthorizedExtensionURIsAction.LABEL), `Extensions: Manage Authorized Extension URIs...`, ExtensionsLabel); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ManageAuthorizedExtensionURIsAction, ManageAuthorizedExtensionURIsAction.ID, ManageAuthorizedExtensionURIsAction.LABEL), `Extensions: Manage Authorized Extension URIs...`, ExtensionsLabel); diff --git a/src/vs/workbench/services/extensions/browser/webWorkerFileSystemProvider.ts b/src/vs/workbench/services/extensions/browser/webWorkerFileSystemProvider.ts index 80644cd17b..735c38bb9f 100644 --- a/src/vs/workbench/services/extensions/browser/webWorkerFileSystemProvider.ts +++ b/src/vs/workbench/services/extensions/browser/webWorkerFileSystemProvider.ts @@ -3,14 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IFileSystemProvider, FileSystemProviderCapabilities, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileSystemProviderError, FileSystemProviderErrorCode } from 'vs/platform/files/common/files'; - +import { FileSystemProviderCapabilities, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileSystemProviderError, FileSystemProviderErrorCode, IFileSystemProviderWithFileReadWriteCapability } from 'vs/platform/files/common/files'; import { Event } from 'vs/base/common/event'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { NotImplementedError } from 'vs/base/common/errors'; -export class FetchFileSystemProvider implements IFileSystemProvider { +export class FetchFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapability { readonly capabilities = FileSystemProviderCapabilities.Readonly + FileSystemProviderCapabilities.FileReadWrite + FileSystemProviderCapabilities.PathCaseSensitive; readonly onDidChangeCapabilities = Event.None; diff --git a/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts b/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts index b0e8ade7c3..7d0e77ccad 100644 --- a/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts @@ -118,7 +118,12 @@ export class ExtensionDescriptionRegistry { hasOnlyGoodArcs(id: string, good: Set): boolean { const dependencies = G.getArcs(id); - return dependencies.every(dependency => good.has(dependency)); + for (let i = 0; i < dependencies.length; i++) { + if (!good.has(dependencies[i])) { + return false; + } + } + return true; } getNodes(): string[] { diff --git a/src/vs/workbench/services/extensions/common/extensionDevOptions.ts b/src/vs/workbench/services/extensions/common/extensionDevOptions.ts index b4902eb1f6..acddff78c0 100644 --- a/src/vs/workbench/services/extensions/common/extensionDevOptions.ts +++ b/src/vs/workbench/services/extensions/common/extensionDevOptions.ts @@ -29,7 +29,7 @@ export function parseExtensionDevOptions(environmentService: IEnvironmentService let isExtensionDevDebug = debugOk && typeof environmentService.debugExtensionHost.port === 'number'; let isExtensionDevDebugBrk = debugOk && !!environmentService.debugExtensionHost.break; - let isExtensionDevTestFromCli = isExtensionDevHost && !!environmentService.extensionTestsLocationURI && !environmentService.debugExtensionHost.break; + let isExtensionDevTestFromCli = isExtensionDevHost && !!environmentService.extensionTestsLocationURI && !environmentService.debugExtensionHost.debugId; return { isExtensionDevHost, isExtensionDevDebug, diff --git a/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts b/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts index 82a901cdbf..cdd2cf6d38 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts @@ -22,7 +22,7 @@ import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IUntitledResourceInput } from 'vs/workbench/common/editor'; +import { IUntitledTextResourceInput } from 'vs/workbench/common/editor'; import { StopWatch } from 'vs/base/common/stopwatch'; import { VSBuffer } from 'vs/base/common/buffer'; import { IExtensionHostStarter } from 'vs/workbench/services/extensions/common/extensions'; @@ -57,7 +57,7 @@ export class ExtensionHostProcessManager extends Disposable { constructor( public readonly isLocal: boolean, extensionHostProcessWorker: IExtensionHostStarter, - private readonly _remoteAuthority: string, + private readonly _remoteAuthority: string | null, initialActivationEvents: string[], @IInstantiationService private readonly _instantiationService: IInstantiationService, @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @@ -185,7 +185,7 @@ export class ExtensionHostProcessManager extends Disposable { this._extensionHostProcessRPCProtocol = new RPCProtocol(protocol, logger); this._register(this._extensionHostProcessRPCProtocol.onDidChangeResponsiveState((responsiveState: ResponsiveState) => this._onDidChangeResponsiveState.fire(responsiveState))); const extHostContext: IExtHostContext = { - remoteAuthority: this._remoteAuthority, + remoteAuthority: this._remoteAuthority! /* TODO: alexdima, remove not-null assertion */, getProxy: (identifier: ProxyIdentifier): T => this._extensionHostProcessRPCProtocol!.getProxy(identifier), set: (identifier: ProxyIdentifier, instance: R): R => this._extensionHostProcessRPCProtocol!.set(identifier, instance), assertRegistered: (identifiers: ProxyIdentifier[]): void => this._extensionHostProcessRPCProtocol!.assertRegistered(identifiers), @@ -362,7 +362,7 @@ class RPCLogger implements IRPCProtocolLogger { } interface ExtHostLatencyResult { - remoteAuthority: string; + remoteAuthority: string | null; up: number; down: number; latency: number; @@ -405,10 +405,13 @@ export class MeasureExtHostLatencyAction extends Action { public async run(): Promise { const measurements = await Promise.all(getLatencyTestProviders().map(provider => provider.measure())); - this._editorService.openEditor({ contents: measurements.map(MeasureExtHostLatencyAction._print).join('\n\n'), options: { pinned: true } } as IUntitledResourceInput); + this._editorService.openEditor({ contents: measurements.map(MeasureExtHostLatencyAction._print).join('\n\n'), options: { pinned: true } } as IUntitledTextResourceInput); } - private static _print(m: ExtHostLatencyResult): string { + private static _print(m: ExtHostLatencyResult | null): string { + if (!m) { + return ''; + } return `${m.remoteAuthority ? `Authority: ${m.remoteAuthority}\n` : ``}Roundtrip latency: ${m.latency.toFixed(3)}ms\nUp: ${MeasureExtHostLatencyAction._printSpeed(m.up)}\nDown: ${MeasureExtHostLatencyAction._printSpeed(m.down)}\n`; } @@ -424,4 +427,4 @@ export class MeasureExtHostLatencyAction extends Action { } const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(MeasureExtHostLatencyAction, MeasureExtHostLatencyAction.ID, MeasureExtHostLatencyAction.LABEL), 'Developer: Measure Extension Host Latency', nls.localize('developer', "Developer")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MeasureExtHostLatencyAction, MeasureExtHostLatencyAction.ID, MeasureExtHostLatencyAction.LABEL), 'Developer: Measure Extension Host Latency', nls.localize('developer', "Developer")); diff --git a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts index 3aff7c62a1..cfcb6c0297 100644 --- a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts @@ -146,8 +146,20 @@ export class ExtensionPoint implements IExtensionPoint { } } +const extensionKindSchema: IJSONSchema = { + type: 'string', + enum: [ + 'ui', + 'workspace' + ], + enumDescriptions: [ + nls.localize('ui', "UI extension kind. In a remote window, such extensions are enabled only when available on the local machine."), + nls.localize('workspace', "Workspace extension kind. In a remote window, such extensions are enabled only when available on the remote.") + ], +}; + const schemaId = 'vscode://schemas/vscode-extensions'; -export const schema = { +export const schema: IJSONSchema = { properties: { engines: { type: 'object', @@ -345,17 +357,32 @@ export const schema = { } }, extensionKind: { - description: nls.localize('extensionKind', "Define the kind of an extension. `ui` extensions are installed and run on the local machine while `workspace` extensions are run on the remote."), - type: 'string', - enum: [ - 'ui', - 'workspace' - ], - enumDescriptions: [ - nls.localize('ui', "UI extension kind. In a remote window, such extensions are enabled only when available on the local machine."), - nls.localize('workspace', "Workspace extension kind. In a remote window, such extensions are enabled only when available on the remote.") - ], - default: 'workspace' + description: nls.localize('extensionKind', "Define the kind of an extension. `ui` extensions are installed and run on the local machine while `workspace` extensions run on the remote."), + type: 'array', + items: extensionKindSchema, + default: ['workspace'], + defaultSnippets: [ + { + body: ['ui'], + description: nls.localize('extensionKind.ui', "Define an extension which can run only on the local machine when connected to remote window.") + }, + { + body: ['workspace'], + description: nls.localize('extensionKind.workspace', "Define an extension which can run only on the remote machine when connected remote window.") + }, + { + body: ['ui', 'workspace'], + description: nls.localize('extensionKind.ui-workspace', "Define an extension which can run on either side, with a preference towards running on the local machine.") + }, + { + body: ['workspace', 'ui'], + description: nls.localize('extensionKind.workspace-ui', "Define an extension which can run on either side, with a preference towards running on the remote machine.") + }, + { + body: [], + description: nls.localize('extensionKind.empty', "Define an extension which cannot run in a remote context, neither on the local, nor on the remote machine.") + } + ] }, scripts: { type: 'object', @@ -395,7 +422,7 @@ export class ExtensionsRegistryImpl { const result = new ExtensionPoint(desc.extensionPoint, desc.defaultExtensionKind); this._extensionPoints.set(desc.extensionPoint, result); - schema.properties['contributes'].properties[desc.extensionPoint] = desc.jsonSchema; + schema.properties!['contributes'].properties![desc.extensionPoint] = desc.jsonSchema; schemaRegistry.registerSchema(schemaId, schema); return result; diff --git a/src/vs/workbench/services/extensions/common/extensionsUtil.ts b/src/vs/workbench/services/extensions/common/extensionsUtil.ts index 043f5e2837..907f84f0c5 100644 --- a/src/vs/workbench/services/extensions/common/extensionsUtil.ts +++ b/src/vs/workbench/services/extensions/common/extensionsUtil.ts @@ -4,55 +4,124 @@ *--------------------------------------------------------------------------------------------*/ import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { IExtensionManifest, ExtensionKind, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { getGalleryExtensionId, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { IProductService } from 'vs/platform/product/common/productService'; -export function isWebExtension(manifest: IExtensionManifest, configurationService: IConfigurationService): boolean { - const extensionKind = getExtensionKind(manifest, configurationService); - return extensionKind === 'web'; +export function prefersExecuteOnUI(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): boolean { + const extensionKind = getExtensionKind(manifest, productService, configurationService); + return (extensionKind.length > 0 && extensionKind[0] === 'ui'); } -export function isUIExtension(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): boolean { - const uiContributions = ExtensionsRegistry.getExtensionPoints().filter(e => e.defaultExtensionKind !== 'workspace').map(e => e.name); - const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name); - const extensionKind = getExtensionKind(manifest, configurationService); - switch (extensionKind) { - case 'ui': return true; - case 'workspace': return false; - default: { - // Tagged as UI extension in product - if (isNonEmptyArray(productService.uiExtensions) && productService.uiExtensions.some(id => areSameExtensions({ id }, { id: extensionId }))) { - return true; - } - // Not an UI extension if it has main - if (manifest.main) { - return false; - } - // Not an UI extension if it has dependencies or an extension pack - if (isNonEmptyArray(manifest.extensionDependencies) || isNonEmptyArray(manifest.extensionPack)) { - return false; - } - if (manifest.contributes) { - // Not an UI extension if it has no ui contributions - if (!uiContributions.length || Object.keys(manifest.contributes).some(contribution => uiContributions.indexOf(contribution) === -1)) { - return false; - } - } - return true; - } - } +export function prefersExecuteOnWorkspace(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): boolean { + const extensionKind = getExtensionKind(manifest, productService, configurationService); + return (extensionKind.length > 0 && extensionKind[0] === 'workspace'); } -function getExtensionKind(manifest: IExtensionManifest, configurationService: IConfigurationService): string | undefined { - const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name); - const configuredExtensionKinds = configurationService.getValue<{ [key: string]: string }>('remote.extensionKind') || {}; - for (const id of Object.keys(configuredExtensionKinds)) { - if (areSameExtensions({ id: extensionId }, { id })) { - return configuredExtensionKinds[id]; +export function canExecuteOnUI(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): boolean { + const extensionKind = getExtensionKind(manifest, productService, configurationService); + return extensionKind.some(kind => kind === 'ui'); +} + +export function canExecuteOnWorkspace(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): boolean { + const extensionKind = getExtensionKind(manifest, productService, configurationService); + return extensionKind.some(kind => kind === 'workspace'); +} + +export function canExecuteOnWeb(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): boolean { + const extensionKind = getExtensionKind(manifest, productService, configurationService); + return extensionKind.some(kind => kind === 'web'); +} + +export function getExtensionKind(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): ExtensionKind[] { + // check in config + let result = getConfiguredExtensionKind(manifest, configurationService); + if (typeof result !== 'undefined') { + return toArray(result); + } + + // check product.json + result = getProductExtensionKind(manifest, productService); + if (typeof result !== 'undefined') { + return toArray(result); + } + + // check the manifest itself + result = manifest.extensionKind; + if (typeof result !== 'undefined') { + return toArray(result); + } + + // Not an UI extension if it has main + if (manifest.main) { + return ['workspace']; + } + + // Not an UI extension if it has dependencies or an extension pack + if (isNonEmptyArray(manifest.extensionDependencies) || isNonEmptyArray(manifest.extensionPack)) { + return ['workspace']; + } + + if (manifest.contributes) { + // Not an UI extension if it has no ui contributions + for (const contribution of Object.keys(manifest.contributes)) { + if (!isUIExtensionPoint(contribution)) { + return ['workspace']; + } } } - return manifest.extensionKind; + + return ['ui', 'workspace']; +} + +let _uiExtensionPoints: Set | null = null; +function isUIExtensionPoint(extensionPoint: string): boolean { + if (_uiExtensionPoints === null) { + const uiExtensionPoints = new Set(); + ExtensionsRegistry.getExtensionPoints().filter(e => e.defaultExtensionKind !== 'workspace').forEach(e => { + uiExtensionPoints.add(e.name); + }); + _uiExtensionPoints = uiExtensionPoints; + } + return _uiExtensionPoints.has(extensionPoint); +} + +let _productExtensionKindsMap: Map | null = null; +function getProductExtensionKind(manifest: IExtensionManifest, productService: IProductService): ExtensionKind | ExtensionKind[] | undefined { + if (_productExtensionKindsMap === null) { + const productExtensionKindsMap = new Map(); + if (productService.extensionKind) { + for (const id of Object.keys(productService.extensionKind)) { + productExtensionKindsMap.set(ExtensionIdentifier.toKey(id), productService.extensionKind[id]); + } + } + _productExtensionKindsMap = productExtensionKindsMap; + } + + const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name); + return _productExtensionKindsMap.get(ExtensionIdentifier.toKey(extensionId)); +} + +let _configuredExtensionKindsMap: Map | null = null; +function getConfiguredExtensionKind(manifest: IExtensionManifest, configurationService: IConfigurationService): ExtensionKind | ExtensionKind[] | undefined { + if (_configuredExtensionKindsMap === null) { + const configuredExtensionKindsMap = new Map(); + const configuredExtensionKinds = configurationService.getValue<{ [key: string]: ExtensionKind | ExtensionKind[] }>('remote.extensionKind') || {}; + for (const id of Object.keys(configuredExtensionKinds)) { + configuredExtensionKindsMap.set(ExtensionIdentifier.toKey(id), configuredExtensionKinds[id]); + } + _configuredExtensionKindsMap = configuredExtensionKindsMap; + } + + const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name); + return _configuredExtensionKindsMap.get(ExtensionIdentifier.toKey(extensionId)); +} + +function toArray(extensionKind: ExtensionKind | ExtensionKind[]): ExtensionKind[] { + if (Array.isArray(extensionKind)) { + return extensionKind; + } + return extensionKind === 'ui' ? ['ui', 'workspace'] : [extensionKind]; } diff --git a/src/vs/workbench/services/extensions/common/rpcProtocol.ts b/src/vs/workbench/services/extensions/common/rpcProtocol.ts index 968c3e6bcd..f08e8d8faf 100644 --- a/src/vs/workbench/services/extensions/common/rpcProtocol.ts +++ b/src/vs/workbench/services/extensions/common/rpcProtocol.ts @@ -543,10 +543,16 @@ class MessageBuffer { const el = arr[i]; const elType = arrType[i]; size += 1; // arg type - if (elType === ArgType.String) { - size += this.sizeLongString(el); - } else { - size += this.sizeVSBuffer(el); + switch (elType) { + case ArgType.String: + size += this.sizeLongString(el); + break; + case ArgType.VSBuffer: + size += this.sizeVSBuffer(el); + break; + case ArgType.Undefined: + // empty... + break; } } return size; @@ -557,19 +563,25 @@ class MessageBuffer { for (let i = 0, len = arr.length; i < len; i++) { const el = arr[i]; const elType = arrType[i]; - if (elType === ArgType.String) { - this.writeUInt8(ArgType.String); - this.writeLongString(el); - } else { - this.writeUInt8(ArgType.VSBuffer); - this.writeVSBuffer(el); + switch (elType) { + case ArgType.String: + this.writeUInt8(ArgType.String); + this.writeLongString(el); + break; + case ArgType.VSBuffer: + this.writeUInt8(ArgType.VSBuffer); + this.writeVSBuffer(el); + break; + case ArgType.Undefined: + this.writeUInt8(ArgType.Undefined); + break; } } } - public readMixedArray(): Array { + public readMixedArray(): Array { const arrLen = this._buff.readUInt8(this._offset); this._offset += 1; - let arr: Array = new Array(arrLen); + let arr: Array = new Array(arrLen); for (let i = 0; i < arrLen; i++) { const argType = this.readUInt8(); switch (argType) { @@ -579,6 +591,9 @@ class MessageBuffer { case ArgType.VSBuffer: arr[i] = this.readVSBuffer(); break; + case ArgType.Undefined: + arr[i] = undefined; + break; } } return arr; @@ -587,12 +602,20 @@ class MessageBuffer { class MessageIO { - private static _arrayContainsBuffer(arr: any[]): boolean { - return arr.some(value => value instanceof VSBuffer); + private static _arrayContainsBufferOrUndefined(arr: any[]): boolean { + for (let i = 0, len = arr.length; i < len; i++) { + if (arr[i] instanceof VSBuffer) { + return true; + } + if (typeof arr[i] === 'undefined') { + return true; + } + } + return false; } public static serializeRequest(req: number, rpcId: number, method: string, args: any[], usesCancellationToken: boolean, replacer: JSONStringifyReplacer | null): VSBuffer { - if (this._arrayContainsBuffer(args)) { + if (this._arrayContainsBufferOrUndefined(args)) { let massagedArgs: VSBuffer[] = []; let massagedArgsType: ArgType[] = []; for (let i = 0, len = args.length; i < len; i++) { @@ -600,6 +623,9 @@ class MessageIO { if (arg instanceof VSBuffer) { massagedArgs[i] = arg; massagedArgsType[i] = ArgType.VSBuffer; + } else if (typeof arg === 'undefined') { + massagedArgs[i] = VSBuffer.alloc(0); + massagedArgsType[i] = ArgType.Undefined; } else { massagedArgs[i] = VSBuffer.fromString(safeStringify(arg, replacer)); massagedArgsType[i] = ArgType.String; @@ -767,5 +793,6 @@ const enum MessageType { const enum ArgType { String = 1, - VSBuffer = 2 + VSBuffer = 2, + Undefined = 3 } diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index 9e23965882..6b0fbf34b7 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -37,7 +37,7 @@ import { parseExtensionDevOptions } from '../common/extensionDevOptions'; import { VSBuffer } from 'vs/base/common/buffer'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { IExtensionHostStarter } from 'vs/workbench/services/extensions/common/extensions'; -import { isEqualOrParent } from 'vs/base/common/resources'; +import { isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces'; import { IHostService } from 'vs/workbench/services/host/browser/host'; export class ExtensionHostProcessWorker implements IExtensionHostStarter { @@ -158,6 +158,8 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { '--nolazy', (this._isExtensionDevDebugBrk ? '--inspect-brk=' : '--inspect=') + portNumber ]; + } else { + opts.execArgv = ['--inspect-port=0']; } const crashReporterOptions = undefined; // TODO@electron pass this in as options to the extension host after verifying this actually works @@ -170,10 +172,10 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { // Catch all output coming from the extension host process type Output = { data: string, format: string[] }; - this._extensionHostProcess.stdout.setEncoding('utf8'); - this._extensionHostProcess.stderr.setEncoding('utf8'); - const onStdout = Event.fromNodeEventEmitter(this._extensionHostProcess.stdout, 'data'); - const onStderr = Event.fromNodeEventEmitter(this._extensionHostProcess.stderr, 'data'); + this._extensionHostProcess.stdout!.setEncoding('utf8'); + this._extensionHostProcess.stderr!.setEncoding('utf8'); + const onStdout = Event.fromNodeEventEmitter(this._extensionHostProcess.stdout!, 'data'); + const onStderr = Event.fromNodeEventEmitter(this._extensionHostProcess.stderr!, 'data'); const onOutput = Event.any( Event.map(onStdout, o => ({ data: `%c${o}`, format: [''] })), Event.map(onStderr, o => ({ data: `%c${o}`, format: ['color: red'] })) @@ -411,7 +413,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { configuration: withNullAsUndefined(workspace.configuration), id: workspace.id, name: this._labelService.getWorkspaceLabel(workspace), - isUntitled: workspace.configuration ? isEqualOrParent(workspace.configuration, this._environmentService.untitledWorkspacesHome) : false + isUntitled: workspace.configuration ? isUntitledWorkspace(workspace.configuration, this._environmentService) : false }, remote: { authority: this._environmentService.configuration.remoteAuthority, @@ -432,19 +434,19 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { private _logExtensionHostMessage(entry: IRemoteConsoleLog) { - // Send to local console unless we run tests from cli - if (!this._isExtensionDevTestFromCli) { - log(entry, 'Extension Host'); - } - - // Log on main side if running tests from cli if (this._isExtensionDevTestFromCli) { - logRemoteEntry(this._logService, entry); - } - // Broadcast to other windows if we are in development mode - else if (this._environmentService.debugExtensionHost.debugId && (!this._environmentService.isBuilt || this._isExtensionDevHost)) { - this._extensionHostDebugService.logToSession(this._environmentService.debugExtensionHost.debugId, entry); + // Log on main side if running tests from cli + logRemoteEntry(this._logService, entry); + } else { + + // Send to local console + log(entry, 'Extension Host'); + + // Broadcast to other windows if we are in development mode + if (this._environmentService.debugExtensionHost.debugId && (!this._environmentService.isBuilt || this._isExtensionDevHost)) { + this._extensionHostDebugService.logToSession(this._environmentService.debugExtensionHost.debugId, entry); + } } } diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index e51432cf92..5ad6cedc6b 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -19,7 +19,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IInitDataProvider, RemoteExtensionHostClient } from 'vs/workbench/services/extensions/common/remoteExtensionHostClient'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { isUIExtension as isUIExtensionFunc } from 'vs/workbench/services/extensions/common/extensionsUtil'; +import { getExtensionKind } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; @@ -439,8 +439,6 @@ export class ExtensionService extends AbstractExtensionService implements IExten } protected async _scanAndHandleExtensions(): Promise { - const isUIExtension = (extension: IExtensionDescription) => isUIExtensionFunc(extension, this._productService, this._configurationService); - this._extensionScanner.startScanningExtensions(this.createLogger()); const remoteAuthority = this._environmentService.configuration.remoteAuthority; @@ -510,14 +508,38 @@ export class ExtensionService extends AbstractExtensionService implements IExten // remove disabled extensions remoteEnv.extensions = remove(remoteEnv.extensions, extension => this._isDisabled(extension)); + // Determine where each extension will execute, based on extensionKind + const isInstalledLocally = new Set(); + localExtensions.forEach(ext => isInstalledLocally.add(ExtensionIdentifier.toKey(ext.identifier))); + + const isInstalledRemotely = new Set(); + remoteEnv.extensions.forEach(ext => isInstalledRemotely.add(ExtensionIdentifier.toKey(ext.identifier))); + + const enum RunningLocation { None, Local, Remote } + const pickRunningLocation = (extension: IExtensionDescription): RunningLocation => { + for (const extensionKind of getExtensionKind(extension, this._productService, this._configurationService)) { + if (extensionKind === 'ui') { + if (isInstalledLocally.has(ExtensionIdentifier.toKey(extension.identifier))) { + return RunningLocation.Local; + } + } else if (extensionKind === 'workspace') { + if (isInstalledRemotely.has(ExtensionIdentifier.toKey(extension.identifier))) { + return RunningLocation.Remote; + } + } + } + return RunningLocation.None; + }; + + const runningLocation = new Map(); + localExtensions.forEach(ext => runningLocation.set(ExtensionIdentifier.toKey(ext.identifier), pickRunningLocation(ext))); + remoteEnv.extensions.forEach(ext => runningLocation.set(ExtensionIdentifier.toKey(ext.identifier), pickRunningLocation(ext))); + // remove non-UI extensions from the local extensions - localExtensions = remove(localExtensions, extension => !extension.isBuiltin && !isUIExtension(extension)); + localExtensions = localExtensions.filter(ext => runningLocation.get(ExtensionIdentifier.toKey(ext.identifier)) === RunningLocation.Local); // in case of UI extensions overlap, the local extension wins - remoteEnv.extensions = remove(remoteEnv.extensions, localExtensions.filter(extension => isUIExtension(extension))); - - // in case of other extensions overlap, the remote extension wins - localExtensions = remove(localExtensions, remoteEnv.extensions); + remoteEnv.extensions = remoteEnv.extensions.filter(ext => runningLocation.get(ExtensionIdentifier.toKey(ext.identifier)) === RunningLocation.Remote); // save for remote extension's init data this._remoteExtensionsEnvironmentData.set(remoteAuthority, remoteEnv); @@ -550,14 +572,12 @@ export class ExtensionService extends AbstractExtensionService implements IExten } public _onExtensionHostExit(code: number): void { - // Expected development extension termination: When the extension host goes down we also shutdown the window - if (!this._isExtensionDevTestFromCli) { - this._electronService.closeWindow(); - } - - // When CLI testing make sure to exit with proper exit code - else { + if (this._isExtensionDevTestFromCli) { + // When CLI testing make sure to exit with proper exit code ipc.send('vscode:exit', code); + } else { + // Expected development extension termination: When the extension host goes down we also shutdown the window + this._electronService.closeWindow(); } } } diff --git a/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts b/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts index 3e74d33c9c..846d4a96c7 100644 --- a/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts +++ b/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts @@ -11,7 +11,7 @@ import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ILogService } from 'vs/platform/log/common/log'; import { toErrorMessage } from 'vs/base/common/errorMessage'; -import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; +import { prefersExecuteOnUI } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { values } from 'vs/base/common/map'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -48,6 +48,10 @@ export class RemoteExtensionManagementChannelClient extends ExtensionManagementC } private async doInstallFromGallery(extension: IGalleryExtension): Promise { + if (this.configurationService.getValue('remote.downloadExtensionsLocally')) { + this.logService.trace(`Download '${extension.identifier.id}' extension locally and install`); + return this.downloadCompatibleAndInstall(extension); + } try { const local = await super.installFromGallery(extension); return local; @@ -116,7 +120,7 @@ export class RemoteExtensionManagementChannelClient extends ExtensionManagementC for (let idx = 0; idx < extensions.length; idx++) { const extension = extensions[idx]; const manifest = manifests[idx]; - if (manifest && isUIExtension(manifest, this.productService, this.configurationService) === uiExtension) { + if (manifest && prefersExecuteOnUI(manifest, this.productService, this.configurationService) === uiExtension) { result.set(extension.identifier.id.toLowerCase(), extension); extensionsManifests.push(manifest); } diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts index 4f8aed30ad..91c65e44fc 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts @@ -27,6 +27,17 @@ interface ParsedExtHostArgs { uriTransformerPath?: string; } +// workaround for https://github.com/microsoft/vscode/issues/85490 +// remove --inspect-port=0 after start so that it doesn't trigger LSP debugging +(function removeInspectPort() { + for (let i = 0; i < process.execArgv.length; i++) { + if (process.execArgv[i] === '--inspect-port=0') { + process.execArgv.splice(i, 1); + i--; + } + } +})(); + const args = minimist(process.argv.slice(2), { string: [ 'uriTransformerPath' diff --git a/src/vs/workbench/services/extensions/node/extensionPoints.ts b/src/vs/workbench/services/extensions/node/extensionPoints.ts index c93d977c30..30fbf23e7d 100644 --- a/src/vs/workbench/services/extensions/node/extensionPoints.ts +++ b/src/vs/workbench/services/extensions/node/extensionPoints.ts @@ -51,7 +51,7 @@ class ExtensionManifestParser extends ExtensionManifestHandler { return pfs.readFile(this._absoluteManifestPath).then((manifestContents) => { const errors: json.ParseError[] = []; const manifest = json.parse(manifestContents.toString(), errors); - if (!!manifest && errors.length === 0) { + if (errors.length === 0 && json.getNodeType(manifest) === 'object') { if (manifest.__metadata) { manifest.uuid = manifest.__metadata.id; } @@ -108,6 +108,9 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler { this._log.error(this._absoluteFolderPath, nls.localize('jsonsParseReportErrors', "Failed to parse {0}: {1}.", localized, getParseErrorMessage(error.error))); }); }; + const reportInvalidFormat = (localized: string | null): void => { + this._log.error(this._absoluteFolderPath, nls.localize('jsonInvalidFormat', "Invalid format {0}: JSON object expected.", localized)); + }; let extension = path.extname(this._absoluteManifestPath); let basename = this._absoluteManifestPath.substr(0, this._absoluteManifestPath.length - extension.length); @@ -122,6 +125,9 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler { if (errors.length > 0) { reportErrors(translationPath, errors); return { values: undefined, default: `${basename}.nls.json` }; + } else if (json.getNodeType(translationBundle) !== 'object') { + reportInvalidFormat(translationPath); + return { values: undefined, default: `${basename}.nls.json` }; } else { let values = translationBundle.contents ? translationBundle.contents.package : undefined; return { values: values, default: `${basename}.nls.json` }; @@ -144,6 +150,9 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler { if (errors.length > 0) { reportErrors(messageBundle.localized, errors); return { values: undefined, default: messageBundle.original }; + } else if (json.getNodeType(messages) !== 'object') { + reportInvalidFormat(messageBundle.localized); + return { values: undefined, default: messageBundle.original }; } return { values: messages, default: messageBundle.original }; }, (err) => { @@ -165,6 +174,9 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler { if (errors.length > 0) { reportErrors(localizedMessages.default, errors); return extensionDescription; + } else if (json.getNodeType(localizedMessages) !== 'object') { + reportInvalidFormat(localizedMessages.default); + return extensionDescription; } const localized = localizedMessages.values || Object.create(null); ExtensionManifestNLSReplacer._replaceNLStrings(this._nlsConfig, extensionDescription, localized, defaults, this._log, this._absoluteFolderPath); @@ -397,7 +409,15 @@ class ExtensionManifestValidator extends ExtensionManifestHandler { } private static _isStringArray(arr: string[]): boolean { - return Array.isArray(arr) && arr.every(value => typeof value === 'string'); + if (!Array.isArray(arr)) { + return false; + } + for (let i = 0, len = arr.length; i < len; i++) { + if (typeof arr[i] !== 'string') { + return false; + } + } + return true; } } diff --git a/src/vs/workbench/services/extensions/node/proxyResolver.ts b/src/vs/workbench/services/extensions/node/proxyResolver.ts index 81812dfc29..83d2361768 100644 --- a/src/vs/workbench/services/extensions/node/proxyResolver.ts +++ b/src/vs/workbench/services/extensions/node/proxyResolver.ts @@ -343,9 +343,13 @@ function patches(originals: typeof http | typeof https, resolveProxy: ReturnType return original.apply(null, arguments as unknown as any[]); } - const optionsPatched = options.agent instanceof ProxyAgent; + const originalAgent = options.agent; + if (originalAgent === true) { + throw new Error('Unexpected agent option: true'); + } + const optionsPatched = originalAgent instanceof ProxyAgent; const config = onRequest && ((options)._vscodeProxySupport || /* LS */ (options)._vscodeSystemProxy) || proxySetting.config; - const useProxySettings = !optionsPatched && (config === 'override' || config === 'on' && !options.agent); + const useProxySettings = !optionsPatched && (config === 'override' || config === 'on' && originalAgent === undefined); const useSystemCertificates = !optionsPatched && certSetting.config && originals === https && !(options as https.RequestOptions).ca; if (useProxySettings || useSystemCertificates) { @@ -367,7 +371,7 @@ function patches(originals: typeof http | typeof https, resolveProxy: ReturnType options.agent = new ProxyAgent({ resolveProxy: resolveProxy.bind(undefined, { useProxySettings, useSystemCertificates }), defaultPort: originals === https ? 443 : 80, - originalAgent: options.agent + originalAgent }); return original(options, callback); } @@ -469,7 +473,9 @@ async function readCaCertificates() { } async function readWindowsCaCertificates() { - const winCA = await import('vscode-windows-ca-certs'); + const winCA = await new Promise((resolve, reject) => { + require(['vscode-windows-ca-certs'], resolve, reject); + }); let ders: any[] = []; const store = winCA(); diff --git a/src/vs/workbench/services/extensions/test/node/rpcProtocol.test.ts b/src/vs/workbench/services/extensions/test/node/rpcProtocol.test.ts index d11c7ccb6e..94b7a355e4 100644 --- a/src/vs/workbench/services/extensions/test/node/rpcProtocol.test.ts +++ b/src/vs/workbench/services/extensions/test/node/rpcProtocol.test.ts @@ -200,4 +200,15 @@ suite('RPCProtocol', () => { done(null); }); }); + + test('undefined arguments arrive as null', function () { + delegate = (a1: any, a2: any) => { + assert.equal(typeof a1, 'undefined'); + assert.equal(a2, null); + return 7; + }; + return bProxy.$m(undefined, null).then((res) => { + assert.equal(res, 7); + }); + }); }); diff --git a/src/vs/workbench/services/extensions/worker/extHost.services.ts b/src/vs/workbench/services/extensions/worker/extHost.services.ts index aa0f90c762..2a33b13962 100644 --- a/src/vs/workbench/services/extensions/worker/extHost.services.ts +++ b/src/vs/workbench/services/extensions/worker/extHost.services.ts @@ -12,8 +12,8 @@ import { IExtHostCommands, ExtHostCommands } from 'vs/workbench/api/common/extHo import { IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { IExtHostTerminalService, WorkerExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; // import { IExtHostTask, WorkerExtHostTask } from 'vs/workbench/api/common/extHostTask'; -// import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService'; -import { IExtHostSearch } from 'vs/workbench/api/common/extHostSearch'; +// import { IExtHostDebugService, WorkerExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService'; +import { IExtHostSearch, ExtHostSearch } from 'vs/workbench/api/common/extHostSearch'; import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; import { IExtHostStorage, ExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; @@ -32,6 +32,7 @@ registerSingleton(IExtHostCommands, ExtHostCommands); registerSingleton(IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors); registerSingleton(IExtHostStorage, ExtHostStorage); registerSingleton(IExtHostExtensionService, ExtHostExtensionService); +registerSingleton(IExtHostSearch, ExtHostSearch); // register services that only throw errors function NotImplementedProxy(name: ServiceIdentifier): { new(): T } { @@ -49,9 +50,8 @@ function NotImplementedProxy(name: ServiceIdentifier): { new(): T } { }; } registerSingleton(IExtHostTerminalService, WorkerExtHostTerminalService); -// registerSingleton(IExtHostTask, WorkerExtHostTask); {{SQL CARBON EDIT}} disable tasks -// registerSingleton(IExtHostDebugService, class extends NotImplementedProxy(IExtHostDebugService) { }); {{SQL CARBON EDIT}} remove debug service -registerSingleton(IExtHostSearch, class extends NotImplementedProxy(IExtHostSearch) { }); +// registerSingleton(IExtHostTask, WorkerExtHostTask); {{SQL CARBON EDIT}} disable +// registerSingleton(IExtHostDebugService, WorkerExtHostDebugService); {{SQL CARBON EDIT}} disable registerSingleton(IExtensionStoragePaths, class extends NotImplementedProxy(IExtensionStoragePaths) { whenReady = Promise.resolve(); }); diff --git a/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts b/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts new file mode 100644 index 0000000000..35ff71ab03 --- /dev/null +++ b/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts @@ -0,0 +1,208 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { Event, Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { IFilesConfiguration, AutoSaveConfiguration, HotExitConfiguration } from 'vs/platform/files/common/files'; +import { isUndefinedOrNull } from 'vs/base/common/types'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { equals } from 'vs/base/common/objects'; + +export const AutoSaveAfterShortDelayContext = new RawContextKey('autoSaveAfterShortDelayContext', false); + +export interface IAutoSaveConfiguration { + autoSaveDelay?: number; + autoSaveFocusChange: boolean; + autoSaveApplicationChange: boolean; +} + +export const enum AutoSaveMode { + OFF, + AFTER_SHORT_DELAY, + AFTER_LONG_DELAY, + ON_FOCUS_CHANGE, + ON_WINDOW_CHANGE +} + +export const IFilesConfigurationService = createDecorator('filesConfigurationService'); + +export interface IFilesConfigurationService { + + _serviceBrand: undefined; + + //#region Auto Save + + readonly onAutoSaveConfigurationChange: Event; + + getAutoSaveMode(): AutoSaveMode; + + getAutoSaveConfiguration(): IAutoSaveConfiguration; + + toggleAutoSave(): Promise; + + //#endregion + + readonly onFilesAssociationChange: Event; + + readonly isHotExitEnabled: boolean; + + readonly hotExitConfiguration: string | undefined; +} + +export class FilesConfigurationService extends Disposable implements IFilesConfigurationService { + + _serviceBrand: undefined; + + private readonly _onAutoSaveConfigurationChange = this._register(new Emitter()); + readonly onAutoSaveConfigurationChange = this._onAutoSaveConfigurationChange.event; + + private readonly _onFilesAssociationChange = this._register(new Emitter()); + readonly onFilesAssociationChange = this._onFilesAssociationChange.event; + + private configuredAutoSaveDelay?: number; + private configuredAutoSaveOnFocusChange: boolean | undefined; + private configuredAutoSaveOnWindowChange: boolean | undefined; + + private autoSaveAfterShortDelayContext: IContextKey; + + private currentFilesAssociationConfig: { [key: string]: string; }; + + private currentHotExitConfig: string; + + constructor( + @IContextKeyService contextKeyService: IContextKeyService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService + ) { + super(); + + this.autoSaveAfterShortDelayContext = AutoSaveAfterShortDelayContext.bindTo(contextKeyService); + + const configuration = configurationService.getValue(); + + this.currentFilesAssociationConfig = configuration?.files?.associations; + this.currentHotExitConfig = configuration?.files?.hotExit || HotExitConfiguration.ON_EXIT; + + this.onFilesConfigurationChange(configuration); + + this.registerListeners(); + } + + private registerListeners(): void { + + // Files configuration changes + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('files')) { + this.onFilesConfigurationChange(this.configurationService.getValue()); + } + })); + } + + protected onFilesConfigurationChange(configuration: IFilesConfiguration): void { + + // Auto Save + const autoSaveMode = configuration?.files?.autoSave || AutoSaveConfiguration.OFF; + switch (autoSaveMode) { + case AutoSaveConfiguration.AFTER_DELAY: + this.configuredAutoSaveDelay = configuration?.files?.autoSaveDelay; + this.configuredAutoSaveOnFocusChange = false; + this.configuredAutoSaveOnWindowChange = false; + break; + + case AutoSaveConfiguration.ON_FOCUS_CHANGE: + this.configuredAutoSaveDelay = undefined; + this.configuredAutoSaveOnFocusChange = true; + this.configuredAutoSaveOnWindowChange = false; + break; + + case AutoSaveConfiguration.ON_WINDOW_CHANGE: + this.configuredAutoSaveDelay = undefined; + this.configuredAutoSaveOnFocusChange = false; + this.configuredAutoSaveOnWindowChange = true; + break; + + default: + this.configuredAutoSaveDelay = undefined; + this.configuredAutoSaveOnFocusChange = false; + this.configuredAutoSaveOnWindowChange = false; + break; + } + + this.autoSaveAfterShortDelayContext.set(this.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY); + + // Emit as event + this._onAutoSaveConfigurationChange.fire(this.getAutoSaveConfiguration()); + + // Check for change in files associations + const filesAssociation = configuration?.files?.associations; + if (!equals(this.currentFilesAssociationConfig, filesAssociation)) { + this.currentFilesAssociationConfig = filesAssociation; + this._onFilesAssociationChange.fire(); + } + + // Hot exit + const hotExitMode = configuration?.files?.hotExit; + if (hotExitMode === HotExitConfiguration.OFF || hotExitMode === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { + this.currentHotExitConfig = hotExitMode; + } else { + this.currentHotExitConfig = HotExitConfiguration.ON_EXIT; + } + } + + getAutoSaveMode(): AutoSaveMode { + if (this.configuredAutoSaveOnFocusChange) { + return AutoSaveMode.ON_FOCUS_CHANGE; + } + + if (this.configuredAutoSaveOnWindowChange) { + return AutoSaveMode.ON_WINDOW_CHANGE; + } + + if (this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0) { + return this.configuredAutoSaveDelay <= 1000 ? AutoSaveMode.AFTER_SHORT_DELAY : AutoSaveMode.AFTER_LONG_DELAY; + } + + return AutoSaveMode.OFF; + } + + getAutoSaveConfiguration(): IAutoSaveConfiguration { + return { + autoSaveDelay: this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0 ? this.configuredAutoSaveDelay : undefined, + autoSaveFocusChange: !!this.configuredAutoSaveOnFocusChange, + autoSaveApplicationChange: !!this.configuredAutoSaveOnWindowChange + }; + } + + async toggleAutoSave(): Promise { + const setting = this.configurationService.inspect('files.autoSave'); + let userAutoSaveConfig = setting.user; + if (isUndefinedOrNull(userAutoSaveConfig)) { + userAutoSaveConfig = setting.default; // use default if setting not defined + } + + let newAutoSaveValue: string; + if ([AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE].some(s => s === userAutoSaveConfig)) { + newAutoSaveValue = AutoSaveConfiguration.OFF; + } else { + newAutoSaveValue = AutoSaveConfiguration.AFTER_DELAY; + } + + return this.configurationService.updateValue('files.autoSave', newAutoSaveValue, ConfigurationTarget.USER); + } + + get isHotExitEnabled(): boolean { + return !this.environmentService.isExtensionDevelopment && this.currentHotExitConfig !== HotExitConfiguration.OFF; + } + + get hotExitConfiguration(): string { + return this.currentHotExitConfig; + } +} + +registerSingleton(IFilesConfigurationService, FilesConfigurationService); diff --git a/src/vs/workbench/services/history/browser/history.ts b/src/vs/workbench/services/history/browser/history.ts index 046f380d7e..85981bdaeb 100644 --- a/src/vs/workbench/services/history/browser/history.ts +++ b/src/vs/workbench/services/history/browser/history.ts @@ -33,6 +33,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { withNullAsUndefined } from 'vs/base/common/types'; import { addDisposableListener, EventType, EventHelper } from 'vs/base/browser/dom'; import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; +import { Schemas } from 'vs/base/common/network'; /** * Stores the selection & view state of an editor and allows to compare it to other selection states. @@ -482,10 +483,10 @@ export class HistoryService extends Disposable implements IHistoryService { } private handleEditorEventInHistory(editor?: IBaseEditor): void { - const input = editor?.input; - // Ensure we have at least a name to show and not configured to exclude input - if (!input || !input.getName() || !this.include(input)) { + // Ensure we have not configured to exclude input and don't track invalid inputs + const input = editor?.input; + if (!input || input.isDisposed() || !this.include(input)) { return; } @@ -592,10 +593,10 @@ export class HistoryService extends Disposable implements IHistoryService { // stack but we need to keep our currentTextEditorState up to date with // the navigtion that occurs. if (this.navigatingInStack) { - if (codeEditor && control?.input) { + if (codeEditor && control?.input && !control.input.isDisposed()) { this.currentTextEditorState = new TextEditorState(control.input, codeEditor.getSelection()); } else { - this.currentTextEditorState = null; // we navigated to a non text editor + this.currentTextEditorState = null; // we navigated to a non text or disposed editor } } @@ -603,15 +604,15 @@ export class HistoryService extends Disposable implements IHistoryService { else { // navigation inside text editor - if (codeEditor && control?.input) { + if (codeEditor && control?.input && !control.input.isDisposed()) { this.handleTextEditorEvent(control, codeEditor, event); } - // navigation to non-text editor + // navigation to non-text disposed editor else { this.currentTextEditorState = null; // at this time we have no active text editor view state - if (control?.input) { + if (control?.input && !control.input.isDisposed()) { this.handleNonTextEditorEvent(control); } } @@ -735,8 +736,10 @@ export class HistoryService extends Disposable implements IHistoryService { private preferResourceInput(input: IEditorInput): IEditorInput | IResourceInput { const resource = input.getResource(); - if (resource && this.fileService.canHandleResource(resource)) { - return { resource: resource }; + if (resource && (resource.scheme === Schemas.file || resource.scheme === Schemas.vscodeRemote || resource.scheme === Schemas.userData)) { + // for now, only prefer well known schemes that we control to prevent + // issues such as https://github.com/microsoft/vscode/issues/85204 + return { resource }; } return input; diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index 8ad22183f2..ecdb00df0c 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -146,7 +146,7 @@ export class BrowserHostService extends Disposable implements IHostService { const windowConfig = this.configurationService.getValue('window'); const openFolderInNewWindowConfig = windowConfig?.openFoldersInNewWindow || 'default' /* default */; - let openFolderInNewWindow = !!options.forceNewWindow && !options.forceReuseWindow; + let openFolderInNewWindow = (options.preferNewWindow || !!options.forceNewWindow) && !options.forceReuseWindow; if (!options.forceNewWindow && !options.forceReuseWindow && (openFolderInNewWindowConfig === 'on' || openFolderInNewWindowConfig === 'off')) { openFolderInNewWindow = (openFolderInNewWindowConfig === 'on'); } diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index 8b1599740b..741f113bc1 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -11,7 +11,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { Keybinding, ResolvedKeybinding, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; -import { OS, OperatingSystem, isWeb } from 'vs/base/common/platform'; +import { OS, OperatingSystem } from 'vs/base/common/platform'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Extensions as ConfigExtensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; @@ -19,7 +19,7 @@ import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/commo import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { Extensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { AbstractKeybindingService } from 'vs/platform/keybinding/common/abstractKeybindingService'; -import { IKeyboardEvent, IUserFriendlyKeybinding, KeybindingSource, IKeybindingService, IKeybindingEvent } from 'vs/platform/keybinding/common/keybinding'; +import { IKeyboardEvent, IUserFriendlyKeybinding, KeybindingSource, IKeybindingService, IKeybindingEvent, KeybindingsSchemaContribution } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver'; import { IKeybindingItem, IKeybindingRule2, KeybindingWeight, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; @@ -43,8 +43,10 @@ import * as objects from 'vs/base/common/objects'; import { IKeymapService } from 'vs/workbench/services/keybinding/common/keymapInfo'; import { getDispatchConfig } from 'vs/workbench/services/keybinding/common/dispatchConfig'; import { isArray } from 'vs/base/common/types'; -import { INavigatorWithKeyboard } from 'vs/workbench/services/keybinding/browser/navigatorKeyboard'; +import { INavigatorWithKeyboard, IKeyboard } from 'vs/workbench/services/keybinding/browser/navigatorKeyboard'; import { ScanCodeUtils, IMMUTABLE_CODE_TO_KEY_CODE } from 'vs/base/common/scanCode'; +import { flatten } from 'vs/base/common/arrays'; +import { BrowserFeatures, KeyboardSupport } from 'vs/base/browser/canIUse'; interface ContributedKeyBinding { command: string; @@ -146,6 +148,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { private _keyboardMapper: IKeyboardMapper; private _cachedResolver: KeybindingResolver | null; private userKeybindings: UserKeybindings; + private readonly _contributions: KeybindingsSchemaContribution[] = []; constructor( @IContextKeyService contextKeyService: IContextKeyService, @@ -161,7 +164,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { ) { super(contextKeyService, commandService, telemetryService, notificationService); - updateSchema(); + this.updateSchema(); let dispatchConfig = getDispatchConfig(configurationService); configurationService.onDidChangeConfiguration((e) => { @@ -190,13 +193,6 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { } }); this._register(this.userKeybindings.onDidChange(() => { - type CustomKeybindingsChangedClassification = { - keyCount: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true } - }; - - this._telemetryService.publicLog2<{ keyCount: number }, CustomKeybindingsChangedClassification>('customKeybindingsChanged', { - keyCount: this.userKeybindings.keybindings.length - }); this.updateResolver({ source: KeybindingSource.User, keybindings: this.userKeybindings.keybindings @@ -214,8 +210,8 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { this.updateResolver({ source: KeybindingSource.Default }); }); - updateSchema(); - this._register(extensionService.onDidRegisterExtensions(() => updateSchema())); + this.updateSchema(); + this._register(extensionService.onDidRegisterExtensions(() => this.updateSchema())); this._register(dom.addDisposableListener(window, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { let keyEvent = new StandardKeyboardEvent(e); @@ -226,6 +222,28 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { })); let data = this.keymapService.getCurrentKeyboardLayout(); + /* __GDPR__FRAGMENT__ + "IKeyboardLayoutInfo" : { + "name" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "id": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "text": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + /* __GDPR__FRAGMENT__ + "IKeyboardLayoutInfo" : { + "model" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "layout": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "variant": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "options": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "rules": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + /* __GDPR__FRAGMENT__ + "IKeyboardLayoutInfo" : { + "id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "lang": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ /* __GDPR__ "keyboardLayout" : { "currentKeyboardLayout": { "${inline}": [ "${IKeyboardLayoutInfo}" ] } @@ -236,16 +254,16 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { }); this._register(browser.onDidChangeFullscreen(() => { - const keyboard = (navigator).keyboard; + const keyboard: IKeyboard | null = (navigator).keyboard; - if (!keyboard) { + if (BrowserFeatures.keyboard === KeyboardSupport.None) { return; } if (browser.isFullscreen()) { - keyboard.lock(['Escape']); + keyboard?.lock(['Escape']); } else { - keyboard.unlock(); + keyboard?.unlock(); } // update resolver which will bring back all unbound keyboard shortcuts @@ -254,6 +272,18 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { })); } + public registerSchemaContribution(contribution: KeybindingsSchemaContribution): void { + this._contributions.push(contribution); + if (contribution.onDidChange) { + this._register(contribution.onDidChange(() => this.updateSchema())); + } + this.updateSchema(); + } + + private updateSchema() { + updateSchema(flatten(this._contributions.map(x => x.getSchemaAdditions()))); + } + public _dumpDebugInfo(): string { const layoutInfo = JSON.stringify(this.keymapService.getCurrentKeyboardLayout(), null, '\t'); const mapperInfo = this._keyboardMapper.dumpDebugInfo(); @@ -337,15 +367,11 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { } private _assertBrowserConflicts(kb: Keybinding, commandId: string): boolean { - if (!isWeb) { + if (BrowserFeatures.keyboard === KeyboardSupport.Always) { return false; } - if (browser.isStandalone) { - return false; - } - - if (browser.isFullscreen() && (navigator).keyboard) { + if (BrowserFeatures.keyboard === KeyboardSupport.FullScreen && browser.isFullscreen()) { return false; } @@ -503,7 +529,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { ); } - private static _getDefaultKeybindings(defaultKeybindings: ResolvedKeybindingItem[]): string { + private static _getDefaultKeybindings(defaultKeybindings: readonly ResolvedKeybindingItem[]): string { let out = new OutputBuilder(); out.writeLine('['); @@ -653,7 +679,7 @@ let schema: IJSONSchema = { let schemaRegistry = Registry.as(Extensions.JSONContribution); schemaRegistry.registerSchema(schemaId, schema); -function updateSchema() { +function updateSchema(additionalContributions: readonly IJSONSchema[]) { commandsSchemas.length = 0; commandsEnum.length = 0; commandsEnumDescriptions.length = 0; @@ -707,6 +733,9 @@ function updateSchema() { for (const commandId of menuCommands.keys()) { addKnownCommand(commandId); } + + commandsSchemas.push(...additionalContributions); + schemaRegistry.notifySchemaChanged(schemaId); } const configurationRegistry = Registry.as(ConfigExtensions.Configuration); diff --git a/src/vs/workbench/services/keybinding/browser/keymapService.ts b/src/vs/workbench/services/keybinding/browser/keymapService.ts index 2445acde0b..fdc98efcc1 100644 --- a/src/vs/workbench/services/keybinding/browser/keymapService.ts +++ b/src/vs/workbench/services/keybinding/browser/keymapService.ts @@ -19,7 +19,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { parse } from 'vs/base/common/json'; +import { parse, getNodeType } from 'vs/base/common/json'; import * as objects from 'vs/base/common/objects'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -335,6 +335,10 @@ export class BrowserKeyboardMapperFactoryBase { return true; } + if (standardKeyboardEvent.browserEvent.key === 'Dead' || standardKeyboardEvent.browserEvent.isComposing) { + return true; + } + const mapping = currentKeymap.mapping[standardKeyboardEvent.code]; if (!mapping) { @@ -482,9 +486,13 @@ class UserKeyboardLayout extends Disposable { try { const content = await this.fileService.readFile(this.keyboardLayoutResource); const value = parse(content.value.toString()); - const layoutInfo = value.layout; - const mappings = value.rawMapping; - this._keyboardLayout = KeymapInfo.createKeyboardLayoutFromDebugInfo(layoutInfo, mappings, true); + if (getNodeType(value) === 'object') { + const layoutInfo = value.layout; + const mappings = value.rawMapping; + this._keyboardLayout = KeymapInfo.createKeyboardLayoutFromDebugInfo(layoutInfo, mappings, true); + } else { + this._keyboardLayout = null; + } } catch (e) { this._keyboardLayout = null; } diff --git a/src/vs/workbench/services/keybinding/common/keybindingEditing.ts b/src/vs/workbench/services/keybinding/common/keybindingEditing.ts index c91b2fd170..a690f63a17 100644 --- a/src/vs/workbench/services/keybinding/common/keybindingEditing.ts +++ b/src/vs/workbench/services/keybinding/common/keybindingEditing.ts @@ -250,7 +250,7 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding private parse(model: ITextModel): { result: IUserFriendlyKeybinding[], parseErrors: json.ParseError[] } { const parseErrors: json.ParseError[] = []; - const result = json.parse(model.getValue(), parseErrors); + const result = json.parse(model.getValue(), parseErrors, { allowTrailingComma: true, allowEmptyContent: true }); return { result, parseErrors }; } diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts index 8288261017..5368a2a88f 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts @@ -38,19 +38,20 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { KeybindingsEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; -import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { TestBackupFileService, TestContextService, TestEditorGroupsService, TestEditorService, TestLifecycleService, TestTextFileService, TestTextResourcePropertiesService } from 'vs/workbench/test/workbenchTestServices'; +import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; +import { TestBackupFileService, TestContextService, TestEditorGroupsService, TestEditorService, TestLifecycleService, TestTextFileService, TestTextResourcePropertiesService, TestWorkingCopyService } from 'vs/workbench/test/workbenchTestServices'; import { FileService } from 'vs/platform/files/common/fileService'; import { Schemas } from 'vs/base/common/network'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { URI } from 'vs/base/common/uri'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; -import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; +import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -class TestEnvironmentService extends WorkbenchEnvironmentService { +class TestEnvironmentService extends NativeWorkbenchEnvironmentService { constructor(private _appSettingsHome: URI) { super(parseArgs(process.argv, OPTIONS) as IWindowConfiguration, process.execPath, 0); @@ -67,7 +68,7 @@ interface Modifiers { shiftKey?: boolean; } -suite('KeybindingsEditing', () => { +suite.skip('KeybindingsEditing', () => { let instantiationService: TestInstantiationService; let testObject: KeybindingsEditingService; @@ -93,6 +94,7 @@ suite('KeybindingsEditing', () => { instantiationService.stub(IContextKeyService, instantiationService.createInstance(MockContextKeyService)); instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService()); instantiationService.stub(IEditorService, new TestEditorService()); + instantiationService.stub(IWorkingCopyService, new TestWorkingCopyService()); instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(IModeService, ModeServiceImpl); instantiationService.stub(ILogService, new NullLogService()); @@ -103,7 +105,7 @@ suite('KeybindingsEditing', () => { fileService.registerProvider(Schemas.file, diskFileSystemProvider); fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, diskFileSystemProvider, environmentService)); instantiationService.stub(IFileService, fileService); - instantiationService.stub(IUntitledEditorService, instantiationService.createInstance(UntitledEditorService)); + instantiationService.stub(IUntitledTextEditorService, instantiationService.createInstance(UntitledTextEditorService)); instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); instantiationService.stub(IBackupFileService, new TestBackupFileService()); diff --git a/src/vs/workbench/services/label/common/labelService.ts b/src/vs/workbench/services/label/common/labelService.ts index 1d6cb967f0..0d1d0bc776 100644 --- a/src/vs/workbench/services/label/common/labelService.ts +++ b/src/vs/workbench/services/label/common/labelService.ts @@ -12,10 +12,10 @@ import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWo import { Registry } from 'vs/platform/registry/common/platform'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceContextService, IWorkspace } from 'vs/platform/workspace/common/workspace'; -import { isEqual, basenameOrAuthority, isEqualOrParent, basename, joinPath, dirname } from 'vs/base/common/resources'; +import { isEqual, basenameOrAuthority, basename, joinPath, dirname } from 'vs/base/common/resources'; import { tildify, getPathLabel } from 'vs/base/common/labels'; import { ltrim, endsWith } from 'vs/base/common/strings'; -import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, WORKSPACE_EXTENSION, toWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, WORKSPACE_EXTENSION, toWorkspaceIdentifier, isWorkspaceIdentifier, isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces'; import { ILabelService, ResourceLabelFormatter, ResourceLabelFormatting } from 'vs/platform/label/common/label'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { match } from 'vs/base/common/glob'; @@ -117,7 +117,7 @@ export class LabelService implements ILabelService { return; } - if (match(formatter.authority, resource.authority) && (!bestResult || !bestResult.authority || formatter.authority.length > bestResult.authority.length || ((formatter.authority.length === bestResult.authority.length) && formatter.priority))) { + if (match(formatter.authority.toLowerCase(), resource.authority.toLowerCase()) && (!bestResult || !bestResult.authority || formatter.authority.length > bestResult.authority.length || ((formatter.authority.length === bestResult.authority.length) && formatter.priority))) { bestResult = formatter; } } @@ -193,7 +193,7 @@ export class LabelService implements ILabelService { if (isWorkspaceIdentifier(workspace)) { // Workspace: Untitled - if (isEqualOrParent(workspace.configPath, this.environmentService.untitledWorkspacesHome)) { + if (isUntitledWorkspace(workspace.configPath, this.environmentService)) { return localize('untitledWorkspace', "Untitled (Workspace)"); } diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index 0f192a8d39..39d37c14ef 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -41,6 +41,11 @@ export interface IWorkbenchLayoutService extends ILayoutService { */ readonly onFullscreenChange: Event; + /** + * Emits when the window is maximized or unmaximized. + */ + readonly onMaximizeChange: Event; + /** * Emits when centered layout is enabled or disabled. */ @@ -114,6 +119,16 @@ export interface IWorkbenchLayoutService extends ILayoutService { */ toggleMaximizedPanel(): void; + /** + * Returns true if the window has a border. + */ + hasWindowBorder(): boolean; + + /** + * Returns the window border radius if any. + */ + getWindowBorderRadius(): string | undefined; + /** * Returns true if the panel is maximized. */ @@ -178,4 +193,15 @@ export interface IWorkbenchLayoutService extends ILayoutService { * Register a part to participate in the layout. */ registerPart(part: Part): void; + + + /** + * Returns whether the window is maximized. + */ + isWindowMaximized(): boolean; + + /** + * Updates the maximized state of the window. + */ + updateWindowMaximizedState(maximized: boolean): void; } diff --git a/src/vs/workbench/services/mode/common/workbenchModeService.ts b/src/vs/workbench/services/mode/common/workbenchModeService.ts index ade3a29230..f91f58da63 100644 --- a/src/vs/workbench/services/mode/common/workbenchModeService.ts +++ b/src/vs/workbench/services/mode/common/workbenchModeService.ts @@ -105,7 +105,7 @@ export class WorkbenchModeServiceImpl extends ModeServiceImpl { this._configurationService = configurationService; this._extensionService = extensionService; - languagesExtPoint.setHandler((extensions: IExtensionPointUser[]) => { + languagesExtPoint.setHandler((extensions: readonly IExtensionPointUser[]) => { let allValidLanguages: ILanguageExtensionPoint[] = []; for (let i = 0, len = extensions.length; i < len; i++) { diff --git a/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts b/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts index fa2ec956f3..749ecc4d8d 100644 --- a/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts +++ b/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts @@ -21,7 +21,7 @@ export class PreferencesEditorInput extends SideBySideEditorInput { return PreferencesEditorInput.ID; } - getTitle(verbosity: Verbosity): string | undefined { + getTitle(verbosity: Verbosity): string { return this.master.getTitle(verbosity); } } diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index 61a6e50628..0f53ec4bb9 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -1117,7 +1117,7 @@ export function createValidator(prop: IConfigurationPropertySchema): (value: any patternRegex = new RegExp(prop.pattern); } - const type = Array.isArray(prop.type) ? prop.type : [prop.type]; + const type: (string | undefined)[] = Array.isArray(prop.type) ? prop.type : [prop.type]; const canBeType = (t: string) => type.indexOf(t) > -1; const isNullable = canBeType('null'); diff --git a/src/vs/workbench/services/preferences/test/common/keybindingsEditorModel.test.ts b/src/vs/workbench/services/preferences/test/common/keybindingsEditorModel.test.ts index b7fc5de557..8031c77c24 100644 --- a/src/vs/workbench/services/preferences/test/common/keybindingsEditorModel.test.ts +++ b/src/vs/workbench/services/preferences/test/common/keybindingsEditorModel.test.ts @@ -577,7 +577,7 @@ suite('KeybindingsEditorModel test', () => { function registerCommandWithTitle(command: string, title: string): void { const registry = Registry.as(ActionExtensions.WorkbenchActions); - registry.registerWorkbenchAction(new SyncActionDescriptor(AnAction, command, title, { primary: 0 }), ''); + registry.registerWorkbenchAction(SyncActionDescriptor.create(AnAction, command, title, { primary: 0 }), ''); } function assertKeybindingItems(actual: ResolvedKeybindingItem[], expected: ResolvedKeybindingItem[]) { diff --git a/src/vs/workbench/services/progress/browser/editorProgressService.ts b/src/vs/workbench/services/progress/browser/editorProgressService.ts deleted file mode 100644 index 60392f50e4..0000000000 --- a/src/vs/workbench/services/progress/browser/editorProgressService.ts +++ /dev/null @@ -1,11 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ProgressBarIndicator } from 'vs/workbench/services/progress/browser/progressIndicator'; - -export class EditorProgressService extends ProgressBarIndicator { - - _serviceBrand: undefined; -} diff --git a/src/vs/workbench/services/progress/browser/media/progressService.css b/src/vs/workbench/services/progress/browser/media/progressService.css index cc899d47dd..d86980fa1e 100644 --- a/src/vs/workbench/services/progress/browser/media/progressService.css +++ b/src/vs/workbench/services/progress/browser/media/progressService.css @@ -12,6 +12,7 @@ } .monaco-workbench .progress-badge > .badge-content::before { + mask: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNCIgaGVpZ2h0PSIxNCIgdmlld0JveD0iMiAyIDE0IDE0IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDIgMiAxNCAxNCI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTkgMTZjLTMuODYgMC03LTMuMTQtNy03czMuMTQtNyA3LTdjMy44NTkgMCA3IDMuMTQxIDcgN3MtMy4xNDEgNy03IDd6bTAtMTIuNmMtMy4wODggMC01LjYgMi41MTMtNS42IDUuNnMyLjUxMiA1LjYgNS42IDUuNiA1LjYtMi41MTIgNS42LTUuNi0yLjUxMi01LjYtNS42LTUuNnptMy44NiA3LjFsLTMuMTYtMS44OTZ2LTMuODA0aC0xLjR2NC41OTZsMy44NCAyLjMwNS43Mi0xLjIwMXoiLz48L3N2Zz4="); -webkit-mask: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNCIgaGVpZ2h0PSIxNCIgdmlld0JveD0iMiAyIDE0IDE0IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDIgMiAxNCAxNCI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTkgMTZjLTMuODYgMC03LTMuMTQtNy03czMuMTQtNyA3LTdjMy44NTkgMCA3IDMuMTQxIDcgN3MtMy4xNDEgNy03IDd6bTAtMTIuNmMtMy4wODggMC01LjYgMi41MTMtNS42IDUuNnMyLjUxMiA1LjYgNS42IDUuNiA1LjYtMi41MTIgNS42LTUuNi0yLjUxMi01LjYtNS42LTUuNnptMy44NiA3LjFsLTMuMTYtMS44OTZ2LTMuODA0aC0xLjR2NC41OTZsMy44NCAyLjMwNS43Mi0xLjIwMXoiLz48L3N2Zz4="); width: 14px; height: 14px; diff --git a/src/vs/workbench/services/progress/browser/progressIndicator.ts b/src/vs/workbench/services/progress/browser/progressIndicator.ts index 6f2f66f51f..6782047344 100644 --- a/src/vs/workbench/services/progress/browser/progressIndicator.ts +++ b/src/vs/workbench/services/progress/browser/progressIndicator.ts @@ -8,11 +8,14 @@ import { isUndefinedOrNull } from 'vs/base/common/types'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IProgressRunner, IProgressIndicator } from 'vs/platform/progress/common/progress'; +import { IProgressRunner, IProgressIndicator, emptyProgressRunner } from 'vs/platform/progress/common/progress'; +import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; -export class ProgressBarIndicator implements IProgressIndicator { +export class ProgressBarIndicator extends Disposable implements IProgressIndicator { - constructor(private progressbar: ProgressBar) { } + constructor(protected progressbar: ProgressBar) { + super(); + } show(infinite: true, delay?: number): IProgressRunner; show(total: number, delay?: number): IProgressRunner; @@ -55,6 +58,55 @@ export class ProgressBarIndicator implements IProgressIndicator { } } +export class EditorProgressIndicator extends ProgressBarIndicator { + + _serviceBrand: undefined; + + constructor(progressBar: ProgressBar, private readonly group: IEditorGroupView) { + super(progressBar); + + this.registerListeners(); + } + + private registerListeners() { + this._register(this.group.onDidCloseEditor(e => { + if (this.group.isEmpty) { + this.progressbar.stop().hide(); + } + })); + } + + show(infinite: true, delay?: number): IProgressRunner; + show(total: number, delay?: number): IProgressRunner; + show(infiniteOrTotal: true | number, delay?: number): IProgressRunner { + + // No editor open: ignore any progress reporting + if (this.group.isEmpty) { + return emptyProgressRunner; + } + + if (infiniteOrTotal === true) { + return super.show(true, delay); + } + + return super.show(infiniteOrTotal, delay); + } + + async showWhile(promise: Promise, delay?: number): Promise { + + // No editor open: ignore any progress reporting + if (this.group.isEmpty) { + try { + await promise; + } catch (error) { + // ignore + } + } + + return super.showWhile(promise, delay); + } +} + namespace ProgressIndicatorState { export const enum Type { @@ -65,9 +117,9 @@ namespace ProgressIndicatorState { Work } - export const None = new class { readonly type = Type.None; }; - export const Done = new class { readonly type = Type.Done; }; - export const Infinite = new class { readonly type = Type.Infinite; }; + export const None = { type: Type.None } as const; + export const Done = { type: Type.Done } as const; + export const Infinite = { type: Type.Infinite } as const; export class While { readonly type = Type.While; diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index cc8f82eac4..26cc44e552 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -45,7 +45,7 @@ export class ProgressService extends Disposable implements IProgressService { super(); } - withProgress(options: IProgressOptions, task: (progress: IProgress) => Promise, onDidCancel?: (choice?: number) => void): Promise { + async withProgress(options: IProgressOptions, task: (progress: IProgress) => Promise, onDidCancel?: (choice?: number) => void): Promise { const { location } = options; if (typeof location === 'string') { if (this.viewletService.getProgressIndicator(location)) { @@ -56,7 +56,7 @@ export class ProgressService extends Disposable implements IProgressService { return this.withPanelProgress(location, task, { ...options, location }); } - return Promise.reject(new Error(`Bad progress location: ${location}`)); + throw new Error(`Bad progress location: ${location}`); } switch (location) { @@ -73,7 +73,7 @@ export class ProgressService extends Disposable implements IProgressService { case ProgressLocation.Dialog: return this.withDialogProgress(options, task, onDidCancel); default: - return Promise.reject(new Error(`Bad progress location: ${location}`)); + throw new Error(`Bad progress location: ${location}`); } } diff --git a/src/vs/workbench/services/progress/test/progressIndicator.test.ts b/src/vs/workbench/services/progress/test/progressIndicator.test.ts index d7add3b9a1..b85279c4f2 100644 --- a/src/vs/workbench/services/progress/test/progressIndicator.test.ts +++ b/src/vs/workbench/services/progress/test/progressIndicator.test.ts @@ -11,11 +11,15 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { TestViewletService, TestPanelService } from 'vs/workbench/test/workbenchTestServices'; +import { Event } from 'vs/base/common/event'; class TestViewlet implements IViewlet { constructor(private id: string) { } + readonly onDidBlur = Event.None; + readonly onDidFocus = Event.None; + getId(): string { return this.id; } getTitle(): string { return this.id; } getActions(): IAction[] { return []; } diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts new file mode 100644 index 0000000000..1d1c2ebf37 --- /dev/null +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -0,0 +1,236 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { Event, Emitter } from 'vs/base/common/event'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { ITunnelService } from 'vs/platform/remote/common/tunnel'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IEditableData } from 'vs/workbench/common/views'; + +export const IRemoteExplorerService = createDecorator('remoteExplorerService'); +export const REMOTE_EXPLORER_TYPE_KEY: string = 'remote.explorerType'; + +export interface Tunnel { + remote: number; + localAddress: string; + local?: number; + name?: string; + description?: string; + closeable?: boolean; +} + +export class TunnelModel extends Disposable { + readonly forwarded: Map; + readonly published: Map; + readonly candidates: Map; + private _onForwardPort: Emitter = new Emitter(); + public onForwardPort: Event = this._onForwardPort.event; + private _onClosePort: Emitter = new Emitter(); + public onClosePort: Event = this._onClosePort.event; + private _onPortName: Emitter = new Emitter(); + public onPortName: Event = this._onPortName.event; + constructor( + @ITunnelService private readonly tunnelService: ITunnelService + ) { + super(); + this.forwarded = new Map(); + this.tunnelService.tunnels.then(tunnels => { + tunnels.forEach(tunnel => { + if (tunnel.localAddress) { + this.forwarded.set(tunnel.tunnelRemotePort, { + remote: tunnel.tunnelRemotePort, + localAddress: tunnel.localAddress, + local: tunnel.tunnelLocalPort + }); + } + }); + }); + + this.published = new Map(); + this.candidates = new Map(); + this._register(this.tunnelService.onTunnelOpened(tunnel => { + if (this.candidates.has(tunnel.tunnelRemotePort)) { + this.candidates.delete(tunnel.tunnelRemotePort); + } + if (!this.forwarded.has(tunnel.tunnelRemotePort) && tunnel.localAddress) { + this.forwarded.set(tunnel.tunnelRemotePort, { + remote: tunnel.tunnelRemotePort, + localAddress: tunnel.localAddress, + local: tunnel.tunnelLocalPort + }); + } + this._onForwardPort.fire(this.forwarded.get(tunnel.tunnelRemotePort)!); + })); + this._register(this.tunnelService.onTunnelClosed(remotePort => { + if (this.forwarded.has(remotePort)) { + this.forwarded.delete(remotePort); + this._onClosePort.fire(remotePort); + } + })); + } + + async forward(remote: number, local?: number, name?: string): Promise { + if (!this.forwarded.has(remote)) { + const tunnel = await this.tunnelService.openTunnel(remote, local); + if (tunnel && tunnel.localAddress) { + const newForward: Tunnel = { + remote: tunnel.tunnelRemotePort, + local: tunnel.tunnelLocalPort, + name: name, + closeable: true, + localAddress: tunnel.localAddress + }; + this.forwarded.set(remote, newForward); + this._onForwardPort.fire(newForward); + } + } + } + + name(remote: number, name: string) { + if (this.forwarded.has(remote)) { + this.forwarded.get(remote)!.name = name; + this._onPortName.fire(remote); + } + } + + async close(remote: number): Promise { + return this.tunnelService.closeTunnel(remote); + } + + address(remote: number): string | undefined { + return (this.forwarded.get(remote) || this.published.get(remote))?.localAddress; + } +} + +export interface IRemoteExplorerService { + _serviceBrand: undefined; + onDidChangeTargetType: Event; + targetType: string; + readonly helpInformation: HelpInformation[]; + readonly tunnelModel: TunnelModel; + onDidChangeEditable: Event; + setEditable(remote: number | undefined, data: IEditableData | null): void; + getEditableData(remote: number | undefined): IEditableData | undefined; +} + +export interface HelpInformation { + extensionDescription: IExtensionDescription; + getStarted?: string; + documentation?: string; + feedback?: string; + issues?: string; + remoteName?: string[] | string; +} + +const remoteHelpExtPoint = ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'remoteHelp', + jsonSchema: { + description: nls.localize('RemoteHelpInformationExtPoint', 'Contributes help information for Remote'), + type: 'object', + properties: { + 'getStarted': { + description: nls.localize('RemoteHelpInformationExtPoint.getStarted', "The url to your project's Getting Started page"), + type: 'string' + }, + 'documentation': { + description: nls.localize('RemoteHelpInformationExtPoint.documentation', "The url to your project's documentation page"), + type: 'string' + }, + 'feedback': { + description: nls.localize('RemoteHelpInformationExtPoint.feedback', "The url to your project's feedback reporter"), + type: 'string' + }, + 'issues': { + description: nls.localize('RemoteHelpInformationExtPoint.issues', "The url to your project's issues list"), + type: 'string' + } + } + } +}); + +class RemoteExplorerService implements IRemoteExplorerService { + public _serviceBrand: undefined; + private _targetType: string = ''; + private readonly _onDidChangeTargetType: Emitter = new Emitter(); + public readonly onDidChangeTargetType: Event = this._onDidChangeTargetType.event; + private _helpInformation: HelpInformation[] = []; + private _tunnelModel: TunnelModel; + private editable: { remote: number | undefined, data: IEditableData } | undefined; + private readonly _onDidChangeEditable: Emitter = new Emitter(); + public readonly onDidChangeEditable: Event = this._onDidChangeEditable.event; + + constructor( + @IStorageService private readonly storageService: IStorageService, + @ITunnelService tunnelService: ITunnelService) { + this._tunnelModel = new TunnelModel(tunnelService); + remoteHelpExtPoint.setHandler((extensions) => { + let helpInformation: HelpInformation[] = []; + for (let extension of extensions) { + this._handleRemoteInfoExtensionPoint(extension, helpInformation); + } + + this._helpInformation = helpInformation; + }); + } + + set targetType(name: string) { + if (this._targetType !== name) { + this._targetType = name; + this.storageService.store(REMOTE_EXPLORER_TYPE_KEY, this._targetType, StorageScope.WORKSPACE); + this.storageService.store(REMOTE_EXPLORER_TYPE_KEY, this._targetType, StorageScope.GLOBAL); + this._onDidChangeTargetType.fire(this._targetType); + } + } + get targetType(): string { + return this._targetType; + } + + private _handleRemoteInfoExtensionPoint(extension: IExtensionPointUser, helpInformation: HelpInformation[]) { + if (!extension.description.enableProposedApi) { + return; + } + + if (!extension.value.documentation && !extension.value.feedback && !extension.value.getStarted && !extension.value.issues) { + return; + } + + helpInformation.push({ + extensionDescription: extension.description, + getStarted: extension.value.getStarted, + documentation: extension.value.documentation, + feedback: extension.value.feedback, + issues: extension.value.issues, + remoteName: extension.value.remoteName + }); + } + + get helpInformation(): HelpInformation[] { + return this._helpInformation; + } + + get tunnelModel(): TunnelModel { + return this._tunnelModel; + } + + setEditable(remote: number | undefined, data: IEditableData | null): void { + if (!data) { + this.editable = undefined; + } else { + this.editable = { remote, data }; + } + this._onDidChangeEditable.fire(remote); + } + + getEditableData(remote: number | undefined): IEditableData | undefined { + return this.editable && this.editable.remote === remote ? this.editable.data : undefined; + } +} + +registerSingleton(IRemoteExplorerService, RemoteExplorerService, true); diff --git a/src/vs/workbench/services/remote/node/tunnelService.ts b/src/vs/workbench/services/remote/node/tunnelService.ts index 6a905d07b4..28895626c5 100644 --- a/src/vs/workbench/services/remote/node/tunnelService.ts +++ b/src/vs/workbench/services/remote/node/tunnelService.ts @@ -17,9 +17,10 @@ import { ISignService } from 'vs/platform/sign/common/sign'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { findFreePort } from 'vs/base/node/ports'; +import { Event, Emitter } from 'vs/base/common/event'; -export async function createRemoteTunnel(options: IConnectionOptions, tunnelRemotePort: number): Promise { - const tunnel = new NodeRemoteTunnel(options, tunnelRemotePort); +export async function createRemoteTunnel(options: IConnectionOptions, tunnelRemotePort: number, tunnelLocalPort?: number): Promise { + const tunnel = new NodeRemoteTunnel(options, tunnelRemotePort, tunnelLocalPort); return tunnel.waitForReady(); } @@ -27,6 +28,7 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel { public readonly tunnelRemotePort: number; public tunnelLocalPort!: number; + public localAddress?: string; private readonly _options: IConnectionOptions; private readonly _server: net.Server; @@ -35,7 +37,7 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel { private readonly _listeningListener: () => void; private readonly _connectionListener: (socket: net.Socket) => void; - constructor(options: IConnectionOptions, tunnelRemotePort: number) { + constructor(options: IConnectionOptions, tunnelRemotePort: number, private readonly suggestedLocalPort?: number) { super(); this._options = options; this._server = net.createServer(); @@ -61,12 +63,14 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel { public async waitForReady(): Promise { // try to get the same port number as the remote port number... - const localPort = await findFreePort(this.tunnelRemotePort, 1, 1000); + const localPort = await findFreePort(this.suggestedLocalPort ?? this.tunnelRemotePort, 1, 1000); // if that fails, the method above returns 0, which works out fine below... - this.tunnelLocalPort = (this._server.listen(localPort).address()).port; + const address = (this._server.listen(localPort).address()); + this.tunnelLocalPort = address.port; await this._barrier.wait(); + this.localAddress = 'localhost:' + address.port; return this; } @@ -96,6 +100,10 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel { export class TunnelService implements ITunnelService { _serviceBrand: undefined; + private _onTunnelOpened: Emitter = new Emitter(); + public onTunnelOpened: Event = this._onTunnelOpened.event; + private _onTunnelClosed: Emitter = new Emitter(); + public onTunnelClosed: Event = this._onTunnelClosed.event; private readonly _tunnels = new Map }>(); public constructor( @@ -116,33 +124,51 @@ export class TunnelService implements ITunnelService { this._tunnels.clear(); } - openTunnel(remotePort: number): Promise | undefined { + openTunnel(remotePort: number, localPort: number): Promise | undefined { const remoteAuthority = this.environmentService.configuration.remoteAuthority; if (!remoteAuthority) { return undefined; } - const resolvedTunnel = this.retainOrCreateTunnel(remoteAuthority, remotePort); + const resolvedTunnel = this.retainOrCreateTunnel(remoteAuthority, remotePort, localPort); if (!resolvedTunnel) { return resolvedTunnel; } - return resolvedTunnel.then(tunnel => ({ + return resolvedTunnel.then(tunnel => { + const newTunnel = this.makeTunnel(tunnel); + this._onTunnelOpened.fire(newTunnel); + return newTunnel; + }); + } + + private makeTunnel(tunnel: RemoteTunnel): RemoteTunnel { + return { tunnelRemotePort: tunnel.tunnelRemotePort, tunnelLocalPort: tunnel.tunnelLocalPort, + localAddress: tunnel.localAddress, dispose: () => { - const existing = this._tunnels.get(remotePort); + const existing = this._tunnels.get(tunnel.tunnelRemotePort); if (existing) { if (--existing.refcount <= 0) { existing.value.then(tunnel => tunnel.dispose()); - this._tunnels.delete(remotePort); + this._tunnels.delete(tunnel.tunnelRemotePort); + this._onTunnelClosed.fire(tunnel.tunnelRemotePort); } } } - })); + }; } - private retainOrCreateTunnel(remoteAuthority: string, remotePort: number): Promise | undefined { + async closeTunnel(remotePort: number): Promise { + if (this._tunnels.has(remotePort)) { + const value = this._tunnels.get(remotePort)!; + (await value.value).dispose(); + value.refcount = 0; + } + } + + private retainOrCreateTunnel(remoteAuthority: string, remotePort: number, localPort?: number): Promise | undefined { const existing = this._tunnels.get(remotePort); if (existing) { ++existing.refcount; @@ -162,8 +188,9 @@ export class TunnelService implements ITunnelService { logService: this.logService }; - const tunnel = createRemoteTunnel(options, remotePort); - this._tunnels.set(remotePort, { refcount: 1, value: tunnel }); + const tunnel = createRemoteTunnel(options, remotePort, localPort); + // Using makeTunnel here for the value does result in dispose getting called twice, but it also ensures that _onTunnelClosed will be fired when closeTunnel is called. + this._tunnels.set(remotePort, { refcount: 1, value: tunnel.then(value => this.makeTunnel(value)) }); return tunnel; } } diff --git a/src/vs/workbench/services/search/node/fileSearchManager.ts b/src/vs/workbench/services/search/common/fileSearchManager.ts similarity index 99% rename from src/vs/workbench/services/search/node/fileSearchManager.ts rename to src/vs/workbench/services/search/common/fileSearchManager.ts index 7f7d3da4ef..7e161e4eaf 100644 --- a/src/vs/workbench/services/search/node/fileSearchManager.ts +++ b/src/vs/workbench/services/search/common/fileSearchManager.ts @@ -12,6 +12,7 @@ import { StopWatch } from 'vs/base/common/stopwatch'; import { URI } from 'vs/base/common/uri'; import { IFileMatch, IFileSearchProviderStats, IFolderQuery, ISearchCompleteStats, IFileQuery, QueryGlobTester, resolvePatternsForProvider } from 'vs/workbench/services/search/common/search'; import { FileSearchProvider, FileSearchOptions } from 'vs/workbench/services/search/common/searchExtTypes'; +import { nextTick } from 'vs/base/common/process'; export interface IInternalFileMatch { base: URI; @@ -114,7 +115,7 @@ class FileSearchEngine { const noSiblingsClauses = !queryTester.hasSiblingExcludeClauses(); let providerSW: StopWatch; - new Promise(_resolve => process.nextTick(_resolve)) + new Promise(_resolve => nextTick(_resolve)) .then(() => { this.activeCancellationTokens.add(cancellation); diff --git a/src/vs/workbench/services/search/common/replace.ts b/src/vs/workbench/services/search/common/replace.ts index a180601f43..d29dc756d1 100644 --- a/src/vs/workbench/services/search/common/replace.ts +++ b/src/vs/workbench/services/search/common/replace.ts @@ -61,9 +61,9 @@ export class ReplacePattern { if (match) { if (this.hasParameters) { if (match[0] === text) { - return text.replace(this._regExp, this.pattern); + return text.replace(this._regExp, this.buildReplaceString(match, preserveCase)); } - let replaceString = text.replace(this._regExp, this.pattern); + let replaceString = text.replace(this._regExp, this.buildReplaceString(match, preserveCase)); return replaceString.substr(match.index, match[0].length - (text.length - replaceString.length)); } return this.buildReplaceString(match, preserveCase); diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 3bf1d4f0ec..b8a3b15d15 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -329,6 +329,10 @@ export interface ISearchConfigurationProperties { actionsPosition: 'auto' | 'right'; maintainFileSearchCache: boolean; collapseResults: 'auto' | 'alwaysCollapse' | 'alwaysExpand'; + searchOnType: boolean; + searchOnTypeDebouncePeriod: number; + enableSearchEditorPreview: boolean; + searchEditorPreviewForceAbsolutePaths: boolean; } export interface ISearchConfiguration extends IFilesConfiguration { diff --git a/src/vs/workbench/services/search/common/searchService.ts b/src/vs/workbench/services/search/common/searchService.ts index db5d626296..78c297c635 100644 --- a/src/vs/workbench/services/search/common/searchService.ts +++ b/src/vs/workbench/services/search/common/searchService.ts @@ -19,7 +19,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { deserializeSearchError, FileMatch, ICachedSearchStats, IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, IProgressMessage, ISearchComplete, ISearchEngineStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, ITextQuery, pathIncludedInQuery, QueryType, SearchError, SearchErrorCode, SearchProviderType, isFileMatch, isProgressMessage } from 'vs/workbench/services/search/common/search'; import { addContextToEditorMatches, editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class SearchService extends Disposable implements ISearchService { @@ -32,7 +32,7 @@ export class SearchService extends Disposable implements ISearchService { constructor( private readonly modelService: IModelService, - private readonly untitledEditorService: IUntitledEditorService, + private readonly untitledTextEditorService: IUntitledTextEditorService, private readonly editorService: IEditorService, private readonly telemetryService: ITelemetryService, private readonly logService: ILogService, @@ -391,9 +391,15 @@ export class SearchService extends Disposable implements ISearchService { return; } + // Skip search results + if (model.getModeId() === 'search-result' && !(query.includePattern && query.includePattern['**/*.code-search'])) { + // TODO: untitled search editors will be excluded from search even when include *.code-search is specified + return; + } + // Support untitled files if (resource.scheme === Schemas.untitled) { - if (!this.untitledEditorService.exists(resource)) { + if (!this.untitledTextEditorService.exists(resource)) { return; } } @@ -442,14 +448,14 @@ export class SearchService extends Disposable implements ISearchService { export class RemoteSearchService extends SearchService { constructor( @IModelService modelService: IModelService, - @IUntitledEditorService untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService untitledTextEditorService: IUntitledTextEditorService, @IEditorService editorService: IEditorService, @ITelemetryService telemetryService: ITelemetryService, @ILogService logService: ILogService, @IExtensionService extensionService: IExtensionService, @IFileService fileService: IFileService ) { - super(modelService, untitledEditorService, editorService, telemetryService, logService, extensionService, fileService); + super(modelService, untitledTextEditorService, editorService, telemetryService, logService, extensionService, fileService); } } diff --git a/src/vs/workbench/services/search/common/textSearchManager.ts b/src/vs/workbench/services/search/common/textSearchManager.ts new file mode 100644 index 0000000000..a77a393826 --- /dev/null +++ b/src/vs/workbench/services/search/common/textSearchManager.ts @@ -0,0 +1,355 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as path from 'vs/base/common/path'; +import { mapArrayOrNot } from 'vs/base/common/arrays'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; +import * as resources from 'vs/base/common/resources'; +import * as glob from 'vs/base/common/glob'; +import { URI } from 'vs/base/common/uri'; +import { IExtendedExtensionSearchOptions, IFileMatch, IFolderQuery, IPatternInfo, ISearchCompleteStats, ITextQuery, ITextSearchContext, ITextSearchMatch, ITextSearchResult, QueryGlobTester, resolvePatternsForProvider } from 'vs/workbench/services/search/common/search'; +import { TextSearchProvider, TextSearchResult, TextSearchMatch, TextSearchComplete, Range, TextSearchOptions, TextSearchQuery } from 'vs/workbench/services/search/common/searchExtTypes'; +import { nextTick } from 'vs/base/common/process'; + +export interface IFileUtils { + readdir: (resource: URI) => Promise; + toCanonicalName: (encoding: string) => string; +} + +export class TextSearchManager { + + private collector: TextSearchResultsCollector | null = null; + + private isLimitHit = false; + private resultCount = 0; + + constructor(private query: ITextQuery, private provider: TextSearchProvider, private fileUtils: IFileUtils) { } + + search(onProgress: (matches: IFileMatch[]) => void, token: CancellationToken): Promise { + const folderQueries = this.query.folderQueries || []; + const tokenSource = new CancellationTokenSource(); + token.onCancellationRequested(() => tokenSource.cancel()); + + return new Promise((resolve, reject) => { + this.collector = new TextSearchResultsCollector(onProgress); + + let isCanceled = false; + const onResult = (result: TextSearchResult, folderIdx: number) => { + if (isCanceled) { + return; + } + + if (!this.isLimitHit) { + const resultSize = this.resultSize(result); + if (extensionResultIsMatch(result) && typeof this.query.maxResults === 'number' && this.resultCount + resultSize > this.query.maxResults) { + this.isLimitHit = true; + isCanceled = true; + tokenSource.cancel(); + + result = this.trimResultToSize(result, this.query.maxResults - this.resultCount); + } + + const newResultSize = this.resultSize(result); + this.resultCount += newResultSize; + if (newResultSize > 0) { + this.collector!.add(result, folderIdx); + } + } + }; + + // For each root folder + Promise.all(folderQueries.map((fq, i) => { + return this.searchInFolder(fq, r => onResult(r, i), tokenSource.token); + })).then(results => { + tokenSource.dispose(); + this.collector!.flush(); + + const someFolderHitLImit = results.some(result => !!result && !!result.limitHit); + resolve({ + limitHit: this.isLimitHit || someFolderHitLImit, + stats: { + type: 'textSearchProvider' + } + }); + }, (err: Error) => { + tokenSource.dispose(); + const errMsg = toErrorMessage(err); + reject(new Error(errMsg)); + }); + }); + } + + private resultSize(result: TextSearchResult): number { + const match = result; + return Array.isArray(match.ranges) ? + match.ranges.length : + 1; + } + + private trimResultToSize(result: TextSearchMatch, size: number): TextSearchMatch { + const rangesArr = Array.isArray(result.ranges) ? result.ranges : [result.ranges]; + const matchesArr = Array.isArray(result.preview.matches) ? result.preview.matches : [result.preview.matches]; + + return { + ranges: rangesArr.slice(0, size), + preview: { + matches: matchesArr.slice(0, size), + text: result.preview.text + }, + uri: result.uri + }; + } + + private searchInFolder(folderQuery: IFolderQuery, onResult: (result: TextSearchResult) => void, token: CancellationToken): Promise { + const queryTester = new QueryGlobTester(this.query, folderQuery); + const testingPs: Promise[] = []; + const progress = { + report: (result: TextSearchResult) => { + if (!this.validateProviderResult(result)) { + return; + } + + const hasSibling = folderQuery.folder.scheme === 'file' ? + glob.hasSiblingPromiseFn(() => { + return this.fileUtils.readdir(resources.dirname(result.uri)); + }) : + undefined; + + const relativePath = resources.relativePath(folderQuery.folder, result.uri); + if (relativePath) { + testingPs.push( + queryTester.includedInQuery(relativePath, path.basename(relativePath), hasSibling) + .then(included => { + if (included) { + onResult(result); + } + })); + } + } + }; + + const searchOptions = this.getSearchOptionsForFolder(folderQuery); + return new Promise(resolve => nextTick(resolve)) + .then(() => this.provider.provideTextSearchResults(patternInfoToQuery(this.query.contentPattern), searchOptions, progress, token)) + .then(result => { + return Promise.all(testingPs) + .then(() => result); + }); + } + + private validateProviderResult(result: TextSearchResult): boolean { + if (extensionResultIsMatch(result)) { + if (Array.isArray(result.ranges)) { + if (!Array.isArray(result.preview.matches)) { + console.warn('INVALID - A text search provider match\'s`ranges` and`matches` properties must have the same type.'); + return false; + } + + if ((result.preview.matches).length !== result.ranges.length) { + console.warn('INVALID - A text search provider match\'s`ranges` and`matches` properties must have the same length.'); + return false; + } + } else { + if (Array.isArray(result.preview.matches)) { + console.warn('INVALID - A text search provider match\'s`ranges` and`matches` properties must have the same length.'); + return false; + } + } + } + + return true; + } + + private getSearchOptionsForFolder(fq: IFolderQuery): TextSearchOptions { + const includes = resolvePatternsForProvider(this.query.includePattern, fq.includePattern); + const excludes = resolvePatternsForProvider(this.query.excludePattern, fq.excludePattern); + + const options = { + folder: URI.from(fq.folder), + excludes, + includes, + useIgnoreFiles: !fq.disregardIgnoreFiles, + useGlobalIgnoreFiles: !fq.disregardGlobalIgnoreFiles, + followSymlinks: !fq.ignoreSymlinks, + encoding: fq.fileEncoding && this.fileUtils.toCanonicalName(fq.fileEncoding), + maxFileSize: this.query.maxFileSize, + maxResults: this.query.maxResults, + previewOptions: this.query.previewOptions, + afterContext: this.query.afterContext, + beforeContext: this.query.beforeContext + }; + (options).usePCRE2 = this.query.usePCRE2; + return options; + } +} + +function patternInfoToQuery(patternInfo: IPatternInfo): TextSearchQuery { + return { + isCaseSensitive: patternInfo.isCaseSensitive || false, + isRegExp: patternInfo.isRegExp || false, + isWordMatch: patternInfo.isWordMatch || false, + isMultiline: patternInfo.isMultiline || false, + pattern: patternInfo.pattern + }; +} + +export class TextSearchResultsCollector { + private _batchedCollector: BatchedCollector; + + private _currentFolderIdx: number = -1; + private _currentUri: URI | undefined; + private _currentFileMatch: IFileMatch | null = null; + + constructor(private _onResult: (result: IFileMatch[]) => void) { + this._batchedCollector = new BatchedCollector(512, items => this.sendItems(items)); + } + + add(data: TextSearchResult, folderIdx: number): void { + // Collects TextSearchResults into IInternalFileMatches and collates using BatchedCollector. + // This is efficient for ripgrep which sends results back one file at a time. It wouldn't be efficient for other search + // providers that send results in random order. We could do this step afterwards instead. + if (this._currentFileMatch && (this._currentFolderIdx !== folderIdx || !resources.isEqual(this._currentUri, data.uri))) { + this.pushToCollector(); + this._currentFileMatch = null; + } + + if (!this._currentFileMatch) { + this._currentFolderIdx = folderIdx; + this._currentFileMatch = { + resource: data.uri, + results: [] + }; + } + + this._currentFileMatch.results!.push(extensionResultToFrontendResult(data)); + } + + private pushToCollector(): void { + const size = this._currentFileMatch && this._currentFileMatch.results ? + this._currentFileMatch.results.length : + 0; + this._batchedCollector.addItem(this._currentFileMatch!, size); + } + + flush(): void { + this.pushToCollector(); + this._batchedCollector.flush(); + } + + private sendItems(items: IFileMatch[]): void { + this._onResult(items); + } +} + +function extensionResultToFrontendResult(data: TextSearchResult): ITextSearchResult { + // Warning: result from RipgrepTextSearchEH has fake Range. Don't depend on any other props beyond these... + if (extensionResultIsMatch(data)) { + return { + preview: { + matches: mapArrayOrNot(data.preview.matches, m => ({ + startLineNumber: m.start.line, + startColumn: m.start.character, + endLineNumber: m.end.line, + endColumn: m.end.character + })), + text: data.preview.text + }, + ranges: mapArrayOrNot(data.ranges, r => ({ + startLineNumber: r.start.line, + startColumn: r.start.character, + endLineNumber: r.end.line, + endColumn: r.end.character + })) + }; + } else { + return { + text: data.text, + lineNumber: data.lineNumber + }; + } +} + +export function extensionResultIsMatch(data: TextSearchResult): data is TextSearchMatch { + return !!(data).preview; +} + +/** + * Collects items that have a size - before the cumulative size of collected items reaches START_BATCH_AFTER_COUNT, the callback is called for every + * set of items collected. + * But after that point, the callback is called with batches of maxBatchSize. + * If the batch isn't filled within some time, the callback is also called. + */ +export class BatchedCollector { + private static readonly TIMEOUT = 4000; + + // After START_BATCH_AFTER_COUNT items have been collected, stop flushing on timeout + private static readonly START_BATCH_AFTER_COUNT = 50; + + private totalNumberCompleted = 0; + private batch: T[] = []; + private batchSize = 0; + private timeoutHandle: any; + + constructor(private maxBatchSize: number, private cb: (items: T[]) => void) { + } + + addItem(item: T, size: number): void { + if (!item) { + return; + } + + this.addItemToBatch(item, size); + } + + addItems(items: T[], size: number): void { + if (!items) { + return; + } + + this.addItemsToBatch(items, size); + } + + private addItemToBatch(item: T, size: number): void { + this.batch.push(item); + this.batchSize += size; + this.onUpdate(); + } + + private addItemsToBatch(item: T[], size: number): void { + this.batch = this.batch.concat(item); + this.batchSize += size; + this.onUpdate(); + } + + private onUpdate(): void { + if (this.totalNumberCompleted < BatchedCollector.START_BATCH_AFTER_COUNT) { + // Flush because we aren't batching yet + this.flush(); + } else if (this.batchSize >= this.maxBatchSize) { + // Flush because the batch is full + this.flush(); + } else if (!this.timeoutHandle) { + // No timeout running, start a timeout to flush + this.timeoutHandle = setTimeout(() => { + this.flush(); + }, BatchedCollector.TIMEOUT); + } + } + + flush(): void { + if (this.batchSize) { + this.totalNumberCompleted += this.batchSize; + this.cb(this.batch); + this.batch = []; + this.batchSize = 0; + + if (this.timeoutHandle) { + clearTimeout(this.timeoutHandle); + this.timeoutHandle = 0; + } + } + } +} diff --git a/src/vs/workbench/services/search/node/rawSearchService.ts b/src/vs/workbench/services/search/node/rawSearchService.ts index 4bc07a28c1..082e9a3add 100644 --- a/src/vs/workbench/services/search/node/rawSearchService.ts +++ b/src/vs/workbench/services/search/node/rawSearchService.ts @@ -277,7 +277,7 @@ export class SearchService implements IRawSearchService { for (const previousSearch in cache.resultsToSearchCache) { // If we narrow down, we might be able to reuse the cached results if (strings.startsWith(searchValue, previousSearch)) { - if (hasPathSep && previousSearch.indexOf(sep) < 0) { + if (hasPathSep && previousSearch.indexOf(sep) < 0 && previousSearch !== '') { continue; // since a path character widens the search for potential more matches, require it in previous search too } @@ -383,7 +383,7 @@ export class SearchService implements IRawSearchService { cancel() { // Do nothing } - then(resolve: any, reject: any) { + then(resolve?: ((value: C) => TResult1 | Promise) | undefined | null, reject?: ((reason: any) => TResult2 | Promise) | undefined | null): Promise { return promise.then(resolve, reject); } catch(reject?: any) { diff --git a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts index 2fa1aa7c29..03d396fdaf 100644 --- a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts +++ b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts @@ -79,15 +79,15 @@ export class RipgrepTextSearchEngine { cancel(); }); - rgProc.stdout.on('data', data => { + rgProc.stdout!.on('data', data => { ripgrepParser.handleData(data); }); let gotData = false; - rgProc.stdout.once('data', () => gotData = true); + rgProc.stdout!.once('data', () => gotData = true); let stderr = ''; - rgProc.stderr.on('data', data => { + rgProc.stderr!.on('data', data => { const message = data.toString(); this.outputChannel.appendLine(message); stderr += message; diff --git a/src/vs/workbench/services/search/node/searchService.ts b/src/vs/workbench/services/search/node/searchService.ts index 76c2d3bcc2..5d43f9e2b8 100644 --- a/src/vs/workbench/services/search/node/searchService.ts +++ b/src/vs/workbench/services/search/node/searchService.ts @@ -21,16 +21,17 @@ import { SearchChannelClient } from './searchIpc'; import { SearchService } from 'vs/workbench/services/search/common/searchService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { parseSearchPort } from 'vs/platform/environment/node/environmentService'; export class LocalSearchService extends SearchService { constructor( @IModelService modelService: IModelService, - @IUntitledEditorService untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService untitledTextEditorService: IUntitledTextEditorService, @IEditorService editorService: IEditorService, @ITelemetryService telemetryService: ITelemetryService, @ILogService logService: ILogService, @@ -39,10 +40,10 @@ export class LocalSearchService extends SearchService { @IWorkbenchEnvironmentService readonly environmentService: IWorkbenchEnvironmentService, @IInstantiationService readonly instantiationService: IInstantiationService ) { - super(modelService, untitledEditorService, editorService, telemetryService, logService, extensionService, fileService); + super(modelService, untitledTextEditorService, editorService, telemetryService, logService, extensionService, fileService); - this.diskSearch = instantiationService.createInstance(DiskSearch, !environmentService.isBuilt || environmentService.verbose, environmentService.debugSearch); + this.diskSearch = instantiationService.createInstance(DiskSearch, !environmentService.isBuilt || environmentService.verbose, parseSearchPort(environmentService.args, environmentService.isBuilt)); } } diff --git a/src/vs/workbench/services/search/node/textSearchAdapter.ts b/src/vs/workbench/services/search/node/textSearchAdapter.ts index 0576ec2651..bb96d536f3 100644 --- a/src/vs/workbench/services/search/node/textSearchAdapter.ts +++ b/src/vs/workbench/services/search/node/textSearchAdapter.ts @@ -7,12 +7,11 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import * as pfs from 'vs/base/node/pfs'; import { IFileMatch, IProgressMessage, ITextQuery, ITextSearchStats, ITextSearchMatch, ISerializedFileMatch, ISerializedSearchSuccess } from 'vs/workbench/services/search/common/search'; import { RipgrepTextSearchEngine } from 'vs/workbench/services/search/node/ripgrepTextSearchEngine'; -import { TextSearchManager } from 'vs/workbench/services/search/node/textSearchManager'; +import { NativeTextSearchManager } from 'vs/workbench/services/search/node/textSearchManager'; export class TextSearchEngineAdapter { - constructor(private query: ITextQuery) { - } + constructor(private query: ITextQuery) { } search(token: CancellationToken, onResult: (matches: ISerializedFileMatch[]) => void, onMessage: (message: IProgressMessage) => void): Promise { if ((!this.query.folderQueries || !this.query.folderQueries.length) && (!this.query.extraFileResources || !this.query.extraFileResources.length)) { @@ -30,7 +29,7 @@ export class TextSearchEngineAdapter { onMessage({ message: msg }); } }; - const textSearchManager = new TextSearchManager(this.query, new RipgrepTextSearchEngine(pretendOutputChannel), pfs); + const textSearchManager = new NativeTextSearchManager(this.query, new RipgrepTextSearchEngine(pretendOutputChannel), pfs); return new Promise((resolve, reject) => { return textSearchManager .search( diff --git a/src/vs/workbench/services/search/node/textSearchManager.ts b/src/vs/workbench/services/search/node/textSearchManager.ts index 5e16591a94..355212f281 100644 --- a/src/vs/workbench/services/search/node/textSearchManager.ts +++ b/src/vs/workbench/services/search/node/textSearchManager.ts @@ -3,352 +3,18 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'vs/base/common/path'; -import { mapArrayOrNot } from 'vs/base/common/arrays'; -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { toErrorMessage } from 'vs/base/common/errorMessage'; -import * as resources from 'vs/base/common/resources'; -import * as glob from 'vs/base/common/glob'; -import { URI } from 'vs/base/common/uri'; import { toCanonicalName } from 'vs/base/node/encoding'; import * as pfs from 'vs/base/node/pfs'; -import { IExtendedExtensionSearchOptions, IFileMatch, IFolderQuery, IPatternInfo, ISearchCompleteStats, ITextQuery, ITextSearchContext, ITextSearchMatch, ITextSearchResult, QueryGlobTester, resolvePatternsForProvider } from 'vs/workbench/services/search/common/search'; -import { TextSearchProvider, TextSearchResult, TextSearchMatch, TextSearchComplete, Range, TextSearchOptions, TextSearchQuery } from 'vs/workbench/services/search/common/searchExtTypes'; +import { ITextQuery } from 'vs/workbench/services/search/common/search'; +import { TextSearchProvider } from 'vs/workbench/services/search/common/searchExtTypes'; +import { TextSearchManager } from 'vs/workbench/services/search/common/textSearchManager'; -export class TextSearchManager { +export class NativeTextSearchManager extends TextSearchManager { - private collector: TextSearchResultsCollector | null = null; - - private isLimitHit = false; - private resultCount = 0; - - constructor(private query: ITextQuery, private provider: TextSearchProvider, private _pfs: typeof pfs = pfs) { - } - - search(onProgress: (matches: IFileMatch[]) => void, token: CancellationToken): Promise { - const folderQueries = this.query.folderQueries || []; - const tokenSource = new CancellationTokenSource(); - token.onCancellationRequested(() => tokenSource.cancel()); - - return new Promise((resolve, reject) => { - this.collector = new TextSearchResultsCollector(onProgress); - - let isCanceled = false; - const onResult = (result: TextSearchResult, folderIdx: number) => { - if (isCanceled) { - return; - } - - if (!this.isLimitHit) { - const resultSize = this.resultSize(result); - if (extensionResultIsMatch(result) && typeof this.query.maxResults === 'number' && this.resultCount + resultSize > this.query.maxResults) { - this.isLimitHit = true; - isCanceled = true; - tokenSource.cancel(); - - result = this.trimResultToSize(result, this.query.maxResults - this.resultCount); - } - - const newResultSize = this.resultSize(result); - this.resultCount += newResultSize; - if (newResultSize > 0) { - this.collector!.add(result, folderIdx); - } - } - }; - - // For each root folder - Promise.all(folderQueries.map((fq, i) => { - return this.searchInFolder(fq, r => onResult(r, i), tokenSource.token); - })).then(results => { - tokenSource.dispose(); - this.collector!.flush(); - - const someFolderHitLImit = results.some(result => !!result && !!result.limitHit); - resolve({ - limitHit: this.isLimitHit || someFolderHitLImit, - stats: { - type: 'textSearchProvider' - } - }); - }, (err: Error) => { - tokenSource.dispose(); - const errMsg = toErrorMessage(err); - reject(new Error(errMsg)); - }); + constructor(query: ITextQuery, provider: TextSearchProvider, _pfs: typeof pfs = pfs) { + super(query, provider, { + readdir: resource => _pfs.readdir(resource.fsPath), + toCanonicalName: name => toCanonicalName(name) }); } - - private resultSize(result: TextSearchResult): number { - const match = result; - return Array.isArray(match.ranges) ? - match.ranges.length : - 1; - } - - private trimResultToSize(result: TextSearchMatch, size: number): TextSearchMatch { - const rangesArr = Array.isArray(result.ranges) ? result.ranges : [result.ranges]; - const matchesArr = Array.isArray(result.preview.matches) ? result.preview.matches : [result.preview.matches]; - - return { - ranges: rangesArr.slice(0, size), - preview: { - matches: matchesArr.slice(0, size), - text: result.preview.text - }, - uri: result.uri - }; - } - - private searchInFolder(folderQuery: IFolderQuery, onResult: (result: TextSearchResult) => void, token: CancellationToken): Promise { - const queryTester = new QueryGlobTester(this.query, folderQuery); - const testingPs: Promise[] = []; - const progress = { - report: (result: TextSearchResult) => { - if (!this.validateProviderResult(result)) { - return; - } - - const hasSibling = folderQuery.folder.scheme === 'file' ? - glob.hasSiblingPromiseFn(() => { - return this.readdir(path.dirname(result.uri.fsPath)); - }) : - undefined; - - const relativePath = path.relative(folderQuery.folder.fsPath, result.uri.fsPath); - testingPs.push( - queryTester.includedInQuery(relativePath, path.basename(relativePath), hasSibling) - .then(included => { - if (included) { - onResult(result); - } - })); - } - }; - - const searchOptions = this.getSearchOptionsForFolder(folderQuery); - return new Promise(resolve => process.nextTick(resolve)) - .then(() => this.provider.provideTextSearchResults(patternInfoToQuery(this.query.contentPattern), searchOptions, progress, token)) - .then(result => { - return Promise.all(testingPs) - .then(() => result); - }); - } - - private validateProviderResult(result: TextSearchResult): boolean { - if (extensionResultIsMatch(result)) { - if (Array.isArray(result.ranges)) { - if (!Array.isArray(result.preview.matches)) { - console.warn('INVALID - A text search provider match\'s`ranges` and`matches` properties must have the same type.'); - return false; - } - - if ((result.preview.matches).length !== result.ranges.length) { - console.warn('INVALID - A text search provider match\'s`ranges` and`matches` properties must have the same length.'); - return false; - } - } else { - if (Array.isArray(result.preview.matches)) { - console.warn('INVALID - A text search provider match\'s`ranges` and`matches` properties must have the same length.'); - return false; - } - } - } - - return true; - } - - private readdir(dirname: string): Promise { - return this._pfs.readdir(dirname); - } - - private getSearchOptionsForFolder(fq: IFolderQuery): TextSearchOptions { - const includes = resolvePatternsForProvider(this.query.includePattern, fq.includePattern); - const excludes = resolvePatternsForProvider(this.query.excludePattern, fq.excludePattern); - - const options = { - folder: URI.from(fq.folder), - excludes, - includes, - useIgnoreFiles: !fq.disregardIgnoreFiles, - useGlobalIgnoreFiles: !fq.disregardGlobalIgnoreFiles, - followSymlinks: !fq.ignoreSymlinks, - encoding: fq.fileEncoding && toCanonicalName(fq.fileEncoding), - maxFileSize: this.query.maxFileSize, - maxResults: this.query.maxResults, - previewOptions: this.query.previewOptions, - afterContext: this.query.afterContext, - beforeContext: this.query.beforeContext - }; - (options).usePCRE2 = this.query.usePCRE2; - return options; - } -} - -function patternInfoToQuery(patternInfo: IPatternInfo): TextSearchQuery { - return { - isCaseSensitive: patternInfo.isCaseSensitive || false, - isRegExp: patternInfo.isRegExp || false, - isWordMatch: patternInfo.isWordMatch || false, - isMultiline: patternInfo.isMultiline || false, - pattern: patternInfo.pattern - }; -} - -export class TextSearchResultsCollector { - private _batchedCollector: BatchedCollector; - - private _currentFolderIdx: number = -1; - private _currentUri: URI | undefined; - private _currentFileMatch: IFileMatch | null = null; - - constructor(private _onResult: (result: IFileMatch[]) => void) { - this._batchedCollector = new BatchedCollector(512, items => this.sendItems(items)); - } - - add(data: TextSearchResult, folderIdx: number): void { - // Collects TextSearchResults into IInternalFileMatches and collates using BatchedCollector. - // This is efficient for ripgrep which sends results back one file at a time. It wouldn't be efficient for other search - // providers that send results in random order. We could do this step afterwards instead. - if (this._currentFileMatch && (this._currentFolderIdx !== folderIdx || !resources.isEqual(this._currentUri, data.uri))) { - this.pushToCollector(); - this._currentFileMatch = null; - } - - if (!this._currentFileMatch) { - this._currentFolderIdx = folderIdx; - this._currentFileMatch = { - resource: data.uri, - results: [] - }; - } - - this._currentFileMatch.results!.push(extensionResultToFrontendResult(data)); - } - - private pushToCollector(): void { - const size = this._currentFileMatch && this._currentFileMatch.results ? - this._currentFileMatch.results.length : - 0; - this._batchedCollector.addItem(this._currentFileMatch!, size); - } - - flush(): void { - this.pushToCollector(); - this._batchedCollector.flush(); - } - - private sendItems(items: IFileMatch[]): void { - this._onResult(items); - } -} - -function extensionResultToFrontendResult(data: TextSearchResult): ITextSearchResult { - // Warning: result from RipgrepTextSearchEH has fake Range. Don't depend on any other props beyond these... - if (extensionResultIsMatch(data)) { - return { - preview: { - matches: mapArrayOrNot(data.preview.matches, m => ({ - startLineNumber: m.start.line, - startColumn: m.start.character, - endLineNumber: m.end.line, - endColumn: m.end.character - })), - text: data.preview.text - }, - ranges: mapArrayOrNot(data.ranges, r => ({ - startLineNumber: r.start.line, - startColumn: r.start.character, - endLineNumber: r.end.line, - endColumn: r.end.character - })) - }; - } else { - return { - text: data.text, - lineNumber: data.lineNumber - }; - } -} - -export function extensionResultIsMatch(data: TextSearchResult): data is TextSearchMatch { - return !!(data).preview; -} - -/** - * Collects items that have a size - before the cumulative size of collected items reaches START_BATCH_AFTER_COUNT, the callback is called for every - * set of items collected. - * But after that point, the callback is called with batches of maxBatchSize. - * If the batch isn't filled within some time, the callback is also called. - */ -export class BatchedCollector { - private static readonly TIMEOUT = 4000; - - // After START_BATCH_AFTER_COUNT items have been collected, stop flushing on timeout - private static readonly START_BATCH_AFTER_COUNT = 50; - - private totalNumberCompleted = 0; - private batch: T[] = []; - private batchSize = 0; - private timeoutHandle: any; - - constructor(private maxBatchSize: number, private cb: (items: T[]) => void) { - } - - addItem(item: T, size: number): void { - if (!item) { - return; - } - - this.addItemToBatch(item, size); - } - - addItems(items: T[], size: number): void { - if (!items) { - return; - } - - this.addItemsToBatch(items, size); - } - - private addItemToBatch(item: T, size: number): void { - this.batch.push(item); - this.batchSize += size; - this.onUpdate(); - } - - private addItemsToBatch(item: T[], size: number): void { - this.batch = this.batch.concat(item); - this.batchSize += size; - this.onUpdate(); - } - - private onUpdate(): void { - if (this.totalNumberCompleted < BatchedCollector.START_BATCH_AFTER_COUNT) { - // Flush because we aren't batching yet - this.flush(); - } else if (this.batchSize >= this.maxBatchSize) { - // Flush because the batch is full - this.flush(); - } else if (!this.timeoutHandle) { - // No timeout running, start a timeout to flush - this.timeoutHandle = setTimeout(() => { - this.flush(); - }, BatchedCollector.TIMEOUT); - } - } - - flush(): void { - if (this.batchSize) { - this.totalNumberCompleted += this.batchSize; - this.cb(this.batch); - this.batch = []; - this.batchSize = 0; - - if (this.timeoutHandle) { - clearTimeout(this.timeoutHandle); - this.timeoutHandle = 0; - } - } - } } diff --git a/src/vs/workbench/services/search/test/common/replace.test.ts b/src/vs/workbench/services/search/test/common/replace.test.ts index a1dde74b95..cc9ea1424b 100644 --- a/src/vs/workbench/services/search/test/common/replace.test.ts +++ b/src/vs/workbench/services/search/test/common/replace.test.ts @@ -214,5 +214,21 @@ suite('Replace Pattern test', () => { testObject = new ReplacePattern('$0ah', { pattern: 'b(la)(?=\\stext$)', isRegExp: true }); actual = testObject.getReplaceString('this is a bla text'); assert.equal('blaah', actual); + + testObject = new ReplacePattern('newrege$1', true, /Testrege(\w*)/); + actual = testObject.getReplaceString('Testregex', true); + assert.equal('Newregex', actual); + + testObject = new ReplacePattern('newrege$1', true, /TESTREGE(\w*)/); + actual = testObject.getReplaceString('TESTREGEX', true); + assert.equal('NEWREGEX', actual); + + testObject = new ReplacePattern('new_rege$1', true, /Test_Rege(\w*)/); + actual = testObject.getReplaceString('Test_Regex', true); + assert.equal('New_Regex', actual); + + testObject = new ReplacePattern('new-rege$1', true, /Test-Rege(\w*)/); + actual = testObject.getReplaceString('Test-Regex', true); + assert.equal('New-Regex', actual); }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/services/search/test/node/textSearchManager.test.ts b/src/vs/workbench/services/search/test/node/textSearchManager.test.ts index c654d90a92..e2da74999d 100644 --- a/src/vs/workbench/services/search/test/node/textSearchManager.test.ts +++ b/src/vs/workbench/services/search/test/node/textSearchManager.test.ts @@ -9,9 +9,9 @@ import { URI } from 'vs/base/common/uri'; import { Progress } from 'vs/platform/progress/common/progress'; import { ITextQuery, QueryType } from 'vs/workbench/services/search/common/search'; import { ProviderResult, TextSearchComplete, TextSearchOptions, TextSearchProvider, TextSearchQuery, TextSearchResult } from 'vs/workbench/services/search/common/searchExtTypes'; -import { TextSearchManager } from 'vs/workbench/services/search/node/textSearchManager'; +import { NativeTextSearchManager } from 'vs/workbench/services/search/node/textSearchManager'; -suite('TextSearchManager', () => { +suite('NativeTextSearchManager', () => { test('fixes encoding', async () => { let correctEncoding = false; const provider: TextSearchProvider = { @@ -33,7 +33,7 @@ suite('TextSearchManager', () => { }] }; - const m = new TextSearchManager(query, provider); + const m = new NativeTextSearchManager(query, provider); await m.search(() => { }, new CancellationTokenSource().token); assert.ok(correctEncoding); diff --git a/src/vs/workbench/services/statusbar/common/statusbar.ts b/src/vs/workbench/services/statusbar/common/statusbar.ts index 120d2a8bdc..814a7c2541 100644 --- a/src/vs/workbench/services/statusbar/common/statusbar.ts +++ b/src/vs/workbench/services/statusbar/common/statusbar.ts @@ -6,6 +6,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable } from 'vs/base/common/lifecycle'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; +import { Event } from 'vs/base/common/event'; export const IStatusbarService = createDecorator('statusbarService'); @@ -74,7 +75,17 @@ export interface IStatusbarService { addEntry(entry: IStatusbarEntry, id: string, name: string, alignment: StatusbarAlignment, priority?: number): IStatusbarEntryAccessor; /** - * Allows to update an entry's visibilty with the provided ID. + * An event that is triggered when an entry's visibility is changed. + */ + readonly onDidChangeEntryVisibility: Event<{ id: string, visible: boolean }>; + + /** + * Return if an entry is visible or not. + */ + isEntryVisible(id: string): boolean; + + /** + * Allows to update an entry's visibility with the provided ID. */ updateEntryVisibility(id: string, visible: boolean): void; } diff --git a/src/vs/workbench/services/telemetry/browser/telemetryService.ts b/src/vs/workbench/services/telemetry/browser/telemetryService.ts index cb1178a21f..1e793ddc4d 100644 --- a/src/vs/workbench/services/telemetry/browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/browser/telemetryService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ITelemetryService, ITelemetryInfo, ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; -import { NullTelemetryService, combinedAppender, LogAppender, ITelemetryAppender, validateTelemetryData } from 'vs/platform/telemetry/common/telemetryUtils'; +import { NullTelemetryService, combinedAppender, LogAppender, ITelemetryAppender } from 'vs/platform/telemetry/common/telemetryUtils'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -19,18 +19,11 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA export class WebTelemetryAppender implements ITelemetryAppender { - constructor(private _logService: ILogService, private _appender: IRemoteAgentService, - @IWorkbenchEnvironmentService private _environmentService: IWorkbenchEnvironmentService) { } // {{ SQL CARBON EDIT }} + constructor(private _logService: ILogService, private _appender: IRemoteAgentService) { } log(eventName: string, data: any): void { - data = validateTelemetryData(data); this._logService.trace(`telemetry/${eventName}`, data); - - const eventPrefix = this._environmentService.appQuality !== 'stable' ? '/adsworkbench/' : '/monacoworkbench/'; // {{SQL CARBON EDIT}} - this._appender.logTelemetry(eventPrefix + eventName, { - properties: data.properties, - measurements: data.measurements - }); + this._appender.logTelemetry(eventName, data); } flush(): Promise { @@ -54,11 +47,10 @@ export class TelemetryService extends Disposable implements ITelemetryService { ) { super(); - if (!environmentService.args['disable-telemetry'] && !!productService.enableTelemetry) { + if (!!productService.enableTelemetry) { const config: ITelemetryServiceConfig = { - appender: combinedAppender(new WebTelemetryAppender(logService, remoteAgentService, environmentService), new LogAppender(logService)), // {{SQL CARBON EDIT}} - commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.configuration.machineId, environmentService.configuration.remoteAuthority), - piiPaths: [environmentService.appRoot] + appender: combinedAppender(new WebTelemetryAppender(logService, remoteAgentService), new LogAppender(logService)), + commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.configuration.remoteAuthority, environmentService.options && environmentService.options.resolveCommonTelemetryProperties) }; this.impl = this._register(new BaseTelemetryService(config, configurationService)); diff --git a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts index 49781cbd4e..99f3eb0b60 100644 --- a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts @@ -38,7 +38,7 @@ export class TelemetryService extends Disposable implements ITelemetryService { const channel = sharedProcessService.getChannel('telemetryAppender'); const config: ITelemetryServiceConfig = { appender: combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(logService)), - commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.configuration.machineId, productService.msftInternalDomains, environmentService.installSourcePath, environmentService.configuration.remoteAuthority), + commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.configuration.machineId!, productService.msftInternalDomains, environmentService.installSourcePath, environmentService.configuration.remoteAuthority), piiPaths: environmentService.extensionsPath ? [environmentService.appRoot, environmentService.extensionsPath] : [environmentService.appRoot] }; diff --git a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts index e91cd7e531..a48300e306 100644 --- a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts +++ b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts @@ -10,25 +10,26 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import * as resources from 'vs/base/common/resources'; import * as types from 'vs/base/common/types'; +import { equals as equalArray } from 'vs/base/common/arrays'; import { URI } from 'vs/base/common/uri'; import { TokenizationResult, TokenizationResult2 } from 'vs/editor/common/core/token'; import { IState, ITokenizationSupport, LanguageId, TokenMetadata, TokenizationRegistry, StandardTokenType, LanguageIdentifier } from 'vs/editor/common/modes'; import { nullTokenize2 } from 'vs/editor/common/modes/nullMode'; import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { ITMSyntaxExtensionPoint, grammarsExtPoint } from 'vs/workbench/services/textMate/common/TMGrammars'; import { ITextMateService } from 'vs/workbench/services/textMate/common/textMateService'; -import { ITokenColorizationRule, IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { ITextMateThemingRule, IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IGrammar, StackElement, IOnigLib, IRawTheme } from 'vscode-textmate'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IValidGrammarDefinition, IValidEmbeddedLanguagesMap, IValidTokenTypeMap } from 'vs/workbench/services/textMate/common/TMScopeRegistry'; import { TMGrammarFactory } from 'vs/workbench/services/textMate/common/TMGrammarFactory'; +import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; export abstract class AbstractTextMateService extends Disposable implements ITextMateService { public _serviceBrand: undefined; @@ -44,11 +45,12 @@ export abstract class AbstractTextMateService extends Disposable implements ITex private _grammarFactory: TMGrammarFactory | null; private _tokenizersRegistrations: IDisposable[]; protected _currentTheme: IRawTheme | null; + protected _currentTokenColorMap: string[] | null; constructor( @IModeService private readonly _modeService: IModeService, @IWorkbenchThemeService private readonly _themeService: IWorkbenchThemeService, - @IFileService protected readonly _fileService: IFileService, + @IExtensionResourceLoaderService protected readonly _extensionResourceLoaderService: IExtensionResourceLoaderService, @INotificationService private readonly _notificationService: INotificationService, @ILogService private readonly _logService: ILogService, @IConfigurationService private readonly _configurationService: IConfigurationService, @@ -65,6 +67,7 @@ export abstract class AbstractTextMateService extends Disposable implements ITex this._tokenizersRegistrations = []; this._currentTheme = null; + this._currentTokenColorMap = null; grammarsExtPoint.setHandler((extensions) => { this._grammarDefinitions = null; @@ -191,10 +194,7 @@ export abstract class AbstractTextMateService extends Disposable implements ITex this._grammarFactory = new TMGrammarFactory({ logTrace: (msg: string) => this._logService.trace(msg), logError: (msg: string, err: any) => this._logService.error(msg, err), - readFile: async (resource: URI) => { - const content = await this._fileService.readFile(resource); - return content.value.toString(); - } + readFile: (resource: URI) => this._extensionResourceLoaderService.readExtensionResource(resource) }, this._grammarDefinitions || [], vscodeTextmate, this._loadOnigLib()); this._onDidCreateGrammarFactory(this._grammarDefinitions || []); @@ -221,6 +221,9 @@ export abstract class AbstractTextMateService extends Disposable implements ITex return null; } const r = await grammarFactory.createGrammar(languageId); + if (!r.grammar) { + return null; + } const tokenization = new TMTokenization(r.grammar, r.initialState, r.containsEmbeddedLanguages); tokenization.onDidEncounterLanguage((languageId) => { if (!this._encounteredLanguages[languageId]) { @@ -245,22 +248,23 @@ export abstract class AbstractTextMateService extends Disposable implements ITex } private _updateTheme(grammarFactory: TMGrammarFactory, colorTheme: IColorTheme, forceUpdate: boolean): void { - if (!forceUpdate && this._currentTheme && AbstractTextMateService.equalsTokenRules(this._currentTheme.settings, colorTheme.tokenColors)) { + if (!forceUpdate && this._currentTheme && this._currentTokenColorMap && AbstractTextMateService.equalsTokenRules(this._currentTheme.settings, colorTheme.tokenColors) && equalArray(this._currentTokenColorMap, colorTheme.tokenColorMap)) { return; } this._currentTheme = { name: colorTheme.label, settings: colorTheme.tokenColors }; - this._doUpdateTheme(grammarFactory, this._currentTheme); + this._currentTokenColorMap = colorTheme.tokenColorMap; + this._doUpdateTheme(grammarFactory, this._currentTheme, this._currentTokenColorMap); } - protected _doUpdateTheme(grammarFactory: TMGrammarFactory, theme: IRawTheme): void { - grammarFactory.setTheme(theme); - let colorMap = AbstractTextMateService._toColorMap(grammarFactory.getColorMap()); + protected _doUpdateTheme(grammarFactory: TMGrammarFactory, theme: IRawTheme, tokenColorMap: string[]): void { + grammarFactory.setTheme(theme, tokenColorMap); + let colorMap = AbstractTextMateService._toColorMap(tokenColorMap); let cssRules = generateTokensCSSForColorMap(colorMap); this._styleElement.innerHTML = cssRules; TokenizationRegistry.setColorMap(colorMap); } - private static equalsTokenRules(a: ITokenColorizationRule[] | null, b: ITokenColorizationRule[] | null): boolean { + private static equalsTokenRules(a: ITextMateThemingRule[] | null, b: ITextMateThemingRule[] | null): boolean { if (!b || !a || b.length !== a.length) { return false; } @@ -317,7 +321,7 @@ export abstract class AbstractTextMateService extends Disposable implements ITex return true; } - public async createGrammar(modeId: string): Promise { + public async createGrammar(modeId: string): Promise { const grammarFactory = await this._getOrCreateGrammarFactory(); const { grammar } = await grammarFactory.createGrammar(this._modeService.getLanguageIdentifier(modeId)!.id); return grammar; diff --git a/src/vs/workbench/services/textMate/browser/textMateService.ts b/src/vs/workbench/services/textMate/browser/textMateService.ts index ebd30c8277..0d5a26f663 100644 --- a/src/vs/workbench/services/textMate/browser/textMateService.ts +++ b/src/vs/workbench/services/textMate/browser/textMateService.ts @@ -8,25 +8,25 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { AbstractTextMateService } from 'vs/workbench/services/textMate/browser/abstractTextMateService'; import { IOnigLib } from 'vscode-textmate'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; export class TextMateService extends AbstractTextMateService { constructor( @IModeService modeService: IModeService, @IWorkbenchThemeService themeService: IWorkbenchThemeService, - @IFileService fileService: IFileService, + @IExtensionResourceLoaderService extensionResourceLoaderService: IExtensionResourceLoaderService, @INotificationService notificationService: INotificationService, @ILogService logService: ILogService, @IConfigurationService configurationService: IConfigurationService, @IStorageService storageService: IStorageService ) { - super(modeService, themeService, fileService, notificationService, logService, configurationService, storageService); + super(modeService, themeService, extensionResourceLoaderService, notificationService, logService, configurationService, storageService); } protected _loadVSCodeTextmate(): Promise { diff --git a/src/vs/workbench/services/textMate/common/TMGrammarFactory.ts b/src/vs/workbench/services/textMate/common/TMGrammarFactory.ts index a51b45def6..d4f2ced0b3 100644 --- a/src/vs/workbench/services/textMate/common/TMGrammarFactory.ts +++ b/src/vs/workbench/services/textMate/common/TMGrammarFactory.ts @@ -18,7 +18,7 @@ interface ITMGrammarFactoryHost { export interface ICreateGrammarResult { languageId: LanguageId; - grammar: IGrammar; + grammar: IGrammar | null; initialState: StackElement; containsEmbeddedLanguages: boolean; } @@ -102,8 +102,8 @@ export class TMGrammarFactory extends Disposable { return this._languageToScope2[languageId] ? true : false; } - public setTheme(theme: IRawTheme): void { - this._grammarRegistry.setTheme(theme); + public setTheme(theme: IRawTheme, colorMap: string[]): void { + this._grammarRegistry.setTheme(theme, colorMap); } public getColorMap(): string[] { diff --git a/src/vs/workbench/services/textMate/common/textMateService.ts b/src/vs/workbench/services/textMate/common/textMateService.ts index fb15c50272..207b2d07b8 100644 --- a/src/vs/workbench/services/textMate/common/textMateService.ts +++ b/src/vs/workbench/services/textMate/common/textMateService.ts @@ -14,7 +14,7 @@ export interface ITextMateService { onDidEncounterLanguage: Event; - createGrammar(modeId: string): Promise; + createGrammar(modeId: string): Promise; } // -------------- Types "liberated" from vscode-textmate due to usage in /common/ diff --git a/src/vs/workbench/services/textMate/electron-browser/textMateService.ts b/src/vs/workbench/services/textMate/electron-browser/textMateService.ts index 228ac18d01..7a15f4676a 100644 --- a/src/vs/workbench/services/textMate/electron-browser/textMateService.ts +++ b/src/vs/workbench/services/textMate/electron-browser/textMateService.ts @@ -8,7 +8,6 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { AbstractTextMateService } from 'vs/workbench/services/textMate/browser/abstractTextMateService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { IFileService } from 'vs/platform/files/common/files'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ILogService } from 'vs/platform/log/common/log'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -24,6 +23,7 @@ import { MultilineTokensBuilder } from 'vs/editor/common/model/tokensStore'; import { TMGrammarFactory } from 'vs/workbench/services/textMate/common/TMGrammarFactory'; import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; const RUN_TEXTMATE_IN_WORKER = false; @@ -117,14 +117,13 @@ export class TextMateWorkerHost { constructor( private readonly textMateService: TextMateService, - @IFileService private readonly _fileService: IFileService + @IExtensionResourceLoaderService private readonly _extensionResourceLoaderService: IExtensionResourceLoaderService, ) { } async readFile(_resource: UriComponents): Promise { const resource = URI.revive(_resource); - const content = await this._fileService.readFile(resource); - return content.value.toString(); + return this._extensionResourceLoaderService.readExtensionResource(resource); } async setTokens(_resource: UriComponents, versionId: number, tokens: Uint8Array): Promise { @@ -142,14 +141,14 @@ export class TextMateService extends AbstractTextMateService { constructor( @IModeService modeService: IModeService, @IWorkbenchThemeService themeService: IWorkbenchThemeService, - @IFileService fileService: IFileService, + @IExtensionResourceLoaderService extensionResourceLoaderService: IExtensionResourceLoaderService, @INotificationService notificationService: INotificationService, @ILogService logService: ILogService, @IConfigurationService configurationService: IConfigurationService, @IStorageService storageService: IStorageService, @IModelService private readonly _modelService: IModelService, ) { - super(modeService, themeService, fileService, notificationService, logService, configurationService, storageService); + super(modeService, themeService, extensionResourceLoaderService, notificationService, logService, configurationService, storageService); this._worker = null; this._workerProxy = null; this._tokenizers = Object.create(null); @@ -190,7 +189,7 @@ export class TextMateService extends AbstractTextMateService { this._killWorker(); if (RUN_TEXTMATE_IN_WORKER) { - const workerHost = new TextMateWorkerHost(this, this._fileService); + const workerHost = new TextMateWorkerHost(this, this._extensionResourceLoaderService); const worker = createWebWorker(this._modelService, { createData: { grammarDefinitions @@ -207,18 +206,18 @@ export class TextMateService extends AbstractTextMateService { return; } this._workerProxy = proxy; - if (this._currentTheme) { - this._workerProxy.acceptTheme(this._currentTheme); + if (this._currentTheme && this._currentTokenColorMap) { + this._workerProxy.acceptTheme(this._currentTheme, this._currentTokenColorMap); } this._modelService.getModels().forEach((model) => this._onModelAdded(model)); }); } } - protected _doUpdateTheme(grammarFactory: TMGrammarFactory, theme: IRawTheme): void { - super._doUpdateTheme(grammarFactory, theme); - if (this._currentTheme && this._workerProxy) { - this._workerProxy.acceptTheme(this._currentTheme); + protected _doUpdateTheme(grammarFactory: TMGrammarFactory, theme: IRawTheme, colorMap: string[]): void { + super._doUpdateTheme(grammarFactory, theme, colorMap); + if (this._currentTheme && this._currentTokenColorMap && this._workerProxy) { + this._workerProxy.acceptTheme(this._currentTheme, this._currentTokenColorMap); } } diff --git a/src/vs/workbench/services/textMate/electron-browser/textMateWorker.ts b/src/vs/workbench/services/textMate/electron-browser/textMateWorker.ts index 481371dc39..098c592968 100644 --- a/src/vs/workbench/services/textMate/electron-browser/textMateWorker.ts +++ b/src/vs/workbench/services/textMate/electron-browser/textMateWorker.ts @@ -185,9 +185,9 @@ export class TextMateWorker { return this._grammarCache[languageId]; } - public acceptTheme(theme: IRawTheme): void { + public acceptTheme(theme: IRawTheme, colorMap: string[]): void { if (this._grammarFactory) { - this._grammarFactory.setTheme(theme); + this._grammarFactory.setTheme(theme, colorMap); } } diff --git a/src/vs/workbench/services/textfile/browser/browserTextFileService.ts b/src/vs/workbench/services/textfile/browser/browserTextFileService.ts index e040466be4..b8877c162f 100644 --- a/src/vs/workbench/services/textfile/browser/browserTextFileService.ts +++ b/src/vs/workbench/services/textfile/browser/browserTextFileService.ts @@ -35,7 +35,7 @@ export class BrowserTextFileService extends AbstractTextFileService { return false; // no dirty: no veto } - if (!this.isHotExitEnabled) { + if (!this.filesConfigurationService.isHotExitEnabled) { return true; // dirty without backup: veto } @@ -46,7 +46,7 @@ export class BrowserTextFileService extends AbstractTextFileService { const model = this.models.get(dirtyResource); hasBackup = !!(model?.hasBackup()); } else if (dirtyResource.scheme === Schemas.untitled) { - hasBackup = this.untitledEditorService.hasBackup(dirtyResource); + hasBackup = this.untitledTextEditorService.hasBackup(dirtyResource); } if (!hasBackup) { diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 4a75134f4e..dfd74a862c 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -5,32 +5,28 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; -import * as errors from 'vs/base/common/errors'; -import * as objects from 'vs/base/common/objects'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Emitter, AsyncEmitter } from 'vs/base/common/event'; import * as platform from 'vs/base/common/platform'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, IAutoSaveConfiguration, AutoSaveMode, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ISaveOptions, AutoSaveContext, IWillMoveEvent, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; -import { ConfirmResult, IRevertOptions } from 'vs/workbench/common/editor'; +import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult, FileOperationWillRunEvent, FileOperationDidRunEvent, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; +import { SaveReason, IRevertOptions } from 'vs/workbench/common/editor'; import { ILifecycleService, ShutdownReason, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IFileService, IFilesConfiguration, FileOperationError, FileOperationResult, AutoSaveConfiguration, HotExitConfiguration, IFileStatWithMetadata, ICreateFileOptions } from 'vs/platform/files/common/files'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IFileService, FileOperationError, FileOperationResult, HotExitConfiguration, IFileStatWithMetadata, ICreateFileOptions, FileOperation } from 'vs/platform/files/common/files'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; +import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ResourceMap } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { createTextBufferFactoryFromSnapshot, createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { INotificationService } from 'vs/platform/notification/common/notification'; import { isEqualOrParent, isEqual, joinPath, dirname, extname, basename, toLocalResource } from 'vs/base/common/resources'; -import { getConfirmMessage, IDialogService, IFileDialogService, ISaveDialogOptions, IConfirmation } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService, IFileDialogService, ISaveDialogOptions, IConfirmation, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { coalesce } from 'vs/base/common/arrays'; @@ -39,6 +35,8 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { ITextSnapshot } from 'vs/editor/common/model'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; +import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { CancellationToken } from 'vs/base/common/cancellation'; /** * The workbench file service implementation implements the raw file service spec and adds additional methods on top. @@ -47,55 +45,42 @@ export abstract class AbstractTextFileService extends Disposable implements ITex _serviceBrand: undefined; - private readonly _onAutoSaveConfigurationChange: Emitter = this._register(new Emitter()); - readonly onAutoSaveConfigurationChange: Event = this._onAutoSaveConfigurationChange.event; + //#region events - private readonly _onFilesAssociationChange: Emitter = this._register(new Emitter()); - readonly onFilesAssociationChange: Event = this._onFilesAssociationChange.event; + private _onWillRunOperation = this._register(new AsyncEmitter()); + readonly onWillRunOperation = this._onWillRunOperation.event; - private readonly _onWillMove = this._register(new Emitter()); - readonly onWillMove: Event = this._onWillMove.event; + private _onDidRunOperation = this._register(new Emitter()); + readonly onDidRunOperation = this._onDidRunOperation.event; + + //#endregion private _models: TextFileEditorModelManager; get models(): ITextFileEditorModelManager { return this._models; } abstract get encoding(): IResourceEncodings; - private currentFilesAssociationConfig: { [key: string]: string; }; - private configuredAutoSaveDelay?: number; - private configuredAutoSaveOnFocusChange: boolean | undefined; - private configuredAutoSaveOnWindowChange: boolean | undefined; - private configuredHotExit: string | undefined; - private autoSaveContext: IContextKey; - constructor( @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IFileService protected readonly fileService: IFileService, - @IUntitledEditorService protected readonly untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService protected readonly untitledTextEditorService: IUntitledTextEditorService, @ILifecycleService private readonly lifecycleService: ILifecycleService, @IInstantiationService protected readonly instantiationService: IInstantiationService, - @IConfigurationService private readonly configurationService: IConfigurationService, @IModeService private readonly modeService: IModeService, @IModelService private readonly modelService: IModelService, @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, @INotificationService private readonly notificationService: INotificationService, @IBackupFileService private readonly backupFileService: IBackupFileService, @IHistoryService private readonly historyService: IHistoryService, - @IContextKeyService contextKeyService: IContextKeyService, @IDialogService private readonly dialogService: IDialogService, @IFileDialogService private readonly fileDialogService: IFileDialogService, @IEditorService private readonly editorService: IEditorService, - @ITextResourceConfigurationService protected readonly textResourceConfigurationService: ITextResourceConfigurationService + @ITextResourceConfigurationService protected readonly textResourceConfigurationService: ITextResourceConfigurationService, + @IFilesConfigurationService protected readonly filesConfigurationService: IFilesConfigurationService ) { super(); this._models = this._register(instantiationService.createInstance(TextFileEditorModelManager)); - this.autoSaveContext = AutoSaveContext.bindTo(contextKeyService); - - const configuration = configurationService.getValue(); - this.currentFilesAssociationConfig = configuration?.files?.associations; - - this.onFilesConfigurationChange(configuration); this.registerListeners(); } @@ -108,12 +93,16 @@ export abstract class AbstractTextFileService extends Disposable implements ITex this.lifecycleService.onBeforeShutdown(event => event.veto(this.onBeforeShutdown(event.reason))); this.lifecycleService.onShutdown(this.dispose, this); - // Files configuration changes - this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('files')) { - this.onFilesConfigurationChange(this.configurationService.getValue()); - } - })); + // Auto save changes + this._register(this.filesConfigurationService.onAutoSaveConfigurationChange(() => this.onAutoSaveConfigurationChange())); + } + + private onAutoSaveConfigurationChange(): void { + + // save all dirty when enabling auto save + if (this.filesConfigurationService.getAutoSaveMode() !== AutoSaveMode.OFF) { + this.saveAll(); + } } protected onBeforeShutdown(reason: ShutdownReason): boolean | Promise { @@ -124,7 +113,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // If auto save is enabled, save all files and then check again for dirty files // We DO NOT run any save participant if we are in the shutdown phase for performance reasons - if (this.getAutoSaveMode() !== AutoSaveMode.OFF) { + if (this.filesConfigurationService.getAutoSaveMode() !== AutoSaveMode.OFF) { return this.saveAll(false /* files only */, { skipSaveParticipants: true }).then(() => { // If we still have dirty files, we either have untitled ones or files that cannot be saved @@ -148,7 +137,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex private handleDirtyBeforeShutdown(dirty: URI[], reason: ShutdownReason): boolean | Promise { // If hot exit is enabled, backup dirty files and allow to exit without confirmation - if (this.isHotExitEnabled) { + if (this.filesConfigurationService.isHotExitEnabled) { return this.backupBeforeShutdown(dirty, reason).then(didBackup => { if (didBackup) { return this.noVeto({ cleanUpBackups: false }); // no veto and no backup cleanup (since backup was successful) @@ -176,7 +165,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex let doBackup: boolean | undefined; switch (reason) { case ShutdownReason.CLOSE: - if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.configuredHotExit === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { + if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.filesConfigurationService.hotExitConfiguration === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured } else if (await this.getWindowCount() > 1 || platform.isMacintosh) { doBackup = false; // do not backup if a window is closed that does not cause quitting of the application @@ -194,7 +183,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex break; case ShutdownReason.LOAD: - if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.configuredHotExit === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { + if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.filesConfigurationService.hotExitConfiguration === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured } else { doBackup = false; // do not backup because we are switching contexts @@ -239,18 +228,18 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // Handle untitled resources await Promise.all(untitledResources - .filter(untitled => this.untitledEditorService.exists(untitled)) - .map(async untitled => (await this.untitledEditorService.loadOrCreate({ resource: untitled })).backup())); + .filter(untitled => this.untitledTextEditorService.exists(untitled)) + .map(async untitled => (await this.untitledTextEditorService.loadOrCreate({ resource: untitled })).backup())); } private async confirmBeforeShutdown(): Promise { - const confirm = await this.confirmSave(); + const confirm = await this.fileDialogService.showSaveConfirm(this.getDirty()); // Save if (confirm === ConfirmResult.SAVE) { const result = await this.saveAll(true /* includeUntitled */, { skipSaveParticipants: true }); - if (result.results.some(r => !r.success)) { + if (result.results.some(r => r.error)) { return true; // veto if some saves failed } @@ -262,7 +251,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // Make sure to revert untitled so that they do not restore // see https://github.com/Microsoft/vscode/issues/29572 - this.untitledEditorService.revertAll(); + this.untitledTextEditorService.revertAll(); return this.noVeto({ cleanUpBackups: true }); } @@ -295,61 +284,6 @@ export abstract class AbstractTextFileService extends Disposable implements ITex await this.backupFileService.discardAllWorkspaceBackups(); } - protected onFilesConfigurationChange(configuration: IFilesConfiguration): void { - const wasAutoSaveEnabled = (this.getAutoSaveMode() !== AutoSaveMode.OFF); - - const autoSaveMode = configuration?.files?.autoSave || AutoSaveConfiguration.OFF; - this.autoSaveContext.set(autoSaveMode); - switch (autoSaveMode) { - case AutoSaveConfiguration.AFTER_DELAY: - this.configuredAutoSaveDelay = configuration?.files?.autoSaveDelay; - this.configuredAutoSaveOnFocusChange = false; - this.configuredAutoSaveOnWindowChange = false; - break; - - case AutoSaveConfiguration.ON_FOCUS_CHANGE: - this.configuredAutoSaveDelay = undefined; - this.configuredAutoSaveOnFocusChange = true; - this.configuredAutoSaveOnWindowChange = false; - break; - - case AutoSaveConfiguration.ON_WINDOW_CHANGE: - this.configuredAutoSaveDelay = undefined; - this.configuredAutoSaveOnFocusChange = false; - this.configuredAutoSaveOnWindowChange = true; - break; - - default: - this.configuredAutoSaveDelay = undefined; - this.configuredAutoSaveOnFocusChange = false; - this.configuredAutoSaveOnWindowChange = false; - break; - } - - // Emit as event - this._onAutoSaveConfigurationChange.fire(this.getAutoSaveConfiguration()); - - // save all dirty when enabling auto save - if (!wasAutoSaveEnabled && this.getAutoSaveMode() !== AutoSaveMode.OFF) { - this.saveAll(); - } - - // Check for change in files associations - const filesAssociation = configuration?.files?.associations; - if (!objects.equals(this.currentFilesAssociationConfig, filesAssociation)) { - this.currentFilesAssociationConfig = filesAssociation; - this._onFilesAssociationChange.fire(); - } - - // Hot exit - const hotExitMode = configuration?.files?.hotExit; - if (hotExitMode === HotExitConfiguration.OFF || hotExitMode === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { - this.configuredHotExit = hotExitMode; - } else { - this.configuredHotExit = HotExitConfiguration.ON_EXIT; - } - } - //#endregion //#region primitives (read, create, move, delete, update) @@ -409,6 +343,10 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } async create(resource: URI, value?: string | ITextSnapshot, options?: ICreateFileOptions): Promise { + + // before event + await this._onWillRunOperation.fireAsync({ operation: FileOperation.CREATE, target: resource }, CancellationToken.None); + const stat = await this.doCreate(resource, value, options); // If we had an existing model for the given resource, load @@ -420,6 +358,9 @@ export abstract class AbstractTextFileService extends Disposable implements ITex await existingModel.revert(); } + // after event + this._onDidRunOperation.fire(new FileOperationDidRunEvent(FileOperation.CREATE, resource)); + return stat; } @@ -432,23 +373,37 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } async delete(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Promise { - const dirtyFiles = this.getDirty().filter(dirty => isEqualOrParent(dirty, resource)); + // before event + await this._onWillRunOperation.fireAsync({ operation: FileOperation.DELETE, target: resource }, CancellationToken.None); + + const dirtyFiles = this.getDirty().filter(dirty => isEqualOrParent(dirty, resource)); await this.revertAll(dirtyFiles, { soft: true }); - return this.fileService.del(resource, options); + await this.fileService.del(resource, options); + + // after event + this._onDidRunOperation.fire(new FileOperationDidRunEvent(FileOperation.DELETE, resource)); } async move(source: URI, target: URI, overwrite?: boolean): Promise { + return this.moveOrCopy(source, target, true, overwrite); + } - // await onWillMove event joiners - await this.notifyOnWillMove(source, target); + async copy(source: URI, target: URI, overwrite?: boolean): Promise { + return this.moveOrCopy(source, target, false, overwrite); + } + + private async moveOrCopy(source: URI, target: URI, move: boolean, overwrite?: boolean): Promise { + + // before event + await this._onWillRunOperation.fireAsync({ operation: move ? FileOperation.MOVE : FileOperation.COPY, target, source }, CancellationToken.None); // find all models that related to either source or target (can be many if resource is a folder) const sourceModels: ITextFileEditorModel[] = []; const conflictingModels: ITextFileEditorModel[] = []; for (const model of this.getFileModels()) { - const resource = model.getResource(); + const resource = model.resource; if (isEqualOrParent(resource, target, false /* do not ignorecase, see https://github.com/Microsoft/vscode/issues/56384 */)) { conflictingModels.push(model); @@ -464,7 +419,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex type ModelToRestore = { resource: URI; snapshot?: ITextSnapshot }; const modelsToRestore: ModelToRestore[] = []; for (const sourceModel of sourceModels) { - const sourceModelResource = sourceModel.getResource(); + const sourceModelResource = sourceModel.resource; // If the source is the actual model, just use target as new resource let modelToRestoreResource: URI; @@ -486,15 +441,19 @@ export abstract class AbstractTextFileService extends Disposable implements ITex modelsToRestore.push(modelToRestore); } - // in order to move, we need to soft revert all dirty models, + // in order to move and copy, we need to soft revert all dirty models, // both from the source as well as the target if any const dirtyModels = [...sourceModels, ...conflictingModels].filter(model => model.isDirty()); - await this.revertAll(dirtyModels.map(dirtyModel => dirtyModel.getResource()), { soft: true }); + await this.revertAll(dirtyModels.map(dirtyModel => dirtyModel.resource), { soft: true }); // now we can rename the source to target via file operation let stat: IFileStatWithMetadata; try { - stat = await this.fileService.move(source, target, overwrite); + if (move) { + stat = await this.fileService.move(source, target, overwrite); + } else { + stat = await this.fileService.copy(source, target, overwrite); + } } catch (error) { // in case of any error, ensure to set dirty flag back @@ -521,32 +480,17 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } })); + // after event + this._onDidRunOperation.fire(new FileOperationDidRunEvent(move ? FileOperation.MOVE : FileOperation.COPY, target, source)); + return stat; } - private async notifyOnWillMove(source: URI, target: URI): Promise { - const waitForPromises: Promise[] = []; - - // fire event - this._onWillMove.fire({ - oldResource: source, - newResource: target, - waitUntil(promise: Promise) { - waitForPromises.push(promise.then(undefined, errors.onUnexpectedError)); - } - }); - - // prevent async waitUntil-calls - Object.freeze(waitForPromises); - - await Promise.all(waitForPromises); - } - //#endregion //#region save/revert - async save(resource: URI, options?: ISaveOptions): Promise { + async save(resource: URI, options?: ITextFileSaveOptions): Promise { // Run a forced save if we detect the file is not dirty so that save participants can still run if (options?.force && this.fileService.canHandleResource(resource) && !this.isDirty(resource)) { @@ -560,39 +504,12 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } } - const result = await this.saveAll([resource], options); - - return result.results.length === 1 && !!result.results[0].success; + return !(await this.saveAll([resource], options)).results.some(result => result.error); } - async confirmSave(resources?: URI[]): Promise { - if (this.environmentService.isExtensionDevelopment) { - if (!this.environmentService.args['extension-development-confirm-save']) { - return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests) - } - } - - const resourcesToConfirm = this.getDirty(resources); - if (resourcesToConfirm.length === 0) { - return ConfirmResult.DONT_SAVE; - } - return promptSave(this.dialogService, resourcesToConfirm); - } - - async confirmOverwrite(resource: URI): Promise { - const confirm: IConfirmation = { - message: nls.localize('confirmOverwrite', "'{0}' already exists. Do you want to replace it?", basename(resource)), - detail: nls.localize('irreversible', "A file or folder with the same name already exists in the folder {0}. Replacing it will overwrite its current contents.", basename(dirname(resource))), - primaryButton: nls.localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"), - type: 'warning' - }; - - return (await this.dialogService.confirm(confirm)).confirmed; - } - - saveAll(includeUntitled?: boolean, options?: ISaveOptions): Promise; - saveAll(resources: URI[], options?: ISaveOptions): Promise; - saveAll(arg1?: boolean | URI[], options?: ISaveOptions): Promise { + saveAll(includeUntitled?: boolean, options?: ITextFileSaveOptions): Promise; + saveAll(resources: URI[], options?: ITextFileSaveOptions): Promise; + saveAll(arg1?: boolean | URI[], options?: ITextFileSaveOptions): Promise { // get all dirty let toSave: URI[] = []; @@ -616,7 +533,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return this.doSaveAll(filesToSave, untitledToSave, options); } - private async doSaveAll(fileResources: URI[], untitledResources: URI[], options?: ISaveOptions): Promise { + private async doSaveAll(fileResources: URI[], untitledResources: URI[], options?: ITextFileSaveOptions): Promise { // Handle files first that can just be saved const result = await this.doSaveAllFiles(fileResources, options); @@ -624,11 +541,11 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // Preflight for untitled to handle cancellation from the dialog const targetsForUntitled: URI[] = []; for (const untitled of untitledResources) { - if (this.untitledEditorService.exists(untitled)) { + if (this.untitledTextEditorService.exists(untitled)) { let targetUri: URI; // Untitled with associated file path don't need to prompt - if (this.untitledEditorService.hasAssociatedFilePath(untitled)) { + if (this.untitledTextEditorService.hasAssociatedFilePath(untitled)) { targetUri = toLocalResource(untitled, this.environmentService.configuration.remoteAuthority); } @@ -653,7 +570,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex result.results.push({ source: untitledResources[index], target: uri, - success: !!uri + error: !uri // the operation was canceled or failed, so mark as error }); })); @@ -721,7 +638,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return options; } - private async doSaveAllFiles(resources?: URI[], options: ISaveOptions = Object.create(null)): Promise { + private async doSaveAllFiles(resources?: URI[], options: ITextFileSaveOptions = Object.create(null)): Promise { const dirtyFileModels = this.getDirtyFileModels(Array.isArray(resources) ? resources : undefined /* Save All */) .filter(model => { if ((model.hasState(ModelState.CONFLICT) || model.hasState(ModelState.ERROR)) && (options.reason === SaveReason.AUTO || options.reason === SaveReason.FOCUS_CHANGE || options.reason === SaveReason.WINDOW_CHANGE)) { @@ -732,19 +649,20 @@ export abstract class AbstractTextFileService extends Disposable implements ITex }); const mapResourceToResult = new ResourceMap(); - dirtyFileModels.forEach(m => { - mapResourceToResult.set(m.getResource(), { - source: m.getResource() + dirtyFileModels.forEach(dirtyModel => { + mapResourceToResult.set(dirtyModel.resource, { + source: dirtyModel.resource }); }); await Promise.all(dirtyFileModels.map(async model => { await model.save(options); - if (!model.isDirty()) { - const result = mapResourceToResult.get(model.getResource()); + // If model is still dirty, mark the resulting operation as error + if (model.isDirty()) { + const result = mapResourceToResult.get(model.resource); if (result) { - result.success = true; + result.error = true; } } })); @@ -769,7 +687,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return this.getFileModels(resources).filter(model => model.isDirty()); } - async saveAs(resource: URI, targetResource?: URI, options?: ISaveOptions): Promise { + async saveAs(resource: URI, targetResource?: URI, options?: ITextFileSaveOptions): Promise { // Get to target resource if (!targetResource) { @@ -796,14 +714,14 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return this.doSaveAs(resource, targetResource, options); } - private async doSaveAs(resource: URI, target: URI, options?: ISaveOptions): Promise { + private async doSaveAs(resource: URI, target: URI, options?: ITextFileSaveOptions): Promise { // Retrieve text model from provided resource if any - let model: ITextFileEditorModel | UntitledEditorModel | undefined; + let model: ITextFileEditorModel | UntitledTextEditorModel | undefined; if (this.fileService.canHandleResource(resource)) { model = this._models.get(resource); - } else if (resource.scheme === Schemas.untitled && this.untitledEditorService.exists(resource)) { - model = await this.untitledEditorService.loadOrCreate({ resource }); + } else if (resource.scheme === Schemas.untitled && this.untitledTextEditorService.exists(resource)) { + model = await this.untitledTextEditorService.loadOrCreate({ resource }); } // We have a model: Use it (can be null e.g. if this file is binary and not a text file or was never opened before) @@ -830,7 +748,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return target; } - private async doSaveTextFileAs(sourceModel: ITextFileEditorModel | UntitledEditorModel, resource: URI, target: URI, options?: ISaveOptions): Promise { + private async doSaveTextFileAs(sourceModel: ITextFileEditorModel | UntitledTextEditorModel, resource: URI, target: URI, options?: ITextFileSaveOptions): Promise { // Prefer an existing model if it is already loaded for the given target resource let targetExists: boolean = false; @@ -858,7 +776,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // path. This can happen if the file was created after the untitled file was opened. // See https://github.com/Microsoft/vscode/issues/67946 let write: boolean; - if (sourceModel instanceof UntitledEditorModel && sourceModel.hasAssociatedFilePath && targetExists && isEqual(target, toLocalResource(sourceModel.getResource(), this.environmentService.configuration.remoteAuthority))) { + if (sourceModel instanceof UntitledTextEditorModel && sourceModel.hasAssociatedFilePath && targetExists && isEqual(target, toLocalResource(sourceModel.resource, this.environmentService.configuration.remoteAuthority))) { write = await this.confirmOverwrite(target); } else { write = true; @@ -900,8 +818,19 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } } + private async confirmOverwrite(resource: URI): Promise { + const confirm: IConfirmation = { + message: nls.localize('confirmOverwrite', "'{0}' already exists. Do you want to replace it?", basename(resource)), + detail: nls.localize('irreversible', "A file or folder with the name '{0}' already exists in the folder '{1}'. Replacing it will overwrite its current contents.", basename(resource), basename(dirname(resource))), + primaryButton: nls.localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"), + type: 'warning' + }; + + return (await this.dialogService.confirm(confirm)).confirmed; + } + private suggestFileName(untitledResource: URI): URI { - const untitledFileName = this.untitledEditorService.suggestFileName(untitledResource); + const untitledFileName = this.untitledTextEditorService.suggestFileName(untitledResource); const remoteAuthority = this.environmentService.configuration.remoteAuthority; const schemeFilter = remoteAuthority ? Schemas.vscodeRemote : Schemas.file; @@ -920,9 +849,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } async revert(resource: URI, options?: IRevertOptions): Promise { - const result = await this.revertAll([resource], options); - - return result.results.length === 1 && !!result.results[0].success; + return !(await this.revertAll([resource], options)).results.some(result => result.error); } async revertAll(resources?: URI[], options?: IRevertOptions): Promise { @@ -931,8 +858,8 @@ export abstract class AbstractTextFileService extends Disposable implements ITex const revertOperationResult = await this.doRevertAllFiles(resources, options); // Revert untitled - const untitledReverted = this.untitledEditorService.revertAll(resources); - untitledReverted.forEach(untitled => revertOperationResult.results.push({ source: untitled, success: true })); + const untitledReverted = this.untitledTextEditorService.revertAll(resources); + untitledReverted.forEach(untitled => revertOperationResult.results.push({ source: untitled })); return revertOperationResult; } @@ -941,30 +868,28 @@ export abstract class AbstractTextFileService extends Disposable implements ITex const fileModels = options?.force ? this.getFileModels(resources) : this.getDirtyFileModels(resources); const mapResourceToResult = new ResourceMap(); - fileModels.forEach(m => { - mapResourceToResult.set(m.getResource(), { - source: m.getResource() + fileModels.forEach(fileModel => { + mapResourceToResult.set(fileModel.resource, { + source: fileModel.resource }); }); await Promise.all(fileModels.map(async model => { try { - await model.revert(options?.soft); + await model.revert(options); - if (!model.isDirty()) { - const result = mapResourceToResult.get(model.getResource()); + // If model is still dirty, mark the resulting operation as error + if (model.isDirty()) { + const result = mapResourceToResult.get(model.resource); if (result) { - result.success = true; + result.error = true; } } } catch (error) { - // FileNotFound means the file got deleted meanwhile, so still record as successful revert + // FileNotFound means the file got deleted meanwhile, so ignore it if ((error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND) { - const result = mapResourceToResult.get(model.getResource()); - if (result) { - result.success = true; - } + return; } // Otherwise bubble up the error @@ -980,10 +905,10 @@ export abstract class AbstractTextFileService extends Disposable implements ITex getDirty(resources?: URI[]): URI[] { // Collect files - const dirty = this.getDirtyFileModels(resources).map(m => m.getResource()); + const dirty = this.getDirtyFileModels(resources).map(dirtyFileModel => dirtyFileModel.resource); // Add untitled ones - dirty.push(...this.untitledEditorService.getDirty(resources)); + dirty.push(...this.untitledTextEditorService.getDirty(resources)); return dirty; } @@ -996,39 +921,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } // Check for dirty untitled - return this.untitledEditorService.getDirty().some(dirty => !resource || dirty.toString() === resource.toString()); - } - - //#endregion - - //#region config - - getAutoSaveMode(): AutoSaveMode { - if (this.configuredAutoSaveOnFocusChange) { - return AutoSaveMode.ON_FOCUS_CHANGE; - } - - if (this.configuredAutoSaveOnWindowChange) { - return AutoSaveMode.ON_WINDOW_CHANGE; - } - - if (this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0) { - return this.configuredAutoSaveDelay <= 1000 ? AutoSaveMode.AFTER_SHORT_DELAY : AutoSaveMode.AFTER_LONG_DELAY; - } - - return AutoSaveMode.OFF; - } - - getAutoSaveConfiguration(): IAutoSaveConfiguration { - return { - autoSaveDelay: this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0 ? this.configuredAutoSaveDelay : undefined, - autoSaveFocusChange: !!this.configuredAutoSaveOnFocusChange, - autoSaveApplicationChange: !!this.configuredAutoSaveOnWindowChange - }; - } - - get isHotExitEnabled(): boolean { - return !this.environmentService.isExtensionDevelopment && this.configuredHotExit !== HotExitConfiguration.OFF; + return this.untitledTextEditorService.getDirty().some(dirty => !resource || dirty.toString() === resource.toString()); } //#endregion @@ -1041,26 +934,3 @@ export abstract class AbstractTextFileService extends Disposable implements ITex super.dispose(); } } - -export async function promptSave(dialogService: IDialogService, resourcesToConfirm: readonly URI[]) { - const message = resourcesToConfirm.length === 1 - ? nls.localize('saveChangesMessage', "Do you want to save the changes you made to {0}?", basename(resourcesToConfirm[0])) - : getConfirmMessage(nls.localize('saveChangesMessages', "Do you want to save the changes to the following {0} files?", resourcesToConfirm.length), resourcesToConfirm); - - const buttons: string[] = [ - resourcesToConfirm.length > 1 ? nls.localize({ key: 'saveAll', comment: ['&& denotes a mnemonic'] }, "&&Save All") : nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save"), - nls.localize({ key: 'dontSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save"), - nls.localize('cancel', "Cancel") - ]; - - const { choice } = await dialogService.show(Severity.Warning, message, buttons, { - cancelId: 2, - detail: nls.localize('saveChangesDetail', "Your changes will be lost if you don't save them.") - }); - - switch (choice) { - case 0: return ConfirmResult.SAVE; - case 1: return ConfirmResult.DONT_SAVE; - default: return ConfirmResult.CANCEL; - } -} diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index c4191ed4ed..22fedc32e0 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -4,18 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { guessMimeTypes } from 'vs/base/common/mime'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { URI } from 'vs/base/common/uri'; import { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ITextFileService, IAutoSaveConfiguration, ModelState, ITextFileEditorModel, ISaveOptions, ISaveErrorHandler, ISaveParticipant, StateChange, SaveReason, ITextFileStreamContent, ILoadOptions, LoadReason, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; -import { EncodingMode } from 'vs/workbench/common/editor'; +import { ITextFileService, ModelState, ITextFileEditorModel, ISaveErrorHandler, ISaveParticipant, StateChange, ITextFileStreamContent, ILoadOptions, LoadReason, IResolvedTextFileEditorModel, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; +import { EncodingMode, IRevertOptions, SaveReason } from 'vs/workbench/common/editor'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { IFileService, FileOperationError, FileOperationResult, CONTENT_CHANGE_EVENT_BUFFER_DELAY, FileChangesEvent, FileChangeType, IFileStatWithMetadata, ETAG_DISABLED } from 'vs/platform/files/common/files'; +import { IFileService, FileOperationError, FileOperationResult, CONTENT_CHANGE_EVENT_BUFFER_DELAY, FileChangesEvent, FileChangeType, IFileStatWithMetadata, ETAG_DISABLED, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -29,9 +29,12 @@ import { ILogService } from 'vs/platform/log/common/log'; import { isEqual, isEqualOrParent, extname, basename, joinPath } from 'vs/base/common/resources'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Schemas } from 'vs/base/common/network'; +import { IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IFilesConfigurationService, IAutoSaveConfiguration } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; export interface IBackupMetaData { mtime: number; + ctime: number; size: number; etag: string; orphaned: boolean; @@ -69,11 +72,16 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private static saveParticipant: ISaveParticipant | null; static setSaveParticipant(handler: ISaveParticipant | null): void { TextFileEditorModel.saveParticipant = handler; } - private readonly _onDidContentChange: Emitter = this._register(new Emitter()); - readonly onDidContentChange: Event = this._onDidContentChange.event; + private readonly _onDidContentChange = this._register(new Emitter()); + readonly onDidContentChange = this._onDidContentChange.event; - private readonly _onDidStateChange: Emitter = this._register(new Emitter()); - readonly onDidStateChange: Event = this._onDidStateChange.event; + private readonly _onDidStateChange = this._register(new Emitter()); + readonly onDidStateChange = this._onDidStateChange.event; + + private readonly _onDidChangeDirty = this._register(new Emitter()); + readonly onDidChangeDirty = this._onDidChangeDirty.event; + + readonly capabilities = WorkingCopyCapabilities.AutoSave; private contentEncoding: string | undefined; // encoding as reported from disk @@ -100,7 +108,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private disposed = false; constructor( - private resource: URI, + public readonly resource: URI, private preferredEncoding: string | undefined, // encoding as chosen by the user private preferredMode: string | undefined, // mode as chosen by the user @INotificationService private readonly notificationService: INotificationService, @@ -113,19 +121,24 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil @IBackupFileService private readonly backupFileService: IBackupFileService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @ILogService private readonly logService: ILogService + @ILogService private readonly logService: ILogService, + @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService ) { super(modelService, modeService); - this.updateAutoSaveConfiguration(textFileService.getAutoSaveConfiguration()); + this.updateAutoSaveConfiguration(filesConfigurationService.getAutoSaveConfiguration()); + + // Make known to working copy service + this._register(this.workingCopyService.registerWorkingCopy(this)); this.registerListeners(); } private registerListeners(): void { this._register(this.fileService.onFileChanges(e => this.onFileChanges(e))); - this._register(this.textFileService.onAutoSaveConfigurationChange(config => this.updateAutoSaveConfiguration(config))); - this._register(this.textFileService.onFilesAssociationChange(e => this.onFilesAssociationChange())); + this._register(this.filesConfigurationService.onAutoSaveConfigurationChange(config => this.updateAutoSaveConfiguration(config))); + this._register(this.filesConfigurationService.onFilesAssociationChange(e => this.onFilesAssociationChange())); this._register(this.onDidStateChange(e => this.onStateChange(e))); } @@ -224,6 +237,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil if (isEqual(target, this.resource) && this.lastResolvedFileStat) { meta = { mtime: this.lastResolvedFileStat.mtime, + ctime: this.lastResolvedFileStat.ctime, size: this.lastResolvedFileStat.size, etag: this.lastResolvedFileStat.etag, orphaned: this.inOrphanMode @@ -238,19 +252,21 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return this.backupFileService.hasBackupSync(this.resource, this.versionId); } - async revert(soft?: boolean): Promise { + async revert(options?: IRevertOptions): Promise { if (!this.isResolved()) { - return; + return false; } // Cancel any running auto-save this.autoSaveDisposable.clear(); // Unset flags + const wasDirty = this.dirty; const undo = this.setDirty(false); // Force read from disk unless reverting soft - if (!soft) { + const softUndo = options?.soft; + if (!softUndo) { try { await this.load({ forceReadFromDisk: true }); } catch (error) { @@ -264,6 +280,13 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Emit file change event this._onDidStateChange.fire(StateChange.REVERTED); + + // Emit dirty change event + if (wasDirty) { + this._onDidChangeDirty.fire(); + } + + return true; } async load(options?: ILoadOptions): Promise { @@ -313,11 +336,11 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil resource: this.resource, name: basename(this.resource), mtime: resolvedBackup.meta ? resolvedBackup.meta.mtime : Date.now(), + ctime: resolvedBackup.meta ? resolvedBackup.meta.ctime : Date.now(), size: resolvedBackup.meta ? resolvedBackup.meta.size : 0, etag: resolvedBackup.meta ? resolvedBackup.meta.etag : ETAG_DISABLED, // etag disabled if unknown! value: resolvedBackup.value, - encoding: this.textFileService.encoding.getPreferredWriteEncoding(this.resource, this.preferredEncoding).encoding, - isReadonly: false + encoding: this.textFileService.encoding.getPreferredWriteEncoding(this.resource, this.preferredEncoding).encoding }, options, true /* from backup */); // Restore orphaned flag based on state @@ -397,11 +420,12 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil resource: this.resource, name: content.name, mtime: content.mtime, + ctime: content.ctime, size: content.size, etag: content.etag, + isFile: true, isDirectory: false, - isSymbolicLink: false, - isReadonly: content.isReadonly + isSymbolicLink: false }); // Keep the original encoding to not loose it when saving @@ -523,6 +547,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Emit event if (wasDirty) { this._onDidStateChange.fire(StateChange.REVERTED); + this._onDidChangeDirty.fire(); } return; @@ -563,6 +588,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Emit as Event if we turned dirty if (!wasDirty) { this._onDidStateChange.fire(StateChange.DIRTY); + this._onDidChangeDirty.fire(); } } @@ -587,9 +613,9 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.autoSaveDisposable.value = toDisposable(() => clearTimeout(handle)); } - async save(options: ISaveOptions = Object.create(null)): Promise { + async save(options: ITextFileSaveOptions = Object.create(null)): Promise { if (!this.isResolved()) { - return; + return false; } this.logService.trace('save() - enter', this.resource); @@ -597,10 +623,12 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Cancel any currently running auto saves to make this the one that succeeds this.autoSaveDisposable.clear(); - return this.doSave(this.versionId, options); + await this.doSave(this.versionId, options); + + return true; } - private doSave(versionId: number, options: ISaveOptions): Promise { + private doSave(versionId: number, options: ITextFileSaveOptions): Promise { if (isUndefinedOrNull(options.reason)) { options.reason = SaveReason.EXPLICIT; } @@ -734,8 +762,9 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Cancel any content change event promises as they are no longer valid this.contentChangeEventScheduler.cancel(); - // Emit File Saved Event + // Emit Events this._onDidStateChange.fire(StateChange.SAVED); + this._onDidChangeDirty.fire(); // Telemetry const settingsType = this.getTypeIfSettings(); @@ -1001,17 +1030,13 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } isReadonly(): boolean { - return !!(this.lastResolvedFileStat && this.lastResolvedFileStat.isReadonly); + return this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly); } isDisposed(): boolean { return this.disposed; } - getResource(): URI { - return this.resource; - } - getStat(): IFileStatWithMetadata | undefined { return this.lastResolvedFileStat; } @@ -1122,6 +1147,6 @@ class DefaultSaveErrorHandler implements ISaveErrorHandler { constructor(@INotificationService private readonly notificationService: INotificationService) { } onSaveError(error: Error, model: TextFileEditorModel): void { - this.notificationService.error(nls.localize('genericSaveError', "Failed to save '{0}': {1}", basename(model.getResource()), toErrorMessage(error, false))); + this.notificationService.error(nls.localize('genericSaveError', "Failed to save '{0}': {1}", basename(model.resource), toErrorMessage(error, false))); } } diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index 669f85a6c7..6a68f44bac 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -282,15 +282,15 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE this.mapResourceToModel.clear(); this.mapResourceToPendingModelLoaders.clear(); - // dispose dispose listeners + // dispose the dispose listeners this.mapResourceToDisposeListener.forEach(l => l.dispose()); this.mapResourceToDisposeListener.clear(); - // dispose state change listeners + // dispose the state change listeners this.mapResourceToStateChangeListener.forEach(l => l.dispose()); this.mapResourceToStateChangeListener.clear(); - // dispose model content change listeners + // dispose the model content change listeners this.mapResourceToModelContentChangeListener.forEach(l => l.dispose()); this.mapResourceToModelContentChangeListener.clear(); } @@ -304,7 +304,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE return; // already disposed } - if (this.mapResourceToPendingModelLoaders.has(model.getResource())) { + if (this.mapResourceToPendingModelLoaders.has(model.resource)) { return; // not yet loaded } diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 348721f868..625657db41 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -4,17 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { Event } from 'vs/base/common/event'; +import { Event, IWaitUntil } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { IEncodingSupport, ConfirmResult, IRevertOptions, IModeSupport } from 'vs/workbench/common/editor'; -import { IBaseStatWithMetadata, IFileStatWithMetadata, IReadFileOptions, IWriteFileOptions, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; +import { IEncodingSupport, IModeSupport, ISaveOptions, IRevertOptions, SaveReason } from 'vs/workbench/common/editor'; +import { IBaseStatWithMetadata, IFileStatWithMetadata, IReadFileOptions, IWriteFileOptions, FileOperationError, FileOperationResult, FileOperation } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ITextEditorModel } from 'vs/editor/common/services/resolverService'; import { ITextBufferFactory, ITextModel, ITextSnapshot } from 'vs/editor/common/model'; -import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { isNative } from 'vs/base/common/platform'; +import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; export const ITextFileService = createDecorator('textFileService'); @@ -22,13 +22,15 @@ export interface ITextFileService extends IDisposable { _serviceBrand: undefined; - readonly onWillMove: Event; + /** + * An event that is fired before attempting a certain file operation. + */ + readonly onWillRunOperation: Event; - readonly onAutoSaveConfigurationChange: Event; - - readonly onFilesAssociationChange: Event; - - readonly isHotExitEnabled: boolean; + /** + * An event that is fired after a file operation has been performed. + */ + readonly onDidRunOperation: Event; /** * Access to the manager of text file editor models providing further methods to work with them. @@ -129,22 +131,24 @@ export interface ITextFileService extends IDisposable { move(source: URI, target: URI, overwrite?: boolean): Promise; /** - * Brings up the confirm dialog to either save, don't save or cancel. - * - * @param resources the resources of the files to ask for confirmation or null if - * confirming for all dirty resources. + * Copy a file. If the file is dirty, its contents will be preserved and restored. */ - confirmSave(resources?: URI[]): Promise; + copy(source: URI, target: URI, overwrite?: boolean): Promise; +} - /** - * Convenient fast access to the current auto save mode. - */ - getAutoSaveMode(): AutoSaveMode; +export interface FileOperationWillRunEvent extends IWaitUntil { + operation: FileOperation; + target: URI; + source?: URI; +} - /** - * Convenient fast access to the raw configured auto save settings. - */ - getAutoSaveConfiguration(): IAutoSaveConfiguration; +export class FileOperationDidRunEvent { + + constructor( + readonly operation: FileOperation, + readonly target: URI, + readonly source?: URI | undefined + ) { } } export interface IReadTextFileOptions extends IReadFileOptions { @@ -291,7 +295,7 @@ export class TextFileModelChangeEvent { private _resource: URI; constructor(model: ITextFileEditorModel, private _kind: StateChange) { - this._resource = model.getResource(); + this._resource = model.resource; } get resource(): URI { @@ -303,8 +307,6 @@ export class TextFileModelChangeEvent { } } -export const AutoSaveContext = new RawContextKey('config.files.autoSave', undefined); - export interface ITextFileOperationResult { results: IResult[]; } @@ -312,28 +314,7 @@ export interface ITextFileOperationResult { export interface IResult { source: URI; target?: URI; - success?: boolean; -} - -export interface IAutoSaveConfiguration { - autoSaveDelay?: number; - autoSaveFocusChange: boolean; - autoSaveApplicationChange: boolean; -} - -export const enum AutoSaveMode { - OFF, - AFTER_SHORT_DELAY, - AFTER_LONG_DELAY, - ON_FOCUS_CHANGE, - ON_WINDOW_CHANGE -} - -export const enum SaveReason { - EXPLICIT = 1, - AUTO = 2, - FOCUS_CHANGE = 3, - WINDOW_CHANGE = 4 + error?: boolean; } export const enum LoadReason { @@ -427,14 +408,10 @@ export interface ITextFileEditorModelManager { disposeModel(model: ITextFileEditorModel): void; } -export interface ISaveOptions { - force?: boolean; - reason?: SaveReason; +export interface ITextFileSaveOptions extends ISaveOptions { overwriteReadonly?: boolean; overwriteEncoding?: boolean; - skipSaveParticipants?: boolean; writeElevated?: boolean; - availableFileSystems?: readonly string[]; } export interface ILoadOptions { @@ -455,22 +432,20 @@ export interface ILoadOptions { reason?: LoadReason; } -export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport, IModeSupport { +export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport, IModeSupport, IWorkingCopy { readonly onDidContentChange: Event; readonly onDidStateChange: Event; - getResource(): URI; - hasState(state: ModelState): boolean; updatePreferredEncoding(encoding: string | undefined): void; - save(options?: ISaveOptions): Promise; + save(options?: ITextFileSaveOptions): Promise; load(options?: ILoadOptions): Promise; - revert(soft?: boolean): Promise; + revert(options?: IRevertOptions): Promise; backup(target?: URI): Promise; @@ -492,13 +467,6 @@ export interface IResolvedTextFileEditorModel extends ITextFileEditorModel { createSnapshot(): ITextSnapshot; } -export interface IWillMoveEvent { - oldResource: URI; - newResource: URI; - - waitUntil(p: Promise): void; -} - /** * Helper method to convert a snapshot into its full string form. */ diff --git a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts index d063ddf29b..4fa52a8681 100644 --- a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts @@ -14,7 +14,7 @@ import { Schemas } from 'vs/base/common/network'; import { exists, stat, chmod, rimraf, MAX_FILE_SIZE, MAX_HEAP_SIZE } from 'vs/base/node/pfs'; import { join, dirname } from 'vs/base/common/path'; import { isMacintosh } from 'vs/base/common/platform'; -import product from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { UTF8, UTF8_with_bom, UTF16be, UTF16le, encodingExists, encodeStream, UTF8_BOM, toDecodeStream, IDecodeStreamResult, detectEncodingByBOMFromBuffer, isUTFEncoding } from 'vs/base/node/encoding'; @@ -22,49 +22,49 @@ import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; import { joinPath, extname, isEqualOrParent } from 'vs/base/common/resources'; import { Disposable } from 'vs/base/common/lifecycle'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { VSBufferReadable } from 'vs/base/common/buffer'; +import { VSBufferReadable, bufferToStream } from 'vs/base/common/buffer'; import { Readable } from 'stream'; import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; import { ITextSnapshot } from 'vs/editor/common/model'; import { nodeReadableToString, streamToNodeReadable, nodeStreamToVSBufferReadable } from 'vs/base/node/stream'; import { IElectronService } from 'vs/platform/electron/node/electron'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { assign } from 'vs/base/common/objects'; +import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; export class NativeTextFileService extends AbstractTextFileService { constructor( @IWorkspaceContextService contextService: IWorkspaceContextService, @IFileService fileService: IFileService, - @IUntitledEditorService untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService untitledTextEditorService: IUntitledTextEditorService, @ILifecycleService lifecycleService: ILifecycleService, @IInstantiationService instantiationService: IInstantiationService, - @IConfigurationService configurationService: IConfigurationService, @IModeService modeService: IModeService, @IModelService modelService: IModelService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @INotificationService notificationService: INotificationService, @IBackupFileService backupFileService: IBackupFileService, @IHistoryService historyService: IHistoryService, - @IContextKeyService contextKeyService: IContextKeyService, @IDialogService dialogService: IDialogService, @IFileDialogService fileDialogService: IFileDialogService, @IEditorService editorService: IEditorService, @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, - @IElectronService private readonly electronService: IElectronService + @IElectronService private readonly electronService: IElectronService, + @IProductService private readonly productService: IProductService, + @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService ) { - super(contextService, fileService, untitledEditorService, lifecycleService, instantiationService, configurationService, modeService, modelService, environmentService, notificationService, backupFileService, historyService, contextKeyService, dialogService, fileDialogService, editorService, textResourceConfigurationService); + super(contextService, fileService, untitledTextEditorService, lifecycleService, instantiationService, modeService, modelService, environmentService, notificationService, backupFileService, historyService, dialogService, fileDialogService, editorService, textResourceConfigurationService, filesConfigurationService); } private _encoding: EncodingOracle | undefined; @@ -77,7 +77,16 @@ export class NativeTextFileService extends AbstractTextFileService { } async read(resource: URI, options?: IReadTextFileOptions): Promise { - const [bufferStream, decoder] = await this.doRead(resource, options); + const [bufferStream, decoder] = await this.doRead(resource, + assign({ + // optimization: since we know that the caller does not + // care about buffering, we indicate this to the reader. + // this reduces all the overhead the buffered reading + // has (open, read, close) if the provider supports + // unbuffered reading. + preferUnbuffered: true + }, options || Object.create(null)) + ); return { ...bufferStream, @@ -96,13 +105,22 @@ export class NativeTextFileService extends AbstractTextFileService { }; } - private async doRead(resource: URI, options?: IReadTextFileOptions): Promise<[IFileStreamContent, IDecodeStreamResult]> { + private async doRead(resource: URI, options?: IReadTextFileOptions & { preferUnbuffered?: boolean }): Promise<[IFileStreamContent, IDecodeStreamResult]> { // ensure limits options = this.ensureLimits(options); - // read stream raw - const bufferStream = await this.fileService.readFileStream(resource, options); + // read stream raw (either buffered or unbuffered) + let bufferStream: IFileStreamContent; + if (options.preferUnbuffered) { + const content = await this.fileService.readFile(resource, options); + bufferStream = { + ...content, + value: bufferToStream(content.value) + }; + } else { + bufferStream = await this.fileService.readFileStream(resource, options); + } // read through encoding library const decoder = await toDecodeStream(streamToNodeReadable(bufferStream.value), { @@ -272,8 +290,8 @@ export class NativeTextFileService extends AbstractTextFileService { return new Promise((resolve, reject) => { const promptOptions = { - name: this.environmentService.appNameLong.replace('-', ''), - icns: (isMacintosh && this.environmentService.isBuilt) ? join(dirname(this.environmentService.appRoot), `${product.nameShort}.icns`) : undefined + name: this.productService.nameLong.replace('-', ''), + icns: (isMacintosh && this.environmentService.isBuilt) ? join(dirname(this.environmentService.appRoot), `${this.productService.nameShort}.icns`) : undefined }; const sudoCommand: string[] = [`"${this.environmentService.cliPath}"`]; @@ -357,7 +375,7 @@ export class EncodingOracle extends Disposable implements IResourceEncodings { if (!overwriteEncoding && encoding === UTF8) { try { const buffer = (await this.fileService.readFile(resource, { length: UTF8_BOM.length })).value; - if (detectEncodingByBOMFromBuffer(buffer, buffer.byteLength) === UTF8) { + if (detectEncodingByBOMFromBuffer(buffer, buffer.byteLength) === UTF8_with_bom) { return { encoding, addBOM: true }; } } catch (error) { @@ -382,7 +400,7 @@ export class EncodingOracle extends Disposable implements IResourceEncodings { // Encoding passed in as option if (options?.encoding) { - if (detectedEncoding === UTF8 && options.encoding === UTF8) { + if (detectedEncoding === UTF8_with_bom && options.encoding === UTF8) { preferredEncoding = UTF8_with_bom; // indicate the file has BOM if we are to resolve with UTF 8 } else { preferredEncoding = options.encoding; // give passed in encoding highest priority @@ -391,11 +409,7 @@ export class EncodingOracle extends Disposable implements IResourceEncodings { // Encoding detected else if (detectedEncoding) { - if (detectedEncoding === UTF8) { - preferredEncoding = UTF8_with_bom; // if we detected UTF-8, it can only be because of a BOM - } else { - preferredEncoding = detectedEncoding; - } + preferredEncoding = detectedEncoding; } // Encoding configured diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts index bad9a2ebca..03bef53a6d 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts @@ -16,9 +16,15 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { timeout } from 'vs/base/common/async'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; import { assertIsDefined } from 'vs/base/common/types'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; class ServiceAccessor { - constructor(@ITextFileService public textFileService: TestTextFileService, @IModelService public modelService: IModelService, @IFileService public fileService: TestFileService) { + constructor( + @ITextFileService public readonly textFileService: TestTextFileService, + @IModelService public readonly modelService: IModelService, + @IFileService public readonly fileService: TestFileService, + @IWorkingCopyService public readonly workingCopyService: IWorkingCopyService + ) { } } @@ -51,10 +57,15 @@ suite('Files - TextFileEditorModel', () => { await model.load(); + assert.equal(accessor.workingCopyService.dirtyCount, 0); + model.textEditorModel!.setValue('bar'); assert.ok(getLastModifiedTime(model) <= Date.now()); assert.ok(model.hasState(ModelState.DIRTY)); + assert.equal(accessor.workingCopyService.dirtyCount, 1); + assert.equal(accessor.workingCopyService.isDirty(model.resource), true); + let savedEvent = false; model.onDidStateChange(e => { if (e === StateChange.SAVED) { @@ -62,6 +73,13 @@ suite('Files - TextFileEditorModel', () => { } }); + let workingCopyEvent = false; + accessor.workingCopyService.onDidChangeDirty(e => { + if (e.resource.toString() === model.resource.toString()) { + workingCopyEvent = true; + } + }); + const pendingSave = model.save(); assert.ok(model.hasState(ModelState.PENDING_SAVE)); @@ -71,9 +89,13 @@ suite('Files - TextFileEditorModel', () => { assert.ok(model.hasState(ModelState.SAVED)); assert.ok(!model.isDirty()); assert.ok(savedEvent); + assert.ok(workingCopyEvent); + + assert.equal(accessor.workingCopyService.dirtyCount, 0); + assert.equal(accessor.workingCopyService.isDirty(model.resource), false); model.dispose(); - assert.ok(!accessor.modelService.getModel(model.getResource())); + assert.ok(!accessor.modelService.getModel(model.resource)); }); test('save - touching also emits saved event', async function () { @@ -88,12 +110,20 @@ suite('Files - TextFileEditorModel', () => { } }); + let workingCopyEvent = false; + accessor.workingCopyService.onDidChangeDirty(e => { + if (e.resource.toString() === model.resource.toString()) { + workingCopyEvent = true; + } + }); + await model.save({ force: true }); assert.ok(savedEvent); + assert.ok(!workingCopyEvent); model.dispose(); - assert.ok(!accessor.modelService.getModel(model.getResource())); + assert.ok(!accessor.modelService.getModel(model.resource)); }); test('setEncoding - encode', function () { @@ -132,7 +162,7 @@ suite('Files - TextFileEditorModel', () => { assert.equal(model.textEditorModel!.getModeId(), mode); model.dispose(); - assert.ok(!accessor.modelService.getModel(model.getResource())); + assert.ok(!accessor.modelService.getModel(model.resource)); }); test('disposes when underlying model is destroyed', async function () { @@ -155,7 +185,7 @@ suite('Files - TextFileEditorModel', () => { await model.load(); assert.ok(model.isResolved()); model.dispose(); - assert.ok(!accessor.modelService.getModel(model.getResource())); + assert.ok(!accessor.modelService.getModel(model.resource)); }); test('Load returns dirty model as long as model is dirty', async function () { @@ -182,14 +212,29 @@ suite('Files - TextFileEditorModel', () => { } }); + let workingCopyEvent = false; + accessor.workingCopyService.onDidChangeDirty(e => { + if (e.resource.toString() === model.resource.toString()) { + workingCopyEvent = true; + } + }); + await model.load(); model.textEditorModel!.setValue('foo'); assert.ok(model.isDirty()); + assert.equal(accessor.workingCopyService.dirtyCount, 1); + assert.equal(accessor.workingCopyService.isDirty(model.resource), true); + await model.revert(); assert.ok(!model.isDirty()); assert.equal(model.textEditorModel!.getValue(), 'Hello Html'); assert.equal(eventCounter, 1); + + assert.ok(workingCopyEvent); + assert.equal(accessor.workingCopyService.dirtyCount, 0); + assert.equal(accessor.workingCopyService.isDirty(model.resource), false); + model.dispose(); }); @@ -204,14 +249,29 @@ suite('Files - TextFileEditorModel', () => { } }); + let workingCopyEvent = false; + accessor.workingCopyService.onDidChangeDirty(e => { + if (e.resource.toString() === model.resource.toString()) { + workingCopyEvent = true; + } + }); + await model.load(); model.textEditorModel!.setValue('foo'); assert.ok(model.isDirty()); - await model.revert(true /* soft revert */); + assert.equal(accessor.workingCopyService.dirtyCount, 1); + assert.equal(accessor.workingCopyService.isDirty(model.resource), true); + + await model.revert({ soft: true }); assert.ok(!model.isDirty()); assert.equal(model.textEditorModel!.getValue(), 'foo'); assert.equal(eventCounter, 1); + + assert.ok(workingCopyEvent); + assert.equal(accessor.workingCopyService.dirtyCount, 0); + assert.equal(accessor.workingCopyService.isDirty(model.resource), false); + model.dispose(); }); @@ -223,6 +283,9 @@ suite('Files - TextFileEditorModel', () => { await model.load(); model.textEditorModel!.undo(); assert.ok(model.isDirty()); + + assert.equal(accessor.workingCopyService.dirtyCount, 1); + assert.equal(accessor.workingCopyService.isDirty(model.resource), true); }); test('Make Dirty', async function () { @@ -237,7 +300,7 @@ suite('Files - TextFileEditorModel', () => { model.textEditorModel!.setValue('foo'); assert.ok(model.isDirty()); - await model.revert(true /* soft revert */); + await model.revert({ soft: true }); assert.ok(!model.isDirty()); model.onDidStateChange(e => { @@ -246,9 +309,18 @@ suite('Files - TextFileEditorModel', () => { } }); + let workingCopyEvent = false; + accessor.workingCopyService.onDidChangeDirty(e => { + if (e.resource.toString() === model.resource.toString()) { + workingCopyEvent = true; + } + }); + model.makeDirty(); assert.ok(model.isDirty()); assert.equal(eventCounter, 1); + assert.ok(workingCopyEvent); + model.dispose(); }); @@ -343,7 +415,6 @@ suite('Files - TextFileEditorModel', () => { }); test('Save Participant, async participant', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); TextFileEditorModel.setSaveParticipant({ diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts index 07461d0992..db3a03da9b 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts @@ -273,7 +273,7 @@ suite('Files - TextFileEditorModelManager', () => { const model = await manager.loadOrCreate(resource, { encoding: 'utf8' }); model.dispose(); assert.ok(!manager.get(resource)); - assert.ok(!accessor.modelService.getModel(model.getResource())); + assert.ok(!accessor.modelService.getModel(model.resource)); manager.dispose(); }); @@ -286,7 +286,7 @@ suite('Files - TextFileEditorModelManager', () => { model.textEditorModel!.setValue('make dirty'); manager.disposeModel((model as TextFileEditorModel)); assert.ok(!model.isDisposed()); - model.revert(true); + model.revert({ soft: true }); manager.disposeModel((model as TextFileEditorModel)); assert.ok(model.isDisposed()); manager.dispose(); diff --git a/src/vs/workbench/services/textfile/test/textFileService.io.test.ts b/src/vs/workbench/services/textfile/test/textFileService.io.test.ts index e82195a408..dc748f0001 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.io.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.io.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { workbenchInstantiationService, TestTextFileService } from 'vs/workbench/test/workbenchTestServices'; import { ITextFileService, snapshotToString, TextFileOperationResult, TextFileOperationError } from 'vs/workbench/services/textfile/common/textfiles'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IFileService } from 'vs/platform/files/common/files'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { Schemas } from 'vs/base/common/network'; @@ -32,7 +32,7 @@ import { detectEncodingByBOM } from 'vs/base/test/node/encoding/encoding.test'; class ServiceAccessor { constructor( @ITextFileService public textFileService: TestTextFileService, - @IUntitledEditorService public untitledEditorService: IUntitledEditorService + @IUntitledTextEditorService public untitledTextEditorService: IUntitledTextEditorService ) { } } @@ -96,7 +96,7 @@ suite('Files - TextFileService i/o', () => { teardown(async () => { (accessor.textFileService.models).clear(); (accessor.textFileService.models).dispose(); - accessor.untitledEditorService.revertAll(); + accessor.untitledTextEditorService.revertAll(); disposables.clear(); @@ -172,7 +172,7 @@ suite('Files - TextFileService i/o', () => { assert.equal(await exists(resource.fsPath), true); const detectedEncoding = await detectEncodingByBOM(resource.fsPath); - assert.equal(detectedEncoding, UTF8); + assert.equal(detectedEncoding, UTF8_with_bom); }); test('create - UTF 8 BOM - content provided', async () => { @@ -183,7 +183,7 @@ suite('Files - TextFileService i/o', () => { assert.equal(await exists(resource.fsPath), true); const detectedEncoding = await detectEncodingByBOM(resource.fsPath); - assert.equal(detectedEncoding, UTF8); + assert.equal(detectedEncoding, UTF8_with_bom); }); test('create - UTF 8 BOM - empty content - snapshot', async () => { @@ -194,7 +194,7 @@ suite('Files - TextFileService i/o', () => { assert.equal(await exists(resource.fsPath), true); const detectedEncoding = await detectEncodingByBOM(resource.fsPath); - assert.equal(detectedEncoding, UTF8); + assert.equal(detectedEncoding, UTF8_with_bom); }); test('create - UTF 8 BOM - content provided - snapshot', async () => { @@ -205,7 +205,7 @@ suite('Files - TextFileService i/o', () => { assert.equal(await exists(resource.fsPath), true); const detectedEncoding = await detectEncodingByBOM(resource.fsPath); - assert.equal(detectedEncoding, UTF8); + assert.equal(detectedEncoding, UTF8_with_bom); }); test('write - use encoding (UTF 16 BE) - small content as string', async () => { @@ -325,12 +325,12 @@ suite('Files - TextFileService i/o', () => { await service.write(resource, content, { encoding: UTF8_with_bom }); detectedEncoding = await detectEncodingByBOM(resource.fsPath); - assert.equal(detectedEncoding, UTF8); + assert.equal(detectedEncoding, UTF8_with_bom); // ensure BOM preserved await service.write(resource, content, { encoding: UTF8 }); detectedEncoding = await detectEncodingByBOM(resource.fsPath); - assert.equal(detectedEncoding, UTF8); + assert.equal(detectedEncoding, UTF8_with_bom); // allow to remove BOM await service.write(resource, content, { encoding: UTF8, overwriteEncoding: true }); @@ -353,12 +353,12 @@ suite('Files - TextFileService i/o', () => { await service.write(resource, model.createSnapshot(), { encoding: UTF8_with_bom }); detectedEncoding = await detectEncodingByBOM(resource.fsPath); - assert.equal(detectedEncoding, UTF8); + assert.equal(detectedEncoding, UTF8_with_bom); // ensure BOM preserved await service.write(resource, model.createSnapshot(), { encoding: UTF8 }); detectedEncoding = await detectEncodingByBOM(resource.fsPath); - assert.equal(detectedEncoding, UTF8); + assert.equal(detectedEncoding, UTF8_with_bom); // allow to remove BOM await service.write(resource, model.createSnapshot(), { encoding: UTF8, overwriteEncoding: true }); @@ -375,11 +375,11 @@ suite('Files - TextFileService i/o', () => { const resource = URI.file(join(testDir, 'some_utf8_bom.txt')); let detectedEncoding = await detectEncodingByBOM(resource.fsPath); - assert.equal(detectedEncoding, UTF8); + assert.equal(detectedEncoding, UTF8_with_bom); await service.write(resource, 'Hello World'); detectedEncoding = await detectEncodingByBOM(resource.fsPath); - assert.equal(detectedEncoding, UTF8); + assert.equal(detectedEncoding, UTF8_with_bom); }); test('write - ensure BOM in empty file - content as string', async () => { @@ -388,7 +388,7 @@ suite('Files - TextFileService i/o', () => { await service.write(resource, '', { encoding: UTF8_with_bom }); let detectedEncoding = await detectEncodingByBOM(resource.fsPath); - assert.equal(detectedEncoding, UTF8); + assert.equal(detectedEncoding, UTF8_with_bom); }); test('write - ensure BOM in empty file - content as snapshot', async () => { @@ -397,7 +397,7 @@ suite('Files - TextFileService i/o', () => { await service.write(resource, TextModel.createFromString('').createSnapshot(), { encoding: UTF8_with_bom }); let detectedEncoding = await detectEncodingByBOM(resource.fsPath); - assert.equal(detectedEncoding, UTF8); + assert.equal(detectedEncoding, UTF8_with_bom); }); test('readStream - small text', async () => { diff --git a/src/vs/workbench/services/textfile/test/textFileService.test.ts b/src/vs/workbench/services/textfile/test/textFileService.test.ts index e8f21a04d4..b6728f7a9e 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.test.ts @@ -7,13 +7,12 @@ import * as sinon from 'sinon'; import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { ILifecycleService, BeforeShutdownEvent, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; -import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, TestContextService, TestFileService, TestElectronService } from 'vs/workbench/test/workbenchTestServices'; +import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, TestContextService, TestFileService, TestElectronService, TestFilesConfigurationService, TestFileDialogService } from 'vs/workbench/test/workbenchTestServices'; import { toResource } from 'vs/base/test/common/utils'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { ConfirmResult } from 'vs/workbench/common/editor'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { HotExitConfiguration, IFileService } from 'vs/platform/files/common/files'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { IWorkspaceContextService, Workspace } from 'vs/platform/workspace/common/workspace'; @@ -21,16 +20,20 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { Schemas } from 'vs/base/common/network'; import { IElectronService } from 'vs/platform/electron/node/electron'; +import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { IFileDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; class ServiceAccessor { constructor( @ILifecycleService public lifecycleService: TestLifecycleService, @ITextFileService public textFileService: TestTextFileService, - @IUntitledEditorService public untitledEditorService: IUntitledEditorService, + @IFilesConfigurationService public filesConfigurationService: TestFilesConfigurationService, + @IUntitledTextEditorService public untitledTextEditorService: IUntitledTextEditorService, @IWorkspaceContextService public contextService: TestContextService, @IModelService public modelService: ModelServiceImpl, @IFileService public fileService: TestFileService, - @IElectronService public electronService: TestElectronService + @IElectronService public electronService: TestElectronService, + @IFileDialogService public fileDialogService: TestFileDialogService ) { } } @@ -62,12 +65,12 @@ suite('Files - TextFileService', () => { } (accessor.textFileService.models).clear(); (accessor.textFileService.models).dispose(); - accessor.untitledEditorService.revertAll(); + accessor.untitledTextEditorService.revertAll(); }); test('confirm onWillShutdown - no veto', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.models).add(model.getResource(), model); + (accessor.textFileService.models).add(model.resource, model); const event = new BeforeShutdownEventImpl(); accessor.lifecycleService.fireWillShutdown(event); @@ -82,10 +85,10 @@ suite('Files - TextFileService', () => { test('confirm onWillShutdown - veto if user cancels', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.models).add(model.getResource(), model); + (accessor.textFileService.models).add(model.resource, model); const service = accessor.textFileService; - service.setConfirmResult(ConfirmResult.CANCEL); + accessor.fileDialogService.setConfirmResult(ConfirmResult.CANCEL); await model.load(); model.textEditorModel!.setValue('foo'); @@ -98,11 +101,11 @@ suite('Files - TextFileService', () => { test('confirm onWillShutdown - no veto and backups cleaned up if user does not want to save (hot.exit: off)', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.models).add(model.getResource(), model); + (accessor.textFileService.models).add(model.resource, model); const service = accessor.textFileService; - service.setConfirmResult(ConfirmResult.DONT_SAVE); - service.onFilesConfigurationChange({ files: { hotExit: 'off' } }); + accessor.fileDialogService.setConfirmResult(ConfirmResult.DONT_SAVE); + accessor.filesConfigurationService.onFilesConfigurationChange({ files: { hotExit: 'off' } }); await model.load(); model.textEditorModel!.setValue('foo'); @@ -124,11 +127,11 @@ suite('Files - TextFileService', () => { test('confirm onWillShutdown - save (hot.exit: off)', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.models).add(model.getResource(), model); + (accessor.textFileService.models).add(model.resource, model); const service = accessor.textFileService; - service.setConfirmResult(ConfirmResult.SAVE); - service.onFilesConfigurationChange({ files: { hotExit: 'off' } }); + accessor.fileDialogService.setConfirmResult(ConfirmResult.SAVE); + accessor.filesConfigurationService.onFilesConfigurationChange({ files: { hotExit: 'off' } }); await model.load(); model.textEditorModel!.setValue('foo'); @@ -143,20 +146,20 @@ suite('Files - TextFileService', () => { test('isDirty/getDirty - files and untitled', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.models).add(model.getResource(), model); + (accessor.textFileService.models).add(model.resource, model); const service = accessor.textFileService; await model.load(); - assert.ok(!service.isDirty(model.getResource())); + assert.ok(!service.isDirty(model.resource)); model.textEditorModel!.setValue('foo'); - assert.ok(service.isDirty(model.getResource())); + assert.ok(service.isDirty(model.resource)); assert.equal(service.getDirty().length, 1); - assert.equal(service.getDirty([model.getResource()])[0].toString(), model.getResource().toString()); + assert.equal(service.getDirty([model.resource])[0].toString(), model.resource.toString()); - const untitled = accessor.untitledEditorService.createOrGet(); + const untitled = accessor.untitledTextEditorService.createOrGet(); const untitledModel = await untitled.resolve(); assert.ok(!service.isDirty(untitled.getResource())); @@ -170,30 +173,30 @@ suite('Files - TextFileService', () => { test('save - file', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.models).add(model.getResource(), model); + (accessor.textFileService.models).add(model.resource, model); const service = accessor.textFileService; await model.load(); model.textEditorModel!.setValue('foo'); - assert.ok(service.isDirty(model.getResource())); + assert.ok(service.isDirty(model.resource)); - const res = await service.save(model.getResource()); + const res = await service.save(model.resource); assert.ok(res); - assert.ok(!service.isDirty(model.getResource())); + assert.ok(!service.isDirty(model.resource)); }); test('save - UNC path', async function () { const untitledUncUri = URI.from({ scheme: 'untitled', authority: 'server', path: '/share/path/file.txt' }); model = instantiationService.createInstance(TextFileEditorModel, untitledUncUri, 'utf8', undefined); - (accessor.textFileService.models).add(model.getResource(), model); + (accessor.textFileService.models).add(model.resource, model); const mockedFileUri = untitledUncUri.with({ scheme: Schemas.file }); const mockedEditorInput = instantiationService.createInstance(TextFileEditorModel, mockedFileUri, 'utf8', undefined); const loadOrCreateStub = sinon.stub(accessor.textFileService.models, 'loadOrCreate', () => Promise.resolve(mockedEditorInput)); - sinon.stub(accessor.untitledEditorService, 'exists', () => true); - sinon.stub(accessor.untitledEditorService, 'hasAssociatedFilePath', () => true); + sinon.stub(accessor.untitledTextEditorService, 'exists', () => true); + sinon.stub(accessor.untitledTextEditorService, 'hasAssociatedFilePath', () => true); sinon.stub(accessor.modelService, 'updateModel', () => { }); await model.load(); @@ -202,7 +205,7 @@ suite('Files - TextFileService', () => { const res = await accessor.textFileService.saveAll(true); assert.ok(loadOrCreateStub.calledOnce); assert.equal(res.results.length, 1); - assert.ok(res.results[0].success); + assert.ok(!res.results[0].error); assert.equal(res.results[0].target!.scheme, Schemas.file); assert.equal(res.results[0].target!.authority, untitledUncUri.authority); assert.equal(res.results[0].target!.path, untitledUncUri.path); @@ -210,65 +213,65 @@ suite('Files - TextFileService', () => { test('saveAll - file', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.models).add(model.getResource(), model); + (accessor.textFileService.models).add(model.resource, model); const service = accessor.textFileService; await model.load(); model.textEditorModel!.setValue('foo'); - assert.ok(service.isDirty(model.getResource())); + assert.ok(service.isDirty(model.resource)); - const res = await service.saveAll([model.getResource()]); + const res = await service.saveAll([model.resource]); assert.ok(res); - assert.ok(!service.isDirty(model.getResource())); + assert.ok(!service.isDirty(model.resource)); assert.equal(res.results.length, 1); - assert.equal(res.results[0].source.toString(), model.getResource().toString()); + assert.equal(res.results[0].source.toString(), model.resource.toString()); }); test('saveAs - file', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.models).add(model.getResource(), model); + (accessor.textFileService.models).add(model.resource, model); const service = accessor.textFileService; - service.setPromptPath(model.getResource()); + service.setPromptPath(model.resource); await model.load(); model.textEditorModel!.setValue('foo'); - assert.ok(service.isDirty(model.getResource())); + assert.ok(service.isDirty(model.resource)); - const res = await service.saveAs(model.getResource()); - assert.equal(res!.toString(), model.getResource().toString()); - assert.ok(!service.isDirty(model.getResource())); + const res = await service.saveAs(model.resource); + assert.equal(res!.toString(), model.resource.toString()); + assert.ok(!service.isDirty(model.resource)); }); test('revert - file', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.models).add(model.getResource(), model); + (accessor.textFileService.models).add(model.resource, model); const service = accessor.textFileService; - service.setPromptPath(model.getResource()); + service.setPromptPath(model.resource); await model.load(); model!.textEditorModel!.setValue('foo'); - assert.ok(service.isDirty(model.getResource())); + assert.ok(service.isDirty(model.resource)); - const res = await service.revert(model.getResource()); + const res = await service.revert(model.resource); assert.ok(res); - assert.ok(!service.isDirty(model.getResource())); + assert.ok(!service.isDirty(model.resource)); }); test('delete - dirty file', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.models).add(model.getResource(), model); + (accessor.textFileService.models).add(model.resource, model); const service = accessor.textFileService; await model.load(); model!.textEditorModel!.setValue('foo'); - assert.ok(service.isDirty(model.getResource())); + assert.ok(service.isDirty(model.resource)); - await service.delete(model.getResource()); - assert.ok(!service.isDirty(model.getResource())); + await service.delete(model.resource); + assert.ok(!service.isDirty(model.resource)); }); test('move - dirty file', async function () { @@ -282,27 +285,27 @@ suite('Files - TextFileService', () => { async function testMove(source: URI, target: URI, targetDirty?: boolean): Promise { let sourceModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, source, 'utf8', undefined); let targetModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, target, 'utf8', undefined); - (accessor.textFileService.models).add(sourceModel.getResource(), sourceModel); - (accessor.textFileService.models).add(targetModel.getResource(), targetModel); + (accessor.textFileService.models).add(sourceModel.resource, sourceModel); + (accessor.textFileService.models).add(targetModel.resource, targetModel); const service = accessor.textFileService; await sourceModel.load(); sourceModel.textEditorModel!.setValue('foo'); - assert.ok(service.isDirty(sourceModel.getResource())); + assert.ok(service.isDirty(sourceModel.resource)); if (targetDirty) { await targetModel.load(); targetModel.textEditorModel!.setValue('bar'); - assert.ok(service.isDirty(targetModel.getResource())); + assert.ok(service.isDirty(targetModel.resource)); } - await service.move(sourceModel.getResource(), targetModel.getResource(), true); + await service.move(sourceModel.resource, targetModel.resource, true); assert.equal(targetModel.textEditorModel!.getValue(), 'foo'); - assert.ok(!service.isDirty(sourceModel.getResource())); - assert.ok(service.isDirty(targetModel.getResource())); + assert.ok(!service.isDirty(sourceModel.resource)); + assert.ok(service.isDirty(targetModel.resource)); sourceModel.dispose(); targetModel.dispose(); @@ -413,11 +416,11 @@ suite('Files - TextFileService', () => { async function hotExitTest(this: any, setting: string, shutdownReason: ShutdownReason, multipleWindows: boolean, workspace: true, shouldVeto: boolean): Promise { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.models).add(model.getResource(), model); + (accessor.textFileService.models).add(model.resource, model); const service = accessor.textFileService; // Set hot exit config - service.onFilesConfigurationChange({ files: { hotExit: setting } }); + accessor.filesConfigurationService.onFilesConfigurationChange({ files: { hotExit: setting } }); // Set empty workspace if required if (!workspace) { accessor.contextService.setWorkspace(new Workspace('empty:1508317022751')); @@ -427,7 +430,7 @@ suite('Files - TextFileService', () => { accessor.electronService.windowCount = Promise.resolve(2); } // Set cancel to force a veto if hot exit does not trigger - service.setConfirmResult(ConfirmResult.CANCEL); + accessor.fileDialogService.setConfirmResult(ConfirmResult.CANCEL); await model.load(); model.textEditorModel!.setValue('foo'); diff --git a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts index e824bcf627..58906fdc61 100644 --- a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts +++ b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts @@ -13,7 +13,7 @@ import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorMo import { ITextFileService, LoadReason } from 'vs/workbench/services/textfile/common/textfiles'; import * as network from 'vs/base/common/network'; import { ITextModelService, ITextModelContentProvider, ITextEditorModel, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { IFileService } from 'vs/platform/files/common/files'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -126,7 +126,7 @@ export class TextModelResolverService implements ITextModelService { private resourceModelCollection: ResourceModelCollection; constructor( - @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IModelService private readonly modelService: IModelService ) { @@ -141,7 +141,7 @@ export class TextModelResolverService implements ITextModelService { // Untitled Schema: go through cached input if (resource.scheme === network.Schemas.untitled) { - const model = await this.untitledEditorService.loadOrCreate({ resource }); + const model = await this.untitledTextEditorService.loadOrCreate({ resource }); return new ImmortalReference(model as IResolvedTextEditorModel); } @@ -179,4 +179,4 @@ export class TextModelResolverService implements ITextModelService { } } -registerSingleton(ITextModelService, TextModelResolverService, true); \ No newline at end of file +registerSingleton(ITextModelService, TextModelResolverService, true); diff --git a/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts b/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts index d276b52b3f..42a8c582df 100644 --- a/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts +++ b/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts @@ -16,7 +16,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { ITextFileService, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { Event } from 'vs/base/common/event'; import { timeout } from 'vs/base/common/async'; @@ -27,7 +27,7 @@ class ServiceAccessor { @IModelService public modelService: IModelService, @IModeService public modeService: IModeService, @ITextFileService public textFileService: TestTextFileService, - @IUntitledEditorService public untitledEditorService: IUntitledEditorService + @IUntitledTextEditorService public untitledTextEditorService: IUntitledTextEditorService ) { } } @@ -50,7 +50,7 @@ suite('Workbench - TextModelResolverService', () => { } (accessor.textFileService.models).clear(); (accessor.textFileService.models).dispose(); - accessor.untitledEditorService.revertAll(); + accessor.untitledTextEditorService.revertAll(); }); test('resolve resource', async () => { @@ -88,11 +88,11 @@ suite('Workbench - TextModelResolverService', () => { test('resolve file', async function () { const textModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_resolver.txt'), 'utf8', undefined); - (accessor.textFileService.models).add(textModel.getResource(), textModel); + (accessor.textFileService.models).add(textModel.resource, textModel); await textModel.load(); - const ref = await accessor.textModelResolverService.createModelReference(textModel.getResource()); + const ref = await accessor.textModelResolverService.createModelReference(textModel.resource); const model = ref.object; const editorModel = model.textEditorModel; @@ -111,7 +111,7 @@ suite('Workbench - TextModelResolverService', () => { }); test('resolve untitled', async () => { - const service = accessor.untitledEditorService; + const service = accessor.untitledTextEditorService; const input = service.createOrGet(); await input.resolve(); diff --git a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts index 5137574ccd..8cf07a66eb 100644 --- a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts +++ b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts @@ -189,8 +189,10 @@ function _loadIconThemeDocument(fileService: IFileService, location: URI): Promi return fileService.readFile(location).then((content) => { let errors: Json.ParseError[] = []; let contentValue = Json.parse(content.value.toString(), errors); - if (errors.length > 0 || !contentValue) { + if (errors.length > 0) { return Promise.reject(new Error(nls.localize('error.cannotparseicontheme', "Problems parsing file icons file: {0}", errors.map(e => getParseErrorMessage(e.error)).join(', ')))); + } else if (Json.getNodeType(contentValue) !== 'object') { + return Promise.reject(new Error(nls.localize('error.invalidformat', "Invalid format for file icons theme file: Object expected."))); } return Promise.resolve(contentValue); }); diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 64579fb075..ddb3595a39 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IWorkbenchThemeService, IColorTheme, ITokenColorCustomizations, IFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING, CUSTOM_WORKBENCH_COLORS_SETTING, CUSTOM_EDITOR_COLORS_SETTING, DETECT_HC_SETTING, HC_THEME_ID, IColorCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService, IColorTheme, ITokenColorCustomizations, IFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING, CUSTOM_WORKBENCH_COLORS_SETTING, CUSTOM_EDITOR_COLORS_SETTING, DETECT_HC_SETTING, HC_THEME_ID, IColorCustomizations, CUSTOM_EDITOR_TOKENSTYLES_SETTING, IExperimentalTokenStyleCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -29,9 +29,11 @@ import * as resources from 'vs/base/common/resources'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { textmateColorsSchemaId, registerColorThemeSchemas, textmateColorSettingsSchemaId } from 'vs/workbench/services/themes/common/colorThemeSchema'; import { workbenchColorsSchemaId } from 'vs/platform/theme/common/colorRegistry'; +import { tokenStylingSchemaId } from 'vs/platform/theme/common/tokenClassificationRegistry'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; // implementation // {{SQL CARBON EDIT}} @@ -93,6 +95,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return this.configurationService.getValue(CUSTOM_EDITOR_COLORS_SETTING) || {}; } + private get tokenStylesCustomizations(): IExperimentalTokenStyleCustomizations { + return this.configurationService.getValue(CUSTOM_EDITOR_TOKENSTYLES_SETTING) || {}; + } + constructor( @IExtensionService extensionService: IExtensionService, @IStorageService private readonly storageService: IStorageService, @@ -100,6 +106,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { @ITelemetryService private readonly telemetryService: ITelemetryService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IFileService private readonly fileService: IFileService, + @IExtensionResourceLoaderService private readonly extensionResourceLoaderService: IExtensionResourceLoaderService, @IWorkbenchLayoutService readonly layoutService: IWorkbenchLayoutService ) { @@ -126,6 +133,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } themeData.setCustomColors(this.colorCustomizations); themeData.setCustomTokenColors(this.tokenColorCustomizations); + themeData.setCustomTokenStyleRules(this.tokenStylesCustomizations); this.updateDynamicCSSRules(themeData); this.applyTheme(themeData, undefined, true); @@ -154,18 +162,22 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { const themeSpecificWorkbenchColors: IJSONSchema = { properties: {} }; const themeSpecificTokenColors: IJSONSchema = { properties: {} }; + const themeSpecificTokenStyling: IJSONSchema = { properties: {} }; const workbenchColors = { $ref: workbenchColorsSchemaId, additionalProperties: false }; const tokenColors = { properties: tokenColorSchema.properties, additionalProperties: false }; + const tokenStyling = { $ref: tokenStylingSchemaId, additionalProperties: false }; for (let t of event.themes) { // add theme specific color customization ("[Abyss]":{ ... }) const themeId = `[${t.settingsId}]`; themeSpecificWorkbenchColors.properties![themeId] = workbenchColors; themeSpecificTokenColors.properties![themeId] = tokenColors; + themeSpecificTokenStyling.properties![themeId] = tokenStyling; } colorCustomizationsSchema.allOf![1] = themeSpecificWorkbenchColors; tokenColorCustomizationSchema.allOf![1] = themeSpecificTokenColors; + experimentalTokenStylingCustomizationSchema.allOf![1] = themeSpecificTokenStyling; configurationRegistry.notifyConfigurationSchemaUpdated(themeSettingsConfiguration, tokenColorCustomizationConfiguration); @@ -308,6 +320,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { this.currentColorTheme.setCustomTokenColors(this.tokenColorCustomizations); hasColorChanges = true; } + if (e.affectsConfiguration(CUSTOM_EDITOR_TOKENSTYLES_SETTING)) { + this.currentColorTheme.setCustomTokenStyleRules(this.tokenStylesCustomizations); + hasColorChanges = true; + } if (hasColorChanges) { this.updateDynamicCSSRules(this.currentColorTheme); this.onColorThemeChange.fire(this.currentColorTheme); @@ -343,16 +359,19 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return null; } const themeData = data; - return themeData.ensureLoaded(this.fileService).then(_ => { + return themeData.ensureLoaded(this.extensionResourceLoaderService).then(_ => { if (themeId === this.currentColorTheme.id && !this.currentColorTheme.isLoaded && this.currentColorTheme.hasEqualData(themeData)) { + this.currentColorTheme.clearCaches(); // the loaded theme is identical to the perisisted theme. Don't need to send an event. this.currentColorTheme = themeData; themeData.setCustomColors(this.colorCustomizations); themeData.setCustomTokenColors(this.tokenColorCustomizations); + themeData.setCustomTokenStyleRules(this.tokenStylesCustomizations); return Promise.resolve(themeData); } themeData.setCustomColors(this.colorCustomizations); themeData.setCustomTokenColors(this.tokenColorCustomizations); + themeData.setCustomTokenStyleRules(this.tokenStylesCustomizations); this.updateDynamicCSSRules(themeData); return this.applyTheme(themeData, settingsTarget); }, error => { @@ -362,9 +381,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } private async reloadCurrentColorTheme() { - await this.currentColorTheme.reload(this.fileService); + await this.currentColorTheme.reload(this.extensionResourceLoaderService); this.currentColorTheme.setCustomColors(this.colorCustomizations); this.currentColorTheme.setCustomTokenColors(this.tokenColorCustomizations); + this.currentColorTheme.setCustomTokenStyleRules(this.tokenStylesCustomizations); this.updateDynamicCSSRules(this.currentColorTheme); this.applyTheme(this.currentColorTheme, undefined, false); } @@ -401,6 +421,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } addClasses(this.container, newTheme.id); + this.currentColorTheme.clearCaches(); this.currentColorTheme = newTheme; if (!this.themingParticipantChangeListener) { this.themingParticipantChangeListener = themingRegistry.onThemingParticipantAdded(_ => this.updateDynamicCSSRules(this.currentColorTheme)); @@ -662,7 +683,7 @@ const themeSettingsConfiguration: IConfigurationNode = { }; configurationRegistry.registerConfiguration(themeSettingsConfiguration); -function tokenGroupSettings(description: string) { +function tokenGroupSettings(description: string): IJSONSchema { return { description, default: '#FF0000', @@ -698,12 +719,18 @@ const tokenColorCustomizationSchema: IConfigurationPropertySchema = { default: {}, allOf: [tokenColorSchema] }; +const experimentalTokenStylingCustomizationSchema: IConfigurationPropertySchema = { + description: nls.localize('editorColorsTokenStyles', "Overrides token color and styles from the currently selected color theme."), + default: {}, + allOf: [{ $ref: tokenStylingSchemaId }] +}; const tokenColorCustomizationConfiguration: IConfigurationNode = { id: 'editor', order: 7.2, type: 'object', properties: { - [CUSTOM_EDITOR_COLORS_SETTING]: tokenColorCustomizationSchema + [CUSTOM_EDITOR_COLORS_SETTING]: tokenColorCustomizationSchema, + [CUSTOM_EDITOR_TOKENSTYLES_SETTING]: experimentalTokenStylingCustomizationSchema } }; configurationRegistry.registerConfiguration(tokenColorCustomizationConfiguration); diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index 9c10c7e162..ae22024887 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -6,26 +6,31 @@ import { basename } from 'vs/base/common/path'; import * as Json from 'vs/base/common/json'; import { Color } from 'vs/base/common/color'; -import { ExtensionData, ITokenColorCustomizations, ITokenColorizationRule, IColorTheme, IColorMap, IThemeExtensionPoint, VS_LIGHT_THEME, VS_HC_THEME, IColorCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { ExtensionData, ITokenColorCustomizations, ITextMateThemingRule, IColorTheme, IColorMap, IThemeExtensionPoint, VS_LIGHT_THEME, VS_HC_THEME, IColorCustomizations, IExperimentalTokenStyleCustomizations, ITokenColorizationSetting } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { convertSettings } from 'vs/workbench/services/themes/common/themeCompatibility'; import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; import * as objects from 'vs/base/common/objects'; import * as resources from 'vs/base/common/resources'; -import { Extensions, IColorRegistry, ColorIdentifier, editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry'; +import { Extensions as ColorRegistryExtensions, IColorRegistry, ColorIdentifier, editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry'; import { ThemeType } from 'vs/platform/theme/common/themeService'; import { Registry } from 'vs/platform/registry/common/platform'; import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; import { URI } from 'vs/base/common/uri'; -import { IFileService } from 'vs/platform/files/common/files'; import { parse as parsePList } from 'vs/workbench/services/themes/common/plistParser'; import { startsWith } from 'vs/base/common/strings'; +import { TokenStyle, TokenClassification, ProbeScope, TokenStylingRule, getTokenClassificationRegistry, TokenStyleValue, matchTokenStylingRule } from 'vs/platform/theme/common/tokenClassificationRegistry'; +import { MatcherWithPriority, Matcher, createMatchers } from 'vs/workbench/services/themes/common/textMateScopeMatcher'; +import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; +import { FontStyle, ColorId, MetadataConsts } from 'vs/editor/common/modes'; -let colorRegistry = Registry.as(Extensions.ColorContribution); +let colorRegistry = Registry.as(ColorRegistryExtensions.ColorContribution); + +let tokenClassificationRegistry = getTokenClassificationRegistry(); const tokenGroupToScopesMap = { - comments: ['comment'], - strings: ['string'], + comments: ['comment', 'punctuation.definition.comment'], + strings: ['string', 'meta.embedded.assembly'], keywords: ['keyword - keyword.operator', 'keyword.control', 'storage', 'storage.type'], numbers: ['constant.numeric'], types: ['entity.name.type', 'entity.name.class', 'support.type', 'support.class'], @@ -33,6 +38,7 @@ const tokenGroupToScopesMap = { variables: ['variable', 'entity.name.variable'] }; + export class ColorThemeData implements IColorTheme { id: string; @@ -44,11 +50,20 @@ export class ColorThemeData implements IColorTheme { watch?: boolean; extensionData?: ExtensionData; - private themeTokenColors: ITokenColorizationRule[] = []; - private customTokenColors: ITokenColorizationRule[] = []; + private themeTokenColors: ITextMateThemingRule[] = []; + private customTokenColors: ITextMateThemingRule[] = []; private colorMap: IColorMap = {}; private customColorMap: IColorMap = {}; + private tokenStylingRules: TokenStylingRule[] | undefined = undefined; // undefined if the theme has no tokenStylingRules section + private customTokenStylingRules: TokenStylingRule[] = []; + + private themeTokenScopeMatchers: Matcher[] | undefined; + private customTokenScopeMatchers: Matcher[] | undefined; + + private textMateThemingRules: ITextMateThemingRule[] | undefined = undefined; // created on demand + private tokenColorIndex: TokenColorIndex | undefined = undefined; // created on demand + private constructor(id: string, label: string, settingsId: string) { this.id = id; this.label = label; @@ -56,39 +71,42 @@ export class ColorThemeData implements IColorTheme { this.isLoaded = false; } - get tokenColors(): ITokenColorizationRule[] { - const result: ITokenColorizationRule[] = []; + get tokenColors(): ITextMateThemingRule[] { + if (!this.textMateThemingRules) { + const result: ITextMateThemingRule[] = []; - // the default rule (scope empty) is always the first rule. Ignore all other default rules. - const foreground = this.getColor(editorForeground) || this.getDefault(editorForeground)!; - const background = this.getColor(editorBackground) || this.getDefault(editorBackground)!; - result.push({ - settings: { - foreground: Color.Format.CSS.formatHexA(foreground), - background: Color.Format.CSS.formatHexA(background) - } - }); - - let hasDefaultTokens = false; - - function addRule(rule: ITokenColorizationRule) { - if (rule.scope && rule.settings) { - if (rule.scope === 'token.info-token') { - hasDefaultTokens = true; + // the default rule (scope empty) is always the first rule. Ignore all other default rules. + const foreground = this.getColor(editorForeground) || this.getDefault(editorForeground)!; + const background = this.getColor(editorBackground) || this.getDefault(editorBackground)!; + result.push({ + settings: { + foreground: Color.Format.CSS.formatHexA(foreground, true), + background: Color.Format.CSS.formatHexA(background, true) + } + }); + + let hasDefaultTokens = false; + + function addRule(rule: ITextMateThemingRule) { + if (rule.scope && rule.settings) { + if (rule.scope === 'token.info-token') { + hasDefaultTokens = true; + } + result.push(rule); } - result.push(rule); } - } - this.themeTokenColors.forEach(addRule); - // Add the custom colors after the theme colors - // so that they will override them - this.customTokenColors.forEach(addRule); + this.themeTokenColors.forEach(addRule); + // Add the custom colors after the theme colors + // so that they will override them + this.customTokenColors.forEach(addRule); - if (!hasDefaultTokens) { - defaultThemeColors[this.type].forEach(addRule); + if (!hasDefaultTokens) { + defaultThemeColors[this.type].forEach(addRule); + } + this.textMateThemingRules = result; } - return result; + return this.textMateThemingRules; } public getColor(colorId: ColorIdentifier, useDefault?: boolean): Color | undefined { @@ -103,10 +121,180 @@ export class ColorThemeData implements IColorTheme { return color; } + public getTokenStyle(classification: TokenClassification, useDefault?: boolean): TokenStyle | undefined { + let result: any = { + foreground: undefined, + bold: undefined, + underline: undefined, + italic: undefined + }; + let score = { + foreground: -1, + bold: -1, + underline: -1, + italic: -1 + }; + + function _processStyle(matchScore: number, style: TokenStyle) { + if (style.foreground && score.foreground <= matchScore) { + score.foreground = matchScore; + result.foreground = style.foreground; + } + for (let p of ['bold', 'underline', 'italic']) { + const property = p as keyof TokenStyle; + const info = style[property]; + if (info !== undefined) { + if (score[property] <= matchScore) { + score[property] = matchScore; + result[property] = info; + } + } + } + } + if (this.tokenStylingRules === undefined) { + for (const rule of tokenClassificationRegistry.getTokenStylingDefaultRules()) { + const matchScore = matchTokenStylingRule(rule, classification); + if (matchScore >= 0) { + let style = this.resolveScopes(rule.defaults.scopesToProbe); + if (!style && useDefault !== false) { + style = this.resolveTokenStyleValue(rule.defaults[this.type]); + } + if (style) { + _processStyle(matchScore, style); + } + } + } + } else { + for (const rule of this.tokenStylingRules) { + const matchScore = matchTokenStylingRule(rule, classification); + if (matchScore >= 0) { + _processStyle(matchScore, rule.value); + } + } + } + for (const rule of this.customTokenStylingRules) { + const matchScore = matchTokenStylingRule(rule, classification); + if (matchScore >= 0) { + _processStyle(matchScore, rule.value); + } + } + return TokenStyle.fromData(result); + + } + + /** + * @param tokenStyleValue Resolve a tokenStyleValue in the context of a theme + */ + private resolveTokenStyleValue(tokenStyleValue: TokenStyleValue | null): TokenStyle | undefined { + if (tokenStyleValue === null) { + return undefined; + } else if (typeof tokenStyleValue === 'string') { + const [type, ...modifiers] = tokenStyleValue.split('.'); + const classification = tokenClassificationRegistry.getTokenClassification(type, modifiers); + if (classification) { + return this.getTokenStyle(classification); + } + } else if (typeof tokenStyleValue === 'object') { + return tokenStyleValue; + } + return undefined; + } + + private getTokenColorIndex(): TokenColorIndex { + // collect all colors that tokens can have + if (!this.tokenColorIndex) { + const index = new TokenColorIndex(); + this.tokenColors.forEach(rule => { + index.add(rule.settings.foreground); + index.add(rule.settings.background); + }); + + if (this.tokenStylingRules) { + this.tokenStylingRules.forEach(r => index.add(r.value.foreground)); + } else { + tokenClassificationRegistry.getTokenStylingDefaultRules().forEach(r => { + const defaultColor = r.defaults[this.type]; + if (defaultColor && typeof defaultColor === 'object') { + index.add(defaultColor.foreground); + } + }); + } + this.customTokenStylingRules.forEach(r => index.add(r.value.foreground)); + + this.tokenColorIndex = index; + } + return this.tokenColorIndex; + } + + public get tokenColorMap(): string[] { + return this.getTokenColorIndex().asArray(); + } + + public getTokenStyleMetadata(type: string, modifiers: string[], useDefault?: boolean): number | undefined { + const classification = tokenClassificationRegistry.getTokenClassification(type, modifiers); + if (!classification) { + return undefined; + } + const style = this.getTokenStyle(classification, useDefault); + let fontStyle = FontStyle.None; + let foreground = 0; + if (style) { + if (style.bold) { + fontStyle |= FontStyle.Bold; + } + if (style.underline) { + fontStyle |= FontStyle.Underline; + } + if (style.italic) { + fontStyle |= FontStyle.Italic; + } + foreground = this.getTokenColorIndex().get(style.foreground); + } + return toMetadata(fontStyle, foreground, 0); + } + public getDefault(colorId: ColorIdentifier): Color | undefined { return colorRegistry.resolveDefaultColor(colorId, this); } + public resolveScopes(scopes: ProbeScope[]): TokenStyle | undefined { + + if (!this.themeTokenScopeMatchers) { + this.themeTokenScopeMatchers = this.themeTokenColors.map(getScopeMatcher); + } + if (!this.customTokenScopeMatchers) { + this.customTokenScopeMatchers = this.customTokenColors.map(getScopeMatcher); + } + + for (let scope of scopes) { + let foreground: string | undefined = undefined; + let fontStyle: string | undefined = undefined; + let foregroundScore = -1; + let fontStyleScore = -1; + + function findTokenStyleForScopeInScopes(scopeMatchers: Matcher[], tokenColors: ITextMateThemingRule[]) { + for (let i = 0; i < scopeMatchers.length; i++) { + const score = scopeMatchers[i](scope); + if (score >= 0) { + const settings = tokenColors[i].settings; + if (score >= foregroundScore && settings.foreground) { + foreground = settings.foreground; + } + if (score >= fontStyleScore && types.isString(settings.fontStyle)) { + fontStyle = settings.fontStyle; + } + } + } + } + findTokenStyleForScopeInScopes(this.themeTokenScopeMatchers, this.themeTokenColors); + findTokenStyleForScopeInScopes(this.customTokenScopeMatchers, this.customTokenColors); + if (foreground !== undefined || fontStyle !== undefined) { + return getTokenStyle(foreground, fontStyle); + } + } + return undefined; + } + public defines(colorId: ColorIdentifier): boolean { return this.customColorMap.hasOwnProperty(colorId) || this.colorMap.hasOwnProperty(colorId); } @@ -119,6 +307,10 @@ export class ColorThemeData implements IColorTheme { if (types.isObject(themeSpecificColors)) { this.overwriteCustomColors(themeSpecificColors); } + + this.tokenColorIndex = undefined; + this.textMateThemingRules = undefined; + this.customTokenScopeMatchers = undefined; } private overwriteCustomColors(colors: IColorCustomizations) { @@ -132,6 +324,7 @@ export class ColorThemeData implements IColorTheme { public setCustomTokenColors(customTokenColors: ITokenColorCustomizations) { this.customTokenColors = []; + // first add the non-theme specific settings this.addCustomTokenColors(customTokenColors); @@ -140,6 +333,23 @@ export class ColorThemeData implements IColorTheme { if (types.isObject(themeSpecificTokenColors)) { this.addCustomTokenColors(themeSpecificTokenColors); } + + this.tokenColorIndex = undefined; + this.textMateThemingRules = undefined; + this.customTokenScopeMatchers = undefined; + } + + public setCustomTokenStyleRules(tokenStylingRules: IExperimentalTokenStyleCustomizations) { + this.customTokenStylingRules = []; + readCustomTokenStyleRules(tokenStylingRules, this.customTokenStylingRules); + + const themeSpecificColors = tokenStylingRules[`[${this.settingsId}]`] as IExperimentalTokenStyleCustomizations; + if (types.isObject(themeSpecificColors)) { + readCustomTokenStyleRules(themeSpecificColors, this.customTokenStylingRules); + } + + this.tokenColorIndex = undefined; + this.textMateThemingRules = undefined; } private addCustomTokenColors(customTokenColors: ITokenColorCustomizations) { @@ -167,25 +377,41 @@ export class ColorThemeData implements IColorTheme { } } - public ensureLoaded(fileService: IFileService): Promise { - return !this.isLoaded ? this.load(fileService) : Promise.resolve(undefined); + public ensureLoaded(extensionResourceLoaderService: IExtensionResourceLoaderService): Promise { + return !this.isLoaded ? this.load(extensionResourceLoaderService) : Promise.resolve(undefined); } - public reload(fileService: IFileService): Promise { - return this.load(fileService); + public reload(extensionResourceLoaderService: IExtensionResourceLoaderService): Promise { + return this.load(extensionResourceLoaderService); } - private load(fileService: IFileService): Promise { + private load(extensionResourceLoaderService: IExtensionResourceLoaderService): Promise { if (!this.location) { return Promise.resolve(undefined); } this.themeTokenColors = []; - this.colorMap = {}; - return _loadColorTheme(fileService, this.location, this.themeTokenColors, this.colorMap).then(_ => { + this.clearCaches(); + + const result = { + colors: {}, + textMateRules: [], + stylingRules: undefined + }; + return _loadColorTheme(extensionResourceLoaderService, this.location, result).then(_ => { this.isLoaded = true; + this.tokenStylingRules = result.stylingRules; + this.colorMap = result.colors; + this.themeTokenColors = result.textMateRules; }); } + public clearCaches() { + this.tokenColorIndex = undefined; + this.textMateThemingRules = undefined; + this.themeTokenScopeMatchers = undefined; + this.customTokenScopeMatchers = undefined; + } + toStorageData() { let colorMapData: { [key: string]: string } = {}; for (let key in this.colorMap) { @@ -295,21 +521,23 @@ function toCSSSelector(extensionId: string, path: string) { return str; } -function _loadColorTheme(fileService: IFileService, themeLocation: URI, resultRules: ITokenColorizationRule[], resultColors: IColorMap): Promise { +function _loadColorTheme(extensionResourceLoaderService: IExtensionResourceLoaderService, themeLocation: URI, result: { textMateRules: ITextMateThemingRule[], colors: IColorMap, stylingRules: TokenStylingRule[] | undefined }): Promise { if (resources.extname(themeLocation) === '.json') { - return fileService.readFile(themeLocation).then(content => { + return extensionResourceLoaderService.readExtensionResource(themeLocation).then(content => { let errors: Json.ParseError[] = []; - let contentValue = Json.parse(content.value.toString(), errors); + let contentValue = Json.parse(content, errors); if (errors.length > 0) { return Promise.reject(new Error(nls.localize('error.cannotparsejson', "Problems parsing JSON theme file: {0}", errors.map(e => getParseErrorMessage(e.error)).join(', ')))); + } else if (Json.getNodeType(contentValue) !== 'object') { + return Promise.reject(new Error(nls.localize('error.invalidformat', "Invalid format for JSON theme file: Object expected."))); } let includeCompletes: Promise = Promise.resolve(null); if (contentValue.include) { - includeCompletes = _loadColorTheme(fileService, resources.joinPath(resources.dirname(themeLocation), contentValue.include), resultRules, resultColors); + includeCompletes = _loadColorTheme(extensionResourceLoaderService, resources.joinPath(resources.dirname(themeLocation), contentValue.include), result); } return includeCompletes.then(_ => { if (Array.isArray(contentValue.settings)) { - convertSettings(contentValue.settings, resultRules, resultColors); + convertSettings(contentValue.settings, result); return null; } let colors = contentValue.colors; @@ -321,38 +549,42 @@ function _loadColorTheme(fileService: IFileService, themeLocation: URI, resultRu for (let colorId in colors) { let colorHex = colors[colorId]; if (typeof colorHex === 'string') { // ignore colors tht are null - resultColors[colorId] = Color.fromHex(colors[colorId]); + result.colors[colorId] = Color.fromHex(colors[colorId]); } } } let tokenColors = contentValue.tokenColors; if (tokenColors) { if (Array.isArray(tokenColors)) { - resultRules.push(...tokenColors); + result.textMateRules.push(...tokenColors); return null; } else if (typeof tokenColors === 'string') { - return _loadSyntaxTokens(fileService, resources.joinPath(resources.dirname(themeLocation), tokenColors), resultRules, {}); + return _loadSyntaxTokens(extensionResourceLoaderService, resources.joinPath(resources.dirname(themeLocation), tokenColors), result); } else { return Promise.reject(new Error(nls.localize({ key: 'error.invalidformat.tokenColors', comment: ['{0} will be replaced by a path. Values in quotes should not be translated.'] }, "Problem parsing color theme file: {0}. Property 'tokenColors' should be either an array specifying colors or a path to a TextMate theme file", themeLocation.toString()))); } } + let tokenStylingRules = contentValue.tokenStylingRules; + if (tokenStylingRules && typeof tokenStylingRules === 'object') { + result.stylingRules = readCustomTokenStyleRules(tokenStylingRules, result.stylingRules); + } return null; }); }); } else { - return _loadSyntaxTokens(fileService, themeLocation, resultRules, resultColors); + return _loadSyntaxTokens(extensionResourceLoaderService, themeLocation, result); } } -function _loadSyntaxTokens(fileService: IFileService, themeLocation: URI, resultRules: ITokenColorizationRule[], resultColors: IColorMap): Promise { - return fileService.readFile(themeLocation).then(content => { +function _loadSyntaxTokens(extensionResourceLoaderService: IExtensionResourceLoaderService, themeLocation: URI, result: { textMateRules: ITextMateThemingRule[], colors: IColorMap }): Promise { + return extensionResourceLoaderService.readExtensionResource(themeLocation).then(content => { try { - let contentValue = parsePList(content.value.toString()); - let settings: ITokenColorizationRule[] = contentValue.settings; + let contentValue = parsePList(content); + let settings: ITextMateThemingRule[] = contentValue.settings; if (!Array.isArray(settings)) { return Promise.reject(new Error(nls.localize('error.plist.invalidformat', "Problem parsing tmTheme file: {0}. 'settings' is not array."))); } - convertSettings(settings, resultRules, resultColors); + convertSettings(settings, result); return Promise.resolve(null); } catch (e) { return Promise.reject(new Error(nls.localize('error.cannotparse', "Problems parsing tmTheme file: {0}", e.message))); @@ -362,7 +594,7 @@ function _loadSyntaxTokens(fileService: IFileService, themeLocation: URI, result }); } -let defaultThemeColors: { [baseTheme: string]: ITokenColorizationRule[] } = { +let defaultThemeColors: { [baseTheme: string]: ITextMateThemingRule[] } = { 'light': [ { scope: 'token.info-token', settings: { foreground: '#316bcd' } }, { scope: 'token.warn-token', settings: { foreground: '#cd9731' } }, @@ -381,4 +613,197 @@ let defaultThemeColors: { [baseTheme: string]: ITokenColorizationRule[] } = { { scope: 'token.error-token', settings: { foreground: '#FF0000' } }, { scope: 'token.debug-token', settings: { foreground: '#b267e6' } } ], -}; \ No newline at end of file +}; + +const noMatch = (_scope: ProbeScope) => -1; + +function nameMatcher(identifers: string[], scope: ProbeScope): number { + function findInIdents(s: string, lastIndent: number): number { + for (let i = lastIndent - 1; i >= 0; i--) { + if (scopesAreMatching(s, identifers[i])) { + return i; + } + } + return -1; + } + if (scope.length < identifers.length) { + return -1; + } + let lastScopeIndex = scope.length - 1; + let lastIdentifierIndex = findInIdents(scope[lastScopeIndex--], identifers.length); + if (lastIdentifierIndex >= 0) { + const score = (lastIdentifierIndex + 1) * 0x10000 + scope.length; + while (lastScopeIndex >= 0) { + lastIdentifierIndex = findInIdents(scope[lastScopeIndex--], lastIdentifierIndex); + if (lastIdentifierIndex === -1) { + return -1; + } + } + return score; + } + return -1; +} + + +function scopesAreMatching(thisScopeName: string, scopeName: string): boolean { + if (!thisScopeName) { + return false; + } + if (thisScopeName === scopeName) { + return true; + } + const len = scopeName.length; + return thisScopeName.length > len && thisScopeName.substr(0, len) === scopeName && thisScopeName[len] === '.'; +} + +function getScopeMatcher(rule: ITextMateThemingRule): Matcher { + const ruleScope = rule.scope; + if (!ruleScope || !rule.settings) { + return noMatch; + } + const matchers: MatcherWithPriority[] = []; + if (Array.isArray(ruleScope)) { + for (let rs of ruleScope) { + createMatchers(rs, nameMatcher, matchers); + } + } else { + createMatchers(ruleScope, nameMatcher, matchers); + } + + if (matchers.length === 0) { + return noMatch; + } + return (scope: ProbeScope) => { + let max = matchers[0].matcher(scope); + for (let i = 1; i < matchers.length; i++) { + max = Math.max(max, matchers[i].matcher(scope)); + } + return max; + }; +} + +function getTokenStyle(foreground: string | undefined, fontStyle: string | undefined): TokenStyle { + let foregroundColor = undefined; + if (foreground !== undefined) { + foregroundColor = Color.fromHex(foreground); + } + let bold, underline, italic; + if (fontStyle !== undefined) { + fontStyle = fontStyle.trim(); + if (fontStyle.length === 0) { + bold = italic = underline = false; + } else { + const expression = /-?italic|-?bold|-?underline/g; + let match; + while ((match = expression.exec(fontStyle))) { + switch (match[0]) { + case 'bold': bold = true; break; + case '-bold': bold = false; break; + case 'italic': italic = true; break; + case '-italic': italic = false; break; + case 'underline': underline = true; break; + case '-underline': underline = false; break; + } + } + } + } + return new TokenStyle(foregroundColor, bold, underline, italic); + +} + +function readCustomTokenStyleRules(tokenStylingRuleSection: IExperimentalTokenStyleCustomizations, result: TokenStylingRule[] = []) { + for (let key in tokenStylingRuleSection) { + if (key[0] !== '[') { + const [type, ...modifiers] = key.split('.'); + const classification = tokenClassificationRegistry.getTokenClassification(type, modifiers); + if (classification) { + const settings = tokenStylingRuleSection[key]; + let style: TokenStyle | undefined; + if (typeof settings === 'string') { + style = getTokenStyle(settings, undefined); + } else if (isTokenColorizationSetting(settings)) { + style = getTokenStyle(settings.foreground, settings.fontStyle); + } + if (style) { + result.push(tokenClassificationRegistry.getTokenStylingRule(classification, style)); + } + } + } + } + return result; +} + +function isTokenColorizationSetting(style: any): style is ITokenColorizationSetting { + return style && (style.foreground || style.fontStyle); +} + + +class TokenColorIndex { + + private _lastColorId: number; + private _id2color: string[]; + private _color2id: { [color: string]: number; }; + + constructor() { + this._lastColorId = 0; + this._id2color = []; + this._color2id = Object.create(null); + } + + public add(color: string | Color | undefined | null): number { + if (color === null || color === undefined) { + return 0; + } + color = normalizeColorForIndex(color); + + let value = this._color2id[color]; + if (value) { + return value; + } + value = ++this._lastColorId; + this._color2id[color] = value; + this._id2color[value] = color; + return value; + } + + public get(color: string | Color | undefined): number { + if (color === undefined) { + return 0; + } + color = normalizeColorForIndex(color); + let value = this._color2id[color]; + if (value) { + return value; + } + console.log(`Color ${color} not in index.`); + return 0; + } + + public asArray(): string[] { + return this._id2color.slice(0); + } + +} + +function normalizeColorForIndex(color: string | Color): string { + if (typeof color !== 'string') { + color = Color.Format.CSS.formatHexA(color, true); + } + return color.toUpperCase(); +} + +function toMetadata(fontStyle: FontStyle, foreground: ColorId | number, background: ColorId | number) { + const fontStyleBits = fontStyle << MetadataConsts.FONT_STYLE_OFFSET; + const foregroundBits = foreground << MetadataConsts.FOREGROUND_OFFSET; + const backgroundBits = background << MetadataConsts.BACKGROUND_OFFSET; + if ((fontStyleBits & MetadataConsts.FONT_STYLE_MASK) !== fontStyleBits) { + console.log(`Can not express fontStyle ${fontStyle} in metadata`); + } + if ((backgroundBits & MetadataConsts.BACKGROUND_MASK) !== backgroundBits) { + console.log(`Can not express background ${background} in metadata`); + } + if ((foregroundBits & MetadataConsts.FOREGROUND_MASK) !== foregroundBits) { + console.log(`Can not express foreground ${foreground} in metadata`); + } + return (fontStyleBits | foregroundBits | backgroundBits) >>> 0; +} diff --git a/src/vs/workbench/services/themes/common/textMateScopeMatcher.ts b/src/vs/workbench/services/themes/common/textMateScopeMatcher.ts new file mode 100644 index 0000000000..53fb89ea5d --- /dev/null +++ b/src/vs/workbench/services/themes/common/textMateScopeMatcher.ts @@ -0,0 +1,134 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +export interface MatcherWithPriority { + matcher: Matcher; + priority: -1 | 0 | 1; +} + +export interface Matcher { + (matcherInput: T): number; +} + +export function createMatchers(selector: string, matchesName: (names: string[], matcherInput: T) => number, results: MatcherWithPriority[]): void { + const tokenizer = newTokenizer(selector); + let token = tokenizer.next(); + while (token !== null) { + let priority: -1 | 0 | 1 = 0; + if (token.length === 2 && token.charAt(1) === ':') { + switch (token.charAt(0)) { + case 'R': priority = 1; break; + case 'L': priority = -1; break; + default: + console.log(`Unknown priority ${token} in scope selector`); + } + token = tokenizer.next(); + } + let matcher = parseConjunction(); + if (matcher) { + results.push({ matcher, priority }); + } + if (token !== ',') { + break; + } + token = tokenizer.next(); + } + + function parseOperand(): Matcher | null { + if (token === '-') { + token = tokenizer.next(); + const expressionToNegate = parseOperand(); + if (!expressionToNegate) { + return null; + } + return matcherInput => { + const score = expressionToNegate(matcherInput); + return score < 0 ? 0 : -1; + }; + } + if (token === '(') { + token = tokenizer.next(); + const expressionInParents = parseInnerExpression(); + if (token === ')') { + token = tokenizer.next(); + } + return expressionInParents; + } + if (isIdentifier(token)) { + const identifiers: string[] = []; + do { + identifiers.push(token); + token = tokenizer.next(); + } while (isIdentifier(token)); + return matcherInput => matchesName(identifiers, matcherInput); + } + return null; + } + function parseConjunction(): Matcher | null { + let matcher = parseOperand(); + if (!matcher) { + return null; + } + + const matchers: Matcher[] = []; + while (matcher) { + matchers.push(matcher); + matcher = parseOperand(); + } + return matcherInput => { // and + let min = matchers[0](matcherInput); + for (let i = 1; min >= 0 && i < matchers.length; i++) { + min = Math.min(min, matchers[i](matcherInput)); + } + return min; + }; + } + function parseInnerExpression(): Matcher | null { + let matcher = parseConjunction(); + if (!matcher) { + return null; + } + const matchers: Matcher[] = []; + while (matcher) { + matchers.push(matcher); + if (token === '|' || token === ',') { + do { + token = tokenizer.next(); + } while (token === '|' || token === ','); // ignore subsequent commas + } else { + break; + } + matcher = parseConjunction(); + } + return matcherInput => { // or + let max = matchers[0](matcherInput); + for (let i = 1; i < matchers.length; i++) { + max = Math.max(max, matchers[i](matcherInput)); + } + return max; + }; + } +} + +function isIdentifier(token: string | null): token is string { + return !!token && !!token.match(/[\w\.:]+/); +} + +function newTokenizer(input: string): { next: () => string | null } { + let regex = /([LR]:|[\w\.:][\w\.:\-]*|[\,\|\-\(\)])/g; + let match = regex.exec(input); + return { + next: () => { + if (!match) { + return null; + } + const res = match[0]; + match = regex.exec(input); + return res; + } + }; +} diff --git a/src/vs/workbench/services/themes/common/themeCompatibility.ts b/src/vs/workbench/services/themes/common/themeCompatibility.ts index 43e406a5af..29d88487bb 100644 --- a/src/vs/workbench/services/themes/common/themeCompatibility.ts +++ b/src/vs/workbench/services/themes/common/themeCompatibility.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ITokenColorizationRule, IColorMap } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { ITextMateThemingRule, IColorMap } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { Color } from 'vs/base/common/color'; import * as colorRegistry from 'vs/platform/theme/common/colorRegistry'; @@ -18,9 +18,9 @@ function addSettingMapping(settingId: string, colorId: string) { colorIds.push(colorId); } -export function convertSettings(oldSettings: ITokenColorizationRule[], resultRules: ITokenColorizationRule[], resultColors: IColorMap): void { +export function convertSettings(oldSettings: ITextMateThemingRule[], result: { textMateRules: ITextMateThemingRule[], colors: IColorMap }): void { for (let rule of oldSettings) { - resultRules.push(rule); + result.textMateRules.push(rule); if (!rule.scope) { let settings = rule.settings; if (!settings) { @@ -34,7 +34,7 @@ export function convertSettings(oldSettings: ITokenColorizationRule[], resultRul if (typeof colorHex === 'string') { let color = Color.fromHex(colorHex); for (let colorId of mappings) { - resultColors[colorId] = color; + result.colors[colorId] = color; } } } diff --git a/src/vs/workbench/services/themes/common/workbenchThemeService.ts b/src/vs/workbench/services/themes/common/workbenchThemeService.ts index 00451491f4..ef3cebff37 100644 --- a/src/vs/workbench/services/themes/common/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/common/workbenchThemeService.ts @@ -22,6 +22,7 @@ export const DETECT_HC_SETTING = 'window.autoDetectHighContrast'; export const ICON_THEME_SETTING = 'workbench.iconTheme'; export const CUSTOM_WORKBENCH_COLORS_SETTING = 'workbench.colorCustomizations'; export const CUSTOM_EDITOR_COLORS_SETTING = 'editor.tokenColorCustomizations'; +export const CUSTOM_EDITOR_TOKENSTYLES_SETTING = 'editor.tokenColorCustomizationsExperimental'; export interface IColorTheme extends ITheme { readonly id: string; @@ -30,7 +31,7 @@ export interface IColorTheme extends ITheme { readonly extensionData?: ExtensionData; readonly description?: string; readonly isLoaded: boolean; - readonly tokenColors: ITokenColorizationRule[]; + readonly tokenColors: ITextMateThemingRule[]; } export interface IColorMap { @@ -69,7 +70,7 @@ export interface IColorCustomizations { } export interface ITokenColorCustomizations { - [groupIdOrThemeSettingsId: string]: string | ITokenColorizationSetting | ITokenColorCustomizations | undefined | ITokenColorizationRule[]; + [groupIdOrThemeSettingsId: string]: string | ITokenColorizationSetting | ITokenColorCustomizations | undefined | ITextMateThemingRule[]; comments?: string | ITokenColorizationSetting; strings?: string | ITokenColorizationSetting; numbers?: string | ITokenColorizationSetting; @@ -77,10 +78,14 @@ export interface ITokenColorCustomizations { types?: string | ITokenColorizationSetting; functions?: string | ITokenColorizationSetting; variables?: string | ITokenColorizationSetting; - textMateRules?: ITokenColorizationRule[]; + textMateRules?: ITextMateThemingRule[]; } -export interface ITokenColorizationRule { +export interface IExperimentalTokenStyleCustomizations { + [styleRuleOrThemeSettingsId: string]: string | ITokenColorizationSetting | IExperimentalTokenStyleCustomizations | undefined; +} + +export interface ITextMateThemingRule { name?: string; scope?: string | string[]; settings: ITokenColorizationSetting; @@ -89,7 +94,7 @@ export interface ITokenColorizationRule { export interface ITokenColorizationSetting { foreground?: string; background?: string; - fontStyle?: string; // italic, underline, bold + fontStyle?: string; /* [italic|underline|bold] */ } export interface ExtensionData { @@ -106,4 +111,4 @@ export interface IThemeExtensionPoint { path: string; uiTheme?: typeof VS_LIGHT_THEME | typeof VS_DARK_THEME | typeof VS_HC_THEME; _watch: boolean; // unsupported options to watch location -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts b/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts new file mode 100644 index 0000000000..7bb85407f0 --- /dev/null +++ b/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts @@ -0,0 +1,324 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ColorThemeData } from 'vs/workbench/services/themes/common/colorThemeData'; +import * as assert from 'assert'; +import { ITokenColorCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { TokenStyle, comments, variables, types, functions, keywords, numbers, strings, getTokenClassificationRegistry } from 'vs/platform/theme/common/tokenClassificationRegistry'; +import { Color } from 'vs/base/common/color'; +import { isString } from 'vs/base/common/types'; +import { FileService } from 'vs/platform/files/common/fileService'; +import { NullLogService } from 'vs/platform/log/common/log'; +import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; +import { Schemas } from 'vs/base/common/network'; +import { URI } from 'vs/base/common/uri'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; +import { ExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/electron-browser/extensionResourceLoaderService'; +import { TokenMetadata, FontStyle } from 'vs/editor/common/modes'; + +let tokenClassificationRegistry = getTokenClassificationRegistry(); + +const undefinedStyle = { bold: undefined, underline: undefined, italic: undefined }; +const unsetStyle = { bold: false, underline: false, italic: false }; + +function ts(foreground: string | undefined, styleFlags: { bold?: boolean; underline?: boolean; italic?: boolean } | undefined): TokenStyle { + const foregroundColor = isString(foreground) ? Color.fromHex(foreground) : undefined; + return new TokenStyle(foregroundColor, styleFlags && styleFlags.bold, styleFlags && styleFlags.underline, styleFlags && styleFlags.italic); +} + +function tokenStyleAsString(ts: TokenStyle | undefined | null) { + if (!ts) { + return 'tokenstyle-undefined'; + } + let str = ts.foreground ? ts.foreground.toString() : 'no-foreground'; + if (ts.bold !== undefined) { + str += ts.bold ? '+B' : '-B'; + } + if (ts.underline !== undefined) { + str += ts.underline ? '+U' : '-U'; + } + if (ts.italic !== undefined) { + str += ts.italic ? '+I' : '-I'; + } + return str; +} + +function assertTokenStyle(actual: TokenStyle | undefined | null, expected: TokenStyle | undefined | null, message?: string) { + assert.equal(tokenStyleAsString(actual), tokenStyleAsString(expected), message); +} + +function assertTokenStyleMetaData(colorIndex: string[], actual: number | undefined, expected: TokenStyle | undefined | null, message?: string) { + if (expected === undefined || expected === null || actual === undefined) { + assert.equal(actual, expected, message); + return; + } + const actualFontStyle = TokenMetadata.getFontStyle(actual); + assert.equal((actualFontStyle & FontStyle.Bold) === FontStyle.Bold, expected.bold === true, 'bold'); + assert.equal((actualFontStyle & FontStyle.Italic) === FontStyle.Italic, expected.italic === true, 'italic'); + assert.equal((actualFontStyle & FontStyle.Underline) === FontStyle.Underline, expected.underline === true, 'underline'); + + const actualForegroundIndex = TokenMetadata.getForeground(actual); + if (expected.foreground) { + assert.equal(actualForegroundIndex, colorIndex.indexOf(Color.Format.CSS.formatHexA(expected.foreground, true).toUpperCase()), 'foreground'); + } else { + assert.equal(actualForegroundIndex, 0, 'foreground'); + } + const actualBackgroundIndex = TokenMetadata.getBackground(actual); + assert.equal(actualBackgroundIndex, 0, 'background'); +} + + +function assertTokenStyles(themeData: ColorThemeData, expected: { [qualifiedClassifier: string]: TokenStyle }) { + const colorIndex = themeData.tokenColorMap; + + for (let qualifiedClassifier in expected) { + const [type, ...modifiers] = qualifiedClassifier.split('.'); + + const classification = tokenClassificationRegistry.getTokenClassification(type, modifiers); + assert.ok(classification, 'Classification not found'); + + const tokenStyle = themeData.getTokenStyle(classification!); + const expectedTokenStyle = expected[qualifiedClassifier]; + assertTokenStyle(tokenStyle, expectedTokenStyle, qualifiedClassifier); + + const tokenStyleMetaData = themeData.getTokenStyleMetadata(type, modifiers); + assertTokenStyleMetaData(colorIndex, tokenStyleMetaData, expectedTokenStyle); + } +} + +suite('Themes - TokenStyleResolving', () => { + + + const fileService = new FileService(new NullLogService()); + const extensionResourceLoaderService = new ExtensionResourceLoaderService(fileService); + + const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + fileService.registerProvider(Schemas.file, diskFileSystemProvider); + + + test('color defaults - monokai', async () => { + const themeData = ColorThemeData.createUnloadedTheme('foo'); + const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-monokai/themes/monokai-color-theme.json'); + themeData.location = URI.file(themeLocation); + await themeData.ensureLoaded(extensionResourceLoaderService); + + assert.equal(themeData.isLoaded, true); + + assertTokenStyles(themeData, { + [comments]: ts('#88846f', undefinedStyle), + [variables]: ts('#F8F8F2', unsetStyle), + [types]: ts('#A6E22E', { underline: true }), + [functions]: ts('#A6E22E', unsetStyle), + [strings]: ts('#E6DB74', undefinedStyle), + [numbers]: ts('#AE81FF', undefinedStyle), + [keywords]: ts('#F92672', undefinedStyle) + }); + + }); + + test('color defaults - dark+', async () => { + const themeData = ColorThemeData.createUnloadedTheme('foo'); + const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-defaults/themes/dark_plus.json'); + themeData.location = URI.file(themeLocation); + await themeData.ensureLoaded(extensionResourceLoaderService); + + assert.equal(themeData.isLoaded, true); + + assertTokenStyles(themeData, { + [comments]: ts('#6A9955', undefinedStyle), + [variables]: ts('#9CDCFE', undefinedStyle), + [types]: ts('#4EC9B0', undefinedStyle), + [functions]: ts('#DCDCAA', undefinedStyle), + [strings]: ts('#CE9178', undefinedStyle), + [numbers]: ts('#B5CEA8', undefinedStyle), + [keywords]: ts('#C586C0', undefinedStyle) + }); + + }); + + test('color defaults - light vs', async () => { + const themeData = ColorThemeData.createUnloadedTheme('foo'); + const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-defaults/themes/light_vs.json'); + themeData.location = URI.file(themeLocation); + await themeData.ensureLoaded(extensionResourceLoaderService); + + assert.equal(themeData.isLoaded, true); + + assertTokenStyles(themeData, { + [comments]: ts('#008000', undefinedStyle), + [variables]: ts(undefined, undefinedStyle), + [types]: ts(undefined, undefinedStyle), + [functions]: ts(undefined, undefinedStyle), + [strings]: ts('#a31515', undefinedStyle), + [numbers]: ts('#09885a', undefinedStyle), + [keywords]: ts('#0000ff', undefinedStyle) + }); + + }); + + test('color defaults - hc', async () => { + const themeData = ColorThemeData.createUnloadedTheme('foo'); + const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-defaults/themes/hc_black.json'); + themeData.location = URI.file(themeLocation); + await themeData.ensureLoaded(extensionResourceLoaderService); + + assert.equal(themeData.isLoaded, true); + + assertTokenStyles(themeData, { + [comments]: ts('#7ca668', undefinedStyle), + [variables]: ts('#9CDCFE', undefinedStyle), + [types]: ts('#4EC9B0', undefinedStyle), + [functions]: ts('#DCDCAA', undefinedStyle), + [strings]: ts('#ce9178', undefinedStyle), + [numbers]: ts('#b5cea8', undefinedStyle), + [keywords]: ts('#C586C0', undefinedStyle) + }); + + }); + + test('color defaults - kimbie dark', async () => { + const themeData = ColorThemeData.createUnloadedTheme('foo'); + const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json'); + themeData.location = URI.file(themeLocation); + await themeData.ensureLoaded(extensionResourceLoaderService); + + assert.equal(themeData.isLoaded, true); + + assertTokenStyles(themeData, { + [comments]: ts('#a57a4c', undefinedStyle), + [variables]: ts('#dc3958', undefinedStyle), + [types]: ts('#f06431', undefinedStyle), + [functions]: ts('#8ab1b0', undefinedStyle), + [strings]: ts('#889b4a', undefinedStyle), + [numbers]: ts('#f79a32', undefinedStyle), + [keywords]: ts('#98676a', undefinedStyle) + }); + + }); + + test('color defaults - abyss', async () => { + const themeData = ColorThemeData.createUnloadedTheme('foo'); + const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-abyss/themes/abyss-color-theme.json'); + themeData.location = URI.file(themeLocation); + await themeData.ensureLoaded(extensionResourceLoaderService); + + assert.equal(themeData.isLoaded, true); + + assertTokenStyles(themeData, { + [comments]: ts('#384887', undefinedStyle), + [variables]: ts(undefined, unsetStyle), + [types]: ts('#ffeebb', { underline: true }), + [functions]: ts('#ddbb88', unsetStyle), + [strings]: ts('#22aa44', undefinedStyle), + [numbers]: ts('#f280d0', undefinedStyle), + [keywords]: ts('#225588', undefinedStyle) + }); + + }); + + test('resolveScopes', async () => { + const themeData = ColorThemeData.createLoadedEmptyTheme('test', 'test'); + + const customTokenColors: ITokenColorCustomizations = { + textMateRules: [ + { + scope: 'variable', + settings: { + fontStyle: '', + foreground: '#F8F8F2' + } + }, + { + scope: 'keyword.operator', + settings: { + fontStyle: 'italic bold underline', + foreground: '#F92672' + } + }, + { + scope: 'storage', + settings: { + fontStyle: 'italic', + foreground: '#F92672' + } + }, + { + scope: ['storage.type', 'meta.structure.dictionary.json string.quoted.double.json'], + settings: { + foreground: '#66D9EF' + } + }, + { + scope: 'entity.name.type, entity.name.class, entity.name.namespace, entity.name.scope-resolution', + settings: { + fontStyle: 'underline', + foreground: '#A6E22E' + } + }, + ] + }; + + themeData.setCustomTokenColors(customTokenColors); + + let tokenStyle; + let defaultTokenStyle = undefined; + + tokenStyle = themeData.resolveScopes([['variable']]); + assertTokenStyle(tokenStyle, ts('#F8F8F2', unsetStyle), 'variable'); + + tokenStyle = themeData.resolveScopes([['keyword.operator']]); + assertTokenStyle(tokenStyle, ts('#F92672', { italic: true, bold: true, underline: true }), 'keyword'); + + tokenStyle = themeData.resolveScopes([['keyword']]); + assertTokenStyle(tokenStyle, defaultTokenStyle, 'keyword'); + + tokenStyle = themeData.resolveScopes([['keyword.operator']]); + assertTokenStyle(tokenStyle, ts('#F92672', { italic: true, bold: true, underline: true }), 'keyword.operator'); + + tokenStyle = themeData.resolveScopes([['keyword.operators']]); + assertTokenStyle(tokenStyle, defaultTokenStyle, 'keyword.operators'); + + tokenStyle = themeData.resolveScopes([['storage']]); + assertTokenStyle(tokenStyle, ts('#F92672', { italic: true }), 'storage'); + + tokenStyle = themeData.resolveScopes([['storage.type']]); + assertTokenStyle(tokenStyle, ts('#66D9EF', { italic: true }), 'storage.type'); + + tokenStyle = themeData.resolveScopes([['entity.name.class']]); + assertTokenStyle(tokenStyle, ts('#A6E22E', { underline: true }), 'entity.name.class'); + + tokenStyle = themeData.resolveScopes([['meta.structure.dictionary.json', 'string.quoted.double.json']]); + assertTokenStyle(tokenStyle, ts('#66D9EF', undefined), 'json property'); + + tokenStyle = themeData.resolveScopes([['keyword'], ['storage.type'], ['entity.name.class']]); + assertTokenStyle(tokenStyle, ts('#66D9EF', { italic: true }), 'storage.type'); + + }); + + test('rule matching', async () => { + const themeData = ColorThemeData.createLoadedEmptyTheme('test', 'test'); + themeData.setCustomColors({ 'editor.foreground': '#000000' }); + themeData.setCustomTokenStyleRules({ + 'types': '#ff0000', + 'classes': { foreground: '#0000ff', fontStyle: 'italic' }, + '*.static': { fontStyle: 'bold' }, + '*.declaration': { fontStyle: 'italic' }, + '*.async.static': { fontStyle: 'italic underline' }, + '*.async': { foreground: '#000fff', fontStyle: '-italic underline' } + }); + + assertTokenStyles(themeData, { + 'types': ts('#ff0000', undefinedStyle), + 'types.static': ts('#ff0000', { bold: true }), + 'types.static.declaration': ts('#ff0000', { bold: true, italic: true }), + 'classes': ts('#0000ff', { italic: true }), + 'classes.static.declaration': ts('#0000ff', { bold: true, italic: true }), + 'classes.declaration': ts('#0000ff', { italic: true }), + 'classes.declaration.async': ts('#000fff', { underline: true, italic: false }), + 'classes.declaration.async.static': ts('#000fff', { italic: true, underline: true, bold: true }), + }); + + }); +}); diff --git a/src/vs/workbench/services/untitled/common/untitledEditorService.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts similarity index 80% rename from src/vs/workbench/services/untitled/common/untitledEditorService.ts rename to src/vs/workbench/services/untitled/common/untitledTextEditorService.ts index c3a2213886..b235adaae3 100644 --- a/src/vs/workbench/services/untitled/common/untitledEditorService.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts @@ -6,18 +6,18 @@ import { URI } from 'vs/base/common/uri'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import * as arrays from 'vs/base/common/arrays'; -import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; import { IFilesConfiguration, IFileService } from 'vs/platform/files/common/files'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Event, Emitter } from 'vs/base/common/event'; import { ResourceMap } from 'vs/base/common/map'; -import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel'; +import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; import { Schemas } from 'vs/base/common/network'; import { Disposable } from 'vs/base/common/lifecycle'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { basename } from 'vs/base/common/resources'; -export const IUntitledEditorService = createDecorator('untitledEditorService'); +export const IUntitledTextEditorService = createDecorator('untitledTextEditorService'); export interface IModelLoadOrCreateOptions { resource?: URI; @@ -27,27 +27,27 @@ export interface IModelLoadOrCreateOptions { useResourcePath?: boolean; } -export interface IUntitledEditorService { +export interface IUntitledTextEditorService { _serviceBrand: undefined; /** - * Events for when untitled editors content changes (e.g. any keystroke). + * Events for when untitled text editors content changes (e.g. any keystroke). */ readonly onDidChangeContent: Event; /** - * Events for when untitled editors change (e.g. getting dirty, saved or reverted). + * Events for when untitled text editors change (e.g. getting dirty, saved or reverted). */ readonly onDidChangeDirty: Event; /** - * Events for when untitled editor encodings change. + * Events for when untitled text editor encodings change. */ readonly onDidChangeEncoding: Event; /** - * Events for when untitled editors are disposed. + * Events for when untitled text editors are disposed. */ readonly onDidDisposeModel: Event; @@ -57,7 +57,7 @@ export interface IUntitledEditorService { exists(resource: URI): boolean; /** - * Returns dirty untitled editors as resource URIs. + * Returns dirty untitled text editors as resource URIs. */ getDirty(resources?: URI[]): URI[]; @@ -83,7 +83,7 @@ export interface IUntitledEditorService { * It is valid to pass in a file resource. In that case the path will be used as identifier. * The use case is to be able to create a new file with a specific path with VSCode. */ - createOrGet(resource?: URI, mode?: string, initialValue?: string, encoding?: string): UntitledEditorInput; + createOrGet(resource?: URI, mode?: string, initialValue?: string, encoding?: string): UntitledTextEditorInput; /** * Creates a new untitled model with the optional resource URI or returns an existing one @@ -92,7 +92,7 @@ export interface IUntitledEditorService { * It is valid to pass in a file resource. In that case the path will be used as identifier. * The use case is to be able to create a new file with a specific path with VSCode. */ - loadOrCreate(options: IModelLoadOrCreateOptions): Promise; + loadOrCreate(options: IModelLoadOrCreateOptions): Promise; /** * A check to find out if a untitled resource has a file path associated or not. @@ -110,24 +110,24 @@ export interface IUntitledEditorService { getEncoding(resource: URI): string | undefined; } -export class UntitledEditorService extends Disposable implements IUntitledEditorService { +export class UntitledTextEditorService extends Disposable implements IUntitledTextEditorService { _serviceBrand: undefined; - private mapResourceToInput = new ResourceMap(); + private mapResourceToInput = new ResourceMap(); private mapResourceToAssociatedFilePath = new ResourceMap(); - private readonly _onDidChangeContent: Emitter = this._register(new Emitter()); - readonly onDidChangeContent: Event = this._onDidChangeContent.event; + private readonly _onDidChangeContent = this._register(new Emitter()); + readonly onDidChangeContent = this._onDidChangeContent.event; - private readonly _onDidChangeDirty: Emitter = this._register(new Emitter()); - readonly onDidChangeDirty: Event = this._onDidChangeDirty.event; + private readonly _onDidChangeDirty = this._register(new Emitter()); + readonly onDidChangeDirty = this._onDidChangeDirty.event; - private readonly _onDidChangeEncoding: Emitter = this._register(new Emitter()); - readonly onDidChangeEncoding: Event = this._onDidChangeEncoding.event; + private readonly _onDidChangeEncoding = this._register(new Emitter()); + readonly onDidChangeEncoding = this._onDidChangeEncoding.event; - private readonly _onDidDisposeModel: Emitter = this._register(new Emitter()); - readonly onDidDisposeModel: Event = this._onDidDisposeModel.event; + private readonly _onDidDisposeModel = this._register(new Emitter()); + readonly onDidDisposeModel = this._onDidDisposeModel.event; constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -137,11 +137,11 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor super(); } - protected get(resource: URI): UntitledEditorInput | undefined { + protected get(resource: URI): UntitledTextEditorInput | undefined { return this.mapResourceToInput.get(resource); } - protected getAll(resources?: URI[]): UntitledEditorInput[] { + protected getAll(resources?: URI[]): UntitledTextEditorInput[] { if (resources) { return arrays.coalesce(resources.map(r => this.get(r))); } @@ -160,7 +160,6 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor untitledInputs.forEach(input => { if (input) { input.revert(); - input.dispose(); reverted.push(input.getResource()); } @@ -182,7 +181,7 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor } getDirty(resources?: URI[]): URI[] { - let inputs: UntitledEditorInput[]; + let inputs: UntitledTextEditorInput[]; if (resources) { inputs = arrays.coalesce(resources.map(r => this.get(r))); } else { @@ -194,11 +193,11 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor .map(i => i.getResource()); } - loadOrCreate(options: IModelLoadOrCreateOptions = Object.create(null)): Promise { + loadOrCreate(options: IModelLoadOrCreateOptions = Object.create(null)): Promise { return this.createOrGet(options.resource, options.mode, options.initialValue, options.encoding, options.useResourcePath).resolve(); } - createOrGet(resource?: URI, mode?: string, initialValue?: string, encoding?: string, hasAssociatedFilePath: boolean = false): UntitledEditorInput { + createOrGet(resource?: URI, mode?: string, initialValue?: string, encoding?: string, hasAssociatedFilePath: boolean = false): UntitledTextEditorInput { if (resource) { // Massage resource if it comes with known file based resource @@ -221,7 +220,7 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor return this.doCreate(resource, hasAssociatedFilePath, mode, initialValue, encoding); } - private doCreate(resource?: URI, hasAssociatedFilePath?: boolean, mode?: string, initialValue?: string, encoding?: string): UntitledEditorInput { + private doCreate(resource?: URI, hasAssociatedFilePath?: boolean, mode?: string, initialValue?: string, encoding?: string): UntitledTextEditorInput { let untitledResource: URI; if (resource) { untitledResource = resource; @@ -243,7 +242,7 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor } } - const input = this.instantiationService.createInstance(UntitledEditorInput, untitledResource, !!hasAssociatedFilePath, mode, initialValue, encoding); + const input = this.instantiationService.createInstance(UntitledTextEditorInput, untitledResource, !!hasAssociatedFilePath, mode, initialValue, encoding); const contentListener = input.onDidModelChangeContent(() => this._onDidChangeContent.fire(untitledResource)); const dirtyListener = input.onDidChangeDirty(() => this._onDidChangeDirty.fire(untitledResource)); @@ -284,4 +283,4 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor } } -registerSingleton(IUntitledEditorService, UntitledEditorService, true); +registerSingleton(IUntitledTextEditorService, UntitledTextEditorService, true); diff --git a/src/vs/workbench/services/url/electron-browser/urlService.ts b/src/vs/workbench/services/url/electron-browser/urlService.ts index 8cf6ecbc76..ab882a96f8 100644 --- a/src/vs/workbench/services/url/electron-browser/urlService.ts +++ b/src/vs/workbench/services/url/electron-browser/urlService.ts @@ -8,7 +8,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { URLHandlerChannel } from 'vs/platform/url/common/urlIpc'; import { URLService } from 'vs/platform/url/node/urlService'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IOpenerService, IOpener, matchesScheme } from 'vs/platform/opener/common/opener'; import product from 'vs/platform/product/common/product'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; @@ -20,7 +20,7 @@ export interface IRelayOpenURLOptions extends IOpenURLOptions { openExternal?: boolean; } -export class RelayURLService extends URLService implements IURLHandler { +export class RelayURLService extends URLService implements IURLHandler, IOpener { private urlService: IURLService; @@ -51,11 +51,15 @@ export class RelayURLService extends URLService implements IURLHandler { return uri.with({ query }); } - async open(resource: URI, options?: IRelayOpenURLOptions): Promise { - if (resource.scheme !== product.urlProtocol) { + async open(resource: URI | string, options?: IRelayOpenURLOptions): Promise { + + if (!matchesScheme(resource, product.urlProtocol)) { return false; } + if (typeof resource === 'string') { + resource = URI.parse(resource); + } return await this.urlService.open(resource, options); } diff --git a/src/vs/workbench/services/userData/common/fileUserDataProvider.ts b/src/vs/workbench/services/userData/common/fileUserDataProvider.ts index 6066808d4e..463d4b5e53 100644 --- a/src/vs/workbench/services/userData/common/fileUserDataProvider.ts +++ b/src/vs/workbench/services/userData/common/fileUserDataProvider.ts @@ -5,14 +5,19 @@ import { Event, Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { IFileSystemProviderWithFileReadWriteCapability, IFileChange, IWatchOptions, IStat, FileOverwriteOptions, FileType, FileWriteOptions, FileDeleteOptions, FileSystemProviderCapabilities, IFileSystemProviderWithOpenReadWriteCloseCapability, FileOpenOptions, hasReadWriteCapability, hasOpenReadWriteCloseCapability } from 'vs/platform/files/common/files'; +import { IFileSystemProviderWithFileReadWriteCapability, IFileChange, IWatchOptions, IStat, FileOverwriteOptions, FileType, FileWriteOptions, FileDeleteOptions, FileSystemProviderCapabilities, IFileSystemProviderWithOpenReadWriteCloseCapability, FileOpenOptions, hasReadWriteCapability, hasOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadStreamCapability, FileReadStreamOptions, hasFileReadStreamCapability } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; import { startsWith } from 'vs/base/common/strings'; import { BACKUPS } from 'vs/platform/environment/common/environment'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { ReadableStreamEvents } from 'vs/base/common/stream'; -export class FileUserDataProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability { +export class FileUserDataProvider extends Disposable implements + IFileSystemProviderWithFileReadWriteCapability, + IFileSystemProviderWithOpenReadWriteCloseCapability, + IFileSystemProviderWithFileReadStreamCapability { readonly capabilities: FileSystemProviderCapabilities = this.fileSystemProvider.capabilities; readonly onDidChangeCapabilities: Event = Event.None; @@ -60,6 +65,13 @@ export class FileUserDataProvider extends Disposable implements IFileSystemProvi throw new Error('not supported'); } + readFileStream(resource: URI, opts: FileReadStreamOptions, token?: CancellationToken): ReadableStreamEvents { + if (hasFileReadStreamCapability(this.fileSystemProvider)) { + return this.fileSystemProvider.readFileStream(this.toFileSystemResource(resource), opts, token); + } + throw new Error('not supported'); + } + readdir(resource: URI): Promise<[string, FileType][]> { return this.fileSystemProvider.readdir(this.toFileSystemResource(resource)); } diff --git a/src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts b/src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts index 30c36fe4df..11335b84d5 100644 --- a/src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts +++ b/src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts @@ -6,7 +6,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import * as resources from 'vs/base/common/resources'; -import { FileChangeType, IFileSystemProvider, FileType, IWatchOptions, IStat, FileSystemProviderErrorCode, FileSystemProviderError, FileWriteOptions, IFileChange, FileDeleteOptions, FileSystemProviderCapabilities, FileOverwriteOptions } from 'vs/platform/files/common/files'; +import { FileChangeType, FileType, IWatchOptions, IStat, FileSystemProviderErrorCode, FileSystemProviderError, FileWriteOptions, IFileChange, FileDeleteOptions, FileSystemProviderCapabilities, FileOverwriteOptions, IFileSystemProviderWithFileReadWriteCapability } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; class File implements IStat { @@ -50,7 +50,7 @@ class Directory implements IStat { export type Entry = File | Directory; -export class InMemoryFileSystemProvider extends Disposable implements IFileSystemProvider { +export class InMemoryFileSystemProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability { readonly capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.FileReadWrite; readonly onDidChangeCapabilities: Event = Event.None; diff --git a/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts b/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts index d52f4ac8d3..49f6f6a802 100644 --- a/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts +++ b/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts @@ -23,6 +23,15 @@ import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/enviro import { Emitter, Event } from 'vs/base/common/event'; import { timeout } from 'vs/base/common/async'; +class TestBrowserWorkbenchEnvironmentService extends BrowserWorkbenchEnvironmentService { + + testUserRoamingDataHome!: URI; + + get userRoamingDataHome(): URI { + return this.testUserRoamingDataHome; + } +} + suite('FileUserDataProvider', () => { let testObject: IFileService; @@ -47,8 +56,8 @@ suite('FileUserDataProvider', () => { userDataResource = URI.file(userDataPath).with({ scheme: Schemas.userData }); await Promise.all([pfs.mkdirp(userDataPath), pfs.mkdirp(backupsPath)]); - const environmentService = new BrowserWorkbenchEnvironmentService({ remoteAuthority: 'remote', workspaceId: 'workspaceId', logsPath: URI.file('logFile') }); - environmentService.userRoamingDataHome = userDataResource; + const environmentService = new TestBrowserWorkbenchEnvironmentService({ remoteAuthority: 'remote', workspaceId: 'workspaceId', logsPath: URI.file('logFile') }); + environmentService.testUserRoamingDataHome = userDataResource; const userDataFileSystemProvider = new FileUserDataProvider(URI.file(userDataPath), URI.file(backupsPath), diskFileSystemProvider, environmentService); disposables.add(userDataFileSystemProvider); @@ -321,8 +330,8 @@ suite('FileUserDataProvider - Watching', () => { localUserDataResource = URI.file(userDataPath); userDataResource = localUserDataResource.with({ scheme: Schemas.userData }); - const environmentService = new BrowserWorkbenchEnvironmentService({ remoteAuthority: 'remote', workspaceId: 'workspaceId', logsPath: URI.file('logFile') }); - environmentService.userRoamingDataHome = userDataResource; + const environmentService = new TestBrowserWorkbenchEnvironmentService({ remoteAuthority: 'remote', workspaceId: 'workspaceId', logsPath: URI.file('logFile') }); + environmentService.testUserRoamingDataHome = userDataResource; const userDataFileSystemProvider = new FileUserDataProvider(localUserDataResource, localBackupsResource, new TestFileSystemProvider(fileEventEmitter.event), environmentService); disposables.add(userDataFileSystemProvider); diff --git a/src/vs/workbench/services/userDataSync/common/settingsMergeService.ts b/src/vs/workbench/services/userDataSync/common/settingsMergeService.ts index f589f4bff8..fd90c9088a 100644 --- a/src/vs/workbench/services/userDataSync/common/settingsMergeService.ts +++ b/src/vs/workbench/services/userDataSync/common/settingsMergeService.ts @@ -156,7 +156,7 @@ class SettingsMergeService implements ISettingsMergeService { const remote = parse(remoteContent); const remoteModel = this.modelService.createModel(localContent, this.modeService.create('jsonc')); const ignored = ignoredSettings.reduce((set, key) => { set.add(key); return set; }, new Set()); - for (const key of Object.keys(ignoredSettings)) { + for (const key of ignoredSettings) { if (ignored.has(key)) { this.editSetting(remoteModel, key, undefined); this.editSetting(remoteModel, key, remote[key]); @@ -166,8 +166,8 @@ class SettingsMergeService implements ISettingsMergeService { } private editSetting(model: ITextModel, key: string, value: any | undefined): void { - const insertSpaces = false; - const tabSize = 4; + const insertSpaces = model.getOptions().insertSpaces; + const tabSize = model.getOptions().tabSize; const eol = model.getEOL(); const edit = setProperty(model.getValue(), [key], value, { tabSize, insertSpaces, eol })[0]; if (edit) { diff --git a/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts b/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts new file mode 100644 index 0000000000..f7427e9896 --- /dev/null +++ b/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { FormattingOptions } from 'vs/base/common/jsonFormatter'; +import { URI } from 'vs/base/common/uri'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; + +class UserDataSyncUtilService implements IUserDataSyncUtilService { + + _serviceBrand: undefined; + + constructor( + @IKeybindingService private readonly keybindingsService: IKeybindingService, + @ITextModelService private readonly textModelService: ITextModelService, + ) { } + + public async resolveUserBindings(userBindings: string[]): Promise> { + const keys: IStringDictionary = {}; + for (const userbinding of userBindings) { + keys[userbinding] = this.keybindingsService.resolveUserBinding(userbinding).map(part => part.getUserSettingsLabel()).join(' '); + } + return keys; + } + + async resolveFormattingOptions(resource: URI): Promise { + const modelReference = await this.textModelService.createModelReference(resource); + const { insertSpaces, tabSize } = modelReference.object.textEditorModel.getOptions(); + const eol = modelReference.object.textEditorModel.getEOL(); + modelReference.dispose(); + return { eol, insertSpaces, tabSize }; + } +} + +registerSingleton(IUserDataSyncUtilService, UserDataSyncUtilService); diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts new file mode 100644 index 0000000000..10a2c49b16 --- /dev/null +++ b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts @@ -0,0 +1,162 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { Event, Emitter } from 'vs/base/common/event'; +import { URI } from 'vs/base/common/uri'; +import { Disposable, IDisposable, toDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; +import { TernarySearchTree } from 'vs/base/common/map'; + +export const enum WorkingCopyCapabilities { + + /** + * Signals that the working copy participates + * in auto saving as configured by the user. + */ + AutoSave = 1 << 1 +} + +export interface IWorkingCopy { + + //#region Dirty Tracking + + readonly onDidChangeDirty: Event; + + isDirty(): boolean; + + //#endregion + + + readonly resource: URI; + + readonly capabilities: WorkingCopyCapabilities; +} + +export const IWorkingCopyService = createDecorator('workingCopyService'); + +export interface IWorkingCopyService { + + _serviceBrand: undefined; + + //#region Dirty Tracking + + readonly onDidChangeDirty: Event; + + readonly dirtyCount: number; + + readonly hasDirty: boolean; + + isDirty(resource: URI): boolean; + + //#endregion + + + //#region Registry + + registerWorkingCopy(workingCopy: IWorkingCopy): IDisposable; + + //#endregion +} + +export class WorkingCopyService extends Disposable implements IWorkingCopyService { + + _serviceBrand: undefined; + + //#region Dirty Tracking + + private readonly _onDidChangeDirty = this._register(new Emitter()); + readonly onDidChangeDirty = this._onDidChangeDirty.event; + + isDirty(resource: URI): boolean { + const workingCopies = this.mapResourceToWorkingCopy.get(resource.toString()); + if (workingCopies) { + for (const workingCopy of workingCopies) { + if (workingCopy.isDirty()) { + return true; + } + } + } + + return false; + } + + get hasDirty(): boolean { + for (const workingCopy of this.workingCopies) { + if (workingCopy.isDirty()) { + return true; + } + } + + return false; + } + + get dirtyCount(): number { + let totalDirtyCount = 0; + + for (const workingCopy of this.workingCopies) { + if (workingCopy.isDirty()) { + totalDirtyCount++; + } + } + + return totalDirtyCount; + } + + //#endregion + + + //#region Registry + + private mapResourceToWorkingCopy = TernarySearchTree.forPaths>(); + private workingCopies = new Set(); + + registerWorkingCopy(workingCopy: IWorkingCopy): IDisposable { + const disposables = new DisposableStore(); + + // Registry + let workingCopiesForResource = this.mapResourceToWorkingCopy.get(workingCopy.resource.toString()); + if (!workingCopiesForResource) { + workingCopiesForResource = new Set(); + this.mapResourceToWorkingCopy.set(workingCopy.resource.toString(), workingCopiesForResource); + } + + workingCopiesForResource.add(workingCopy); + + this.workingCopies.add(workingCopy); + + // Dirty Events + disposables.add(workingCopy.onDidChangeDirty(() => this._onDidChangeDirty.fire(workingCopy))); + if (workingCopy.isDirty()) { + this._onDidChangeDirty.fire(workingCopy); + } + + return toDisposable(() => { + this.unregisterWorkingCopy(workingCopy); + dispose(disposables); + }); + } + + private unregisterWorkingCopy(workingCopy: IWorkingCopy): void { + + // Remove from registry + const workingCopiesForResource = this.mapResourceToWorkingCopy.get(workingCopy.resource.toString()); + if (workingCopiesForResource && workingCopiesForResource.delete(workingCopy) && workingCopiesForResource.size === 0) { + this.mapResourceToWorkingCopy.delete(workingCopy.resource.toString()); + } + + this.workingCopies.delete(workingCopy); + + // If copy is dirty, ensure to fire an event to signal the dirty change + // (a disposed working copy cannot account for being dirty in our model) + if (workingCopy.isDirty()) { + this._onDidChangeDirty.fire(workingCopy); + } + } + + //#endregion +} + +registerSingleton(IWorkingCopyService, WorkingCopyService, true); diff --git a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts new file mode 100644 index 0000000000..c7ecec2381 --- /dev/null +++ b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts @@ -0,0 +1,145 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { URI } from 'vs/base/common/uri'; +import { Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { TestWorkingCopyService } from 'vs/workbench/test/workbenchTestServices'; + +suite('WorkingCopyService', () => { + + class TestWorkingCopy extends Disposable implements IWorkingCopy { + + private readonly _onDidChangeDirty = this._register(new Emitter()); + readonly onDidChangeDirty = this._onDidChangeDirty.event; + + private readonly _onDispose = this._register(new Emitter()); + readonly onDispose = this._onDispose.event; + + readonly capabilities = 0; + + private dirty = false; + + constructor(public readonly resource: URI, isDirty = false) { + super(); + + this.dirty = isDirty; + } + + setDirty(dirty: boolean): void { + if (this.dirty !== dirty) { + this.dirty = dirty; + this._onDidChangeDirty.fire(); + } + } + + isDirty(): boolean { + return this.dirty; + } + + dispose(): void { + this._onDispose.fire(); + + super.dispose(); + } + } + + test('registry - basics', () => { + const service = new TestWorkingCopyService(); + + const onDidChangeDirty: IWorkingCopy[] = []; + service.onDidChangeDirty(copy => onDidChangeDirty.push(copy)); + + assert.equal(service.hasDirty, false); + assert.equal(service.dirtyCount, 0); + assert.equal(service.isDirty(URI.file('/')), false); + + // resource 1 + const resource1 = URI.file('/some/folder/file.txt'); + const copy1 = new TestWorkingCopy(resource1); + const unregister1 = service.registerWorkingCopy(copy1); + + assert.equal(service.dirtyCount, 0); + assert.equal(service.isDirty(resource1), false); + assert.equal(service.hasDirty, false); + + copy1.setDirty(true); + + assert.equal(service.dirtyCount, 1); + assert.equal(service.isDirty(resource1), true); + assert.equal(service.hasDirty, true); + assert.equal(onDidChangeDirty.length, 1); + assert.equal(onDidChangeDirty[0], copy1); + + copy1.setDirty(false); + + assert.equal(service.dirtyCount, 0); + assert.equal(service.isDirty(resource1), false); + assert.equal(service.hasDirty, false); + assert.equal(onDidChangeDirty.length, 2); + assert.equal(onDidChangeDirty[1], copy1); + + unregister1.dispose(); + + // resource 2 + const resource2 = URI.file('/some/folder/file-dirty.txt'); + const copy2 = new TestWorkingCopy(resource2, true); + const unregister2 = service.registerWorkingCopy(copy2); + + assert.equal(service.dirtyCount, 1); + assert.equal(service.isDirty(resource2), true); + assert.equal(service.hasDirty, true); + + assert.equal(onDidChangeDirty.length, 3); + assert.equal(onDidChangeDirty[2], copy2); + + unregister2.dispose(); + assert.equal(service.dirtyCount, 0); + assert.equal(service.hasDirty, false); + assert.equal(onDidChangeDirty.length, 4); + assert.equal(onDidChangeDirty[3], copy2); + }); + + test('registry - multiple copies on same resource', () => { + const service = new TestWorkingCopyService(); + + const onDidChangeDirty: IWorkingCopy[] = []; + service.onDidChangeDirty(copy => onDidChangeDirty.push(copy)); + + const resource = URI.parse('custom://some/folder/custom.txt'); + + const copy1 = new TestWorkingCopy(resource); + const unregister1 = service.registerWorkingCopy(copy1); + + const copy2 = new TestWorkingCopy(resource); + const unregister2 = service.registerWorkingCopy(copy2); + + copy1.setDirty(true); + + assert.equal(service.dirtyCount, 1); + assert.equal(onDidChangeDirty.length, 1); + assert.equal(service.isDirty(resource), true); + + copy2.setDirty(true); + + assert.equal(service.dirtyCount, 2); + assert.equal(onDidChangeDirty.length, 2); + assert.equal(service.isDirty(resource), true); + + unregister1.dispose(); + + assert.equal(service.dirtyCount, 1); + assert.equal(onDidChangeDirty.length, 3); + assert.equal(service.isDirty(resource), true); + + unregister2.dispose(); + + assert.equal(service.dirtyCount, 0); + assert.equal(onDidChangeDirty.length, 4); + assert.equal(service.isDirty(resource), false); + }); +}); diff --git a/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts b/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts index 67935a68d6..ebda69b593 100644 --- a/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts +++ b/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts @@ -8,14 +8,10 @@ import { URI } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IJSONEditingService, JSONEditingError, JSONEditingErrorCode } from 'vs/workbench/services/configuration/common/jsonEditing'; -import { IWorkspaceIdentifier, IWorkspaceFolderCreationData, IWorkspacesService, rewriteWorkspaceFileForNewLocation, WORKSPACE_FILTER } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceIdentifier, IWorkspaceFolderCreationData, IWorkspacesService, rewriteWorkspaceFileForNewLocation, WORKSPACE_FILTER, IEnterWorkspaceResult, hasWorkspaceFileExtension, WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; -import { IStorageService } from 'vs/platform/storage/common/storage'; import { ConfigurationScope, IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IBackupFileService, toBackupWorkspaceResource } from 'vs/workbench/services/backup/common/backup'; -import { BackupFileService } from 'vs/workbench/services/backup/common/backupFileService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { distinct } from 'vs/base/common/arrays'; import { isEqual, getComparisonKey } from 'vs/base/common/resources'; @@ -36,9 +32,6 @@ export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditi @IJSONEditingService private readonly jsonEditingService: IJSONEditingService, @IWorkspaceContextService private readonly contextService: WorkspaceService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IStorageService private readonly storageService: IStorageService, - @IExtensionService private readonly extensionService: IExtensionService, - @IBackupFileService private readonly backupFileService: IBackupFileService, @INotificationService private readonly notificationService: INotificationService, @ICommandService private readonly commandService: ICommandService, @IFileService private readonly fileService: IFileService, @@ -50,13 +43,25 @@ export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditi @IHostService protected readonly hostService: IHostService ) { } - pickNewWorkspacePath(): Promise { - return this.fileDialogService.showSaveDialog({ + async pickNewWorkspacePath(): Promise { + let workspacePath = await this.fileDialogService.showSaveDialog({ saveLabel: mnemonicButtonLabel(nls.localize('save', "Save")), title: nls.localize('saveWorkspace', "Save Workspace"), filters: WORKSPACE_FILTER, defaultUri: this.fileDialogService.defaultWorkspacePath() }); + + if (!workspacePath) { + return undefined; // canceled {{SQL CARBON EDIT}} strict-null-checks + } + + if (!hasWorkspaceFileExtension(workspacePath)) { + // Always ensure we have workspace file extension + // (see https://github.com/microsoft/vscode/issues/84818) + workspacePath = workspacePath.with({ path: `${workspacePath.path}.${WORKSPACE_EXTENSION}` }); + } + + return workspacePath; } updateFolders(index: number, deleteCount?: number, foldersToAdd?: IWorkspaceFolderCreationData[], donotNotifyError?: boolean): Promise { @@ -127,7 +132,7 @@ export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditi // Do not allow workspace folders with scheme different than the current remote scheme const schemas = this.contextService.getWorkspace().folders.map(f => f.uri.scheme); if (schemas.length && foldersToAdd.some(f => schemas.indexOf(f.uri.scheme) === -1)) { - return Promise.reject(new Error(nls.localize('differentSchemeRoots', "Workspace folders from different providers are not allowed in the same workspace."))); + throw new Error(nls.localize('differentSchemeRoots', "Workspace folders from different providers are not allowed in the same workspace.")); } } @@ -267,7 +272,9 @@ export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditi ); } - async enterWorkspace(path: URI): Promise { + abstract async enterWorkspace(path: URI): Promise; + + protected async doEnterWorkspace(path: URI): Promise { if (!!this.environmentService.extensionTestsLocationURI) { throw new Error('Entering a new workspace is not possible in tests.'); } @@ -282,34 +289,7 @@ export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditi const workspaceImpl = this.contextService as WorkspaceService; await workspaceImpl.initialize(workspace); - const result = await this.workspacesService.enterWorkspace(path); - if (result) { - - // Migrate storage to new workspace - await this.migrateStorage(result.workspace); - - // Reinitialize backup service - this.environmentService.configuration.backupPath = result.backupPath; - this.environmentService.configuration.backupWorkspaceResource = result.backupPath ? toBackupWorkspaceResource(result.backupPath, this.environmentService) : undefined; - if (this.backupFileService instanceof BackupFileService) { - this.backupFileService.reinitialize(); - } - } - - // TODO@aeschli: workaround until restarting works - if (this.environmentService.configuration.remoteAuthority) { - this.hostService.reload(); - } - - // Restart the extension host: entering a workspace means a new location for - // storage and potentially a change in the workspace.rootPath property. - else { - this.extensionService.restartExtensionHost(); - } - } - - private migrateStorage(toWorkspace: IWorkspaceIdentifier): Promise { - return this.storageService.migrate(toWorkspace); + return this.workspacesService.enterWorkspace(path); } private migrateWorkspaceSettings(toWorkspace: IWorkspaceIdentifier): Promise { diff --git a/src/vs/workbench/services/workspaces/browser/workspaceEditingService.ts b/src/vs/workbench/services/workspaces/browser/workspaceEditingService.ts index 339c8fef6e..95fabe3465 100644 --- a/src/vs/workbench/services/workspaces/browser/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspaces/browser/workspaceEditingService.ts @@ -7,9 +7,6 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; -import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IFileService } from 'vs/platform/files/common/files'; @@ -21,6 +18,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { AbstractWorkspaceEditingService } from 'vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { URI } from 'vs/base/common/uri'; export class BrowserWorkspaceEditingService extends AbstractWorkspaceEditingService { @@ -30,9 +28,6 @@ export class BrowserWorkspaceEditingService extends AbstractWorkspaceEditingServ @IJSONEditingService jsonEditingService: IJSONEditingService, @IWorkspaceContextService contextService: WorkspaceService, @IConfigurationService configurationService: IConfigurationService, - @IStorageService storageService: IStorageService, - @IExtensionService extensionService: IExtensionService, - @IBackupFileService backupFileService: IBackupFileService, @INotificationService notificationService: INotificationService, @ICommandService commandService: ICommandService, @IFileService fileService: IFileService, @@ -43,9 +38,17 @@ export class BrowserWorkspaceEditingService extends AbstractWorkspaceEditingServ @IDialogService dialogService: IDialogService, @IHostService hostService: IHostService ) { - super(jsonEditingService, contextService, configurationService, storageService, extensionService, backupFileService, notificationService, commandService, fileService, textFileService, workspacesService, environmentService, fileDialogService, dialogService, hostService); + super(jsonEditingService, contextService, configurationService, notificationService, commandService, fileService, textFileService, workspacesService, environmentService, fileDialogService, dialogService, hostService); + } + + async enterWorkspace(path: URI): Promise { + const result = await this.doEnterWorkspace(path); + if (result) { + + // Open workspace in same window + await this.hostService.openWindow([{ workspaceUri: path }], { forceReuseWindow: true }); + } } } registerSingleton(IWorkspaceEditingService, BrowserWorkspaceEditingService, true); - diff --git a/src/vs/workbench/services/workspaces/browser/workspacesService.ts b/src/vs/workbench/services/workspaces/browser/workspacesService.ts index db573c09c0..32393f351c 100644 --- a/src/vs/workbench/services/workspaces/browser/workspacesService.ts +++ b/src/vs/workbench/services/workspaces/browser/workspacesService.ts @@ -12,7 +12,6 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/ import { ILogService } from 'vs/platform/log/common/log'; import { Disposable } from 'vs/base/common/lifecycle'; import { getWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces'; -import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IFileService } from 'vs/platform/files/common/files'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { joinPath } from 'vs/base/common/resources'; @@ -31,7 +30,6 @@ export class BrowserWorkspacesService extends Disposable implements IWorkspacesS @IStorageService private readonly storageService: IStorageService, @IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService, @ILogService private readonly logService: ILogService, - @IHostService private readonly hostService: IHostService, @IFileService private readonly fileService: IFileService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService ) { @@ -123,18 +121,12 @@ export class BrowserWorkspacesService extends Disposable implements IWorkspacesS //#region Workspace Management async enterWorkspace(path: URI): Promise { - - // Open workspace in same window - await this.hostService.openWindow([{ workspaceUri: path }], { forceReuseWindow: true }); - - return { - workspace: await this.getWorkspaceIdentifier(path) - }; + return { workspace: await this.getWorkspaceIdentifier(path) }; } async createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise { const randomId = (Date.now() + Math.round(Math.random() * 1000)).toString(); - const newUntitledWorkspacePath = joinPath(this.environmentService.untitledWorkspacesHome, `${randomId}.${WORKSPACE_EXTENSION}`); + const newUntitledWorkspacePath = joinPath(this.environmentService.untitledWorkspacesHome, `Untitled-${randomId}.${WORKSPACE_EXTENSION}`); // Build array of workspace folders to store const storedWorkspaceFolder: IStoredWorkspaceFolder[] = []; diff --git a/src/vs/workbench/services/workspaces/electron-browser/workspaceEditingService.ts b/src/vs/workbench/services/workspaces/electron-browser/workspaceEditingService.ts index 1496e8c92a..e15a76e8da 100644 --- a/src/vs/workbench/services/workspaces/electron-browser/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspaces/electron-browser/workspaceEditingService.ts @@ -8,13 +8,14 @@ import { URI } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; -import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspacesService, isUntitledWorkspace, IWorkspaceIdentifier, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { toBackupWorkspaceResource } from 'vs/workbench/services/backup/electron-browser/backup'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { isEqual, basename, isEqualOrParent } from 'vs/base/common/resources'; +import { isEqual, basename } from 'vs/base/common/resources'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IFileService } from 'vs/platform/files/common/files'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -29,6 +30,7 @@ import { AbstractWorkspaceEditingService } from 'vs/workbench/services/workspace import { IElectronService } from 'vs/platform/electron/node/electron'; import { isMacintosh, isWindows, isLinux } from 'vs/base/common/platform'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; +import { BackupFileService } from 'vs/workbench/services/backup/common/backupFileService'; export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingService { @@ -39,9 +41,9 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi @IWorkspaceContextService contextService: WorkspaceService, @IElectronService private electronService: IElectronService, @IConfigurationService configurationService: IConfigurationService, - @IStorageService storageService: IStorageService, - @IExtensionService extensionService: IExtensionService, - @IBackupFileService backupFileService: IBackupFileService, + @IStorageService private storageService: IStorageService, + @IExtensionService private extensionService: IExtensionService, + @IBackupFileService private backupFileService: IBackupFileService, @INotificationService notificationService: INotificationService, @ICommandService commandService: ICommandService, @IFileService fileService: IFileService, @@ -54,7 +56,7 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi @ILabelService private readonly labelService: ILabelService, @IHostService hostService: IHostService, ) { - super(jsonEditingService, contextService, configurationService, storageService, extensionService, backupFileService, notificationService, commandService, fileService, textFileService, workspacesService, environmentService, fileDialogService, dialogService, hostService); + super(jsonEditingService, contextService, configurationService, notificationService, commandService, fileService, textFileService, workspacesService, environmentService, fileDialogService, dialogService, hostService); this.registerListeners(); } @@ -74,7 +76,7 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi } const workspaceIdentifier = this.getCurrentWorkspaceIdentifier(); - if (!workspaceIdentifier || !isEqualOrParent(workspaceIdentifier.configPath, this.environmentService.untitledWorkspacesHome)) { + if (!workspaceIdentifier || !isUntitledWorkspace(workspaceIdentifier.configPath, this.environmentService)) { return false; // only care about untitled workspaces to ask for saving } @@ -122,7 +124,7 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi // Save: save workspace, but do not veto unload if path provided case ConfirmResult.SAVE: { const newWorkspacePath = await this.pickNewWorkspacePath(); - if (!newWorkspacePath) { + if (!newWorkspacePath || !hasWorkspaceFileExtension(newWorkspacePath)) { return true; // keep veto if no target was provided } @@ -165,6 +167,37 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi return true; // OK } + + async enterWorkspace(path: URI): Promise { + const result = await this.doEnterWorkspace(path); + if (result) { + + // Migrate storage to new workspace + await this.migrateStorage(result.workspace); + + // Reinitialize backup service + this.environmentService.configuration.backupPath = result.backupPath; + this.environmentService.configuration.backupWorkspaceResource = result.backupPath ? toBackupWorkspaceResource(result.backupPath, this.environmentService) : undefined; + if (this.backupFileService instanceof BackupFileService) { + this.backupFileService.reinitialize(); + } + } + + // TODO@aeschli: workaround until restarting works + if (this.environmentService.configuration.remoteAuthority) { + this.hostService.reload(); + } + + // Restart the extension host: entering a workspace means a new location for + // storage and potentially a change in the workspace.rootPath property. + else { + this.extensionService.restartExtensionHost(); + } + } + + private migrateStorage(toWorkspace: IWorkspaceIdentifier): Promise { + return this.storageService.migrate(toWorkspace); + } } registerSingleton(IWorkspaceEditingService, NativeWorkspaceEditingService, true); diff --git a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts index 3f30fa6bab..7af8bb48b5 100644 --- a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts @@ -115,14 +115,14 @@ suite('Workbench base editor', () => { }); test('EditorDescriptor', () => { - let d = new EditorDescriptor(MyEditor, 'id', 'name'); + let d = EditorDescriptor.create(MyEditor, 'id', 'name'); assert.strictEqual(d.getId(), 'id'); assert.strictEqual(d.getName(), 'name'); }); test('Editor Registration', function () { - let d1 = new EditorDescriptor(MyEditor, 'id1', 'name'); - let d2 = new EditorDescriptor(MyOtherEditor, 'id2', 'name'); + let d1 = EditorDescriptor.create(MyEditor, 'id1', 'name'); + let d2 = EditorDescriptor.create(MyOtherEditor, 'id2', 'name'); let oldEditorsCnt = EditorRegistry.getEditors().length; let oldInputCnt = (EditorRegistry).getEditorInputs().length; @@ -142,8 +142,8 @@ suite('Workbench base editor', () => { }); test('Editor Lookup favors specific class over superclass (match on specific class)', function () { - let d1 = new EditorDescriptor(MyEditor, 'id1', 'name'); - let d2 = new EditorDescriptor(MyOtherEditor, 'id2', 'name'); + let d1 = EditorDescriptor.create(MyEditor, 'id1', 'name'); + let d2 = EditorDescriptor.create(MyOtherEditor, 'id2', 'name'); let oldEditors = EditorRegistry.getEditors(); (EditorRegistry).setEditors([]); @@ -163,7 +163,7 @@ suite('Workbench base editor', () => { }); test('Editor Lookup favors specific class over superclass (match on super class)', function () { - let d1 = new EditorDescriptor(MyOtherEditor, 'id1', 'name'); + let d1 = EditorDescriptor.create(MyOtherEditor, 'id1', 'name'); let oldEditors = EditorRegistry.getEditors(); (EditorRegistry).setEditors([]); diff --git a/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts b/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts index 59cc7c9356..b621b883f8 100644 --- a/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts @@ -29,7 +29,7 @@ suite('Breadcrumb Model', function () { test('only uri, inside workspace', function () { - let model = new EditorBreadcrumbsModel(URI.parse('foo:/bar/baz/ws/some/path/file.ts'), undefined, configService, workspaceService); + let model = new EditorBreadcrumbsModel(URI.parse('foo:/bar/baz/ws/some/path/file.ts'), undefined, configService, configService, workspaceService); let elements = model.getElements(); assert.equal(elements.length, 3); @@ -44,7 +44,7 @@ suite('Breadcrumb Model', function () { test('only uri, outside workspace', function () { - let model = new EditorBreadcrumbsModel(URI.parse('foo:/outside/file.ts'), undefined, configService, workspaceService); + let model = new EditorBreadcrumbsModel(URI.parse('foo:/outside/file.ts'), undefined, configService, configService, workspaceService); let elements = model.getElements(); assert.equal(elements.length, 2); diff --git a/src/vs/workbench/test/browser/parts/views/views.test.ts b/src/vs/workbench/test/browser/parts/views/views.test.ts index d4fc28a779..30590a6034 100644 --- a/src/vs/workbench/test/browser/parts/views/views.test.ts +++ b/src/vs/workbench/test/browser/parts/views/views.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { ContributableViewsModel, ViewsService } from 'vs/workbench/browser/parts/views/views'; +import { ContributableViewsModel, ViewsService, IViewState } from 'vs/workbench/browser/parts/views/views'; import { IViewsRegistry, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewsService } from 'vs/workbench/common/views'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { move } from 'vs/base/common/arrays'; @@ -13,6 +13,7 @@ import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestSe import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; +import sinon = require('sinon'); const container = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer('test'); const ViewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); @@ -244,4 +245,130 @@ suite('ContributableViewsModel', () => { assert.deepEqual(model.visibleViewDescriptors, [view1, view2, view3], 'view2 should go to the middle'); assert.deepEqual(seq.elements, [view1, view2, view3]); }); + + test('view states', async function () { + const viewStates = new Map(); + viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); + const model = new ContributableViewsModel(container, viewsService, viewStates); + const seq = new ViewDescriptorSequence(model); + + assert.equal(model.visibleViewDescriptors.length, 0); + assert.equal(seq.elements.length, 0); + + const viewDescriptor: IViewDescriptor = { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1' + }; + + ViewsRegistry.registerViews([viewDescriptor], container); + assert.equal(model.visibleViewDescriptors.length, 0, 'view should not appear since it was set not visible in view state'); + assert.equal(seq.elements.length, 0); + }); + + test('view states and when contexts', async function () { + const viewStates = new Map(); + viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); + const model = new ContributableViewsModel(container, viewsService, viewStates); + const seq = new ViewDescriptorSequence(model); + + assert.equal(model.visibleViewDescriptors.length, 0); + assert.equal(seq.elements.length, 0); + + const viewDescriptor: IViewDescriptor = { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + when: ContextKeyExpr.equals('showview1', true) + }; + + ViewsRegistry.registerViews([viewDescriptor], container); + assert.equal(model.visibleViewDescriptors.length, 0, 'view should not appear since context isnt in'); + assert.equal(seq.elements.length, 0); + + const key = contextKeyService.createKey('showview1', false); + assert.equal(model.visibleViewDescriptors.length, 0, 'view should still not appear since showview1 isnt true'); + assert.equal(seq.elements.length, 0); + + key.set(true); + await new Promise(c => setTimeout(c, 30)); + assert.equal(model.visibleViewDescriptors.length, 0, 'view should still not appear since it was set not visible in view state'); + assert.equal(seq.elements.length, 0); + }); + + test('view states and when contexts multiple views', async function () { + const viewStates = new Map(); + viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); + const model = new ContributableViewsModel(container, viewsService, viewStates); + const seq = new ViewDescriptorSequence(model); + + assert.equal(model.visibleViewDescriptors.length, 0); + assert.equal(seq.elements.length, 0); + + const view1: IViewDescriptor = { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + when: ContextKeyExpr.equals('showview', true) + }; + const view2: IViewDescriptor = { + id: 'view2', + ctorDescriptor: null!, + name: 'Test View 2', + }; + const view3: IViewDescriptor = { + id: 'view3', + ctorDescriptor: null!, + name: 'Test View 3', + when: ContextKeyExpr.equals('showview', true) + }; + + ViewsRegistry.registerViews([view1, view2, view3], container); + assert.deepEqual(model.visibleViewDescriptors, [view2], 'Only view2 should be visible'); + assert.deepEqual(seq.elements, [view2]); + + const key = contextKeyService.createKey('showview', false); + assert.deepEqual(model.visibleViewDescriptors, [view2], 'Only view2 should be visible'); + assert.deepEqual(seq.elements, [view2]); + + key.set(true); + await new Promise(c => setTimeout(c, 30)); + assert.deepEqual(model.visibleViewDescriptors, [view2, view3], 'view3 should be visible'); + assert.deepEqual(seq.elements, [view2, view3]); + + key.set(false); + await new Promise(c => setTimeout(c, 30)); + assert.deepEqual(model.visibleViewDescriptors, [view2], 'Only view2 should be visible'); + assert.deepEqual(seq.elements, [view2]); + }); + + test('remove event is not triggered if view was hidden and removed', async function () { + const model = new ContributableViewsModel(container, viewsService); + const seq = new ViewDescriptorSequence(model); + + const viewDescriptor: IViewDescriptor = { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + when: ContextKeyExpr.equals('showview1', true), + canToggleVisibility: true + }; + + ViewsRegistry.registerViews([viewDescriptor], container); + + const key = contextKeyService.createKey('showview1', true); + await new Promise(c => setTimeout(c, 30)); + assert.equal(model.visibleViewDescriptors.length, 1, 'view should appear after context is set'); + assert.equal(seq.elements.length, 1); + + model.setVisible('view1', false); + assert.equal(model.visibleViewDescriptors.length, 0, 'view should disappear after setting visibility to false'); + assert.equal(seq.elements.length, 0); + + const target = sinon.spy(model.onDidRemove); + key.set(false); + await new Promise(c => setTimeout(c, 30)); + assert.ok(!target.called, 'remove event should not be called since it is already hidden'); + }); + }); diff --git a/src/vs/workbench/test/browser/quickopen.test.ts b/src/vs/workbench/test/browser/quickopen.test.ts index 5a412ad3ba..93cba1b1bc 100644 --- a/src/vs/workbench/test/browser/quickopen.test.ts +++ b/src/vs/workbench/test/browser/quickopen.test.ts @@ -54,7 +54,7 @@ suite('QuickOpen', () => { test('QuickOpen Handler and Registry', () => { let registry = (Registry.as(QuickOpenExtensions.Quickopen)); - let handler = new QuickOpenHandlerDescriptor( + let handler = QuickOpenHandlerDescriptor.create( TestHandler, 'testhandler', ',', @@ -77,4 +77,4 @@ suite('QuickOpen', () => { defaultAction.run(); prefixAction.run(); }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/test/browser/viewlet.test.ts b/src/vs/workbench/test/browser/viewlet.test.ts index 38c7c3e19c..dacedd8c4d 100644 --- a/src/vs/workbench/test/browser/viewlet.test.ts +++ b/src/vs/workbench/test/browser/viewlet.test.ts @@ -22,7 +22,7 @@ suite('Viewlets', () => { } test('ViewletDescriptor API', function () { - let d = new ViewletDescriptor(TestViewlet, 'id', 'name', 'class', 5); + let d = ViewletDescriptor.create(TestViewlet, 'id', 'name', 'class', 5); assert.strictEqual(d.id, 'id'); assert.strictEqual(d.name, 'name'); assert.strictEqual(d.cssClass, 'class'); @@ -30,11 +30,11 @@ suite('Viewlets', () => { }); test('Editor Aware ViewletDescriptor API', function () { - let d = new ViewletDescriptor(TestViewlet, 'id', 'name', 'class', 5); + let d = ViewletDescriptor.create(TestViewlet, 'id', 'name', 'class', 5); assert.strictEqual(d.id, 'id'); assert.strictEqual(d.name, 'name'); - d = new ViewletDescriptor(TestViewlet, 'id', 'name', 'class', 5); + d = ViewletDescriptor.create(TestViewlet, 'id', 'name', 'class', 5); assert.strictEqual(d.id, 'id'); assert.strictEqual(d.name, 'name'); }); @@ -45,7 +45,7 @@ suite('Viewlets', () => { assert(Types.isFunction(Platform.Registry.as(Extensions.Viewlets).getViewlets)); let oldCount = Platform.Registry.as(Extensions.Viewlets).getViewlets().length; - let d = new ViewletDescriptor(TestViewlet, 'reg-test-id', 'name'); + let d = ViewletDescriptor.create(TestViewlet, 'reg-test-id', 'name'); Platform.Registry.as(Extensions.Viewlets).registerViewlet(d); assert(d === Platform.Registry.as(Extensions.Viewlets).getViewlet('reg-test-id')); diff --git a/src/vs/workbench/test/common/editor/dataUriEditorInput.test.ts b/src/vs/workbench/test/common/editor/dataUriEditorInput.test.ts deleted file mode 100644 index aae0682f74..0000000000 --- a/src/vs/workbench/test/common/editor/dataUriEditorInput.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import { URI } from 'vs/base/common/uri'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; -import { DataUriEditorInput } from 'vs/workbench/common/editor/dataUriEditorInput'; -import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; - -suite('DataUriEditorInput', () => { - - let instantiationService: IInstantiationService; - - setup(() => { - instantiationService = workbenchInstantiationService(); - }); - - test('simple', () => { - const resource = URI.parse('data:image/png;label:SomeLabel;description:SomeDescription;size:1024;base64,77+9UE5'); - const input: DataUriEditorInput = instantiationService.createInstance(DataUriEditorInput, undefined, undefined, resource); - - assert.equal(input.getName(), 'SomeLabel'); - assert.equal(input.getDescription(), 'SomeDescription'); - - return input.resolve().then((model: BinaryEditorModel) => { - assert.ok(model); - assert.equal(model.getSize(), 1024); - assert.equal(model.getMime(), 'image/png'); - }); - }); -}); \ No newline at end of file diff --git a/src/vs/workbench/test/common/editor/editor.test.ts b/src/vs/workbench/test/common/editor/editor.test.ts index 2b154890d0..9d77f92dcb 100644 --- a/src/vs/workbench/test/common/editor/editor.test.ts +++ b/src/vs/workbench/test/common/editor/editor.test.ts @@ -8,13 +8,13 @@ import { EditorInput, toResource, SideBySideEditor } from 'vs/workbench/common/e import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { IEditorModel } from 'vs/platform/editor/common/editor'; import { URI } from 'vs/base/common/uri'; -import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; import { Schemas } from 'vs/base/common/network'; class ServiceAccessor { - constructor(@IUntitledEditorService public untitledEditorService: UntitledEditorService) { + constructor(@IUntitledTextEditorService public untitledTextEditorService: UntitledTextEditorService) { } } @@ -48,12 +48,12 @@ suite('Workbench editor', () => { }); teardown(() => { - accessor.untitledEditorService.revertAll(); - accessor.untitledEditorService.dispose(); + accessor.untitledTextEditorService.revertAll(); + accessor.untitledTextEditorService.dispose(); }); test('toResource', () => { - const service = accessor.untitledEditorService; + const service = accessor.untitledTextEditorService; assert.ok(!toResource(null!)); @@ -82,4 +82,4 @@ suite('Workbench editor', () => { assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: Schemas.file })!.toString(), file.getResource().toString()); assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.getResource().toString()); }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/test/common/editor/editorGroups.test.ts b/src/vs/workbench/test/common/editor/editorGroups.test.ts index fcd5010fff..7697eec3e5 100644 --- a/src/vs/workbench/test/common/editor/editorGroups.test.ts +++ b/src/vs/workbench/test/common/editor/editorGroups.test.ts @@ -187,19 +187,79 @@ suite('Workbench editor groups', () => { assert.equal(clone.isActive(input3), true); }); - test('contains() with diff editor support', function () { + test('contains()', function () { const group = createGroup(); const input1 = input(); const input2 = input(); - const diffInput = new DiffEditorInput('name', 'description', input1, input2); + const diffInput1 = new DiffEditorInput('name', 'description', input1, input2); + const diffInput2 = new DiffEditorInput('name', 'description', input2, input1); + + group.openEditor(input1, { pinned: true, active: true }); + + assert.equal(group.contains(input1), true); + assert.equal(group.contains(input1, true), true); + assert.equal(group.contains(input2), false); + assert.equal(group.contains(input2, true), false); + assert.equal(group.contains(diffInput1), false); + assert.equal(group.contains(diffInput2), false); group.openEditor(input2, { pinned: true, active: true }); + assert.equal(group.contains(input1), true); assert.equal(group.contains(input2), true); - assert.equal(group.contains(diffInput), false); - assert.equal(group.contains(diffInput, true), true); + assert.equal(group.contains(diffInput1), false); + assert.equal(group.contains(diffInput2), false); + + group.openEditor(diffInput1, { pinned: true, active: true }); + + assert.equal(group.contains(input1), true); + assert.equal(group.contains(input2), true); + assert.equal(group.contains(diffInput1), true); + assert.equal(group.contains(diffInput2), false); + + group.openEditor(diffInput2, { pinned: true, active: true }); + + assert.equal(group.contains(input1), true); + assert.equal(group.contains(input2), true); + assert.equal(group.contains(diffInput1), true); + assert.equal(group.contains(diffInput2), true); + + group.closeEditor(input1); + + assert.equal(group.contains(input1), false); + assert.equal(group.contains(input1, true), true); + assert.equal(group.contains(input2), true); + assert.equal(group.contains(diffInput1), true); + assert.equal(group.contains(diffInput2), true); + + group.closeEditor(input2); + + assert.equal(group.contains(input1), false); + assert.equal(group.contains(input1, true), true); + assert.equal(group.contains(input2), false); + assert.equal(group.contains(input2, true), true); + assert.equal(group.contains(diffInput1), true); + assert.equal(group.contains(diffInput2), true); + + group.closeEditor(diffInput1); + + assert.equal(group.contains(input1), false); + assert.equal(group.contains(input1, true), true); + assert.equal(group.contains(input2), false); + assert.equal(group.contains(input2, true), true); + assert.equal(group.contains(diffInput1), false); + assert.equal(group.contains(diffInput2), true); + + group.closeEditor(diffInput2); + + assert.equal(group.contains(input1), false); + assert.equal(group.contains(input1, true), false); + assert.equal(group.contains(input2), false); + assert.equal(group.contains(input2, true), false); + assert.equal(group.contains(diffInput1), false); + assert.equal(group.contains(diffInput2), false); }); test('group serialization', function () { @@ -1162,47 +1222,6 @@ suite('Workbench editor groups', () => { assert.equal(group1.getEditors()[1].matches(serializableInput2), true); }); - test('Multiple Editors - Resources', function () { - const group1 = createGroup(); - const group2 = createGroup(); - - const input1Resource = URI.file('/hello/world.txt'); - const input1ResourceUpper = URI.file('/hello/WORLD.txt'); - const input1 = input(undefined, false, input1Resource); - group1.openEditor(input1); - - assert.ok(group1.contains(input1Resource)); - assert.equal(group1.getEditor(input1Resource), input1); - - assert.ok(!group1.getEditor(input1ResourceUpper)); - assert.ok(!group1.contains(input1ResourceUpper)); - - group2.openEditor(input1); - group1.closeEditor(input1); - - assert.ok(!group1.contains(input1Resource)); - assert.ok(!group1.getEditor(input1Resource)); - assert.ok(!group1.getEditor(input1ResourceUpper)); - assert.ok(group2.contains(input1Resource)); - assert.equal(group2.getEditor(input1Resource), input1); - - const input1ResourceClone = URI.file('/hello/world.txt'); - const input1Clone = input(undefined, false, input1ResourceClone); - group1.openEditor(input1Clone); - - assert.ok(group1.contains(input1Resource)); - - group2.closeEditor(input1); - - assert.ok(group1.contains(input1Resource)); - assert.equal(group1.getEditor(input1Resource), input1Clone); - assert.ok(!group2.contains(input1Resource)); - - group1.closeEditor(input1Clone); - - assert.ok(!group1.contains(input1Resource)); - }); - test('Multiple Editors - Editor Dispose', function () { const group1 = createGroup(); const group2 = createGroup(); diff --git a/src/vs/workbench/test/common/editor/editorInput.test.ts b/src/vs/workbench/test/common/editor/editorInput.test.ts index 1c4a315bc0..c19f7b3686 100644 --- a/src/vs/workbench/test/common/editor/editorInput.test.ts +++ b/src/vs/workbench/test/common/editor/editorInput.test.ts @@ -22,7 +22,7 @@ suite('Workbench editor input', () => { assert(input.matches(input)); assert(!input.matches(otherInput)); assert(!input.matches(null)); - assert(!input.getName()); + assert(input.getName()); input.onDispose(() => { assert(true); @@ -84,4 +84,4 @@ suite('Workbench editor input', () => { otherInput.dispose(); assert.equal(counter, 2); }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/test/common/editor/untitledEditor.test.ts b/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts similarity index 73% rename from src/vs/workbench/test/common/editor/untitledEditor.test.ts rename to src/vs/workbench/test/common/editor/untitledTextEditor.test.ts index 1c2d513a99..bcee38af37 100644 --- a/src/vs/workbench/test/common/editor/untitledEditor.test.ts +++ b/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts @@ -6,32 +6,34 @@ import { URI } from 'vs/base/common/uri'; import * as assert from 'assert'; import { join } from 'vs/base/common/path'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; -import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel'; +import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; import { IModeService } from 'vs/editor/common/services/modeService'; import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; -import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; import { timeout } from 'vs/base/common/async'; import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; +import { IWorkingCopyService, IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -export class TestUntitledEditorService extends UntitledEditorService { +export class TestUntitledTextEditorService extends UntitledTextEditorService { get(resource: URI) { return super.get(resource); } - getAll(resources?: URI[]): UntitledEditorInput[] { return super.getAll(resources); } + getAll(resources?: URI[]): UntitledTextEditorInput[] { return super.getAll(resources); } } class ServiceAccessor { constructor( - @IUntitledEditorService public untitledEditorService: TestUntitledEditorService, - @IModeService public modeService: ModeServiceImpl, - @IConfigurationService public testConfigurationService: TestConfigurationService) { + @IUntitledTextEditorService public readonly untitledTextEditorService: TestUntitledTextEditorService, + @IWorkingCopyService public readonly workingCopyService: IWorkingCopyService, + @IModeService public readonly modeService: ModeServiceImpl, + @IConfigurationService public readonly testConfigurationService: TestConfigurationService) { } } -suite('Workbench untitled editors', () => { +suite('Workbench untitled text editors', () => { let instantiationService: IInstantiationService; let accessor: ServiceAccessor; @@ -42,12 +44,14 @@ suite('Workbench untitled editors', () => { }); teardown(() => { - accessor.untitledEditorService.revertAll(); - accessor.untitledEditorService.dispose(); + accessor.untitledTextEditorService.revertAll(); + accessor.untitledTextEditorService.dispose(); }); - test('Untitled Editor Service', async (done) => { - const service = accessor.untitledEditorService; + test('Untitled Text Editor Service', async (done) => { + const service = accessor.untitledTextEditorService; + const workingCopyService = accessor.workingCopyService; + assert.equal(service.getAll().length, 0); const input1 = service.createOrGet(); @@ -83,14 +87,24 @@ suite('Workbench untitled editors', () => { assert.equal(service.getDirty([input2.getResource()])[0].toString(), input2.getResource().toString()); assert.equal(service.getDirty([input1.getResource()]).length, 0); + assert.ok(workingCopyService.isDirty(input2.getResource())); + assert.equal(workingCopyService.dirtyCount, 1); + service.revertAll(); assert.equal(service.getAll().length, 0); assert.ok(!input2.isDirty()); assert.ok(!model.isDirty()); - input2.dispose(); + assert.ok(!workingCopyService.isDirty(input2.getResource())); + assert.equal(workingCopyService.dirtyCount, 0); + assert.ok(input1.revert()); + assert.ok(input1.isDisposed()); + assert.ok(!service.exists(input1.getResource())); + + input2.dispose(); assert.ok(!service.exists(input2.getResource())); + done(); }); @@ -98,7 +112,7 @@ suite('Workbench untitled editors', () => { }); test('Untitled with associated resource', () => { - const service = accessor.untitledEditorService; + const service = accessor.untitledTextEditorService; const file = URI.file(join('C:\\', '/foo/file.txt')); const untitled = service.createOrGet(file); @@ -108,20 +122,23 @@ suite('Workbench untitled editors', () => { }); test('Untitled no longer dirty when content gets empty', async () => { - const service = accessor.untitledEditorService; + const service = accessor.untitledTextEditorService; + const workingCopyService = accessor.workingCopyService; const input = service.createOrGet(); // dirty const model = await input.resolve(); model.textEditorModel.setValue('foo bar'); assert.ok(model.isDirty()); + assert.ok(workingCopyService.isDirty(model.resource)); model.textEditorModel.setValue(''); assert.ok(!model.isDirty()); + assert.ok(!workingCopyService.isDirty(model.resource)); input.dispose(); }); test('Untitled via loadOrCreate', async () => { - const service = accessor.untitledEditorService; + const service = accessor.untitledTextEditorService; const model1 = await service.loadOrCreate(); @@ -138,11 +155,11 @@ suite('Workbench untitled editors', () => { const model3 = await service.loadOrCreate({ resource: input.getResource() }); - assert.equal(model3.getResource().toString(), input.getResource().toString()); + assert.equal(model3.resource.toString(), input.getResource().toString()); const file = URI.file(join('C:\\', '/foo/file44.txt')); const model4 = await service.loadOrCreate({ resource: file }); - assert.ok(service.hasAssociatedFilePath(model4.getResource())); + assert.ok(service.hasAssociatedFilePath(model4.resource)); assert.ok(model4.isDirty()); model1.dispose(); @@ -153,14 +170,14 @@ suite('Workbench untitled editors', () => { }); test('Untitled suggest name', function () { - const service = accessor.untitledEditorService; + const service = accessor.untitledTextEditorService; const input = service.createOrGet(); assert.ok(service.suggestFileName(input.getResource())); }); test('Untitled with associated path remains dirty when content gets empty', async () => { - const service = accessor.untitledEditorService; + const service = accessor.untitledTextEditorService; const file = URI.file(join('C:\\', '/foo/file.txt')); const input = service.createOrGet(file); @@ -174,13 +191,23 @@ suite('Workbench untitled editors', () => { }); test('Untitled with initial content is dirty', async () => { - const service = accessor.untitledEditorService; + const service = accessor.untitledTextEditorService; const input = service.createOrGet(undefined, undefined, 'Hello World'); + const workingCopyService = accessor.workingCopyService; + + let onDidChangeDirty: IWorkingCopy | undefined = undefined; + const listener = workingCopyService.onDidChangeDirty(copy => { + onDidChangeDirty = copy; + }); // dirty const model = await input.resolve(); assert.ok(model.isDirty()); + assert.equal(workingCopyService.dirtyCount, 1); + assert.equal(onDidChangeDirty, model); + input.dispose(); + listener.dispose(); }); test('Untitled created with files.defaultLanguage setting', () => { @@ -188,7 +215,7 @@ suite('Workbench untitled editors', () => { const config = accessor.testConfigurationService; config.setUserConfiguration('files', { 'defaultLanguage': defaultLanguage }); - const service = accessor.untitledEditorService; + const service = accessor.untitledTextEditorService; const input = service.createOrGet(); assert.equal(input.getMode(), defaultLanguage); @@ -204,7 +231,7 @@ suite('Workbench untitled editors', () => { const config = accessor.testConfigurationService; config.setUserConfiguration('files', { 'defaultLanguage': defaultLanguage }); - const service = accessor.untitledEditorService; + const service = accessor.untitledTextEditorService; const input = service.createOrGet(null!, mode); assert.equal(input.getMode(), mode); @@ -221,7 +248,7 @@ suite('Workbench untitled editors', () => { id: mode, }); - const service = accessor.untitledEditorService; + const service = accessor.untitledTextEditorService; const input = service.createOrGet(null!, mode); assert.equal(input.getMode(), mode); @@ -237,7 +264,7 @@ suite('Workbench untitled editors', () => { }); test('encoding change event', async () => { - const service = accessor.untitledEditorService; + const service = accessor.untitledTextEditorService; const input = service.createOrGet(); let counter = 0; @@ -255,10 +282,10 @@ suite('Workbench untitled editors', () => { }); test('onDidChangeContent event', async () => { - const service = accessor.untitledEditorService; + const service = accessor.untitledTextEditorService; const input = service.createOrGet(); - UntitledEditorModel.DEFAULT_CONTENT_CHANGE_BUFFER_DELAY = 0; + UntitledTextEditorModel.DEFAULT_CONTENT_CHANGE_BUFFER_DELAY = 0; let counter = 0; @@ -293,7 +320,7 @@ suite('Workbench untitled editors', () => { }); test('onDidDisposeModel event', async () => { - const service = accessor.untitledEditorService; + const service = accessor.untitledTextEditorService; const input = service.createOrGet(); let counter = 0; @@ -308,4 +335,4 @@ suite('Workbench untitled editors', () => { input.dispose(); assert.equal(counter, 1); }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/test/contrib/linkProtection.test.ts b/src/vs/workbench/test/contrib/linkProtection.test.ts index 1595e01ccb..b29705e97f 100644 --- a/src/vs/workbench/test/contrib/linkProtection.test.ts +++ b/src/vs/workbench/test/contrib/linkProtection.test.ts @@ -8,42 +8,69 @@ import * as assert from 'assert'; import { isURLDomainTrusted } from 'vs/workbench/contrib/url/common/trustedDomainsValidator'; import { URI } from 'vs/base/common/uri'; +function linkAllowedByRules(link: string, rules: string[]) { + assert.ok(isURLDomainTrusted(URI.parse(link), rules), `Link\n${link}\n should be protected by rules\n${JSON.stringify(rules)}`); +} +function linkNotAllowedByRules(link: string, rules: string[]) { + assert.ok(!isURLDomainTrusted(URI.parse(link), rules), `Link\n${link}\n should NOT be protected by rules\n${JSON.stringify(rules)}`); +} + suite('Link protection domain matching', () => { - test('simple', () => { - assert.ok(!isURLDomainTrusted(URI.parse('https://x.org'), [])); - assert.ok(isURLDomainTrusted(URI.parse('https://x.org'), ['https://x.org'])); - assert.ok(isURLDomainTrusted(URI.parse('https://x.org/foo'), ['https://x.org'])); + linkNotAllowedByRules('https://x.org', []); - assert.ok(!isURLDomainTrusted(URI.parse('https://x.org'), ['http://x.org'])); - assert.ok(!isURLDomainTrusted(URI.parse('http://x.org'), ['https://x.org'])); + linkAllowedByRules('https://x.org', ['https://x.org']); + linkAllowedByRules('https://x.org/foo', ['https://x.org']); - assert.ok(!isURLDomainTrusted(URI.parse('https://www.x.org'), ['https://x.org'])); + linkNotAllowedByRules('https://x.org', ['http://x.org']); + linkNotAllowedByRules('http://x.org', ['https://x.org']); - assert.ok(isURLDomainTrusted(URI.parse('https://www.x.org'), ['https://www.x.org', 'https://y.org'])); + linkNotAllowedByRules('https://www.x.org', ['https://x.org']); + + linkAllowedByRules('https://www.x.org', ['https://www.x.org', 'https://y.org']); }); test('localhost', () => { - assert.ok(isURLDomainTrusted(URI.parse('https://127.0.0.1'), [])); - assert.ok(isURLDomainTrusted(URI.parse('https://127.0.0.1:3000'), [])); - assert.ok(isURLDomainTrusted(URI.parse('https://localhost'), [])); - assert.ok(isURLDomainTrusted(URI.parse('https://localhost:3000'), [])); + linkAllowedByRules('https://127.0.0.1', []); + linkAllowedByRules('https://127.0.0.1:3000', []); + linkAllowedByRules('https://localhost', []); + linkAllowedByRules('https://localhost:3000', []); }); test('* star', () => { - assert.ok(isURLDomainTrusted(URI.parse('https://a.x.org'), ['https://*.x.org'])); - assert.ok(isURLDomainTrusted(URI.parse('https://a.b.x.org'), ['https://*.x.org'])); - assert.ok(isURLDomainTrusted(URI.parse('https://a.x.org'), ['https://a.x.*'])); - assert.ok(isURLDomainTrusted(URI.parse('https://a.x.org'), ['https://a.*.org'])); - assert.ok(isURLDomainTrusted(URI.parse('https://a.x.org'), ['https://*.*.org'])); - assert.ok(isURLDomainTrusted(URI.parse('https://a.b.x.org'), ['https://*.b.*.org'])); - assert.ok(isURLDomainTrusted(URI.parse('https://a.a.b.x.org'), ['https://*.b.*.org'])); + linkAllowedByRules('https://a.x.org', ['https://*.x.org']); + linkAllowedByRules('https://a.b.x.org', ['https://*.x.org']); + linkAllowedByRules('https://a.x.org', ['https://a.x.*']); + linkAllowedByRules('https://a.x.org', ['https://a.*.org']); + linkAllowedByRules('https://a.x.org', ['https://*.*.org']); + linkAllowedByRules('https://a.b.x.org', ['https://*.b.*.org']); + linkAllowedByRules('https://a.a.b.x.org', ['https://*.b.*.org']); }); test('no scheme', () => { - assert.ok(isURLDomainTrusted(URI.parse('https://a.x.org'), ['a.x.org'])); - assert.ok(isURLDomainTrusted(URI.parse('https://a.x.org'), ['*.x.org'])); - assert.ok(isURLDomainTrusted(URI.parse('https://a.b.x.org'), ['*.x.org'])); - assert.ok(isURLDomainTrusted(URI.parse('https://x.org'), ['*.x.org'])); + linkAllowedByRules('https://a.x.org', ['a.x.org']); + linkAllowedByRules('https://a.x.org', ['*.x.org']); + linkAllowedByRules('https://a.b.x.org', ['*.x.org']); + linkAllowedByRules('https://x.org', ['*.x.org']); + }); + + test('sub paths', () => { + linkAllowedByRules('https://x.org/foo', ['https://x.org/foo']); + linkAllowedByRules('https://x.org/foo', ['x.org/foo']); + linkAllowedByRules('https://x.org/foo', ['*.org/foo']); + + linkNotAllowedByRules('https://x.org/bar', ['https://x.org/foo']); + linkNotAllowedByRules('https://x.org/bar', ['x.org/foo']); + linkNotAllowedByRules('https://x.org/bar', ['*.org/foo']); + + linkAllowedByRules('https://x.org/foo/bar', ['https://x.org/foo']); + linkNotAllowedByRules('https://x.org/foo2', ['https://x.org/foo']); + + linkNotAllowedByRules('https://www.x.org/foo', ['https://x.org/foo']); + + linkNotAllowedByRules('https://a.x.org/bar', ['https://*.x.org/foo']); + linkNotAllowedByRules('https://a.b.x.org/bar', ['https://*.x.org/foo']); + + linkAllowedByRules('https://github.com', ['https://github.com/foo/bar', 'https://github.com']); }); }); diff --git a/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts b/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts index 97d6a6e067..bc3dfca0ee 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts @@ -67,13 +67,13 @@ suite('ExtHostLanguageFeatureCommands', function () { rpcProtocol = new TestRPCProtocol(); instantiationService.stub(ICommandService, { _serviceBrand: undefined, - executeCommand(id: string, args: any): any { + executeCommand(id: string, ...args: any): any { const command = CommandsRegistry.getCommands().get(id); if (!command) { return Promise.reject(new Error(id + ' NOT known')); } const { handler } = command; - return Promise.resolve(instantiationService.invokeFunction(handler, args)); + return Promise.resolve(instantiationService.invokeFunction(handler, ...args)); } }); instantiationService.stub(IMarkerService, new MarkerService()); @@ -112,7 +112,7 @@ suite('ExtHostLanguageFeatureCommands', function () { rpcProtocol.set(MainContext.MainThreadCommands, inst.createInstance(MainThreadCommands, rpcProtocol)); ExtHostApiCommands.register(commands); - const diagnostics = new ExtHostDiagnostics(rpcProtocol); + const diagnostics = new ExtHostDiagnostics(rpcProtocol, new NullLogService()); rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, diagnostics); extHost = new ExtHostLanguageFeatures(rpcProtocol, null, extHostDocuments, commands, diagnostics, new NullLogService()); @@ -412,11 +412,8 @@ suite('ExtHostLanguageFeatureCommands', function () { assert.equal(values.length, 4); let [first, second, third, fourth] = values; assert.equal(first.label, 'item1'); - assert.equal(first.textEdit!.newText, 'item1'); - assert.equal(first.textEdit!.range.start.line, 0); - assert.equal(first.textEdit!.range.start.character, 0); - assert.equal(first.textEdit!.range.end.line, 0); - assert.equal(first.textEdit!.range.end.character, 4); + assert.equal(first.textEdit, undefined);// no text edit, default ranges + assert.ok(!types.Range.isRange(first.range)); assert.equal(second.label, 'item2'); assert.equal(second.textEdit!.newText, 'foo'); @@ -434,10 +431,13 @@ suite('ExtHostLanguageFeatureCommands', function () { assert.equal(fourth.label, 'item4'); assert.equal(fourth.textEdit, undefined); - assert.equal(fourth.range!.start.line, 0); - assert.equal(fourth.range!.start.character, 1); - assert.equal(fourth.range!.end.line, 0); - assert.equal(fourth.range!.end.character, 4); + + const range: any = fourth.range!; + assert.ok(types.Range.isRange(range)); + assert.equal(range.start.line, 0); + assert.equal(range.start.character, 1); + assert.equal(range.end.line, 0); + assert.equal(range.end.character, 4); assert.ok(fourth.insertText instanceof types.SnippetString); assert.equal((fourth.insertText).value, 'foo$0bar'); }); @@ -859,31 +859,44 @@ suite('ExtHostLanguageFeatureCommands', function () { // --- call hierarcht - test('Call Hierarchy, back and forth', async function () { + test('CallHierarchy, back and forth', async function () { - disposables.push(extHost.registerCallHierarchyProvider(nullExtensionDescription, defaultSelector, new class implements vscode.CallHierarchyItemProvider { - provideCallHierarchyIncomingCalls(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): vscode.ProviderResult { - return [ - new types.CallHierarchyIncomingCall(new types.CallHierarchyItem(types.SymbolKind.Array, 'IN', '', document.uri, new types.Range(0, 0, 2, 0), new types.Range(0, 0, 2, 0)), [new types.Range(0, 0, 0, 0)]), - ]; + disposables.push(extHost.registerCallHierarchyProvider(nullExtensionDescription, defaultSelector, new class implements vscode.CallHierarchyProvider { + + prepareCallHierarchy(document: vscode.TextDocument, position: vscode.Position, ): vscode.ProviderResult { + return new types.CallHierarchyItem(types.SymbolKind.Constant, 'ROOT', 'ROOT', document.uri, new types.Range(0, 0, 0, 0), new types.Range(0, 0, 0, 0)); } - provideCallHierarchyOutgoingCalls(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): vscode.ProviderResult { - return [ - new types.CallHierarchyOutgoingCall(new types.CallHierarchyItem(types.SymbolKind.Array, 'OUT', '', document.uri, new types.Range(0, 0, 2, 0), new types.Range(0, 0, 2, 0)), [new types.Range(0, 0, 0, 0)]), - ]; + + provideCallHierarchyIncomingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): vscode.ProviderResult { + + return [new types.CallHierarchyIncomingCall( + new types.CallHierarchyItem(types.SymbolKind.Constant, 'INCOMING', 'INCOMING', item.uri, new types.Range(0, 0, 0, 0), new types.Range(0, 0, 0, 0)), + [new types.Range(0, 0, 0, 0)] + )]; + } + + provideCallHierarchyOutgoingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): vscode.ProviderResult { + return [new types.CallHierarchyOutgoingCall( + new types.CallHierarchyItem(types.SymbolKind.Constant, 'OUTGOING', 'OUTGOING', item.uri, new types.Range(0, 0, 0, 0), new types.Range(0, 0, 0, 0)), + [new types.Range(0, 0, 0, 0)] + )]; } })); await rpcProtocol.sync(); - let incoming = await commands.executeCommand('vscode.executeCallHierarchyProviderIncomingCalls', model.uri, new types.Position(0, 10)); - assert.equal(incoming.length, 1); - assert.ok(incoming[0].from instanceof types.CallHierarchyItem); - assert.equal(incoming[0].from.name, 'IN'); + const root = await commands.executeCommand('vscode.prepareCallHierarchy', model.uri, new types.Position(0, 0)); - let outgoing = await commands.executeCommand('vscode.executeCallHierarchyProviderOutgoingCalls', model.uri, new types.Position(0, 10)); + assert.ok(Array.isArray(root)); + assert.equal(root.length, 1); + assert.equal(root[0].name, 'ROOT'); + + const incoming = await commands.executeCommand('vscode.provideIncomingCalls', root[0]); + assert.equal(incoming.length, 1); + assert.equal(incoming[0].from.name, 'INCOMING'); + + const outgoing = await commands.executeCommand('vscode.provideOutgoingCalls', root[0]); assert.equal(outgoing.length, 1); - assert.ok(outgoing[0].to instanceof types.CallHierarchyItem); - assert.equal(outgoing[0].to.name, 'OUT'); + assert.equal(outgoing[0].to.name, 'OUTGOING'); }); }); diff --git a/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts b/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts index 7e47efb085..81cbc9833a 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts @@ -35,7 +35,7 @@ suite('ExtHostConfiguration', function () { if (!shape) { shape = new class extends mock() { }; } - return new ExtHostConfigProvider(shape, createExtHostWorkspace(), createConfigurationData(contents)); + return new ExtHostConfigProvider(shape, createExtHostWorkspace(), createConfigurationData(contents), new NullLogService()); } function createConfigurationData(contents: any): IConfigurationInitData { @@ -283,7 +283,8 @@ suite('ExtHostConfiguration', function () { workspace: new ConfigurationModel({}, []), folders: [], configurationScopes: [] - } + }, + new NullLogService() ); let actual = testObject.getConfiguration().inspect('editor.wordWrap')!; @@ -331,7 +332,8 @@ suite('ExtHostConfiguration', function () { workspace, folders, configurationScopes: [] - } + }, + new NullLogService() ); let actual1 = testObject.getConfiguration().inspect('editor.wordWrap')!; @@ -407,7 +409,8 @@ suite('ExtHostConfiguration', function () { workspace, folders, configurationScopes: [] - } + }, + new NullLogService() ); let actual1 = testObject.getConfiguration().inspect('editor.wordWrap')!; @@ -607,7 +610,8 @@ suite('ExtHostConfiguration', function () { 'config': false, 'updatedconfig': false } - }) + }), + new NullLogService() ); const newConfigData = createConfigurationData({ diff --git a/src/vs/workbench/test/electron-browser/api/extHostDiagnostics.test.ts b/src/vs/workbench/test/electron-browser/api/extHostDiagnostics.test.ts index e763ff57bf..1044f429d8 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostDiagnostics.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostDiagnostics.test.ts @@ -11,6 +11,7 @@ import { MainThreadDiagnosticsShape, IMainContext } from 'vs/workbench/api/commo import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import { Emitter, Event } from 'vs/base/common/event'; +import { NullLogService } from 'vs/platform/log/common/log'; suite('ExtHostDiagnostics', () => { @@ -96,10 +97,10 @@ suite('ExtHostDiagnostics', () => { assert.throws(() => array.pop()); assert.throws(() => array[0] = new Diagnostic(new Range(0, 0, 0, 0), 'evil')); - collection.forEach((uri, array: Diagnostic[]) => { - assert.throws(() => array.length = 0); - assert.throws(() => array.pop()); - assert.throws(() => array[0] = new Diagnostic(new Range(0, 0, 0, 0), 'evil')); + collection.forEach((uri, array: readonly Diagnostic[]) => { + assert.throws(() => (array as Diagnostic[]).length = 0); + assert.throws(() => (array as Diagnostic[]).pop()); + assert.throws(() => (array as Diagnostic[])[0] = new Diagnostic(new Range(0, 0, 0, 0), 'evil')); }); array = collection.get(URI.parse('foo:bar')) as Diagnostic[]; @@ -387,7 +388,7 @@ suite('ExtHostDiagnostics', () => { assertRegistered(): void { } - }); + }, new NullLogService()); let collection1 = diags.createDiagnosticCollection('foo'); let collection2 = diags.createDiagnosticCollection('foo'); // warns, uses a different owner @@ -436,7 +437,7 @@ suite('ExtHostDiagnostics', () => { assertRegistered(): void { } - }); + }, new NullLogService()); // diff --git a/src/vs/workbench/test/electron-browser/api/extHostDocumentData.test.ts b/src/vs/workbench/test/electron-browser/api/extHostDocumentData.test.ts index 0522295185..cf81f513d4 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostDocumentData.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostDocumentData.test.ts @@ -251,11 +251,7 @@ suite('ExtHostDocumentData', () => { assert.equal(range.end.character, 4); // ignore bad regular expresson /.*/ - range = data.document.getWordRangeAtPosition(new Position(0, 2), /.*/)!; - assert.equal(range.start.line, 0); - assert.equal(range.start.character, 0); - assert.equal(range.end.line, 0); - assert.equal(range.end.character, 4); + assert.throws(() => data.document.getWordRangeAtPosition(new Position(0, 2), /.*/)!); range = data.document.getWordRangeAtPosition(new Position(0, 5), /[a-z+]+/)!; assert.equal(range.start.line, 0); diff --git a/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts b/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts index 37dfe46194..c155156234 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts @@ -10,7 +10,7 @@ import { TextDocumentSaveReason, TextEdit, Position, EndOfLine } from 'vs/workbe import { MainThreadTextEditorsShape, IWorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostDocumentSaveParticipant } from 'vs/workbench/api/common/extHostDocumentSaveParticipant'; import { SingleProxyRPCProtocol } from './testRPCProtocol'; -import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; +import { SaveReason } from 'vs/workbench/common/editor'; import * as vscode from 'vscode'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import { NullLogService } from 'vs/platform/log/common/log'; diff --git a/src/vs/workbench/test/electron-browser/api/extHostFileSystemEventService.test.ts b/src/vs/workbench/test/electron-browser/api/extHostFileSystemEventService.test.ts index 4e701b56e5..094657fa5a 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostFileSystemEventService.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostFileSystemEventService.test.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import { ExtHostFileSystemEventService } from 'vs/workbench/api/common/extHostFileSystemEventService'; import { IMainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { NullLogService } from 'vs/platform/log/common/log'; suite('ExtHostFileSystemEventService', () => { @@ -17,12 +18,12 @@ suite('ExtHostFileSystemEventService', () => { assertRegistered: undefined! }; - const watcher1 = new ExtHostFileSystemEventService(protocol, undefined!).createFileSystemWatcher('**/somethingInteresting', false, false, false); + const watcher1 = new ExtHostFileSystemEventService(protocol, new NullLogService(), undefined!).createFileSystemWatcher('**/somethingInteresting', false, false, false); assert.equal(watcher1.ignoreChangeEvents, false); assert.equal(watcher1.ignoreCreateEvents, false); assert.equal(watcher1.ignoreDeleteEvents, false); - const watcher2 = new ExtHostFileSystemEventService(protocol, undefined!).createFileSystemWatcher('**/somethingBoring', true, true, true); + const watcher2 = new ExtHostFileSystemEventService(protocol, new NullLogService(), undefined!).createFileSystemWatcher('**/somethingBoring', true, true, true); assert.equal(watcher2.ignoreChangeEvents, true); assert.equal(watcher2.ignoreCreateEvents, true); assert.equal(watcher2.ignoreDeleteEvents, true); diff --git a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts index 84265cfe4a..dfc5d0b0db 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts @@ -23,10 +23,9 @@ import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocum import { getDocumentSymbols } from 'vs/editor/contrib/quickOpen/quickOpen'; import * as modes from 'vs/editor/common/modes'; import { getCodeLensData } from 'vs/editor/contrib/codelens/codelens'; -import { getDefinitionsAtPosition, getImplementationsAtPosition, getTypeDefinitionsAtPosition, getDeclarationsAtPosition } from 'vs/editor/contrib/goToDefinition/goToDefinition'; +import { getDefinitionsAtPosition, getImplementationsAtPosition, getTypeDefinitionsAtPosition, getDeclarationsAtPosition, getReferencesAtPosition } from 'vs/editor/contrib/gotoSymbol/goToSymbol'; import { getHover } from 'vs/editor/contrib/hover/getHover'; import { getOccurrencesAtPosition } from 'vs/editor/contrib/wordHighlighter/wordHighlighter'; -import { provideReferences } from 'vs/editor/contrib/referenceSearch/referenceSearch'; import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; import { getWorkspaceSymbols } from 'vs/workbench/contrib/search/common/search'; import { rename } from 'vs/editor/contrib/rename/rename'; @@ -103,7 +102,7 @@ suite('ExtHostLanguageFeatures', function () { rpcProtocol.set(ExtHostContext.ExtHostCommands, commands); rpcProtocol.set(MainContext.MainThreadCommands, inst.createInstance(MainThreadCommands, rpcProtocol)); - const diagnostics = new ExtHostDiagnostics(rpcProtocol); + const diagnostics = new ExtHostDiagnostics(rpcProtocol, new NullLogService()); rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, diagnostics); extHost = new ExtHostLanguageFeatures(rpcProtocol, null, extHostDocuments, commands, diagnostics, new NullLogService()); @@ -535,7 +534,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - let value = await provideReferences(model, new EditorPosition(1, 2), CancellationToken.None); + let value = await getReferencesAtPosition(model, new EditorPosition(1, 2), false, CancellationToken.None); assert.equal(value.length, 2); let [first, second] = value; assert.equal(first.uri.path, '/second'); @@ -551,7 +550,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - let value = await provideReferences(model, new EditorPosition(1, 2), CancellationToken.None); + let value = await getReferencesAtPosition(model, new EditorPosition(1, 2), false, CancellationToken.None); assert.equal(value.length, 1); let [item] = value; assert.deepEqual(item.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }); @@ -572,7 +571,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const value = await provideReferences(model, new EditorPosition(1, 2), CancellationToken.None); + const value = await getReferencesAtPosition(model, new EditorPosition(1, 2), false, CancellationToken.None); assert.equal(value.length, 1); }); @@ -590,7 +589,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { actions } = await getCodeActions(model, model.getFullModelRange(), { type: 'manual' }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: 'manual' }, CancellationToken.None); assert.equal(actions.length, 2); const [first, second] = actions; assert.equal(first.title, 'Testing1'); @@ -614,7 +613,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { actions } = await getCodeActions(model, model.getFullModelRange(), { type: 'manual' }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: 'manual' }, CancellationToken.None); assert.equal(actions.length, 1); const [first] = actions; assert.equal(first.title, 'Testing1'); @@ -637,7 +636,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { actions } = await getCodeActions(model, model.getFullModelRange(), { type: 'manual' }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: 'manual' }, CancellationToken.None); assert.equal(actions.length, 1); }); @@ -655,7 +654,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { actions } = await getCodeActions(model, model.getFullModelRange(), { type: 'manual' }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: 'manual' }, CancellationToken.None); assert.equal(actions.length, 1); }); diff --git a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts index 30dfc07729..2a1fb66abb 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts @@ -12,7 +12,7 @@ import { joinPath } from 'vs/base/common/resources'; import { URI, UriComponents } from 'vs/base/common/uri'; import * as pfs from 'vs/base/node/pfs'; import { MainContext, MainThreadSearchShape } from 'vs/workbench/api/common/extHost.protocol'; -import { ExtHostSearch } from 'vs/workbench/api/node/extHostSearch'; +import { NativeExtHostSearch } from 'vs/workbench/api/node/extHostSearch'; import { Range } from 'vs/workbench/api/common/extHostTypes'; import { IFileMatch, IFileQuery, IPatternInfo, IRawFileMatch2, ISearchCompleteStats, ISearchQuery, ITextQuery, QueryType, resultIsMatch } from 'vs/workbench/services/search/common/search'; import { TestRPCProtocol } from 'vs/workbench/test/electron-browser/api/testRPCProtocol'; @@ -21,9 +21,11 @@ import { NullLogService } from 'vs/platform/log/common/log'; import { URITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { TextSearchManager } from 'vs/workbench/services/search/common/textSearchManager'; +import { NativeTextSearchManager } from 'vs/workbench/services/search/node/textSearchManager'; let rpcProtocol: TestRPCProtocol; -let extHostSearch: ExtHostSearch; +let extHostSearch: NativeExtHostSearch; const disposables = new DisposableStore(); let mockMainThreadSearch: MockMainThreadSearch; @@ -138,7 +140,7 @@ suite('ExtHostSearch', () => { rpcProtocol.set(MainContext.MainThreadSearch, mockMainThreadSearch); mockPFS = {}; - extHostSearch = new class extends ExtHostSearch { + extHostSearch = new class extends NativeExtHostSearch { constructor() { super( rpcProtocol, @@ -148,6 +150,10 @@ suite('ExtHostSearch', () => { ); this._pfs = mockPFS as any; } + + protected createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProvider): TextSearchManager { + return new NativeTextSearchManager(query, provider, this._pfs); + } }; }); diff --git a/src/vs/workbench/test/electron-browser/api/extHostTypeConverter.test.ts b/src/vs/workbench/test/electron-browser/api/extHostTypeConverter.test.ts index 4a8822c3b0..90427abd09 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostTypeConverter.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostTypeConverter.test.ts @@ -22,13 +22,11 @@ suite('ExtHostTypeConverter', function () { data = MarkdownString.from('Hello [link](foo)'); assert.equal(data.value, 'Hello [link](foo)'); - assert.equal(size(data.uris!), 1); - assert.ok(!!data.uris!['foo']); + assert.equal(isEmptyObject(data.uris), true); // no scheme, no uri data = MarkdownString.from('Hello [link](www.noscheme.bad)'); assert.equal(data.value, 'Hello [link](www.noscheme.bad)'); - assert.equal(size(data.uris!), 1); - assert.ok(!!data.uris!['www.noscheme.bad']); + assert.equal(isEmptyObject(data.uris), true); // no scheme, no uri data = MarkdownString.from('Hello [link](foo:path)'); assert.equal(data.value, 'Hello [link](foo:path)'); diff --git a/src/vs/workbench/test/electron-browser/api/extHostTypes.test.ts b/src/vs/workbench/test/electron-browser/api/extHostTypes.test.ts index 06f8131bb8..14aee58867 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostTypes.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostTypes.test.ts @@ -527,6 +527,25 @@ suite('ExtHostTypes', function () { string.appendVariable('BAR', b => { }); assert.equal(string.value, '${BAR}'); + string = new types.SnippetString(); + string.appendChoice(['b', 'a', 'r']); + assert.equal(string.value, '${1|b,a,r|}'); + + string = new types.SnippetString(); + string.appendChoice(['b', 'a', 'r'], 0); + assert.equal(string.value, '${0|b,a,r|}'); + + string = new types.SnippetString(); + string.appendText('foo').appendChoice(['far', 'boo']).appendText('bar'); + assert.equal(string.value, 'foo${1|far,boo|}bar'); + + string = new types.SnippetString(); + string.appendText('foo').appendChoice(['far', '$boo']).appendText('bar'); + assert.equal(string.value, 'foo${1|far,\\$boo|}bar'); + + string = new types.SnippetString(); + string.appendText('foo').appendPlaceholder('farboo').appendChoice(['far', 'boo']).appendText('bar'); + assert.equal(string.value, 'foo${1:farboo}${2|far,boo|}bar'); }); test('instanceof doesn\'t work for FileSystemError #49386', function () { diff --git a/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts b/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts index 3c18e894d1..91146c2e75 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts @@ -4,14 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { URI } from 'vs/base/common/uri'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { NullLogService } from 'vs/platform/log/common/log'; import { MainThreadWebviews } from 'vs/workbench/api/browser/mainThreadWebview'; import { ExtHostWebviews } from 'vs/workbench/api/common/extHostWebview'; +import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import * as vscode from 'vscode'; import { SingleProxyRPCProtocol } from './testRPCProtocol'; -import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; -import { URI } from 'vs/base/common/uri'; -import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; suite('ExtHostWebview', () => { @@ -23,7 +24,7 @@ suite('ExtHostWebview', () => { webviewCspSource: '', webviewResourceRoot: '', isExtensionDevelopmentDebug: false, - }, undefined); + }, undefined, new NullLogService()); let lastInvokedDeserializer: vscode.WebviewPanelSerializer | undefined = undefined; @@ -61,7 +62,7 @@ suite('ExtHostWebview', () => { webviewCspSource: '', webviewResourceRoot: 'vscode-resource://{{resource}}', isExtensionDevelopmentDebug: false, - }, undefined); + }, undefined, new NullLogService()); const webview = extHostWebviews.createWebviewPanel({} as any, 'type', 'title', 1, {}); assert.strictEqual( @@ -102,7 +103,7 @@ suite('ExtHostWebview', () => { webviewCspSource: '', webviewResourceRoot: `https://{{uuid}}.webview.contoso.com/commit/{{resource}}`, isExtensionDevelopmentDebug: false, - }, undefined); + }, undefined, new NullLogService()); const webview = extHostWebviews.createWebviewPanel({} as any, 'type', 'title', 1, {}); function stripEndpointUuid(input: string) { diff --git a/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts b/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts index d98bba7384..58706c9fd5 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts @@ -11,13 +11,15 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IWorkspaceFolderData } from 'vs/platform/workspace/common/workspace'; import { MainThreadWorkspace } from 'vs/workbench/api/browser/mainThreadWorkspace'; -import { IMainContext, IWorkspaceData, MainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { IMainContext, IWorkspaceData, MainContext, ITextSearchComplete } from 'vs/workbench/api/common/extHost.protocol'; import { RelativePattern } from 'vs/workbench/api/common/extHostTypes'; import { ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import { TestRPCProtocol } from './testRPCProtocol'; import { ExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder'; +import { IPatternInfo } from 'vs/workbench/services/search/common/search'; function createExtHostWorkspace(mainContext: IMainContext, data: IWorkspaceData, logService: ILogService): ExtHostWorkspace { const result = new ExtHostWorkspace( @@ -674,4 +676,106 @@ suite('ExtHostWorkspace', function () { assert(mainThreadCalled, 'mainThreadCalled'); }); }); + + test('findTextInFiles - no include', async () => { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + async $startTextSearch(query: IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise { + mainThreadCalled = true; + assert.equal(query.pattern, 'foo'); + assert.equal(folder, null); + assert.equal(options.includePattern, null); + assert.equal(options.excludePattern, null); + return null; + } + }); + + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); + await ws.findTextInFiles({ pattern: 'foo' }, {}, () => { }, new ExtensionIdentifier('test')); + assert(mainThreadCalled, 'mainThreadCalled'); + }); + + test('findTextInFiles - string include', async () => { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + async $startTextSearch(query: IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise { + mainThreadCalled = true; + assert.equal(query.pattern, 'foo'); + assert.equal(folder, null); + assert.equal(options.includePattern, '**/files'); + assert.equal(options.excludePattern, null); + return null; + } + }); + + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); + await ws.findTextInFiles({ pattern: 'foo' }, { include: '**/files' }, () => { }, new ExtensionIdentifier('test')); + assert(mainThreadCalled, 'mainThreadCalled'); + }); + + test('findTextInFiles - RelativePattern include', async () => { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + async $startTextSearch(query: IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise { + mainThreadCalled = true; + assert.equal(query.pattern, 'foo'); + assert.deepEqual(folder, URI.file('/other/folder').toJSON()); + assert.equal(options.includePattern, 'glob/**'); + assert.equal(options.excludePattern, null); + return null; + } + }); + + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); + await ws.findTextInFiles({ pattern: 'foo' }, { include: new RelativePattern('/other/folder', 'glob/**') }, () => { }, new ExtensionIdentifier('test')); + assert(mainThreadCalled, 'mainThreadCalled'); + }); + + test('findTextInFiles - with cancelled token', async () => { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + async $startTextSearch(query: IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise { + mainThreadCalled = true; + return null; + } + }); + + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); + const token = CancellationToken.Cancelled; + await ws.findTextInFiles({ pattern: 'foo' }, {}, () => { }, new ExtensionIdentifier('test'), token); + assert(!mainThreadCalled, '!mainThreadCalled'); + }); + + test('findTextInFiles - RelativePattern exclude', async () => { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + async $startTextSearch(query: IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise { + mainThreadCalled = true; + assert.equal(query.pattern, 'foo'); + assert.deepEqual(folder, null); + assert.equal(options.includePattern, null); + assert.equal(options.excludePattern, 'glob/**'); // exclude folder is ignored... + return null; + } + }); + + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); + await ws.findTextInFiles({ pattern: 'foo' }, { exclude: new RelativePattern('/other/folder', 'glob/**') }, () => { }, new ExtensionIdentifier('test')); + assert(mainThreadCalled, 'mainThreadCalled'); + }); }); diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts index 02e8c84347..067af27114 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts @@ -20,6 +20,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IFileService } from 'vs/platform/files/common/files'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; suite('MainThreadDocumentsAndEditors', () => { @@ -42,7 +43,7 @@ suite('MainThreadDocumentsAndEditors', () => { deltas.length = 0; const configService = new TestConfigurationService(); configService.setUserConfiguration('editor', { 'detectIndentation': false }); - modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService)); + modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService()); codeEditorService = new TestCodeEditorService(); textFileService = new class extends mock() { isDirty() { return false; } diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts index 264a693dd3..23fde4ff2e 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts @@ -27,6 +27,7 @@ import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/se import { IReference, ImmortalReference } from 'vs/base/common/lifecycle'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { LabelService } from 'vs/workbench/services/label/common/labelService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; suite('MainThreadEditors', () => { @@ -36,15 +37,17 @@ suite('MainThreadEditors', () => { let editors: MainThreadTextEditors; const movedResources = new Map(); + const copiedResources = new Map(); const createdResources = new Set(); const deletedResources = new Set(); setup(() => { const configService = new TestConfigurationService(); - modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService)); + modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService()); const codeEditorService = new TestCodeEditorService(); movedResources.clear(); + copiedResources.clear(); createdResources.clear(); deletedResources.clear(); @@ -64,6 +67,10 @@ suite('MainThreadEditors', () => { movedResources.set(source, target); return Promise.resolve(Object.create(null)); } + copy(source: URI, target: URI) { + copiedResources.set(source, target); + return Promise.resolve(Object.create(null)); + } models = { onModelSaved: Event.None, onModelReverted: Event.None, diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts index 2d084b99dd..2c4e4cef9b 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts @@ -13,7 +13,8 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { ITextFileService, SaveReason, IResolvedTextFileEditorModel, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, IResolvedTextFileEditorModel, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; +import { SaveReason } from 'vs/workbench/common/editor'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; class ServiceAccessor { diff --git a/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts index 0deb0cf40a..433c11fefc 100644 --- a/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts @@ -27,9 +27,10 @@ import 'vs/workbench/contrib/search/browser/search.contribution'; // load contri import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { LocalSearchService } from 'vs/workbench/services/search/node/searchService'; -import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { TestContextService, TestEditorGroupsService, TestEditorService, TestEnvironmentService, TestTextResourcePropertiesService } from 'vs/workbench/test/workbenchTestServices'; import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; namespace Timer { export interface ITimerEvent { @@ -73,12 +74,12 @@ suite.skip('QuickOpen performance (integration)', () => { [ITelemetryService, telemetryService], [IConfigurationService, configurationService], [ITextResourcePropertiesService, textResourcePropertiesService], - [IModelService, new ModelServiceImpl(configurationService, textResourcePropertiesService)], + [IModelService, new ModelServiceImpl(configurationService, textResourcePropertiesService, new TestThemeService())], [IWorkspaceContextService, new TestContextService(testWorkspace(URI.file(testWorkspacePath)))], [IEditorService, new TestEditorService()], [IEditorGroupsService, new TestEditorGroupsService()], [IEnvironmentService, TestEnvironmentService], - [IUntitledEditorService, createSyncDescriptor(UntitledEditorService)], + [IUntitledTextEditorService, createSyncDescriptor(UntitledTextEditorService)], [ISearchService, createSyncDescriptor(LocalSearchService)] )); diff --git a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts index 7740878118..cc49483db1 100644 --- a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts @@ -11,7 +11,7 @@ import { createSyncDescriptor } from 'vs/platform/instantiation/common/descripto import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ISearchService } from 'vs/workbench/services/search/common/search'; import { ITelemetryService, ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry'; -import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import * as minimist from 'vscode-minimist'; import * as path from 'vs/base/common/path'; @@ -34,6 +34,7 @@ import { testWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { NullLogService, ILogService } from 'vs/platform/log/common/log'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; declare var __dirname: string; @@ -63,12 +64,12 @@ suite.skip('TextSearch performance (integration)', () => { [ITelemetryService, telemetryService], [IConfigurationService, configurationService], [ITextResourcePropertiesService, textResourcePropertiesService], - [IModelService, new ModelServiceImpl(configurationService, textResourcePropertiesService)], + [IModelService, new ModelServiceImpl(configurationService, textResourcePropertiesService, new TestThemeService())], [IWorkspaceContextService, new TestContextService(testWorkspace(URI.file(testWorkspacePath)))], [IEditorService, new TestEditorService()], [IEditorGroupsService, new TestEditorGroupsService()], [IEnvironmentService, TestEnvironmentService], - [IUntitledEditorService, createSyncDescriptor(UntitledEditorService)], + [IUntitledTextEditorService, createSyncDescriptor(UntitledTextEditorService)], [ISearchService, createSyncDescriptor(LocalSearchService)], [ILogService, new NullLogService()] )); diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index d537cb5a9a..4816c7c9e8 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -8,10 +8,10 @@ import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileE import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { join } from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; -import { URI, UriComponents } from 'vs/base/common/uri'; +import { URI } from 'vs/base/common/uri'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { ConfirmResult, IEditorInputWithOptions, CloseDirection, IEditorIdentifier, IUntitledResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInput, IEditor, IEditorCloseEvent, IEditorPartOptions } from 'vs/workbench/common/editor'; +import { IEditorInputWithOptions, CloseDirection, IEditorIdentifier, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInput, IEditor, IEditorCloseEvent, IEditorPartOptions, IRevertOptions } from 'vs/workbench/common/editor'; import { IEditorOpeningEvent, EditorServiceImpl, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; import { Event, Emitter } from 'vs/base/common/event'; import Severity from 'vs/base/common/severity'; @@ -21,7 +21,7 @@ import { IWorkbenchLayoutService, Parts, Position as PartPosition } from 'vs/wor import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { IEditorOptions, IResourceInput } from 'vs/platform/editor/common/editor'; -import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IWorkspaceContextService, IWorkspace as IWorkbenchWorkspace, WorkbenchState, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, Workspace } from 'vs/platform/workspace/common/workspace'; import { ILifecycleService, BeforeShutdownEvent, ShutdownReason, StartupKind, LifecyclePhase, WillShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -49,7 +49,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MockContextKeyService, MockKeybindingService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { ITextBufferFactory, DefaultEndOfLine, EndOfLinePreference, IModelDecorationOptions, ITextModel, ITextSnapshot } from 'vs/editor/common/model'; import { Range } from 'vs/editor/common/core/range'; -import { IConfirmation, IConfirmationResult, IDialogService, IDialogOptions, IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService, IShowResult } from 'vs/platform/dialogs/common/dialogs'; +import { IConfirmation, IConfirmationResult, IDialogService, IDialogOptions, IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService, IShowResult, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { IExtensionService, NullExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -57,7 +57,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IDecorationsService, IResourceDecorationChangeEvent, IDecoration, IDecorationData, IDecorationsProvider } from 'vs/workbench/services/decorations/browser/decorations'; import { IDisposable, toDisposable, Disposable } from 'vs/base/common/lifecycle'; import { IEditorGroupsService, IEditorGroup, GroupsOrder, GroupsArrangement, GroupDirection, IAddGroupOptions, IMergeGroupOptions, IMoveEditorOptions, ICopyEditorOptions, IEditorReplacement, IGroupChangeEvent, EditorsOrder, IFindGroupScope, EditorGroupLayout, ICloseEditorOptions } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IEditorService, IOpenEditorOverrideHandler, IVisibleEditor } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService, IOpenEditorOverrideHandler, IVisibleEditor, ISaveEditorsOptions, IRevertAllEditorsOptions } from 'vs/workbench/services/editor/common/editorService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IDecorationRenderOptions } from 'vs/editor/common/editorCommon'; @@ -70,7 +70,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ViewletDescriptor, Viewlet } from 'vs/workbench/browser/viewlet'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/common/storage'; -import { isLinux, isMacintosh, IProcessEnvironment } from 'vs/base/common/platform'; +import { isLinux, isMacintosh } from 'vs/base/common/platform'; import { LabelService } from 'vs/workbench/services/label/common/labelService'; import { IDimension } from 'vs/platform/layout/browser/layoutService'; import { Part } from 'vs/workbench/browser/part'; @@ -79,7 +79,7 @@ import { IPanel } from 'vs/workbench/common/panel'; import { IBadge } from 'vs/workbench/services/activity/common/activity'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; +import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer'; import { NativeTextFileService } from 'vs/workbench/services/textfile/electron-browser/nativeTextFileService'; import { Schemas } from 'vs/base/common/network'; @@ -92,12 +92,14 @@ import { IBackupMainService, IWorkspaceBackupInfo } from 'vs/platform/backup/ele import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup'; import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; import { find } from 'vs/base/common/arrays'; +import { WorkingCopyService, IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IFilesConfigurationService, FilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined); } -export const TestEnvironmentService = new WorkbenchEnvironmentService(parseArgs(process.argv, OPTIONS) as IWindowConfiguration, process.execPath, 0); +export const TestEnvironmentService = new NativeWorkbenchEnvironmentService(parseArgs(process.argv, OPTIONS) as IWindowConfiguration, process.execPath, 0); export class TestContextService implements IWorkspaceContextService { public _serviceBrand: undefined; @@ -190,48 +192,47 @@ export class TestTextFileService extends NativeTextFileService { public cleanupBackupsBeforeShutdownCalled!: boolean; private promptPath!: URI; - private confirmResult!: ConfirmResult; private resolveTextContentError!: FileOperationError | null; constructor( @IWorkspaceContextService contextService: IWorkspaceContextService, @IFileService protected fileService: IFileService, - @IUntitledEditorService untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService untitledTextEditorService: IUntitledTextEditorService, @ILifecycleService lifecycleService: ILifecycleService, @IInstantiationService instantiationService: IInstantiationService, - @IConfigurationService configurationService: IConfigurationService, @IModeService modeService: IModeService, @IModelService modelService: IModelService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @INotificationService notificationService: INotificationService, @IBackupFileService backupFileService: IBackupFileService, @IHistoryService historyService: IHistoryService, - @IContextKeyService contextKeyService: IContextKeyService, @IDialogService dialogService: IDialogService, @IFileDialogService fileDialogService: IFileDialogService, @IEditorService editorService: IEditorService, @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, - @IElectronService electronService: IElectronService + @IElectronService electronService: IElectronService, + @IProductService productService: IProductService, + @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService ) { super( contextService, fileService, - untitledEditorService, + untitledTextEditorService, lifecycleService, instantiationService, - configurationService, modeService, modelService, environmentService, notificationService, backupFileService, historyService, - contextKeyService, dialogService, fileDialogService, editorService, textResourceConfigurationService, - electronService + electronService, + productService, + filesConfigurationService ); } @@ -239,10 +240,6 @@ export class TestTextFileService extends NativeTextFileService { this.promptPath = path; } - public setConfirmResult(result: ConfirmResult): void { - this.confirmResult = result; - } - public setResolveTextContentErrorOnce(error: FileOperationError): void { this.resolveTextContentError = error; } @@ -260,6 +257,7 @@ export class TestTextFileService extends NativeTextFileService { resource: content.resource, name: content.name, mtime: content.mtime, + ctime: content.ctime, etag: content.etag, encoding: 'utf8', value: await createTextBufferFactoryFromStream(content.value), @@ -272,18 +270,6 @@ export class TestTextFileService extends NativeTextFileService { return Promise.resolve(this.promptPath); } - public confirmSave(_resources?: URI[]): Promise { - return Promise.resolve(this.confirmResult); - } - - public confirmOverwrite(_resource: URI): Promise { - return Promise.resolve(true); - } - - public onFilesConfigurationChange(configuration: any): void { - super.onFilesConfigurationChange(configuration); - } - protected cleanupBackupsBeforeShutdown(): Promise { this.cleanupBackupsBeforeShutdownCalled = true; return Promise.resolve(); @@ -293,15 +279,19 @@ export class TestTextFileService extends NativeTextFileService { export function workbenchInstantiationService(): IInstantiationService { let instantiationService = new TestInstantiationService(new ServiceCollection([ILifecycleService, new TestLifecycleService()])); instantiationService.stub(IEnvironmentService, TestEnvironmentService); - instantiationService.stub(IContextKeyService, instantiationService.createInstance(MockContextKeyService)); + const contextKeyService = instantiationService.createInstance(MockContextKeyService); + instantiationService.stub(IContextKeyService, contextKeyService); const workspaceContextService = new TestContextService(TestWorkspace); instantiationService.stub(IWorkspaceContextService, workspaceContextService); const configService = new TestConfigurationService(); instantiationService.stub(IConfigurationService, configService); + instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService(contextKeyService, configService, TestEnvironmentService)); instantiationService.stub(ITextResourceConfigurationService, new TestTextResourceConfigurationService(configService)); - instantiationService.stub(IUntitledEditorService, instantiationService.createInstance(UntitledEditorService)); + instantiationService.stub(IUntitledTextEditorService, instantiationService.createInstance(UntitledTextEditorService)); instantiationService.stub(IStorageService, new TestStorageService()); instantiationService.stub(IWorkbenchLayoutService, new TestLayoutService()); + instantiationService.stub(IDialogService, new TestDialogService()); + instantiationService.stub(IFileDialogService, new TestFileDialogService()); instantiationService.stub(IElectronService, new TestElectronService()); instantiationService.stub(IModeService, instantiationService.createInstance(ModeServiceImpl)); instantiationService.stub(IHistoryService, new TestHistoryService()); @@ -311,7 +301,7 @@ export function workbenchInstantiationService(): IInstantiationService { instantiationService.stub(IBackupFileService, new TestBackupFileService()); instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(INotificationService, new TestNotificationService()); - instantiationService.stub(IUntitledEditorService, instantiationService.createInstance(UntitledEditorService)); + instantiationService.stub(IUntitledTextEditorService, instantiationService.createInstance(UntitledTextEditorService)); instantiationService.stub(IMenuService, new TestMenuService()); instantiationService.stub(IKeybindingService, new MockKeybindingService()); instantiationService.stub(IDecorationsService, new TestDecorationsService()); @@ -327,6 +317,7 @@ export function workbenchInstantiationService(): IInstantiationService { instantiationService.stub(IEditorService, editorService); instantiationService.stub(ICodeEditorService, new TestCodeEditorService()); instantiationService.stub(IViewletService, new TestViewletService()); + instantiationService.stub(IWorkingCopyService, new TestWorkingCopyService()); return instantiationService; } @@ -418,6 +409,8 @@ export class TestFileDialogService implements IFileDialogService { public _serviceBrand: undefined; + private confirmResult!: ConfirmResult; + public defaultFilePath(_schemeFilter?: string): URI | undefined { return undefined; } @@ -448,6 +441,12 @@ export class TestFileDialogService implements IFileDialogService { public showOpenDialog(_options: IOpenDialogOptions): Promise { return Promise.resolve(undefined); } + public setConfirmResult(result: ConfirmResult): void { + this.confirmResult = result; + } + public showSaveConfirm(fileNamesOrResources: (string | URI)[]): Promise { + return Promise.resolve(this.confirmResult); + } } export class TestLayoutService implements IWorkbenchLayoutService { @@ -461,6 +460,7 @@ export class TestLayoutService implements IWorkbenchLayoutService { onZenModeChange: Event = Event.None; onCenteredLayoutChange: Event = Event.None; onFullscreenChange: Event = Event.None; + onMaximizeChange: Event = Event.None; onPanelPositionChange: Event = Event.None; onPartVisibilityChange: Event = Event.None; onLayout = Event.None; @@ -479,6 +479,14 @@ export class TestLayoutService implements IWorkbenchLayoutService { return false; } + public hasWindowBorder(): boolean { + return false; + } + + public getWindowBorderRadius(): string | undefined { + return undefined; + } + public isVisible(_part: Parts): boolean { return true; } @@ -562,6 +570,12 @@ export class TestLayoutService implements IWorkbenchLayoutService { public resizePart(_part: Parts, _sizeChange: number): void { } public registerPart(part: Part): void { } + + isWindowMaximized() { + return false; + } + + public updateWindowMaximizedState(maximized: boolean): void { } } let activeViewlet: Viewlet = {} as any; @@ -795,7 +809,7 @@ export class TestEditorGroup implements IEditorGroupView { return []; } - getEditor(_index: number): IEditorInput { + getEditorByIndex(_index: number): IEditorInput { throw new Error('not implemented'); } @@ -888,11 +902,11 @@ export class TestEditorService implements EditorServiceImpl { throw new Error('not implemented'); } - isOpen(_editor: IEditorInput | IResourceInput | IUntitledResourceInput): boolean { + isOpen(_editor: IEditorInput | IResourceInput | IUntitledTextResourceInput): boolean { return false; } - getOpened(_editor: IEditorInput | IResourceInput | IUntitledResourceInput): IEditorInput { + getOpened(_editor: IEditorInput | IResourceInput | IUntitledTextResourceInput): IEditorInput { throw new Error('not implemented'); } @@ -904,9 +918,25 @@ export class TestEditorService implements EditorServiceImpl { throw new Error('not implemented'); } - createInput(_input: IResourceInput | IUntitledResourceInput | IResourceDiffInput | IResourceSideBySideInput): IEditorInput { + createInput(_input: IResourceInput | IUntitledTextResourceInput | IResourceDiffInput | IResourceSideBySideInput): IEditorInput { throw new Error('not implemented'); } + + save(editors: IEditorIdentifier[], options?: ISaveEditorsOptions): Promise { + throw new Error('Method not implemented.'); + } + + saveAll(options?: ISaveEditorsOptions): Promise { + throw new Error('Method not implemented.'); + } + + revert(editors: IEditorIdentifier[], options?: IRevertOptions): Promise { + throw new Error('Method not implemented.'); + } + + revertAll(options?: IRevertAllEditorsOptions): Promise { + throw new Error('Method not implemented.'); + } } export class TestFileService implements IFileService { @@ -964,7 +994,9 @@ export class TestFileService implements IFileService { encoding: 'utf8', mtime: Date.now(), size: 42, + isFile: true, isDirectory: false, + isSymbolicLink: false, name: resources.basename(resource) }); } @@ -986,6 +1018,7 @@ export class TestFileService implements IFileService { etag: 'index.txt', encoding: 'utf8', mtime: Date.now(), + ctime: Date.now(), name: resources.basename(resource), size: 1 }); @@ -1012,6 +1045,7 @@ export class TestFileService implements IFileService { etag: 'index.txt', encoding: 'utf8', mtime: Date.now(), + ctime: Date.now(), size: 1, name: resources.basename(resource) }); @@ -1023,8 +1057,11 @@ export class TestFileService implements IFileService { etag: 'index.txt', encoding: 'utf8', mtime: Date.now(), + ctime: Date.now(), size: 42, + isFile: true, isDirectory: false, + isSymbolicLink: false, name: resources.basename(resource) })); } @@ -1354,7 +1391,7 @@ export class TestElectronService implements IElectronService { async setRepresentedFilename(path: string): Promise { } async setDocumentEdited(edited: boolean): Promise { } async openExternal(url: string): Promise { return false; } - async updateTouchBar(items: { id: string; title: string | { value: string; original: string; }; category?: string | { value: string; original: string; } | undefined; iconLocation?: { dark: UriComponents; light?: { readonly scheme: string; readonly authority: string; readonly path: string; readonly query: string; readonly fragment: string; readonly fsPath: string; with: {}; toString: {}; toJSON: {}; } | undefined; } | undefined; precondition?: { getType: {}; equals: {}; evaluate: {}; serialize: {}; keys: {}; map: {}; negate: {}; } | undefined; toggled?: { getType: {}; equals: {}; evaluate: {}; serialize: {}; keys: {}; map: {}; negate: {}; } | undefined; }[][]): Promise { } + async updateTouchBar(): Promise { } async newWindowTab(): Promise { } async showPreviousWindowTab(): Promise { } async showNextWindowTab(): Promise { } @@ -1369,7 +1406,6 @@ export class TestElectronService implements IElectronService { async toggleDevTools(): Promise { } async startCrashReporter(options: Electron.CrashReporterStartOptions): Promise { } async resolveProxy(url: string): Promise { return undefined; } - async openExtensionDevelopmentHostWindow(args: string[], env: IProcessEnvironment): Promise { } } export class TestBackupMainService implements IBackupMainService { @@ -1447,3 +1483,12 @@ export class TestDialogMainService implements IDialogMainService { throw new Error('Method not implemented.'); } } + +export class TestWorkingCopyService extends WorkingCopyService { } + +export class TestFilesConfigurationService extends FilesConfigurationService { + + onFilesConfigurationChange(configuration: any): void { + super.onFilesConfigurationChange(configuration); + } +} diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index f51c8d458e..b690871dd0 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -71,7 +71,7 @@ import 'vs/workbench/services/editor/browser/editorService'; import 'vs/workbench/services/history/browser/history'; import 'vs/workbench/services/activity/browser/activityService'; import 'vs/workbench/services/keybinding/browser/keybindingService'; -import 'vs/workbench/services/untitled/common/untitledEditorService'; +import 'vs/workbench/services/untitled/common/untitledTextEditorService'; import 'vs/workbench/services/textfile/common/textResourcePropertiesService'; import 'vs/workbench/services/mode/common/workbenchModeService'; import 'vs/workbench/services/commands/common/commandService'; @@ -81,7 +81,11 @@ import 'vs/workbench/services/extensionManagement/common/extensionEnablementServ import 'vs/workbench/services/notification/common/notificationService'; import 'vs/workbench/services/extensions/common/staticExtensions'; import 'vs/workbench/services/userDataSync/common/settingsMergeService'; +import 'vs/workbench/services/userDataSync/common/userDataSyncUtil'; import 'vs/workbench/services/path/common/remotePathService'; +import 'vs/workbench/services/remote/common/remoteExplorerService'; +import 'vs/workbench/services/workingCopy/common/workingCopyService'; +import 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; @@ -357,6 +361,12 @@ import 'vs/workbench/contrib/feedback/browser/feedback.contribution'; // User Data Sync import 'vs/workbench/contrib/userDataSync/browser/userDataSync.contribution'; +// Code Actions +import 'vs/workbench/contrib/codeActions/common/codeActions.contribution'; + +// Test Custom Editors +import 'vs/workbench/contrib/testCustomEditors/browser/testCustomEditors'; + //#endregion //#region -- contributions diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 89cd098447..59b9822a26 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -62,6 +62,7 @@ import 'vs/workbench/services/clipboard/electron-browser/clipboardService'; import 'vs/workbench/services/update/electron-browser/updateService'; import 'vs/workbench/services/issue/electron-browser/issueService'; import 'vs/workbench/services/menubar/electron-browser/menubarService'; +import 'vs/workbench/services/extensionResourceLoader/electron-browser/extensionResourceLoaderService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; @@ -92,9 +93,9 @@ import 'vs/workbench/contrib/localizations/browser/localizations.contribution'; // Logs import 'vs/workbench/contrib/logs/electron-browser/logs.contribution'; -// Stats -import 'vs/workbench/contrib/stats/electron-browser/workspaceStatsService'; -import 'vs/workbench/contrib/stats/electron-browser/stats.contribution'; +// Tags +import 'vs/workbench/contrib/tags/electron-browser/workspaceTagsService'; +import 'vs/workbench/contrib/tags/electron-browser/tags.contribution'; // Rapid Render Splash import 'vs/workbench/contrib/splash/electron-browser/partsSplash.contribution'; diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index f643c6e7e2..939746a338 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -17,32 +17,49 @@ import { Event, Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IWorkspaceProvider, IWorkspace } from 'vs/workbench/services/host/browser/browserHostService'; +interface IResourceUriProvider { + (uri: URI): URI; +} + +interface IStaticExtension { + packageJSON: IExtensionManifest; + extensionLocation: URI; +} + +interface ICommontTelemetryPropertiesResolver { + (): { [key: string]: any }; +} + +interface IExternalUriResolver { + (uri: URI): Promise; +} + interface IWorkbenchConstructionOptions { /** - * Experimental: the remote authority is the IP:PORT from where the workbench is served + * The remote authority is the IP:PORT from where the workbench is served * from. It is for example being used for the websocket connections as address. */ - remoteAuthority?: string; + readonly remoteAuthority?: string; /** * The connection token to send to the server. */ - connectionToken?: string; + readonly connectionToken?: string; /** - * Experimental: An endpoint to serve iframe content ("webview") from. This is required + * An endpoint to serve iframe content ("webview") from. This is required * to provide full security isolation from the workbench host. */ - webviewEndpoint?: string; + readonly webviewEndpoint?: string; /** - * Experimental: a handler for opening workspaces and providing the initial workspace. + * A handler for opening workspaces and providing the initial workspace. */ - workspaceProvider?: IWorkspaceProvider; + readonly workspaceProvider?: IWorkspaceProvider; /** - * Experimental: The userDataProvider is used to handle user specific application + * The user data provider is used to handle user specific application * state like settings, keybindings, UI state (e.g. opened editors) and snippets. */ userDataProvider?: IFileSystemProvider; @@ -50,56 +67,56 @@ interface IWorkbenchConstructionOptions { /** * A factory for web sockets. */ - webSocketFactory?: IWebSocketFactory; + readonly webSocketFactory?: IWebSocketFactory; /** * A provider for resource URIs. */ - resourceUriProvider?: (uri: URI) => URI; + readonly resourceUriProvider?: IResourceUriProvider; /** - * Experimental: Whether to enable the smoke test driver. + * The credentials provider to store and retrieve secrets. */ - driver?: boolean; + readonly credentialsProvider?: ICredentialsProvider; /** - * Experimental: The credentials provider to store and retrieve secrets. + * Add static extensions that cannot be uninstalled but only be disabled. */ - credentialsProvider?: ICredentialsProvider; + readonly staticExtensions?: ReadonlyArray; /** - * Experimental: Add static extensions that cannot be uninstalled but only be disabled. + * Support for URL callbacks. */ - staticExtensions?: { packageJSON: IExtensionManifest, extensionLocation: URI }[]; + readonly urlCallbackProvider?: IURLCallbackProvider; /** - * Experimental: Support for URL callbacks. + * Support for update reporting. */ - urlCallbackProvider?: IURLCallbackProvider; + readonly updateProvider?: IUpdateProvider; + + /** + * Support adding additional properties to telemetry. + */ + readonly resolveCommonTelemetryProperties?: ICommontTelemetryPropertiesResolver; + + /** + * Resolves an external uri before it is opened. + */ + readonly resolveExternalUri?: IExternalUriResolver; /** * Current logging level. Default is `LogLevel.Info`. */ - logLevel?: LogLevel; + readonly logLevel?: LogLevel; /** - * Experimental: Support for update reporting. + * Whether to enable the smoke test driver. */ - updateProvider?: IUpdateProvider; - - /** - * Experimental: Support adding additional properties to telemetry. - */ - resolveCommonTelemetryProperties?: () => { [key: string]: any }; - - /** - * Experimental: Resolves an external uri before it is opened. - */ - readonly resolveExternalUri?: (uri: URI) => Promise; + readonly driver?: boolean; } /** - * Experimental: Creates the workbench with the provided options in the provided container. + * Creates the workbench with the provided options in the provided container. * * @param domElement the container to create the workbench in * @param options for setting up the workbench @@ -136,10 +153,14 @@ export { IWebSocketFactory, IWebSocket, + // Resources + IResourceUriProvider, + // Credentials ICredentialsProvider, // Static Extensions + IStaticExtension, IExtensionManifest, // Callbacks @@ -151,4 +172,10 @@ export { // Updates IUpdateProvider, IUpdate, + + // Telemetry + ICommontTelemetryPropertiesResolver, + + // External Uris + IExternalUriResolver }; diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index 1aac178d8a..a049b4f2cd 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -39,7 +39,7 @@ import 'vs/workbench/services/configurationResolver/browser/configurationResolve import 'vs/workbench/services/credentials/browser/credentialsService'; import 'vs/workbench/services/url/browser/urlService'; import 'vs/workbench/services/update/browser/updateService'; -import 'vs/workbench/contrib/stats/browser/workspaceStatsService'; +import 'vs/workbench/contrib/tags/browser/workspaceTagsService'; import 'vs/workbench/services/workspaces/browser/workspacesService'; import 'vs/workbench/services/workspaces/browser/workspaceEditingService'; import 'vs/workbench/services/dialogs/browser/dialogService'; @@ -48,6 +48,7 @@ import 'vs/workbench/services/host/browser/browserHostService'; import 'vs/workbench/services/request/browser/requestService'; import 'vs/workbench/services/lifecycle/browser/lifecycleService'; import 'vs/workbench/services/clipboard/browser/clipboardService'; +import 'vs/workbench/services/extensionResourceLoader/browser/extensionResourceLoaderService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; @@ -63,7 +64,7 @@ import { NoOpTunnelService } from 'vs/platform/remote/common/tunnelService'; import { ILoggerService } from 'vs/platform/log/common/log'; import { FileLoggerService } from 'vs/platform/log/common/fileLogService'; import { IAuthTokenService } from 'vs/platform/auth/common/auth'; -import { AuthTokenService } from 'vs/platform/auth/common/authTokenService'; +import { AuthTokenService } from 'vs/workbench/services/authToken/browser/authTokenService'; import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; @@ -108,4 +109,7 @@ import 'vs/workbench/contrib/tasks/browser/taskService'; // Telemetry Opt Out import 'vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut.contribution'; +// Issues +import 'vs/workbench/contrib/issue/browser/issue.contribution'; + //#endregion diff --git a/test/automation/.gitignore b/test/automation/.gitignore index 39397c0225..dcb2722529 100644 --- a/test/automation/.gitignore +++ b/test/automation/.gitignore @@ -5,3 +5,4 @@ node_modules/ out/ keybindings.*.json src/driver.d.ts +*.tgz diff --git a/test/automation/.npmignore b/test/automation/.npmignore new file mode 100644 index 0000000000..356ab4ded8 --- /dev/null +++ b/test/automation/.npmignore @@ -0,0 +1,6 @@ +!/out +/src +/tools +.gitignore +tsconfig.json +*.tgz diff --git a/test/automation/package.json b/test/automation/package.json index a4794be8f8..297dce969b 100644 --- a/test/automation/package.json +++ b/test/automation/package.json @@ -9,7 +9,7 @@ "main": "./out/index.js", "private": true, "scripts": { - "postinstall": "npm run compile", + "prepare": "npm run compile", "compile": "npm run copy-driver && npm run copy-driver-definition && tsc", "watch": "concurrently \"npm run watch-driver\" \"npm run watch-driver-definition\" \"tsc --watch\"", "copy-driver": "cpx src/driver.js out/", diff --git a/test/automation/src/debug.ts b/test/automation/src/debug.ts index 899116c94d..9c9de81d6e 100644 --- a/test/automation/src/debug.ts +++ b/test/automation/src/debug.ts @@ -12,14 +12,14 @@ import { IElement } from '../src/driver'; const VIEWLET = 'div[id="workbench.view.debug"]'; const DEBUG_VIEW = `${VIEWLET} .debug-view-content`; -const CONFIGURE = `div[id="workbench.parts.sidebar"] .actions-container .configure`; +const CONFIGURE = `div[id="workbench.parts.sidebar"] .actions-container .codicon-gear`; const STOP = `.debug-toolbar .action-label[title*="Stop"]`; const STEP_OVER = `.debug-toolbar .action-label[title*="Step Over"]`; const STEP_IN = `.debug-toolbar .action-label[title*="Step Into"]`; const STEP_OUT = `.debug-toolbar .action-label[title*="Step Out"]`; const CONTINUE = `.debug-toolbar .action-label[title*="Continue"]`; const GLYPH_AREA = '.margin-view-overlays>:nth-child'; -const BREAKPOINT_GLYPH = '.debug-breakpoint'; +const BREAKPOINT_GLYPH = '.codicon-debug-breakpoint'; const PAUSE = `.debug-toolbar .action-label[title*="Pause"]`; const DEBUG_STATUS_BAR = `.statusbar.debugging`; const NOT_DEBUG_STATUS_BAR = `.statusbar:not(debugging)`; diff --git a/test/automation/src/editor.ts b/test/automation/src/editor.ts index 09058cb5a7..efe680783f 100644 --- a/test/automation/src/editor.ts +++ b/test/automation/src/editor.ts @@ -40,7 +40,7 @@ export class Editor { async gotoDefinition(filename: string, term: string, line: number): Promise { await this.clickOnTerm(filename, term, line); - await this.commands.runCommand('Go to Implementation'); + await this.commands.runCommand('Go to Implementations'); } async peekDefinition(filename: string, term: string, line: number): Promise { diff --git a/test/automation/src/index.ts b/test/automation/src/index.ts index 118a757690..4302a9c670 100644 --- a/test/automation/src/index.ts +++ b/test/automation/src/index.ts @@ -24,7 +24,7 @@ export * from './statusbar'; export * from './terminal'; export * from './viewlet'; export * from './workbench'; -export * from '../src/driver'; +export * from './driver'; // {{SQL CARBON EDIT}} export * from './sql/connectionDialog'; diff --git a/test/automation/yarn.lock b/test/automation/yarn.lock index 648ea306c6..94a1350861 100644 --- a/test/automation/yarn.lock +++ b/test/automation/yarn.lock @@ -730,9 +730,9 @@ hosted-git-info@^2.1.4: integrity sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ== https-proxy-agent@^2.2.1: - version "2.2.4" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" - integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== + version "2.2.3" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.3.tgz#fb6cd98ed5b9c35056b5a73cd01a8a721d7193d1" + integrity sha512-Ytgnz23gm2DVftnzqRRz2dOXZbGd2uiajSw/95bPp6v53zPRspQjLm/AfBgqbJ2qfeRXWIOMVLpp86+/5yX39Q== dependencies: agent-base "^4.3.0" debug "^3.1.0" diff --git a/test/electron/renderer.html b/test/electron/renderer.html index 38005b2d60..49cfa95c65 100644 --- a/test/electron/renderer.html +++ b/test/electron/renderer.html @@ -23,6 +23,16 @@ window.alert = function () { throw new Error('window.alert() is not supported in tests!'); } window.confirm = function () { throw new Error('window.confirm() is not supported in tests!'); } + // Ignore uncaught cancelled promise errors + window.addEventListener('unhandledrejection', e => { + const name = e && e.reason && e.reason.name; + + if (name === 'Canceled') { + e.preventDefault(); + e.stopPropagation(); + } + }); + mocha.setup({ ui: 'tdd', timeout: 5000 diff --git a/test/smoke/README.md b/test/smoke/README.md index 5f86605f15..7fc0ced719 100644 --- a/test/smoke/README.md +++ b/test/smoke/README.md @@ -43,13 +43,6 @@ yarn smoketest --build PATH_TO_NEW_RELEASE_PARENT_FOLDER --stable-build PATH_TO_ ### Develop -Start two watch tasks: - -```bash -cd test/automation -yarn watch -``` - ```bash cd test/smoke yarn watch @@ -63,6 +56,6 @@ yarn watch - Beware of **focus**. **Never** depend on DOM elements having focus using `.focused` classes or `:focus` pseudo-classes, since they will lose that state as soon as another window appears on top of the running VS Code window. A safe approach which avoids this problem is to use the `waitForActiveElement` API. Many tests use this whenever they need to wait for a specific element to _have focus_. -- Beware of **timing**. You need to read from or write to the DOM... but is it the right time to do that? Can you 100% guarantee that that `input` box will be visible at that point in time? Or are you just hoping that it will be so? Hope is your worst enemy in UI tests. Example: just because you triggered Quick Open with `F1`, it doesn't mean that it's open and you can just start typing; you must first wait for the input element to be in the DOM as well as be the current active element. +- Beware of **timing**. You need to read from or write to the DOM... but is it the right time to do that? Can you 100% guarantee that `input` box will be visible at that point in time? Or are you just hoping that it will be so? Hope is your worst enemy in UI tests. Example: just because you triggered Quick Open with `F1`, it doesn't mean that it's open and you can just start typing; you must first wait for the input element to be in the DOM as well as be the current active element. - Beware of **waiting**. **Never** wait longer than a couple of seconds for anything, unless it's justified. Think of it as a human using Code. Would a human take 10 minutes to run through the Search viewlet smoke test? Then, the computer should even be faster. **Don't** use `setTimeout` just because. Think about what you should wait for in the DOM to be ready and wait for that instead. diff --git a/test/smoke/package.json b/test/smoke/package.json index aedec3f871..2ae2926ada 100644 --- a/test/smoke/package.json +++ b/test/smoke/package.json @@ -13,7 +13,7 @@ "@types/mkdirp": "0.5.1", "@types/mocha": "2.2.41", "@types/ncp": "2.0.1", - "@types/node": "^10.14.8", + "@types/node": "^12.11.7", "@types/rimraf": "2.0.2", "concurrently": "^3.5.1", "cpx": "^1.5.0", diff --git a/test/smoke/src/areas/editor/editor.test.ts b/test/smoke/src/areas/editor/editor.test.ts index 4987db7b41..27884dd73e 100644 --- a/test/smoke/src/areas/editor/editor.test.ts +++ b/test/smoke/src/areas/editor/editor.test.ts @@ -15,24 +15,6 @@ export function setup() { await app.workbench.quickopen.waitForQuickOpenElements(names => names.length >= 6); }); - it(`finds 'All References' to 'app'`, async function () { - const app = this.app as Application; - await app.workbench.quickopen.openFile('www'); - - const references = await app.workbench.editor.findReferences('www', 'app', 7); - - await references.waitForReferencesCountInTitle(3); - await references.waitForReferencesCount(3); - await references.close(); - }); - - it(`renames local 'app' variable`, async function () { - const app = this.app as Application; - await app.workbench.quickopen.openFile('www'); - await app.workbench.editor.rename('www', 7, 'app', 'newApp'); - await app.workbench.editor.waitForEditorContents('www', contents => contents.indexOf('newApp') > -1); - }); - // it('folds/unfolds the code correctly', async function () { // await app.workbench.quickopen.openFile('www'); @@ -48,23 +30,5 @@ export function setup() { // await app.workbench.editor.waitUntilShown(4); // await app.workbench.editor.waitUntilShown(5); // }); - - it(`verifies that 'Go To Definition' works`, async function () { - const app = this.app as Application; - await app.workbench.quickopen.openFile('app.js'); - - await app.workbench.editor.gotoDefinition('app.js', 'app', 14); - - await app.workbench.editor.waitForHighlightingLine('app.js', 11); - }); - - it(`verifies that 'Peek Definition' works`, async function () { - const app = this.app as Application; - await app.workbench.quickopen.openFile('app.js'); - - const peek = await app.workbench.editor.peekDefinition('app.js', 'app', 14); - - await peek.waitForFile('app.js'); - }); }); } diff --git a/test/smoke/src/areas/statusbar/statusbar.test.ts b/test/smoke/src/areas/statusbar/statusbar.test.ts index 64feccc0d7..3e94518631 100644 --- a/test/smoke/src/areas/statusbar/statusbar.test.ts +++ b/test/smoke/src/areas/statusbar/statusbar.test.ts @@ -5,7 +5,7 @@ import { Application, Quality, StatusBarElement } from '../../../../automation'; -export function setup() { +export function setup(isWeb) { describe('Statusbar', () => { it('verifies presence of all default status bar elements', async function () { const app = this.app as Application; @@ -18,7 +18,10 @@ export function setup() { await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.PROBLEMS_STATUS); await app.workbench.quickopen.openFile('app.js'); - await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.ENCODING_STATUS); + if (!isWeb) { + // Encoding picker currently hidden in web (only UTF-8 supported) + await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.ENCODING_STATUS); + } await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.EOL_STATUS); await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.INDENTATION_STATUS); await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.LANGUAGE_STATUS); @@ -36,9 +39,12 @@ export function setup() { await app.workbench.statusbar.clickOn(StatusBarElement.INDENTATION_STATUS); await app.workbench.quickinput.waitForQuickInputOpened(); await app.workbench.quickinput.closeQuickInput(); - await app.workbench.statusbar.clickOn(StatusBarElement.ENCODING_STATUS); - await app.workbench.quickinput.waitForQuickInputOpened(); - await app.workbench.quickinput.closeQuickInput(); + if (!isWeb) { + // Encoding picker currently hidden in web (only UTF-8 supported) + await app.workbench.statusbar.clickOn(StatusBarElement.ENCODING_STATUS); + await app.workbench.quickinput.waitForQuickInputOpened(); + await app.workbench.quickinput.closeQuickInput(); + } await app.workbench.statusbar.clickOn(StatusBarElement.EOL_STATUS); await app.workbench.quickinput.waitForQuickInputOpened(); await app.workbench.quickinput.closeQuickInput(); diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index 461fee2ebe..baac0c4195 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -312,7 +312,7 @@ describe('Running Code', () => { setupDataEditorTests(); if (!opts.web) { setupDataDebugTests(); } setupDataGitTests(); - setupDataStatusbarTests(); + setupDataStatusbarTests(!!opts.web); setupDataExtensionTests(); setupTerminalTests(); if (!opts.web) { setupDataMultirootTests(); } diff --git a/test/smoke/yarn.lock b/test/smoke/yarn.lock index 330b02367e..82626a55c7 100644 --- a/test/smoke/yarn.lock +++ b/test/smoke/yarn.lock @@ -44,10 +44,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.51.tgz#b31d716fb8d58eeb95c068a039b9b6292817d5fb" integrity sha512-El3+WJk2D/ppWNd2X05aiP5l2k4EwF7KwheknQZls+I26eSICoWRhRIJ56jGgw2dqNGQ5LtNajmBU2ajS28EvQ== -"@types/node@^10.14.8": - version "10.14.8" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9" - integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw== +"@types/node@^12.11.7": + version "12.11.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a" + integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA== "@types/rimraf@2.0.2": version "2.0.2" diff --git a/test/splitview/public/index.html b/test/splitview/public/index.html index 2df7131614..0951af8ea5 100644 --- a/test/splitview/public/index.html +++ b/test/splitview/public/index.html @@ -18,7 +18,10 @@ color: white; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-weight: bold; - font-size: 30px; + font-size: 20px; + display: flex; + justify-content: center; + align-items: center; } @@ -65,9 +68,8 @@ this.setVisible(true); } - layout(width, height, orientation) { - console.log(`layout@${this.label}`); - this.element.style.lineHeight = `${height}px`; + layout(width, height, top, left) { + this.element.innerHTML = `(${top}, ${left})
(${width}, ${height})`; } setVisible(visible) { diff --git a/yarn.lock b/yarn.lock index 4a5a9cce61..aeecd02713 100644 --- a/yarn.lock +++ b/yarn.lock @@ -135,11 +135,25 @@ lodash "^4.17.11" to-fast-properties "^2.0.0" +"@types/applicationinsights@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@types/applicationinsights/-/applicationinsights-0.20.0.tgz#fa7b36dc954f635fa9037cad27c378446b1048fb" + integrity sha512-dQ3Hb58ERe5YNKFVyvU9BrEvpgKeb6Ht9HkCyBvsOZxhx6yKSwF3e+xml3PJQ3JiVOvf6gM/PmE3MdWDl1L6aA== + dependencies: + applicationinsights "*" + "@types/chart.js@^2.7.31": version "2.7.48" resolved "https://registry.yarnpkg.com/@types/chart.js/-/chart.js-2.7.48.tgz#db7b6d6ed33659f97ee49181f22c980bb0790a7b" integrity sha512-U8paSPZGkW2WrHf8sgJj7s9MhfRgSz7wfU3CN73JVrcGJ13jGiqiXyr3Bg/4UFl4cbN3S8avHTsbtzYBrnWeVg== +"@types/chokidar@2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@types/chokidar/-/chokidar-2.1.3.tgz#123ab795dba6d89be04bf076e6aecaf8620db674" + integrity sha512-6qK3xoLLAhQVTucQGHTySwOVA1crHRXnJeLwqK6KIFkkKa2aoMFXh+WEi8PotxDtvN6MQJLyYN9ag9P6NLV81w== + dependencies: + chokidar "*" + "@types/commander@^2.11.0": version "2.12.2" resolved "https://registry.yarnpkg.com/@types/commander/-/commander-2.12.2.tgz#183041a23842d4281478fa5d23c5ca78e6fd08ae" @@ -162,13 +176,34 @@ resolved "https://registry.yarnpkg.com/@types/fancy-log/-/fancy-log-1.3.0.tgz#a61ab476e5e628cd07a846330df53b85e05c8ce0" integrity sha512-mQjDxyOM1Cpocd+vm1kZBP7smwKZ4TNokFeds9LV7OZibmPJFEzY3+xZMrKfUdNT71lv8GoCPD6upKwHxubClw== -"@types/htmlparser2@*", "@types/htmlparser2@^3.7.31": +"@types/graceful-fs@4.1.2": + version "4.1.2" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.2.tgz#fbc9575dbcc6d1d91dd768d30c5fc0c19f6c50bd" + integrity sha512-epDhsJAVxJsWfeqpzEDFhLnhHMbHie/VMFY+2Hvt5p7FemeW5ELM+6gcVYL/ZsUwdu3zrWpDE3VUTddXW+EMYg== + dependencies: + "@types/node" "*" + +"@types/htmlparser2@*": version "3.7.31" resolved "https://registry.yarnpkg.com/@types/htmlparser2/-/htmlparser2-3.7.31.tgz#ae89353691ce37fa2463c3b8b4698f20ef67a59b" integrity sha512-6Kjy02k+KfJJE2uUiCytS31SXCYnTjKA+G0ydb83DTlMFzorBlezrV2XiKazRO5HSOEvVW3cpzDFPoP9n/9rSA== dependencies: "@types/node" "*" +"@types/http-proxy-agent@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/http-proxy-agent/-/http-proxy-agent-2.0.1.tgz#2f95077f6bfe7adc39cc0f0042da85997ae77fc7" + integrity sha512-dgsgbsgI3t+ZkdzF9H19uBaLsurIZJJjJsVpj4mCLp8B6YghQ7jVwyqhaL0PcVtuC3nOi0ZBhAi2Dd9jCUwdFA== + dependencies: + "@types/node" "*" + +"@types/iconv-lite@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@types/iconv-lite/-/iconv-lite-0.0.1.tgz#aa3b8bda2be512b1ae0a057b942e869c370a5569" + integrity sha1-qjuL2ivlErGuCgV7lC6GnDcKVWk= + dependencies: + "@types/node" "*" + "@types/keytar@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@types/keytar/-/keytar-4.4.0.tgz#ca24e6ee6d0df10c003aafe26e93113b8faf0d8e" @@ -230,11 +265,35 @@ "@types/uglify-js" "*" source-map "^0.6.0" +"@types/windows-foreground-love@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@types/windows-foreground-love/-/windows-foreground-love-0.3.0.tgz#26bc230b2568aa7ab7c56d35bb5653c0a6965a42" + integrity sha512-tFUVA/fiofNqOh6lZlymvQiQYPY+cZXZPR9mn9wN6/KS8uwx0zgH4Ij/jmFyRYr+x+DGZWEIeknS2BMi7FZJAQ== + +"@types/windows-process-tree@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@types/windows-process-tree/-/windows-process-tree-0.2.0.tgz#2fa205c838a8ef0a07697cd747c954653978d22c" + integrity sha512-vQAnkWpMX4HUPjubkxKta4Rfh2EDy2ksalnr37gFHNrmk+uxx50PRH+/fM5nTsEBCi4ESFT/7t7Za3jGqyTZ4g== + "@types/winreg@^1.2.30": version "1.2.30" resolved "https://registry.yarnpkg.com/@types/winreg/-/winreg-1.2.30.tgz#91d6710e536d345b9c9b017c574cf6a8da64c518" integrity sha1-kdZxDlNtNFucmwF8V0z2qNpkxRg= +"@types/yauzl@^2.9.1": + version "2.9.1" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af" + integrity sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA== + dependencies: + "@types/node" "*" + +"@types/yazl@^2.4.2": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@types/yazl/-/yazl-2.4.2.tgz#d5f8a4752261badbf1a36e8b49e042dc18ec84bc" + integrity sha512-T+9JH8O2guEjXNxqmybzQ92mJUh2oCwDDMSSimZSe1P+pceZiFROZLYmcbqkzV5EUwz6VwcKXCO2S2yUpra6XQ== + dependencies: + "@types/node" "*" + "@webassemblyjs/ast@1.5.13": version "1.5.13" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.5.13.tgz#81155a570bd5803a30ec31436bc2c9c0ede38f25" @@ -619,6 +678,16 @@ append-buffer@^1.0.2: dependencies: buffer-equal "^1.0.0" +applicationinsights@*: + version "1.5.0" + resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.5.0.tgz#074df9e525dcfd592822e7b80723b9284d2716fd" + integrity sha512-D+JyPrDx9RWVNIwukoe03ANKNdyVe/ejExbR7xMvZTm09553TzXenW2oPZmfN9jeguKSDugzIWdbILMPNSRRlg== + dependencies: + cls-hooked "^4.2.2" + continuation-local-storage "^3.2.1" + diagnostic-channel "0.2.0" + diagnostic-channel-publishers "^0.3.3" + applicationinsights@1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.8.tgz#db6e3d983cf9f9405fe1ee5ba30ac6e1914537b5" @@ -853,11 +922,26 @@ async-each@^1.0.0, async-each@^1.0.1: resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" integrity sha1-GdOGodntxufByF04iu28xW0zYC0= +async-hook-jl@^1.7.6: + version "1.7.6" + resolved "https://registry.yarnpkg.com/async-hook-jl/-/async-hook-jl-1.7.6.tgz#4fd25c2f864dbaf279c610d73bf97b1b28595e68" + integrity sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg== + dependencies: + stack-chain "^1.3.7" + async-limiter@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg== +async-listener@^0.6.0: + version "0.6.10" + resolved "https://registry.yarnpkg.com/async-listener/-/async-listener-0.6.10.tgz#a7c97abe570ba602d782273c0de60a51e3e17cbc" + integrity sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw== + dependencies: + semver "^5.3.0" + shimmer "^1.1.0" + async-settle@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/async-settle/-/async-settle-1.0.0.tgz#1d0a914bb02575bec8a8f3a74e5080f72b2c0c6b" @@ -981,11 +1065,6 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -big-integer@^1.6.25: - version "1.6.25" - resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.25.tgz#1de45a9f57542ac20121c682f8d642220a34e823" - integrity sha1-HeRan1dUKsIBIcaC+NZCIgo06CM= - big.js@^3.1.3: version "3.2.0" resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" @@ -1006,11 +1085,6 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== -binary-search-bounds@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/binary-search-bounds/-/binary-search-bounds-2.0.3.tgz#5ff8616d6dd2ca5388bc85b2d6266e2b9da502dc" - integrity sha1-X/hhbW3SylOIvIWy1iZuK52lAtw= - binary@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79" @@ -1465,10 +1539,10 @@ cheerio@^1.0.0-rc.1: lodash "^4.15.0" parse5 "^3.0.1" -chokidar@3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.2.2.tgz#a433973350021e09f2b853a2287781022c0dc935" - integrity sha512-bw3pm7kZ2Wa6+jQWYP/c7bAZy3i4GwiIiMO2EeRjrE48l8vBqC/WvFhSF0xyM8fQiPEGvwMY/5bqDG7sSEOuhg== +chokidar@*, chokidar@3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.2.3.tgz#b9270a565d14f02f6bfdd537a6a2bbf5549b8c8c" + integrity sha512-GtrxGuRf6bzHQmXWRepvsGnXpkQkVU+D2/9a7dAe4a7v1NhrfZOZ2oKf76M3nOs46fFYL8D+Q8JYA4GYeJ8Cjw== dependencies: anymatch "~3.1.1" braces "~3.0.2" @@ -1643,6 +1717,15 @@ cloneable-readable@^1.0.0: process-nextick-args "^1.0.6" through2 "^2.0.1" +cls-hooked@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908" + integrity sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw== + dependencies: + async-hook-jl "^1.7.6" + emitter-listener "^1.0.1" + semver "^5.4.1" + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -1867,6 +1950,14 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== +continuation-local-storage@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz#11f613f74e914fe9b34c92ad2d28fe6ae1db7ffb" + integrity sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA== + dependencies: + async-listener "^0.6.0" + emitter-listener "^1.1.1" + convert-source-map@^1.1.1: version "1.5.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5" @@ -2338,6 +2429,11 @@ diagnostic-channel-publishers@0.2.1: resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3" integrity sha1-ji1geottef6IC1SLxYzGvrKIxPM= +diagnostic-channel-publishers@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.3.3.tgz#376b7798f4fa90f37eb4f94d2caca611b0e9c330" + integrity sha512-qIocRYU5TrGUkBlDDxaziAK1+squ8Yf2Ls4HldL3xxb/jzmWO2Enux7CvevNKYmF2kDXZ9HiRqwjPsjk8L+i2Q== + diagnostic-channel@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17" @@ -2379,18 +2475,6 @@ doctrine@^2.1.0: dependencies: esutils "^2.0.2" -documentdb@^1.5.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/documentdb/-/documentdb-1.14.1.tgz#1a4716c0b38a40daf375dc9a4b2a2beb4e26294a" - integrity sha512-i5PR6/NqDBeYmv2EDEeL9nuw0/7uErFS1h6wRTYIuBMIVswJg2wYjdsGnMFH0bfCgpPk13p24NbPbmMHZNuzxw== - dependencies: - big-integer "^1.6.25" - binary-search-bounds "2.0.3" - int64-buffer "^0.1.9" - priorityqueuejs "1.0.0" - semaphore "1.0.5" - underscore "1.8.3" - dom-serializer@0, dom-serializer@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" @@ -2529,6 +2613,13 @@ elliptic@^6.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.0" +emitter-listener@^1.0.1, emitter-listener@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8" + integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ== + dependencies: + shimmer "^1.2.0" + emoji-regex@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" @@ -4323,11 +4414,6 @@ inquirer@^6.1.0: strip-ansi "^5.0.0" through "^2.3.6" -int64-buffer@^0.1.9: - version "0.1.9" - resolved "https://registry.yarnpkg.com/int64-buffer/-/int64-buffer-0.1.9.tgz#9e039da043b24f78b196b283e04653ef5e990f61" - integrity sha1-ngOdoEOyT3ixlrKD4EZT716ZD2E= - interpret@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" @@ -4874,10 +4960,10 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -jschardet@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-1.6.0.tgz#c7d1a71edcff2839db2f9ec30fc5d5ebd3c1a678" - integrity sha512-xYuhvQ7I9PDJIGBWev9xm0+SMSed3ZDBAmvVjbFR1ZRLAF+vlXcQu6cRI9uAlj81rzikElRVteehwV7DuX2ZmQ== +jschardet@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-2.1.1.tgz#af6f8fd0b3b0f5d46a8fd9614a4fce490575c184" + integrity sha512-pA5qG9Zwm8CBpGlK/lo2GE9jPxwqRgMV7Lzc/1iaPccw6v4Rhj8Zg2BTyrdmHmxlJojnbLupLeRnaPLsq03x6Q== jsdom-no-contextify@^3.1.0: version "3.1.0" @@ -5752,7 +5838,7 @@ mute-stream@0.0.7, mute-stream@~0.0.4: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= -nan@2.14.0, nan@^2.0.0, nan@^2.13.2, nan@^2.14.0: +nan@2.14.0, nan@^2.10.0, nan@^2.13.2, nan@^2.14.0: version "2.14.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== @@ -5784,20 +5870,20 @@ napi-build-utils@^1.0.1: resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.1.tgz#1381a0f92c39d66bf19852e7873432fc2123e508" integrity sha512-boQj1WFgQH3v4clhu3mTNfP+vOBxorDlE8EKiMjUlLG3C4qAESnn9AxIOkFgTR2c9LtzNjPrjS60cT27ZKBhaA== -native-is-elevated@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/native-is-elevated/-/native-is-elevated-0.3.0.tgz#6c5d8f57daeec129abd03b5606a55e56e4337423" - integrity sha512-QJgU7vaCZ199PSEC4LAmwtGfqwGaz8a51YDeze3DPiRzcOq25LIQpxCbBWunIu+csMMHFsDuyO2OVfeSD4ioHQ== +native-is-elevated@0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/native-is-elevated/-/native-is-elevated-0.4.1.tgz#f6391aafb13441f5b949b39ae0b466b06e7f3986" + integrity sha512-2vBXCXCXYKLDjP0WzrXs/AFjDb2njPR31EbGiZ1mR2fMJg211xClK1Xm19RXve35kvAL4dBKOFGCMIyc2+pPsw== -native-keymap@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/native-keymap/-/native-keymap-2.0.0.tgz#7491ba8f9cc75bd6ada7e754dadb7716c793a3e3" - integrity sha512-KIlDZp0yKaHaGIkEVdlYN3QIaZICXwG1qh/oeBeQdM8TwAi90IAZlAD57qsNDkEvIJIzerCzb5jYYQAdHGBgYg== +native-keymap@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/native-keymap/-/native-keymap-2.1.0.tgz#f3a92e647ac021fe552587b0020f8132efb03078" + integrity sha512-a5VYhjMqxe+HK5VzJM8yIcJOKkeuMSKYfmS0p7VEKSc7hM0F5IPsq7XO8KtwAgV8PJhfQVgAgyQmK8u/MQQ0aw== -native-watchdog@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/native-watchdog/-/native-watchdog-1.2.0.tgz#9c710093ac6e9e60b19517cb1ef4ac9d7c997395" - integrity sha512-jOOoA3PLSxt1adaeuEW7ymV9cApZiDxn4y4iFs7b4sP73EG+5Lsz+OgUNFcGMyrtznTw6ZvlLcilIN4jeAIgaQ== +native-watchdog@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/native-watchdog/-/native-watchdog-1.3.0.tgz#88cee94c9dc766b85c8506eda14c8bd8c9618e27" + integrity sha512-WOjGRNGkYZ5MXsntcvCYrKtSYMaewlbCFplbcUVo9bE80LPVt8TAVFHYWB8+a6fWCGYheq21+Wtt6CJrUaCJhw== natural-compare@^1.4.0: version "1.4.0" @@ -5897,10 +5983,10 @@ node-pre-gyp@^0.10.0: semver "^5.3.0" tar "^4" -node-pty@0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.9.0.tgz#8f9bcc0d1c5b970a3184ffd533d862c7eb6590a6" - integrity sha512-MBnCQl83FTYOu7B4xWw10AW77AAh7ThCE1VXEv+JeWj8mSpGo+0bwgsV+b23ljBFwEM9OmsOv3kM27iUPPm84g== +node-pty@^0.10.0-beta2: + version "0.10.0-beta2" + resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.10.0-beta2.tgz#6fd0d2fbbe881869e4e19795a05c557ac958da81" + integrity sha512-IU2lzlPUZ+gKG7pHJjzBHpnuwPTxWGgT3iyQicZfdL7dwLvP5cm00QxavAXCInBmRkOMhvM4aBSKvfzqQnCDBA== dependencies: nan "^2.14.0" @@ -6023,16 +6109,6 @@ npmlog@^4.0.1, npmlog@^4.0.2: gauge "~2.7.3" set-blocking "~2.0.0" -nsfw@1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/nsfw/-/nsfw-1.2.5.tgz#febe581af616f7b042f89df133abe62416c4c803" - integrity sha512-m3mwZUKXiCR69PDMLfAmKmiNzy0Oe9LhFE0DYZC5cc1htNj5Hyb1sAgglXhuaDkibFy22AVvPC5cCFB3A6mYIw== - dependencies: - fs-extra "^7.0.0" - lodash.isinteger "^4.0.4" - lodash.isundefined "^3.0.1" - nan "^2.0.0" - nth-check@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.1.tgz#9929acdf628fc2c41098deab82ac580cf149aae4" @@ -6168,10 +6244,10 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" -onigasm-umd@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/onigasm-umd/-/onigasm-umd-2.2.2.tgz#b989d762df61f899a3052ac794a50bd93fe20257" - integrity sha512-v2eMOJu7iE444L2iJN+U6s6s5S0y7oj/N0DAkrd6wokRtTVoq/v/yaDI1lIqFrTeJbNtqNzYvguDF5yNzW3Rvw== +onigasm-umd@2.2.5: + version "2.2.5" + resolved "https://registry.yarnpkg.com/onigasm-umd/-/onigasm-umd-2.2.5.tgz#f104247334a543accd3f8d641a4d99b3d908d6a1" + integrity sha512-R3qD7hq6i2bBklF+QyjqZl/G4fe7GwtukI28YLH2vuiatqx52tb9vpg2sxwemKc3nF76SgkeyOKJLchBmTm0Aw== oniguruma@^7.2.0: version "7.2.0" @@ -6938,11 +7014,6 @@ prettyjson@^1.2.1: colors "^1.1.2" minimist "^1.2.0" -priorityqueuejs@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/priorityqueuejs/-/priorityqueuejs-1.0.0.tgz#2ee4f23c2560913e08c07ce5ccdd6de3df2c5af8" - integrity sha1-LuTyPCVgkT4IwHzlzN1t498sWvg= - process-nextick-args@^1.0.6, process-nextick-args@^1.0.7, 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" @@ -7721,11 +7792,6 @@ schema-utils@^0.4.4, schema-utils@^0.4.5: ajv "^6.1.0" ajv-keywords "^3.1.0" -semaphore@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/semaphore/-/semaphore-1.0.5.tgz#b492576e66af193db95d65e25ec53f5f19798d60" - integrity sha1-tJJXbmavGT25XWXiXsU/Xxl5jWA= - semver-greatest-satisfied-range@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz#13e8c2658ab9691cb0cd71093240280d36f77a5b" @@ -7862,6 +7928,11 @@ shebang-regex@^1.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= +shimmer@^1.1.0, shimmer@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" + integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== + sigmund@^1.0.1, sigmund@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" @@ -8029,10 +8100,10 @@ sparkles@^1.0.0: resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3" integrity sha1-Gsu/tZJDbRC76PeFt8xvgoFQEsM= -spdlog@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/spdlog/-/spdlog-0.9.0.tgz#c85dd9d0b9cd385f6f3f5b92dc9d2e1691092b5c" - integrity sha512-AeLWPCYjGi4w5DfpXFKb9pCdgMe4gFBMroGfgwXiNfzwmcNYGoFQkIuD1MChZBR1Iwrx0nGhsTSHFslt/qfTAQ== +spdlog@^0.11.1: + version "0.11.1" + resolved "https://registry.yarnpkg.com/spdlog/-/spdlog-0.11.1.tgz#29721b31018a5fe6a3ce2531f9d8d43e0bd6b825" + integrity sha512-M+sg9/Tnr0lrfnW2/hqgpoc4Z8Jzq7W8NUn35iiSslj+1uj1pgutI60MCpulDP2QyFzOpC8VsJmYD6Fub7wHoA== dependencies: bindings "^1.5.0" mkdirp "^0.5.1" @@ -8111,6 +8182,11 @@ ssri@^5.2.4: dependencies: safe-buffer "^5.1.1" +stack-chain@^1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285" + integrity sha1-0ZLJ/06moiyUxN1FkXHj8AzqEoU= + stack-trace@0.0.10: version "0.0.10" resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" @@ -8318,10 +8394,10 @@ strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= -sudo-prompt@9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.0.0.tgz#eebedeee9fcd6f661324e6bb46335e3288e8dc8a" - integrity sha512-kUn5fiOk0nhY2oKD9onIkcNCE4Zt85WTsvOfSmqCplmlEvXCcPOmp1npH5YWuf8Bmyy9wLWkIxx+D+8cThBORQ== +sudo-prompt@9.1.1: + version "9.1.1" + resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.1.1.tgz#73853d729770392caec029e2470db9c221754db0" + integrity sha512-es33J1g2HjMpyAhz8lOR+ICmXXAqTuKbuXuUWLhOLew20oN9oUCgCJx615U/v7aioZg7IX5lIh9x34vwneu4pA== supports-color@1.2.0: version "1.2.0" @@ -8811,10 +8887,10 @@ typescript-formatter@7.1.0: commandpost "^1.0.0" editorconfig "^0.15.0" -typescript@3.7.0-dev.20191017: - version "3.7.0-dev.20191017" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.0-dev.20191017.tgz#e61440dd445edea6d7b9a699e7c5d5fbcd1906f2" - integrity sha512-Yi0lCPEN0cn9Gp8TEEkPpgKNR5SWAmx9Hmzzz+oEuivw6amURqRGynaLyFZkMA9iMsvYG5LLqhdlFO3uu5ZT/w== +typescript@3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb" + integrity sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ== typescript@^2.6.2: version "2.6.2" @@ -9240,10 +9316,10 @@ vscode-debugprotocol@1.37.0: resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.37.0.tgz#e8c4694a078d18ea1a639553a7a241b35c1e6f6d" integrity sha512-ppZLEBbFRVNsK0YpfgFi/x2CDyihx0F+UpdKmgeJcvi05UgSXYdO0n9sDVYwoGvvYQPvlpDQeWuY0nloOC4mPA== -vscode-minimist@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/vscode-minimist/-/vscode-minimist-1.2.1.tgz#e63d3f4a9bf3680dcb8f9304eed612323fd6926a" - integrity sha512-cmB72+qDoiCFJ1UKnGUBdGYfXzdpJ3bQM/D/+XhkVk5v7uZgLbYiCz5JcwVyk7NC7hSi5VGtQ4wihzmi12NeXw== +vscode-minimist@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/vscode-minimist/-/vscode-minimist-1.2.2.tgz#65403f44f0c6010d259b2271d36eb5c6f4ad8aab" + integrity sha512-DXMNG2QgrXn1jOP12LzjVfvxVkzxv/0Qa27JrMBj/XP2esj+fJ/wP2T4YUH5derj73Lc96dC8F25WyfDUbTpxQ== vscode-nls-dev@^3.3.1: version "3.3.1" @@ -9263,10 +9339,20 @@ vscode-nls-dev@^3.3.1: xml2js "^0.4.19" yargs "^13.2.4" -vscode-proxy-agent@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.5.1.tgz#7fd15e157c02176a0dca9f87840ad0991a62ca57" - integrity sha512-Nnkc7gBk9iAbbZURYZm3p/wvDvRVwDvdzEvDqF1Jh40p6przwQU/JTlV1XLrmd4cCwh6TOAWD81Z3cq+K7Xdmw== +vscode-nsfw@1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/vscode-nsfw/-/vscode-nsfw-1.2.8.tgz#1bf452e72ff1304934de63692870d039a2d972af" + integrity sha512-yRLFDk2nwV0Fp+NWJkIbeXkHYIWoTuWC2siz6JfHc21FkRUjDmuI/1rVL6B+MXW15AsSbXnH5dw4Fo9kUyYclw== + dependencies: + fs-extra "^7.0.0" + lodash.isinteger "^4.0.4" + lodash.isundefined "^3.0.1" + nan "^2.10.0" + +vscode-proxy-agent@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.5.2.tgz#0c90d24d353957b841d741da7b2701e3f0a044c4" + integrity sha512-1cCNPxrWIrmUwS+1XGaXxkh3G1y7z2fpXl1sT74OZvELaryQWYb3NMxMLJJ4Q/CpPLEyuhp/bAN7nzHxxFcQ5Q== dependencies: debug "^3.1.0" http-proxy-agent "^2.1.0" @@ -9278,24 +9364,24 @@ vscode-ripgrep@^1.5.7: resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.5.7.tgz#acb6b548af488a4bca5d0f1bb5faf761343289ce" integrity sha512-/Vsz/+k8kTvui0q3O74pif9FK0nKopgFTiGNVvxicZANxtSA8J8gUE9GQ/4dpi7D/2yI/YVORszwVskFbz46hQ== -vscode-sqlite3@4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/vscode-sqlite3/-/vscode-sqlite3-4.0.8.tgz#1eba415b996d2661628d80d4a191a20767713829" - integrity sha512-FsFtYSHmy0mYjtt9ibFKsJqbRzqaltDKZ5SLdpykjvORugFMr0HfGunkh+qGaz9CvAiqjM2KVO91NE9KdyTWKQ== +vscode-sqlite3@4.0.9: + version "4.0.9" + resolved "https://registry.yarnpkg.com/vscode-sqlite3/-/vscode-sqlite3-4.0.9.tgz#cad04964aff1e39121dd8c7cdb87c64a71bad9ce" + integrity sha512-g/xnAn4sgkVO+px/DfC7kpmCQ666W124tfMSVpbnQXsjMHlLhi4Urz4wPhS9YEFPz1Orl6h6MUF6wptzI6QFdg== dependencies: nan "^2.14.0" -vscode-textmate@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-4.2.2.tgz#0b4dabc69a6fba79a065cb6b615f66eac07c8f4c" - integrity sha512-1U4ih0E/KP1zNK/EbpUqyYtI7PY+Ccd2nDGTtiMR/UalLFnmaYkwoWhN1oI7B91ptBN8NdVwWuvyUnvJAulCUw== +vscode-textmate@4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-4.4.0.tgz#14032afeb50152e8f53258c95643e555f2948305" + integrity sha512-dFpm2eK0HwEjeFSD1DDh3j0q47bDSVuZt20RiJWxGqjtm73Wu2jip3C2KaZI3dQx/fSeeXCr/uEN4LNaNj7Ytw== dependencies: oniguruma "^7.2.0" -vscode-windows-ca-certs@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/vscode-windows-ca-certs/-/vscode-windows-ca-certs-0.1.0.tgz#d58eeb40b536130918cfde2b01e6dc7e5c1bd757" - integrity sha512-ZfZbfJIE09Q0dwGqmqTj7kuAq4g6lul9WPJvo0DkKjln8/FL+dY3wUKIKbYwWQp4x56SBTLBq3tJkD72xQ9Gqw== +vscode-windows-ca-certs@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/vscode-windows-ca-certs/-/vscode-windows-ca-certs-0.2.0.tgz#086f0f4de57e2760a35ac6920831bff246237115" + integrity sha512-YBrJRT0zos+Yb1Qdn73GD8QZr7pa2IE96b5Y1hmmp6XeR8aYB7Iiq5gDAF/+/AxL+caSR9KPZQ6jiYWh5biD7w== dependencies: node-addon-api "1.6.2" @@ -9572,20 +9658,25 @@ xtend@~2.1.1: dependencies: object-keys "~0.4.0" -xterm-addon-search@0.3.0-beta5: - version "0.3.0-beta5" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.3.0-beta5.tgz#fd53d33a77a0235018479c712be8c12f7c0d083a" - integrity sha512-3GkGc4hST35/4hzgnQPLLvQ29WH7MkZ0mUrBE/Vm1IQum7TnMvWPTkGemwM+wAl4tdBmynNccHJlFeQzaQtVUg== +xterm-addon-search@0.4.0-beta4: + version "0.4.0-beta4" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.4.0-beta4.tgz#7762ea342c6b4f5e824d83466bd93793c9d7d779" + integrity sha512-TIbEBVhydGIxcyu/CfKJbD+BKHisMGbkAfaWlCPaWis2Xmw8yE7CKrCPn+lhZYl1MdjDVEmb8lQI6WetbC2OZA== xterm-addon-web-links@0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.2.1.tgz#6d1f2ce613e09870badf17615e7a1170a31542b2" integrity sha512-2KnHtiq0IG7hfwv3jw2/jQeH1RBk2d5CH4zvgwQe00rLofSJqSfgnJ7gwowxxpGHrpbPr6Lv4AmH/joaNw2+HQ== -xterm@^4.2.0-beta20: - version "4.2.0-beta20" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.2.0-beta20.tgz#74a759414511b4577159293397914ac33a2375d8" - integrity sha512-lH52ksaNQqWmLVV4OdLKWvhGkSRUXgJNvb2YYmVkBAm1PdVVS36ir8Qr4zYygnc2tBw689Wj65t4cNckmfpU1Q== +xterm-addon-webgl@0.4.0-beta.11: + version "0.4.0-beta.11" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.4.0-beta.11.tgz#0e4a7242e2353cf74aba55e5a5bdc0b4ec87ad10" + integrity sha512-AteDxm1RFy1WnjY9r5iJSETozLebvUkR+jextdZk/ASsK21vsYK0DuVWwRI8afgiN2hUVhxcxuHEJUOV+CJDQA== + +xterm@4.3.0-beta.28: + version "4.3.0-beta.28" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.3.0-beta.28.tgz#80f7c4ba8f6ee3c953e6f33f8ce5aef08d5a8354" + integrity sha512-WWZ4XCvce5h+klL6ObwtMauJff/n2KGGOwJJkDbJhrAjVy2a77GKgAedJTDDFGgKJ6ix1d7puHtVSSKflIVaDQ== y18n@^3.2.1: version "3.2.1"