diff --git a/.vscode/launch.json b/.vscode/launch.json index 25279b09fd3..7e112fc94e1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -131,6 +131,7 @@ "linux": { "runtimeExecutable": "${workspaceFolder}/scripts/code.sh" }, + "port": 9222, "timeout": 20000, "env": { "VSCODE_EXTHOST_WILL_SEND_SOCKET": null diff --git a/.yarnrc b/.yarnrc index 7f3fa82972f..288e7393abd 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,3 +1,3 @@ disturl "https://atom.io/download/electron" -target "6.1.4" +target "6.1.5" runtime "electron" diff --git a/README.md b/README.md index 5d39d7e40c6..0de5f2f71da 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,6 @@ This repository ("`Code - OSS`") is where we (Microsoft) develop the [Visual Stu Visual Studio Code is updated monthly with new features and bug fixes. You can download it for Windows, macOS, and Linux on [Visual Studio Code's website](https://code.visualstudio.com/Download). To get the latest releases every day, install the [Insiders build](https://code.visualstudio.com/insiders). - - ## Contributing There are many ways in which you can participate in the project, for example: @@ -52,11 +50,11 @@ please see the document [How to Contribute](https://github.com/Microsoft/vscode/ ## Related Projects -Many of the core components and extensions to Code live in their own repositories on GitHub. For example, the [node debug adapter](https://github.com/microsoft/vscode-node-debug) and the [mono debug adapter](https://github.com/microsoft/vscode-mono-debug) have their own repositories. For a complete list, please visit the [Related Projects](https://github.com/Microsoft/vscode/wiki/Related-Projects) page on our [wiki](https://github.com/Microsoft/vscode/wiki). +Many of the core components and extensions to VS Code live in their own repositories on GitHub. For example, the [node debug adapter](https://github.com/microsoft/vscode-node-debug) and the [mono debug adapter](https://github.com/microsoft/vscode-mono-debug) have their own repositories. For a complete list, please visit the [Related Projects](https://github.com/Microsoft/vscode/wiki/Related-Projects) page on our [wiki](https://github.com/Microsoft/vscode/wiki). ## Bundled Extensions -Code includes a set of built-in extensions located in the [extensions](extensions) folder, including grammars and snippets for many languages. Extensions that provide rich language support (code completion, Go to Definition) for a language have the suffix `language-features`. For example, the `json` extension provides coloring for `JSON` and the `json-language-features` provides rich language support for `JSON`. +VS Code includes a set of built-in extensions located in the [extensions](extensions) folder, including grammars and snippets for many languages. Extensions that provide rich language support (code completion, Go to Definition) for a language have the suffix `language-features`. For example, the `json` extension provides coloring for `JSON` and the `json-language-features` provides rich language support for `JSON`. ## Code of Conduct diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-compile.yml index 87c70f4f805..8029f8a5661 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: "12.13.0" - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + 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,14 @@ 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 @@ -129,7 +130,7 @@ steps: AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ node build/azure-pipelines/common/createBuild.js $VERSION displayName: Create build - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 inputs: @@ -138,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/builtInExtensions.json b/build/builtInExtensions.json index 7365025c343..cc5777332ac 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -1,7 +1,7 @@ [ { "name": "ms-vscode.node-debug", - "version": "1.41.0", + "version": "1.41.1", "repo": "https://github.com/Microsoft/vscode-node-debug", "metadata": { "id": "b6ded8fb-a0a0-4c1c-acbd-ab2a3bc995a6", diff --git a/build/gulpfile.extensions.js b/build/gulpfile.extensions.js index ca28c413a71..25cccf6d8e5 100644 --- a/build/gulpfile.extensions.js +++ b/build/gulpfile.extensions.js @@ -109,7 +109,8 @@ const tasks = compilations.map(function (tsconfigFile) { const compileTask = task.define(`compile-extension:${name}`, task.series(cleanTask, () => { const pipeline = createPipeline(false, 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()) @@ -118,7 +119,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 @@ -128,7 +130,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/cgmanifest.json b/cgmanifest.json index 3d6e8a21080..bc3086c3100 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -60,12 +60,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "a5b474e8248803f54efc2c2c724c3322590c4fda" + "commitHash": "6f62f91822a80192cb711c604f1a8f1a176f328d" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "6.1.4" + "version": "6.1.5" }, { "component": { diff --git a/extensions/configuration-editing/schemas/attachContainer.schema.json b/extensions/configuration-editing/schemas/attachContainer.schema.json index 5f3d28abb89..2bce4ff6f00 100644 --- a/extensions/configuration-editing/schemas/attachContainer.schema.json +++ b/extensions/configuration-editing/schemas/attachContainer.schema.json @@ -18,6 +18,16 @@ "type": "integer" } }, + "remoteEnv": { + "type": "object", + "additionalProperties": { + "type": [ + "string", + "null" + ] + }, + "description": "Remote environment variables." + }, "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 1358c74f2d2..5a1ace7e41f 100644 --- a/extensions/configuration-editing/schemas/devContainer.schema.json +++ b/extensions/configuration-editing/schemas/devContainer.schema.json @@ -22,6 +22,16 @@ "$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." + }, "postCreateCommand": { "type": [ "string", @@ -55,6 +65,13 @@ ] } }, + "containerEnv": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Container environment variables." + }, "runArgs": { "type": "array", "description": "The arguments required when starting in the container.", diff --git a/extensions/git/extension.webpack.config.js b/extensions/git/extension.webpack.config.js index 5efa2052e88..bf6953d3183 100644 --- a/extensions/git/extension.webpack.config.js +++ b/extensions/git/extension.webpack.config.js @@ -13,6 +13,6 @@ module.exports = withDefaults({ context: __dirname, entry: { main: './src/main.ts', - ['askpass-main']: './src/askpass-main.ts' + ['askpass-main']: './src/askpass/askpass-main.ts' } }); diff --git a/extensions/git/package.json b/extensions/git/package.json index eff48607109..d801ec64f97 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1194,7 +1194,7 @@ { "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", @@ -1204,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" } ] }, @@ -1608,12 +1608,6 @@ "default": "mixed", "description": "%config.untrackedChanges%", "scope": "resource" - }, - "git.restoreCommitTemplateComments": { - "type": "boolean", - "scope": "resource", - "default": true, - "description": "%config.restoreCommitTemplateComments%" } } }, diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index ad377c36b61..5c357b40eb0 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -61,7 +61,7 @@ "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...", @@ -139,7 +139,6 @@ "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.", - "config.restoreCommitTemplateComments": "Controls whether to restore commit template comments in the commit input box.", "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 a4fd677db27..8cee12e7726 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 a0b2d3dad7f..a6eb7ec3d9a 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.ts b/extensions/git/src/askpass.ts deleted file mode 100644 index 03000c7290d..00000000000 --- a/extensions/git/src/askpass.ts +++ /dev/null @@ -1,119 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Disposable, window, InputBoxOptions } from 'vscode'; -import { denodeify } 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); - -export interface AskpassEnvironment { - GIT_ASKPASS: string; - ELECTRON_RUN_AS_NODE?: string; - VSCODE_GIT_ASKPASS_NODE?: string; - VSCODE_GIT_ASKPASS_MAIN?: string; - VSCODE_GIT_ASKPASS_HANDLE?: string; -} - -function getIPCHandlePath(nonce: string): string { - if (process.platform === 'win32') { - return `\\\\.\\pipe\\vscode-git-askpass-${nonce}-sock`; - } - - if (process.env['XDG_RUNTIME_DIR']) { - return path.join(process.env['XDG_RUNTIME_DIR'] as string, `vscode-git-askpass-${nonce}.sock`); - } - - 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 { - const options: InputBoxOptions = { - password: /password/i.test(request), - placeHolder: request, - prompt: `Git: ${host}`, - ignoreFocusOut: true - }; - - return await window.showInputBox(options) || ''; - } - - async getEnv(): Promise { - if (!this.enabled) { - return { - GIT_ASKPASS: path.join(__dirname, 'askpass-empty.sh') - }; - } - - 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 - }; - } - - dispose(): void { - this.server.close(); - - if (this.ipcHandlePath && process.platform !== 'win32') { - fs.unlinkSync(this.ipcHandlePath); - } - } -} \ No newline at end of file diff --git a/extensions/git/src/askpass-empty.sh b/extensions/git/src/askpass/askpass-empty.sh similarity index 100% rename from extensions/git/src/askpass-empty.sh rename to extensions/git/src/askpass/askpass-empty.sh diff --git a/extensions/git/src/askpass-main.ts b/extensions/git/src/askpass/askpass-main.ts similarity index 55% rename from extensions/git/src/askpass-main.ts rename to extensions/git/src/askpass/askpass-main.ts index c08aa2a0afc..c2674d28705 100644 --- a/extensions/git/src/askpass-main.ts +++ b/extensions/git/src/askpass/askpass-main.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. 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.sh b/extensions/git/src/askpass/askpass.sh similarity index 100% rename from extensions/git/src/askpass.sh rename to extensions/git/src/askpass/askpass.sh diff --git a/extensions/git/src/askpass/askpass.ts b/extensions/git/src/askpass/askpass.ts new file mode 100644 index 00000000000..994a32514e5 --- /dev/null +++ b/extensions/git/src/askpass/askpass.ts @@ -0,0 +1,56 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { window, InputBoxOptions } from 'vscode'; +import { IDisposable } from '../util'; +import * as path from 'path'; +import { IIPCHandler, IIPCServer } from '../ipc/ipcServer'; + +export interface AskpassEnvironment { + GIT_ASKPASS: string; + ELECTRON_RUN_AS_NODE?: string; + VSCODE_GIT_ASKPASS_NODE?: string; + VSCODE_GIT_ASKPASS_MAIN?: string; + VSCODE_GIT_ASKPASS_HANDLE?: string; +} + +export class Askpass implements IIPCHandler { + + private disposable: IDisposable; + + static getDisabledEnv(): AskpassEnvironment { + return { + GIT_ASKPASS: path.join(__dirname, 'askpass-empty.sh') + }; + } + + constructor(ipc: IIPCServer) { + this.disposable = ipc.registerHandler('askpass', this); + } + + async handle({ request, host }: { request: string, host: string }): Promise { + const options: InputBoxOptions = { + password: /password/i.test(request), + placeHolder: request, + prompt: `Git: ${host}`, + ignoreFocusOut: true + }; + + return await window.showInputBox(options) || ''; + } + + 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') + }; + } + + dispose(): void { + this.disposable.dispose(); + } +} diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index e58f8c42292..5dd981bbd9c 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -14,7 +14,7 @@ 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 } from './uri'; +import { fromGitUri, toGitUri, isGitUri } from './uri'; import { grep, isDescendant, pathEquals } from './util'; const localize = nls.loadMessageBundle(); @@ -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[]; @@ -295,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); @@ -330,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: @@ -692,7 +653,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]; @@ -771,7 +732,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)`; @@ -1117,7 +1078,7 @@ export class CommandCenter { const modifiedDocument = textEditor.document; const modifiedUri = modifiedDocument.uri; - if (modifiedUri.scheme !== 'git') { + if (!isGitUri(modifiedUri)) { return; } @@ -1445,6 +1406,7 @@ export class CommandCenter { const message = repository.inputBox.value; const getCommitMessage = async () => { let _message: string | undefined = message; + if (!_message) { let value: string | undefined = undefined; @@ -1469,7 +1431,7 @@ export class CommandCenter { }); } - return _message ? repository.cleanUpCommitEditMessage(_message) : _message; + return _message; }; const didCommit = await this.smartCommit(repository, getCommitMessage, opts); @@ -2162,9 +2124,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 }); @@ -2454,7 +2421,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 1b7c08493ed..ad380e70ecf 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/fileSystemProvider.ts b/extensions/git/src/fileSystemProvider.ts new file mode 100644 index 00000000000..198f031309f --- /dev/null +++ b/extensions/git/src/fileSystemProvider.ts @@ -0,0 +1,199 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. 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 a710a539c29..193907a15bd 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. 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; @@ -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); } @@ -1812,26 +1810,17 @@ export class Repository { } } - cleanupCommitEditMessage(message: string): string { - // If the message is a single line starting with whitespace followed by `#`, just allow it. - if (/^\s*#[^\n]*$/.test(message)) { - return message; - } - - // Else, remove all lines starting with whitespace followed by `#`. - // TODO: Support core.commentChar - return message.replace(/^(\s*#)(.*)$(\n?)/gm, (_, prefix, content, suffix) => { - // https://github.com/microsoft/vscode/issues/84201#issuecomment-552834814 - return /^\d/.test(content) ? `${prefix}${content}${suffix}` : ''; - }).trim(); + // 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; } @@ -1854,9 +1843,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 ''; } @@ -1879,7 +1867,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 00000000000..f623b3f7b6f --- /dev/null +++ b/extensions/git/src/ipc/ipcClient.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +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 00000000000..332490896cc --- /dev/null +++ b/extensions/git/src/ipc/ipcServer.ts @@ -0,0 +1,106 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from '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 52b7c3c106a..37cb8e5377c 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -11,8 +11,9 @@ 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 { Askpass } from './askpass/askpass'; import { toDisposable, filterEvent, eventToPromise } from './util'; import TelemetryReporter from 'vscode-extension-telemetry'; import { GitExtension } from './api/git'; @@ -20,6 +21,7 @@ import { GitProtocolHandler } from './protocolHandler'; import { GitExtensionImpl } from './api/extension'; import * as path from 'path'; import * as fs from 'fs'; +import { createIPCServer, IIPCServer } from './ipc/ipcServer'; const deactivateTasks: { (): Promise; }[] = []; @@ -32,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); @@ -62,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() ); diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 5f3c9bee464..4edc79262d2 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -844,15 +844,7 @@ export class Repository implements Disposable { return mergeMessage; } - let template = await this.repository.getCommitTemplate(); - - const config = workspace.getConfiguration('git', Uri.file(this.root)); - - if (!config.get('restoreCommitTemplateComments')) { - template = this.cleanUpCommitEditMessage(template); - } - - return template; + return await this.repository.getCommitTemplate(); } getConfigs(): Promise<{ key: string; value: string; }[]> { @@ -1286,10 +1278,6 @@ export class Repository implements Disposable { return await this.run(Operation.GetCommitTemplate, async () => this.repository.getCommitTemplate()); } - cleanUpCommitEditMessage(editMessage: string): string { - 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`; diff --git a/extensions/git/src/uri.ts b/extensions/git/src/uri.ts index 2eb21adfd6f..3ef84a642f7 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 0722cb16fbd..3110168343b 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/html-language-features/client/src/htmlMain.ts b/extensions/html-language-features/client/src/htmlMain.ts index 89bdc325721..194935b25fc 100644 --- a/extensions/html-language-features/client/src/htmlMain.ts +++ b/extensions/html-language-features/client/src/htmlMain.ts @@ -14,10 +14,14 @@ import { EMPTY_ELEMENTS } from './htmlEmptyTagsShared'; import { activateTagClosing } from './tagClosing'; import TelemetryReporter from 'vscode-extension-telemetry'; import { getCustomDataPathsInAllWorkspaces, getCustomDataPathsFromAllExtensions } from './customData'; +import { activateMatchingTagPosition as activateMatchingTagSelection } from './matchingTag'; namespace TagCloseRequest { export const type: RequestType = new RequestType('html/tag'); } +namespace MatchingTagPositionRequest { + export const type: RequestType = new RequestType('html/matchingTagPosition'); +} interface IPackageInfo { name: string; @@ -84,6 +88,14 @@ export function activate(context: ExtensionContext) { disposable = activateTagClosing(tagRequestor, { html: true, handlebars: true }, 'html.autoClosingTags'); toDispose.push(disposable); + const matchingTagPositionRequestor = (document: TextDocument, position: Position) => { + let param = client.code2ProtocolConverter.asTextDocumentPositionParams(document, position); + return client.sendRequest(MatchingTagPositionRequest.type, param); + }; + + disposable = activateMatchingTagSelection(matchingTagPositionRequestor, { html: true, handlebars: true }, 'html.autoSelectingMatchingTags'); + toDispose.push(disposable); + disposable = client.onTelemetry(e => { if (telemetryReporter) { telemetryReporter.sendTelemetryEvent(e.key, e.data); diff --git a/extensions/html-language-features/client/src/matchingTag.ts b/extensions/html-language-features/client/src/matchingTag.ts new file mode 100644 index 00000000000..6816c2c5e32 --- /dev/null +++ b/extensions/html-language-features/client/src/matchingTag.ts @@ -0,0 +1,143 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { + window, + workspace, + Disposable, + TextDocument, + Position, + TextEditorSelectionChangeEvent, + Selection, + Range, + WorkspaceEdit +} from 'vscode'; + +export function activateMatchingTagPosition( + matchingTagPositionProvider: (document: TextDocument, position: Position) => Thenable, + supportedLanguages: { [id: string]: boolean }, + configName: string +): Disposable { + let disposables: Disposable[] = []; + + window.onDidChangeTextEditorSelection(event => onDidChangeTextEditorSelection(event), null, disposables); + + let isEnabled = false; + updateEnabledState(); + + window.onDidChangeActiveTextEditor(updateEnabledState, null, disposables); + + function updateEnabledState() { + isEnabled = false; + let editor = window.activeTextEditor; + if (!editor) { + return; + } + let document = editor.document; + if (!supportedLanguages[document.languageId]) { + return; + } + if (!workspace.getConfiguration(undefined, document.uri).get(configName)) { + return; + } + isEnabled = true; + } + + let prevCursorCount = 0; + let cursorCount = 0; + let inMirrorMode = false; + + function onDidChangeTextEditorSelection(event: TextEditorSelectionChangeEvent) { + if (!isEnabled) { + return; + } + + prevCursorCount = cursorCount; + cursorCount = event.selections.length; + + if (cursorCount === 1) { + if (inMirrorMode && prevCursorCount === 2) { + return; + } + if (event.selections[0].isEmpty) { + matchingTagPositionProvider(event.textEditor.document, event.selections[0].active).then(position => { + if (position && window.activeTextEditor) { + inMirrorMode = true; + const newCursor = new Selection(position.line, position.character, position.line, position.character); + window.activeTextEditor.selections = [...window.activeTextEditor.selections, newCursor]; + } + }); + } + } + + if (cursorCount === 2 && inMirrorMode) { + // Check two cases + if (event.selections[0].isEmpty && event.selections[1].isEmpty) { + const charBeforePrimarySelection = getCharBefore(event.textEditor.document, event.selections[0].anchor); + const charAfterPrimarySelection = getCharAfter(event.textEditor.document, event.selections[0].anchor); + const charBeforeSecondarySelection = getCharBefore(event.textEditor.document, event.selections[1].anchor); + const charAfterSecondarySelection = getCharAfter(event.textEditor.document, event.selections[1].anchor); + + // Exit mirror mode when cursor position no longer mirror + // Unless it's in the case of `<|>` + const charBeforeBothPositionRoughlyEqual = + charBeforePrimarySelection === charBeforeSecondarySelection || + (charBeforePrimarySelection === '/' && charBeforeSecondarySelection === '<') || + (charBeforeSecondarySelection === '/' && charBeforePrimarySelection === '<'); + const charAfterBothPositionRoughlyEqual = + charAfterPrimarySelection === charAfterSecondarySelection || + (charAfterPrimarySelection === ' ' && charAfterSecondarySelection === '>') || + (charAfterSecondarySelection === ' ' && charAfterPrimarySelection === '>'); + + if (!charBeforeBothPositionRoughlyEqual || !charAfterBothPositionRoughlyEqual) { + inMirrorMode = false; + window.activeTextEditor!.selections = [window.activeTextEditor!.selections[0]]; + return; + } else { + // Need to cleanup in the case of
+ if ( + charBeforePrimarySelection === ' ' && + charAfterPrimarySelection === '>' && + charBeforeSecondarySelection === ' ' && + charAfterSecondarySelection === '>' + ) { + const primaryBeforeSecondary = + event.textEditor.document.offsetAt(event.selections[0].anchor) < + event.textEditor.document.offsetAt(event.selections[1].anchor); + + if (primaryBeforeSecondary) { + inMirrorMode = false; + const cleanupEdit = new WorkspaceEdit(); + const cleanupRange = new Range(event.selections[1].anchor.translate(0, -1), event.selections[1].anchor); + cleanupEdit.replace(event.textEditor.document.uri, cleanupRange, ''); + window.activeTextEditor!.selections = [window.activeTextEditor!.selections[0]]; + workspace.applyEdit(cleanupEdit); + } + } + } + } + } + } + + return Disposable.from(...disposables); +} + +function getCharBefore(document: TextDocument, position: Position) { + const offset = document.offsetAt(position); + if (offset === 0) { + return ''; + } + + return document.getText(new Range(document.positionAt(offset - 1), position)); +} + +function getCharAfter(document: TextDocument, position: Position) { + const offset = document.offsetAt(position); + if (offset === document.getText().length) { + return ''; + } + + return document.getText(new Range(position, document.positionAt(offset + 1))); +} diff --git a/extensions/html-language-features/package.json b/extensions/html-language-features/package.json index 7c4f83d6b5b..a641eb4ca46 100644 --- a/extensions/html-language-features/package.json +++ b/extensions/html-language-features/package.json @@ -161,6 +161,12 @@ "default": true, "description": "%html.autoClosingTags%" }, + "html.autoSelectingMatchingTags": { + "type": "boolean", + "scope": "resource", + "default": true, + "description": "%html.autoSelectingMatchingTags%" + }, "html.trace.server": { "type": "string", "scope": "window", diff --git a/extensions/html-language-features/package.nls.json b/extensions/html-language-features/package.nls.json index 2cb7e3d1697..4e4d666b9f7 100644 --- a/extensions/html-language-features/package.nls.json +++ b/extensions/html-language-features/package.nls.json @@ -24,5 +24,6 @@ "html.trace.server.desc": "Traces the communication between VS Code and the HTML language server.", "html.validate.scripts": "Controls whether the built-in HTML language support validates embedded scripts.", "html.validate.styles": "Controls whether the built-in HTML language support validates embedded styles.", - "html.autoClosingTags": "Enable/disable autoclosing of HTML tags." + "html.autoClosingTags": "Enable/disable autoclosing of HTML tags.", + "html.autoSelectingMatchingTags": "Enable/disable auto selecting matching HTML tags." } diff --git a/extensions/html-language-features/server/package.json b/extensions/html-language-features/server/package.json index 6ce9bce3320..be9e3fd2a21 100644 --- a/extensions/html-language-features/server/package.json +++ b/extensions/html-language-features/server/package.json @@ -10,7 +10,7 @@ "main": "./out/htmlServerMain", "dependencies": { "vscode-css-languageservice": "^4.0.3-next.20", - "vscode-html-languageservice": "^3.0.4-next.8", + "vscode-html-languageservice": "^3.0.4-next.10", "vscode-languageserver": "^6.0.0-next.3", "vscode-nls": "^4.1.1", "vscode-uri": "^2.0.3" diff --git a/extensions/html-language-features/server/src/htmlServerMain.ts b/extensions/html-language-features/server/src/htmlServerMain.ts index 1e6e7e2e0ce..8964b5ee035 100644 --- a/extensions/html-language-features/server/src/htmlServerMain.ts +++ b/extensions/html-language-features/server/src/htmlServerMain.ts @@ -24,6 +24,9 @@ import { getDataProviders } from './customData'; namespace TagCloseRequest { export const type: RequestType = new RequestType('html/tag'); } +namespace MatchingTagPositionRequest { + export const type: RequestType = new RequestType('html/matchingTagPosition'); +} // Create a connection for the server const connection: IConnection = createConnection(); @@ -136,6 +139,7 @@ connection.onInitialize((params: InitializeParams): InitializeResult => { colorProvider: {}, foldingRangeProvider: true, selectionRangeProvider: true, + renameProvider: true }; return { capabilities }; }); @@ -469,6 +473,36 @@ connection.onSelectionRanges((params, token) => { }, [], `Error while computing selection ranges for ${params.textDocument.uri}`, token); }); +connection.onRenameRequest((params, token) => { + return runSafe(() => { + const document = documents.get(params.textDocument.uri); + const position: Position = params.position; + + if (document) { + const htmlMode = languageModes.getMode('html'); + if (htmlMode && htmlMode.doRename) { + return htmlMode.doRename(document, position, params.newName); + } + } + return null; + }, null, `Error while computing rename for ${params.textDocument.uri}`, token); +}); + +connection.onRequest(MatchingTagPositionRequest.type, (params, token) => { + return runSafe(() => { + const document = documents.get(params.textDocument.uri); + if (document) { + const pos = params.position; + if (pos.character > 0) { + const mode = languageModes.getModeAtPosition(document, Position.create(pos.line, pos.character - 1)); + if (mode && mode.findMatchingTagPosition) { + return mode.findMatchingTagPosition(document, pos); + } + } + } + return null; + }, null, `Error while computing matching tag position for ${params.textDocument.uri}`, token); +}); // Listen on the connection connection.listen(); diff --git a/extensions/html-language-features/server/src/modes/htmlMode.ts b/extensions/html-language-features/server/src/modes/htmlMode.ts index 4449b01bd7c..23304a28ec7 100644 --- a/extensions/html-language-features/server/src/modes/htmlMode.ts +++ b/extensions/html-language-features/server/src/modes/htmlMode.ts @@ -74,9 +74,17 @@ export function getHTMLMode(htmlLanguageService: HTMLLanguageService, workspace: } return null; }, + doRename(document: TextDocument, position: Position, newName: string) { + const htmlDocument = htmlDocuments.get(document); + return htmlLanguageService.doRename(document, position, newName, htmlDocument); + }, onDocumentRemoved(document: TextDocument) { htmlDocuments.onDocumentRemoved(document); }, + findMatchingTagPosition(document: TextDocument, position: Position) { + const htmlDocument = htmlDocuments.get(document); + return htmlLanguageService.findMatchingTagPosition(document, position, htmlDocument); + }, dispose() { htmlDocuments.dispose(); } diff --git a/extensions/html-language-features/server/src/modes/languageModes.ts b/extensions/html-language-features/server/src/modes/languageModes.ts index 0a8e53a4ef3..a1fc67b27e1 100644 --- a/extensions/html-language-features/server/src/modes/languageModes.ts +++ b/extensions/html-language-features/server/src/modes/languageModes.ts @@ -8,7 +8,7 @@ import { ClientCapabilities, DocumentContext, getLanguageService as getHTMLLanguageService, IHTMLDataProvider, SelectionRange, CompletionItem, CompletionList, Definition, Diagnostic, DocumentHighlight, DocumentLink, FoldingRange, FormattingOptions, Hover, Location, Position, Range, SignatureHelp, SymbolInformation, TextDocument, TextEdit, - Color, ColorInformation, ColorPresentation + Color, ColorInformation, ColorPresentation, WorkspaceEdit } from 'vscode-html-languageservice'; import { WorkspaceFolder } from 'vscode-languageserver'; import { getLanguageModelCache, LanguageModelCache } from '../languageModelCache'; @@ -38,6 +38,7 @@ export interface LanguageMode { doResolve?: (document: TextDocument, item: CompletionItem) => CompletionItem; doHover?: (document: TextDocument, position: Position) => Hover | null; doSignatureHelp?: (document: TextDocument, position: Position) => SignatureHelp | null; + doRename?: (document: TextDocument, position: Position, newName: string) => WorkspaceEdit | null; findDocumentHighlight?: (document: TextDocument, position: Position) => DocumentHighlight[]; findDocumentSymbols?: (document: TextDocument) => SymbolInformation[]; findDocumentLinks?: (document: TextDocument, documentContext: DocumentContext) => DocumentLink[]; @@ -47,6 +48,7 @@ export interface LanguageMode { findDocumentColors?: (document: TextDocument) => ColorInformation[]; getColorPresentations?: (document: TextDocument, color: Color, range: Range) => ColorPresentation[]; doAutoClose?: (document: TextDocument, position: Position) => string | null; + findMatchingTagPosition?: (document: TextDocument, position: Position) => Position | null; getFoldingRanges?: (document: TextDocument) => FoldingRange[]; onDocumentRemoved(document: TextDocument): void; dispose(): void; diff --git a/extensions/html-language-features/server/yarn.lock b/extensions/html-language-features/server/yarn.lock index 4f9260128b2..07ad507e2ff 100644 --- a/extensions/html-language-features/server/yarn.lock +++ b/extensions/html-language-features/server/yarn.lock @@ -621,10 +621,10 @@ vscode-css-languageservice@^4.0.3-next.20: vscode-nls "^4.1.1" vscode-uri "^2.1.1" -vscode-html-languageservice@^3.0.4-next.8: - version "3.0.4-next.8" - resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-3.0.4-next.8.tgz#b3df7b7e8f69c1bf76392c4fd4df4cf483e77e7d" - integrity sha512-gT34wzCwM1rCJzd0EAFnuVe0FaaSr3eXaJpYcMj6rt1UspIJYaL4WtDLCcw4eBL906N1b1Vu+sapiRmV5PjZpg== +vscode-html-languageservice@^3.0.4-next.10: + version "3.0.4-next.10" + resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-3.0.4-next.10.tgz#da426326833770c51712abb2c7473b9b30bf1cbc" + integrity sha512-8P0QBtMPJ9nDMhW8MF/z+5JGg6rK6UOa9po18KIleNuV0rDHU9CAqDyUjxW0CEfLrHYz6dQdkW12ZTClvQnNHw== dependencies: vscode-languageserver-textdocument "^1.0.0-next.4" vscode-languageserver-types "^3.15.0-next.6" diff --git a/extensions/image-preview/package.json b/extensions/image-preview/package.json index 1ad08ffa55e..f39fc9b6012 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,webp}", - "mime": "image/*" + "filenamePattern": "*.{jpg,jpe,jpeg,png,bmp,gif,ico,webp}" } ] } diff --git a/extensions/image-preview/src/preview.ts b/extensions/image-preview/src/preview.ts index e383ad4e613..dd461b519e0 100644 --- a/extensions/image-preview/src/preview.ts +++ b/extensions/image-preview/src/preview.ts @@ -240,9 +240,9 @@ class Preview extends Disposable implements vscode.WebviewEditorEditingCapabilit default: // Avoid adding cache busting if there is already a query string if (resource.query) { - return encodeURI(webviewEditor.webview.asWebviewUri(resource).toString()); + return webviewEditor.webview.asWebviewUri(resource).toString(true); } - return encodeURI(webviewEditor.webview.asWebviewUri(resource).toString() + `?version=${version}`); + return webviewEditor.webview.asWebviewUri(resource).with({ query: `version=${version}` }).toString(true); } } diff --git a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json b/extensions/javascript/syntaxes/JavaScript.tmLanguage.json index 47894898ab3..c8377485559 100644 --- a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json +++ b/extensions/javascript/syntaxes/JavaScript.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/TypeScript-TmLanguage/commit/477c1b17e273b64af13040c064c9ed62c8b32fba", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/4daff7b8904bc549dfbee8df1e2f7c82194b9f45", "name": "JavaScript (with React support)", "scopeName": "source.js", "patterns": [ @@ -2451,7 +2451,7 @@ }, { "name": "string.regexp.js", - "begin": "(?<=\\))\\s*\\/(?![\\/*])(?=(?:[^\\/\\\\\\[]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\])+\\/[gimsuy]*(?!\\s*[a-zA-Z0-9_$]))", + "begin": "(?<=\\))\\s*\\/(?![\\/*])(?=(?:[^\\/\\\\\\[]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\])+\\/([gimsuy]+|(?![\\/\\*])|(?=\\/\\*))(?!\\s*[a-zA-Z0-9_$]))", "beginCaptures": { "0": { "name": "punctuation.definition.string.begin.js" @@ -4572,7 +4572,7 @@ "patterns": [ { "name": "string.regexp.js", - "begin": "(?|&&|\\|\\||\\*\\/)\\s*(\\/)(?![\\/*])(?=(?:[^\\/\\\\\\[\\()]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\]|\\(([^\\)\\\\]|\\\\.)+\\))+\\/[gimsuy]*(?!\\s*[a-zA-Z0-9_$]))", + "begin": "(?|&&|\\|\\||\\*\\/)\\s*(\\/)(?![\\/*])(?=(?:[^\\/\\\\\\[\\()]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\]|\\(([^\\)\\\\]|\\\\.)+\\))+\\/([gimsuy]+|(?![\\/\\*])|(?=\\/\\*))(?!\\s*[a-zA-Z0-9_$]))", "beginCaptures": { "1": { "name": "punctuation.definition.string.begin.js" @@ -4595,7 +4595,7 @@ }, { "name": "string.regexp.js", - "begin": "((?|&&|\\|\\||\\*\\/)\\s*(\\/)(?![\\/*])(?=(?:[^\\/\\\\\\[\\()]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\]|\\(([^\\)\\\\]|\\\\.)+\\))+\\/[gimsuy]*(?!\\s*[a-zA-Z0-9_$]))", + "begin": "(?|&&|\\|\\||\\*\\/)\\s*(\\/)(?![\\/*])(?=(?:[^\\/\\\\\\[\\()]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\]|\\(([^\\)\\\\]|\\\\.)+\\))+\\/([gimsuy]+|(?![\\/\\*])|(?=\\/\\*))(?!\\s*[a-zA-Z0-9_$]))", "beginCaptures": { "1": { "name": "punctuation.definition.string.begin.js.jsx" @@ -4595,7 +4595,7 @@ }, { "name": "string.regexp.js.jsx", - "begin": "((?|&&|\\|\\||\\*\\/)\\s*(\\/)(?![\\/*])(?=(?:[^\\/\\\\\\[\\()]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\]|\\(([^\\)\\\\]|\\\\.)+\\))+\\/[gimsuy]*(?!\\s*[a-zA-Z0-9_$]))", + "begin": "(?|&&|\\|\\||\\*\\/)\\s*(\\/)(?![\\/*])(?=(?:[^\\/\\\\\\[\\()]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\]|\\(([^\\)\\\\]|\\\\.)+\\))+\\/([gimsuy]+|(?![\\/\\*])|(?=\\/\\*))(?!\\s*[a-zA-Z0-9_$]))", "beginCaptures": { "1": { "name": "punctuation.definition.string.begin.ts" @@ -4644,7 +4644,7 @@ }, { "name": "string.regexp.ts", - "begin": "((?|&&|\\|\\||\\*\\/)\\s*(\\/)(?![\\/*])(?=(?:[^\\/\\\\\\[\\()]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\]|\\(([^\\)\\\\]|\\\\.)+\\))+\\/[gimsuy]*(?!\\s*[a-zA-Z0-9_$]))", + "begin": "(?|&&|\\|\\||\\*\\/)\\s*(\\/)(?![\\/*])(?=(?:[^\\/\\\\\\[\\()]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\]|\\(([^\\)\\\\]|\\\\.)+\\))+\\/([gimsuy]+|(?![\\/\\*])|(?=\\/\\*))(?!\\s*[a-zA-Z0-9_$]))", "beginCaptures": { "1": { "name": "punctuation.definition.string.begin.tsx" @@ -4595,7 +4595,7 @@ }, { "name": "string.regexp.tsx", - "begin": "((? action.kind && Extract_Constant.kind.contains(action.kind))) { + const disabledAction = new vscode.CodeAction('Extract to constant', Extract_Constant.kind); + disabledAction.disabled = localize('extract.disabled', "The current selection cannot be extracted"); + disabledAction.isPreferred = true; + actions.push(disabledAction); + } + return actions; + } } export function register( diff --git a/extensions/typescript-language-features/src/utils/cancellation.ts b/extensions/typescript-language-features/src/utils/cancellation.ts index 10933baa939..72672663ad2 100644 --- a/extensions/typescript-language-features/src/utils/cancellation.ts +++ b/extensions/typescript-language-features/src/utils/cancellation.ts @@ -5,6 +5,9 @@ import * as vscode from 'vscode'; -const nulTokenSource = new vscode.CancellationTokenSource(); +const noopDisposable = vscode.Disposable.from(); -export const nulToken = nulTokenSource.token; \ No newline at end of file +export const nulToken: vscode.CancellationToken = { + isCancellationRequested: false, + onCancellationRequested: () => noopDisposable +}; diff --git a/extensions/typescript-language-features/src/utils/logger.ts b/extensions/typescript-language-features/src/utils/logger.ts index aac42a91301..18317e58e00 100644 --- a/extensions/typescript-language-features/src/utils/logger.ts +++ b/extensions/typescript-language-features/src/utils/logger.ts @@ -41,9 +41,20 @@ export default class Logger { } public logLevel(level: LogLevel, message: string, data?: any): void { - this.output.appendLine(`[${level} - ${(new Date().toTimeString())}] ${message}`); + this.output.appendLine(`[${level} - ${this.now()}] ${message}`); if (data) { this.output.appendLine(this.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(); + } +} + +function padLeft(s: string, n: number, pad = ' ') { + return pad.repeat(Math.max(0, n - s.length)) + s; } diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index 6ed66a3932b..8ac6b2806ca 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -7,7 +7,8 @@ "enableProposedApi": true, "private": true, "activationEvents": [ - "onFileSystem:memfs" + "onFileSystem:memfs", + "onDebug" ], "main": "./out/extension", "engines": { @@ -46,23 +47,70 @@ } }, "taskDefinitions": [ - { - "type": "custombuildscript", - "required": [ - "flavor" - ], - "properties": { - "flavor": { - "type": "string", - "description": "The build flavor. Should be either '32' or '64'." - }, - "flags": { - "type": "array", - "description": "Additional build flags." - } + { + "type": "custombuildscript", + "required": [ + "flavor" + ], + "properties": { + "flavor": { + "type": "string", + "description": "The build flavor. Should be either '32' or '64'." + }, + "flags": { + "type": "array", + "description": "Additional build flags." } } - ] + } + ], + "breakpoints": [ + { + "language": "markdown" + } + ], + "debuggers": [ + { + "type": "mock", + "label": "Mock Debug", + "languages": [ + "markdown" + ], + + "configurationAttributes": { + "launch": { + "required": [ + "program" + ], + "properties": { + "program": { + "type": "string", + "description": "Absolute path to a text file.", + "default": "${workspaceFolder}/file.md" + }, + "stopOnEntry": { + "type": "boolean", + "description": "Automatically stop after launch.", + "default": true + }, + "trace": { + "type": "boolean", + "description": "Enable logging of the Debug Adapter Protocol.", + "default": true + } + } + } + }, + "initialConfigurations": [ + { + "type": "mock", + "request": "launch", + "name": "Debug file.md", + "program": "${workspaceFolder}/file.md" + } + ] + } + ] }, "scripts": { "compile": "node ./node_modules/vscode/bin/compile -watch -p ./", diff --git a/extensions/vscode-api-tests/src/extension.ts b/extensions/vscode-api-tests/src/extension.ts index 7d345d9ae95..f6fa8ebf362 100644 --- a/extensions/vscode-api-tests/src/extension.ts +++ b/extensions/vscode-api-tests/src/extension.ts @@ -18,12 +18,15 @@ const textEncoder = new TextEncoder(); const SCHEME = 'memfs'; export function activate(context: vscode.ExtensionContext) { - const memFs = enableFs(context); - enableProblems(context); - enableSearch(context, memFs); - enableTasks(); + if (typeof window !== 'undefined') { // do not run under node.js + const memFs = enableFs(context); + enableProblems(context); + enableSearch(context, memFs); + enableTasks(); + enableDebug(context, memFs); - vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(`memfs:/sample-folder/large.ts`)); + vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(`memfs:/sample-folder/large.ts`)); + } } function enableFs(context: vscode.ExtensionContext): MemFS { @@ -40,7 +43,7 @@ function enableFs(context: vscode.ExtensionContext): MemFS { memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.json`), textEncoder.encode('{ "json": true }'), { create: true, overwrite: true }); memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.ts`), textEncoder.encode('console.log("TypeScript")'), { create: true, overwrite: true }); memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.css`), textEncoder.encode('* { color: green; }'), { create: true, overwrite: true }); - memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.md`), textEncoder.encode('Hello _World_'), { create: true, overwrite: true }); + memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.md`), textEncoder.encode(getDebuggableFile()), { create: true, overwrite: true }); memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.xml`), textEncoder.encode(''), { create: true, overwrite: true }); memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.py`), textEncoder.encode('import base64, sys; base64.decode(open(sys.argv[1], "rb"), open(sys.argv[2], "wb"))'), { create: true, overwrite: true }); memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.php`), textEncoder.encode('&1\'); ?>'), { create: true, overwrite: true }); @@ -316,6 +319,50 @@ module Mankala { `; } + function getDebuggableFile(): string { + return `# VS Code Mock Debug + +This is a starter sample for developing VS Code debug adapters. + +**Mock Debug** simulates a debug adapter for Visual Studio Code. +It supports *step*, *continue*, *breakpoints*, *exceptions*, and +*variable access* but it is not connected to any real debugger. + +The sample is meant as an educational piece showing how to implement a debug +adapter for VS Code. It can be used as a starting point for developing a real adapter. + +More information about how to develop a new debug adapter can be found +[here](https://code.visualstudio.com/docs/extensions/example-debuggers). +Or discuss debug adapters on Gitter: +[![Gitter Chat](https://img.shields.io/badge/chat-online-brightgreen.svg)](https://gitter.im/Microsoft/vscode) + +## Using Mock Debug + +* Install the **Mock Debug** extension in VS Code. +* Create a new 'program' file 'readme.md' and enter several lines of arbitrary text. +* Switch to the debug viewlet and press the gear dropdown. +* Select the debug environment "Mock Debug". +* Press the green 'play' button to start debugging. + +You can now 'step through' the 'readme.md' file, set and hit breakpoints, and run into exceptions (if the word exception appears in a line). + +![Mock Debug](images/mock-debug.gif) + +## Build and Run + +[![build status](https://travis-ci.org/Microsoft/vscode-mock-debug.svg?branch=master)](https://travis-ci.org/Microsoft/vscode-mock-debug) +[![build status](https://ci.appveyor.com/api/projects/status/empmw5q1tk6h1fly/branch/master?svg=true)](https://ci.appveyor.com/project/weinand/vscode-mock-debug) + + +* Clone the project [https://github.com/Microsoft/vscode-mock-debug.git](https://github.com/Microsoft/vscode-mock-debug.git) +* Open the project folder in VS Code. +* Press 'F5' to build and launch Mock Debug in another VS Code window. In that window: + * Open a new workspace, create a new 'program' file 'readme.md' and enter several lines of arbitrary text. + * Switch to the debug viewlet and press the gear dropdown. + * Select the debug environment "Mock Debug". + * Press 'F5' to start debugging.`; + } + return memFs; } @@ -845,3 +892,3745 @@ export class MemFS implements vscode.FileSystemProvider, vscode.FileSearchProvid return result; } } + +//--------------------------------------------------------------------------- +// DEBUG +//--------------------------------------------------------------------------- + +function enableDebug(context: vscode.ExtensionContext, memFs: MemFS): void { + context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('mock', new MockConfigurationProvider())); + context.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory('mock', new MockDebugAdapterDescriptorFactory(memFs))); +} + +/** + * Declaration module describing the VS Code debug protocol. + * Auto-generated from json schema. Do not edit manually. + */ +declare module DebugProtocol { + + /** Base class of requests, responses, and events. */ + export interface ProtocolMessage { + /** Sequence number (also known as message ID). For protocol messages of type 'request' this ID can be used to cancel the request. */ + seq: number; + /** Message type. + Values: 'request', 'response', 'event', etc. + */ + type: string; + } + + /** A client or debug adapter initiated request. */ + export interface Request extends ProtocolMessage { + // type: 'request'; + /** The command to execute. */ + command: string; + /** Object containing arguments for the command. */ + arguments?: any; + } + + /** A debug adapter initiated event. */ + export interface Event extends ProtocolMessage { + // type: 'event'; + /** Type of event. */ + event: string; + /** Event-specific information. */ + body?: any; + } + + /** Response for a request. */ + export interface Response extends ProtocolMessage { + // type: 'response'; + /** Sequence number of the corresponding request. */ + request_seq: number; + /** Outcome of the request. + If true, the request was successful and the 'body' attribute may contain the result of the request. + If the value is false, the attribute 'message' contains the error in short form and the 'body' may contain additional information (see 'ErrorResponse.body.error'). + */ + success: boolean; + /** The command requested. */ + command: string; + /** Contains the raw error in short form if 'success' is false. + This raw error might be interpreted by the frontend and is not shown in the UI. + Some predefined values exist. + Values: + 'cancelled': request was cancelled. + etc. + */ + message?: string; + /** Contains request result if success is true and optional error details if success is false. */ + body?: any; + } + + /** On error (whenever 'success' is false), the body can provide more details. */ + export interface ErrorResponse extends Response { + body: { + /** An optional, structured error message. */ + error?: Message; + }; + } + + /** Cancel request; value of command field is 'cancel'. + The 'cancel' request is used by the frontend to indicate that it is no longer interested in the result produced by a specific request issued earlier. + This request has a hint characteristic: a debug adapter can only be expected to make a 'best effort' in honouring this request but there are no guarantees. + The 'cancel' request may return an error if it could not cancel an operation but a frontend should refrain from presenting this error to end users. + A frontend client should only call this request if the capability 'supportsCancelRequest' is true. + The request that got canceled still needs to send a response back. + This can either be a normal result ('success' attribute true) or an error response ('success' attribute false and the 'message' set to 'cancelled'). + Returning partial results from a cancelled request is possible but please note that a frontend client has no generic way for detecting that a response is partial or not. + */ + export interface CancelRequest extends Request { + // command: 'cancel'; + arguments?: CancelArguments; + } + + /** Arguments for 'cancel' request. */ + export interface CancelArguments { + /** The ID (attribute 'seq') of the request to cancel. */ + requestId?: number; + } + + /** Response to 'cancel' request. This is just an acknowledgement, so no body field is required. */ + export interface CancelResponse extends Response { + } + + /** Event message for 'initialized' event type. + This event indicates that the debug adapter is ready to accept configuration requests (e.g. SetBreakpointsRequest, SetExceptionBreakpointsRequest). + A debug adapter is expected to send this event when it is ready to accept configuration requests (but not before the 'initialize' request has finished). + The sequence of events/requests is as follows: + - adapters sends 'initialized' event (after the 'initialize' request has returned) + - frontend sends zero or more 'setBreakpoints' requests + - frontend sends one 'setFunctionBreakpoints' request + - frontend sends a 'setExceptionBreakpoints' request if one or more 'exceptionBreakpointFilters' have been defined (or if 'supportsConfigurationDoneRequest' is not defined or false) + - frontend sends other future configuration requests + - frontend sends one 'configurationDone' request to indicate the end of the configuration. + */ + export interface InitializedEvent extends Event { + // event: 'initialized'; + } + + /** Event message for 'stopped' event type. + The event indicates that the execution of the debuggee has stopped due to some condition. + This can be caused by a break point previously set, a stepping action has completed, by executing a debugger statement etc. + */ + export interface StoppedEvent extends Event { + // event: 'stopped'; + body: { + /** The reason for the event. + For backward compatibility this string is shown in the UI if the 'description' attribute is missing (but it must not be translated). + Values: 'step', 'breakpoint', 'exception', 'pause', 'entry', 'goto', 'function breakpoint', 'data breakpoint', etc. + */ + reason: string; + /** The full reason for the event, e.g. 'Paused on exception'. This string is shown in the UI as is and must be translated. */ + description?: string; + /** The thread which was stopped. */ + threadId?: number; + /** A value of true hints to the frontend that this event should not change the focus. */ + preserveFocusHint?: boolean; + /** Additional information. E.g. if reason is 'exception', text contains the exception name. This string is shown in the UI. */ + text?: string; + /** If 'allThreadsStopped' is true, a debug adapter can announce that all threads have stopped. + - The client should use this information to enable that all threads can be expanded to access their stacktraces. + - If the attribute is missing or false, only the thread with the given threadId can be expanded. + */ + allThreadsStopped?: boolean; + }; + } + + /** Event message for 'continued' event type. + The event indicates that the execution of the debuggee has continued. + Please note: a debug adapter is not expected to send this event in response to a request that implies that execution continues, e.g. 'launch' or 'continue'. + It is only necessary to send a 'continued' event if there was no previous request that implied this. + */ + export interface ContinuedEvent extends Event { + // event: 'continued'; + body: { + /** The thread which was continued. */ + threadId: number; + /** If 'allThreadsContinued' is true, a debug adapter can announce that all threads have continued. */ + allThreadsContinued?: boolean; + }; + } + + /** Event message for 'exited' event type. + The event indicates that the debuggee has exited and returns its exit code. + */ + export interface ExitedEvent extends Event { + // event: 'exited'; + body: { + /** The exit code returned from the debuggee. */ + exitCode: number; + }; + } + + /** Event message for 'terminated' event type. + The event indicates that debugging of the debuggee has terminated. This does **not** mean that the debuggee itself has exited. + */ + export interface TerminatedEvent extends Event { + // event: 'terminated'; + body?: { + /** A debug adapter may set 'restart' to true (or to an arbitrary object) to request that the front end restarts the session. + The value is not interpreted by the client and passed unmodified as an attribute '__restart' to the 'launch' and 'attach' requests. + */ + restart?: any; + }; + } + + /** Event message for 'thread' event type. + The event indicates that a thread has started or exited. + */ + export interface ThreadEvent extends Event { + // event: 'thread'; + body: { + /** The reason for the event. + Values: 'started', 'exited', etc. + */ + reason: string; + /** The identifier of the thread. */ + threadId: number; + }; + } + + /** Event message for 'output' event type. + The event indicates that the target has produced some output. + */ + export interface OutputEvent extends Event { + // event: 'output'; + body: { + /** The output category. If not specified, 'console' is assumed. + Values: 'console', 'stdout', 'stderr', 'telemetry', etc. + */ + category?: string; + /** The output to report. */ + output: string; + /** If an attribute 'variablesReference' exists and its value is > 0, the output contains objects which can be retrieved by passing 'variablesReference' to the 'variables' request. The value should be less than or equal to 2147483647 (2^31 - 1). */ + variablesReference?: number; + /** An optional source location where the output was produced. */ + source?: Source; + /** An optional source location line where the output was produced. */ + line?: number; + /** An optional source location column where the output was produced. */ + column?: number; + /** Optional data to report. For the 'telemetry' category the data will be sent to telemetry, for the other categories the data is shown in JSON format. */ + data?: any; + }; + } + + /** Event message for 'breakpoint' event type. + The event indicates that some information about a breakpoint has changed. + */ + export interface BreakpointEvent extends Event { + // event: 'breakpoint'; + body: { + /** The reason for the event. + Values: 'changed', 'new', 'removed', etc. + */ + reason: string; + /** The 'id' attribute is used to find the target breakpoint and the other attributes are used as the new values. */ + breakpoint: Breakpoint; + }; + } + + /** Event message for 'module' event type. + The event indicates that some information about a module has changed. + */ + export interface ModuleEvent extends Event { + // event: 'module'; + body: { + /** The reason for the event. */ + reason: 'new' | 'changed' | 'removed'; + /** The new, changed, or removed module. In case of 'removed' only the module id is used. */ + module: Module; + }; + } + + /** Event message for 'loadedSource' event type. + The event indicates that some source has been added, changed, or removed from the set of all loaded sources. + */ + export interface LoadedSourceEvent extends Event { + // event: 'loadedSource'; + body: { + /** The reason for the event. */ + reason: 'new' | 'changed' | 'removed'; + /** The new, changed, or removed source. */ + source: Source; + }; + } + + /** Event message for 'process' event type. + The event indicates that the debugger has begun debugging a new process. Either one that it has launched, or one that it has attached to. + */ + export interface ProcessEvent extends Event { + // event: 'process'; + body: { + /** The logical name of the process. This is usually the full path to process's executable file. Example: /home/example/myproj/program.js. */ + name: string; + /** The system process id of the debugged process. This property will be missing for non-system processes. */ + systemProcessId?: number; + /** If true, the process is running on the same computer as the debug adapter. */ + isLocalProcess?: boolean; + /** Describes how the debug engine started debugging this process. + 'launch': Process was launched under the debugger. + 'attach': Debugger attached to an existing process. + 'attachForSuspendedLaunch': A project launcher component has launched a new process in a suspended state and then asked the debugger to attach. + */ + startMethod?: 'launch' | 'attach' | 'attachForSuspendedLaunch'; + /** The size of a pointer or address for this process, in bits. This value may be used by clients when formatting addresses for display. */ + pointerSize?: number; + }; + } + + /** Event message for 'capabilities' event type. + The event indicates that one or more capabilities have changed. + Since the capabilities are dependent on the frontend and its UI, it might not be possible to change that at random times (or too late). + Consequently this event has a hint characteristic: a frontend can only be expected to make a 'best effort' in honouring individual capabilities but there are no guarantees. + Only changed capabilities need to be included, all other capabilities keep their values. + */ + export interface CapabilitiesEvent extends Event { + // event: 'capabilities'; + body: { + /** The set of updated capabilities. */ + capabilities: Capabilities; + }; + } + + /** RunInTerminal request; value of command field is 'runInTerminal'. + This request is sent from the debug adapter to the client to run a command in a terminal. This is typically used to launch the debuggee in a terminal provided by the client. + */ + export interface RunInTerminalRequest extends Request { + // command: 'runInTerminal'; + arguments: RunInTerminalRequestArguments; + } + + /** Arguments for 'runInTerminal' request. */ + export interface RunInTerminalRequestArguments { + /** What kind of terminal to launch. */ + kind?: 'integrated' | 'external'; + /** Optional title of the terminal. */ + title?: string; + /** Working directory of the command. */ + cwd: string; + /** List of arguments. The first argument is the command to run. */ + args: string[]; + /** Environment key-value pairs that are added to or removed from the default environment. */ + env?: { [key: string]: string | null; }; + } + + /** Response to 'runInTerminal' request. */ + export interface RunInTerminalResponse extends Response { + body: { + /** The process ID. The value should be less than or equal to 2147483647 (2^31 - 1). */ + processId?: number; + /** The process ID of the terminal shell. The value should be less than or equal to 2147483647 (2^31 - 1). */ + shellProcessId?: number; + }; + } + + /** Initialize request; value of command field is 'initialize'. + The 'initialize' request is sent as the first request from the client to the debug adapter in order to configure it with client capabilities and to retrieve capabilities from the debug adapter. + Until the debug adapter has responded to with an 'initialize' response, the client must not send any additional requests or events to the debug adapter. In addition the debug adapter is not allowed to send any requests or events to the client until it has responded with an 'initialize' response. + The 'initialize' request may only be sent once. + */ + export interface InitializeRequest extends Request { + // command: 'initialize'; + arguments: InitializeRequestArguments; + } + + /** Arguments for 'initialize' request. */ + export interface InitializeRequestArguments { + /** The ID of the (frontend) client using this adapter. */ + clientID?: string; + /** The human readable name of the (frontend) client using this adapter. */ + clientName?: string; + /** The ID of the debug adapter. */ + adapterID: string; + /** The ISO-639 locale of the (frontend) client using this adapter, e.g. en-US or de-CH. */ + locale?: string; + /** If true all line numbers are 1-based (default). */ + linesStartAt1?: boolean; + /** If true all column numbers are 1-based (default). */ + columnsStartAt1?: boolean; + /** Determines in what format paths are specified. The default is 'path', which is the native format. + Values: 'path', 'uri', etc. + */ + pathFormat?: string; + /** Client supports the optional type attribute for variables. */ + supportsVariableType?: boolean; + /** Client supports the paging of variables. */ + supportsVariablePaging?: boolean; + /** Client supports the runInTerminal request. */ + supportsRunInTerminalRequest?: boolean; + /** Client supports memory references. */ + supportsMemoryReferences?: boolean; + } + + /** Response to 'initialize' request. */ + export interface InitializeResponse extends Response { + /** The capabilities of this debug adapter. */ + body?: Capabilities; + } + + /** ConfigurationDone request; value of command field is 'configurationDone'. + The client of the debug protocol must send this request at the end of the sequence of configuration requests (which was started by the 'initialized' event). + */ + export interface ConfigurationDoneRequest extends Request { + // command: 'configurationDone'; + arguments?: ConfigurationDoneArguments; + } + + /** Arguments for 'configurationDone' request. */ + export interface ConfigurationDoneArguments { + } + + /** Response to 'configurationDone' request. This is just an acknowledgement, so no body field is required. */ + export interface ConfigurationDoneResponse extends Response { + } + + /** Launch request; value of command field is 'launch'. + The launch request is sent from the client to the debug adapter to start the debuggee with or without debugging (if 'noDebug' is true). Since launching is debugger/runtime specific, the arguments for this request are not part of this specification. + */ + export interface LaunchRequest extends Request { + // command: 'launch'; + arguments: LaunchRequestArguments; + } + + /** Arguments for 'launch' request. Additional attributes are implementation specific. */ + export interface LaunchRequestArguments { + /** If noDebug is true the launch request should launch the program without enabling debugging. */ + noDebug?: boolean; + /** Optional data from the previous, restarted session. + The data is sent as the 'restart' attribute of the 'terminated' event. + The client should leave the data intact. + */ + __restart?: any; + } + + /** Response to 'launch' request. This is just an acknowledgement, so no body field is required. */ + export interface LaunchResponse extends Response { + } + + /** Attach request; value of command field is 'attach'. + The attach request is sent from the client to the debug adapter to attach to a debuggee that is already running. Since attaching is debugger/runtime specific, the arguments for this request are not part of this specification. + */ + export interface AttachRequest extends Request { + // command: 'attach'; + arguments: AttachRequestArguments; + } + + /** Arguments for 'attach' request. Additional attributes are implementation specific. */ + export interface AttachRequestArguments { + /** Optional data from the previous, restarted session. + The data is sent as the 'restart' attribute of the 'terminated' event. + The client should leave the data intact. + */ + __restart?: any; + } + + /** Response to 'attach' request. This is just an acknowledgement, so no body field is required. */ + export interface AttachResponse extends Response { + } + + /** Restart request; value of command field is 'restart'. + Restarts a debug session. If the capability 'supportsRestartRequest' is missing or has the value false, + the client will implement 'restart' by terminating the debug adapter first and then launching it anew. + A debug adapter can override this default behaviour by implementing a restart request + and setting the capability 'supportsRestartRequest' to true. + */ + export interface RestartRequest extends Request { + // command: 'restart'; + arguments?: RestartArguments; + } + + /** Arguments for 'restart' request. */ + export interface RestartArguments { + } + + /** Response to 'restart' request. This is just an acknowledgement, so no body field is required. */ + export interface RestartResponse extends Response { + } + + /** Disconnect request; value of command field is 'disconnect'. + The 'disconnect' request is sent from the client to the debug adapter in order to stop debugging. It asks the debug adapter to disconnect from the debuggee and to terminate the debug adapter. If the debuggee has been started with the 'launch' request, the 'disconnect' request terminates the debuggee. If the 'attach' request was used to connect to the debuggee, 'disconnect' does not terminate the debuggee. This behavior can be controlled with the 'terminateDebuggee' argument (if supported by the debug adapter). + */ + export interface DisconnectRequest extends Request { + // command: 'disconnect'; + arguments?: DisconnectArguments; + } + + /** Arguments for 'disconnect' request. */ + export interface DisconnectArguments { + /** A value of true indicates that this 'disconnect' request is part of a restart sequence. */ + restart?: boolean; + /** Indicates whether the debuggee should be terminated when the debugger is disconnected. + If unspecified, the debug adapter is free to do whatever it thinks is best. + A client can only rely on this attribute being properly honored if a debug adapter returns true for the 'supportTerminateDebuggee' capability. + */ + terminateDebuggee?: boolean; + } + + /** Response to 'disconnect' request. This is just an acknowledgement, so no body field is required. */ + export interface DisconnectResponse extends Response { + } + + /** Terminate request; value of command field is 'terminate'. + The 'terminate' request is sent from the client to the debug adapter in order to give the debuggee a chance for terminating itself. + */ + export interface TerminateRequest extends Request { + // command: 'terminate'; + arguments?: TerminateArguments; + } + + /** Arguments for 'terminate' request. */ + export interface TerminateArguments { + /** A value of true indicates that this 'terminate' request is part of a restart sequence. */ + restart?: boolean; + } + + /** Response to 'terminate' request. This is just an acknowledgement, so no body field is required. */ + export interface TerminateResponse extends Response { + } + + /** BreakpointLocations request; value of command field is 'breakpointLocations'. + The 'breakpointLocations' request returns all possible locations for source breakpoints in a given range. + */ + export interface BreakpointLocationsRequest extends Request { + // command: 'breakpointLocations'; + arguments?: BreakpointLocationsArguments; + } + + /** Arguments for 'breakpointLocations' request. */ + export interface BreakpointLocationsArguments { + /** The source location of the breakpoints; either 'source.path' or 'source.reference' must be specified. */ + source: Source; + /** Start line of range to search possible breakpoint locations in. If only the line is specified, the request returns all possible locations in that line. */ + line: number; + /** Optional start column of range to search possible breakpoint locations in. If no start column is given, the first column in the start line is assumed. */ + column?: number; + /** Optional end line of range to search possible breakpoint locations in. If no end line is given, then the end line is assumed to be the start line. */ + endLine?: number; + /** Optional end column of range to search possible breakpoint locations in. If no end column is given, then it is assumed to be in the last column of the end line. */ + endColumn?: number; + } + + /** Response to 'breakpointLocations' request. + Contains possible locations for source breakpoints. + */ + export interface BreakpointLocationsResponse extends Response { + body: { + /** Sorted set of possible breakpoint locations. */ + breakpoints: BreakpointLocation[]; + }; + } + + /** SetBreakpoints request; value of command field is 'setBreakpoints'. + Sets multiple breakpoints for a single source and clears all previous breakpoints in that source. + To clear all breakpoint for a source, specify an empty array. + When a breakpoint is hit, a 'stopped' event (with reason 'breakpoint') is generated. + */ + export interface SetBreakpointsRequest extends Request { + // command: 'setBreakpoints'; + arguments: SetBreakpointsArguments; + } + + /** Arguments for 'setBreakpoints' request. */ + export interface SetBreakpointsArguments { + /** The source location of the breakpoints; either 'source.path' or 'source.reference' must be specified. */ + source: Source; + /** The code locations of the breakpoints. */ + breakpoints?: SourceBreakpoint[]; + /** Deprecated: The code locations of the breakpoints. */ + lines?: number[]; + /** A value of true indicates that the underlying source has been modified which results in new breakpoint locations. */ + sourceModified?: boolean; + } + + /** Response to 'setBreakpoints' request. + Returned is information about each breakpoint created by this request. + This includes the actual code location and whether the breakpoint could be verified. + The breakpoints returned are in the same order as the elements of the 'breakpoints' + (or the deprecated 'lines') array in the arguments. + */ + export interface SetBreakpointsResponse extends Response { + body: { + /** Information about the breakpoints. The array elements are in the same order as the elements of the 'breakpoints' (or the deprecated 'lines') array in the arguments. */ + breakpoints: Breakpoint[]; + }; + } + + /** SetFunctionBreakpoints request; value of command field is 'setFunctionBreakpoints'. + Replaces all existing function breakpoints with new function breakpoints. + To clear all function breakpoints, specify an empty array. + When a function breakpoint is hit, a 'stopped' event (with reason 'function breakpoint') is generated. + */ + export interface SetFunctionBreakpointsRequest extends Request { + // command: 'setFunctionBreakpoints'; + arguments: SetFunctionBreakpointsArguments; + } + + /** Arguments for 'setFunctionBreakpoints' request. */ + export interface SetFunctionBreakpointsArguments { + /** The function names of the breakpoints. */ + breakpoints: FunctionBreakpoint[]; + } + + /** Response to 'setFunctionBreakpoints' request. + Returned is information about each breakpoint created by this request. + */ + export interface SetFunctionBreakpointsResponse extends Response { + body: { + /** Information about the breakpoints. The array elements correspond to the elements of the 'breakpoints' array. */ + breakpoints: Breakpoint[]; + }; + } + + /** SetExceptionBreakpoints request; value of command field is 'setExceptionBreakpoints'. + The request configures the debuggers response to thrown exceptions. If an exception is configured to break, a 'stopped' event is fired (with reason 'exception'). + */ + export interface SetExceptionBreakpointsRequest extends Request { + // command: 'setExceptionBreakpoints'; + arguments: SetExceptionBreakpointsArguments; + } + + /** Arguments for 'setExceptionBreakpoints' request. */ + export interface SetExceptionBreakpointsArguments { + /** IDs of checked exception options. The set of IDs is returned via the 'exceptionBreakpointFilters' capability. */ + filters: string[]; + /** Configuration options for selected exceptions. */ + exceptionOptions?: ExceptionOptions[]; + } + + /** Response to 'setExceptionBreakpoints' request. This is just an acknowledgement, so no body field is required. */ + export interface SetExceptionBreakpointsResponse extends Response { + } + + /** DataBreakpointInfo request; value of command field is 'dataBreakpointInfo'. + Obtains information on a possible data breakpoint that could be set on an expression or variable. + */ + export interface DataBreakpointInfoRequest extends Request { + // command: 'dataBreakpointInfo'; + arguments: DataBreakpointInfoArguments; + } + + /** Arguments for 'dataBreakpointInfo' request. */ + export interface DataBreakpointInfoArguments { + /** Reference to the Variable container if the data breakpoint is requested for a child of the container. */ + variablesReference?: number; + /** The name of the Variable's child to obtain data breakpoint information for. If variableReference isn’t provided, this can be an expression. */ + name: string; + } + + /** Response to 'dataBreakpointInfo' request. */ + export interface DataBreakpointInfoResponse extends Response { + body: { + /** An identifier for the data on which a data breakpoint can be registered with the setDataBreakpoints request or null if no data breakpoint is available. */ + dataId: string | null; + /** UI string that describes on what data the breakpoint is set on or why a data breakpoint is not available. */ + description: string; + /** Optional attribute listing the available access types for a potential data breakpoint. A UI frontend could surface this information. */ + accessTypes?: DataBreakpointAccessType[]; + /** Optional attribute indicating that a potential data breakpoint could be persisted across sessions. */ + canPersist?: boolean; + }; + } + + /** SetDataBreakpoints request; value of command field is 'setDataBreakpoints'. + Replaces all existing data breakpoints with new data breakpoints. + To clear all data breakpoints, specify an empty array. + When a data breakpoint is hit, a 'stopped' event (with reason 'data breakpoint') is generated. + */ + export interface SetDataBreakpointsRequest extends Request { + // command: 'setDataBreakpoints'; + arguments: SetDataBreakpointsArguments; + } + + /** Arguments for 'setDataBreakpoints' request. */ + export interface SetDataBreakpointsArguments { + /** The contents of this array replaces all existing data breakpoints. An empty array clears all data breakpoints. */ + breakpoints: DataBreakpoint[]; + } + + /** Response to 'setDataBreakpoints' request. + Returned is information about each breakpoint created by this request. + */ + export interface SetDataBreakpointsResponse extends Response { + body: { + /** Information about the data breakpoints. The array elements correspond to the elements of the input argument 'breakpoints' array. */ + breakpoints: Breakpoint[]; + }; + } + + /** Continue request; value of command field is 'continue'. + The request starts the debuggee to run again. + */ + export interface ContinueRequest extends Request { + // command: 'continue'; + arguments: ContinueArguments; + } + + /** Arguments for 'continue' request. */ + export interface ContinueArguments { + /** Continue execution for the specified thread (if possible). If the backend cannot continue on a single thread but will continue on all threads, it should set the 'allThreadsContinued' attribute in the response to true. */ + threadId: number; + } + + /** Response to 'continue' request. */ + export interface ContinueResponse extends Response { + body: { + /** If true, the 'continue' request has ignored the specified thread and continued all threads instead. If this attribute is missing a value of 'true' is assumed for backward compatibility. */ + allThreadsContinued?: boolean; + }; + } + + /** Next request; value of command field is 'next'. + The request starts the debuggee to run again for one step. + The debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed. + */ + export interface NextRequest extends Request { + // command: 'next'; + arguments: NextArguments; + } + + /** Arguments for 'next' request. */ + export interface NextArguments { + /** Execute 'next' for this thread. */ + threadId: number; + } + + /** Response to 'next' request. This is just an acknowledgement, so no body field is required. */ + export interface NextResponse extends Response { + } + + /** StepIn request; value of command field is 'stepIn'. + The request starts the debuggee to step into a function/method if possible. + If it cannot step into a target, 'stepIn' behaves like 'next'. + The debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed. + If there are multiple function/method calls (or other targets) on the source line, + the optional argument 'targetId' can be used to control into which target the 'stepIn' should occur. + The list of possible targets for a given source line can be retrieved via the 'stepInTargets' request. + */ + export interface StepInRequest extends Request { + // command: 'stepIn'; + arguments: StepInArguments; + } + + /** Arguments for 'stepIn' request. */ + export interface StepInArguments { + /** Execute 'stepIn' for this thread. */ + threadId: number; + /** Optional id of the target to step into. */ + targetId?: number; + } + + /** Response to 'stepIn' request. This is just an acknowledgement, so no body field is required. */ + export interface StepInResponse extends Response { + } + + /** StepOut request; value of command field is 'stepOut'. + The request starts the debuggee to run again for one step. + The debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed. + */ + export interface StepOutRequest extends Request { + // command: 'stepOut'; + arguments: StepOutArguments; + } + + /** Arguments for 'stepOut' request. */ + export interface StepOutArguments { + /** Execute 'stepOut' for this thread. */ + threadId: number; + } + + /** Response to 'stepOut' request. This is just an acknowledgement, so no body field is required. */ + export interface StepOutResponse extends Response { + } + + /** StepBack request; value of command field is 'stepBack'. + The request starts the debuggee to run one step backwards. + The debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed. Clients should only call this request if the capability 'supportsStepBack' is true. + */ + export interface StepBackRequest extends Request { + // command: 'stepBack'; + arguments: StepBackArguments; + } + + /** Arguments for 'stepBack' request. */ + export interface StepBackArguments { + /** Execute 'stepBack' for this thread. */ + threadId: number; + } + + /** Response to 'stepBack' request. This is just an acknowledgement, so no body field is required. */ + export interface StepBackResponse extends Response { + } + + /** ReverseContinue request; value of command field is 'reverseContinue'. + The request starts the debuggee to run backward. Clients should only call this request if the capability 'supportsStepBack' is true. + */ + export interface ReverseContinueRequest extends Request { + // command: 'reverseContinue'; + arguments: ReverseContinueArguments; + } + + /** Arguments for 'reverseContinue' request. */ + export interface ReverseContinueArguments { + /** Execute 'reverseContinue' for this thread. */ + threadId: number; + } + + /** Response to 'reverseContinue' request. This is just an acknowledgement, so no body field is required. */ + export interface ReverseContinueResponse extends Response { + } + + /** RestartFrame request; value of command field is 'restartFrame'. + The request restarts execution of the specified stackframe. + The debug adapter first sends the response and then a 'stopped' event (with reason 'restart') after the restart has completed. + */ + export interface RestartFrameRequest extends Request { + // command: 'restartFrame'; + arguments: RestartFrameArguments; + } + + /** Arguments for 'restartFrame' request. */ + export interface RestartFrameArguments { + /** Restart this stackframe. */ + frameId: number; + } + + /** Response to 'restartFrame' request. This is just an acknowledgement, so no body field is required. */ + export interface RestartFrameResponse extends Response { + } + + /** Goto request; value of command field is 'goto'. + The request sets the location where the debuggee will continue to run. + This makes it possible to skip the execution of code or to executed code again. + The code between the current location and the goto target is not executed but skipped. + The debug adapter first sends the response and then a 'stopped' event with reason 'goto'. + */ + export interface GotoRequest extends Request { + // command: 'goto'; + arguments: GotoArguments; + } + + /** Arguments for 'goto' request. */ + export interface GotoArguments { + /** Set the goto target for this thread. */ + threadId: number; + /** The location where the debuggee will continue to run. */ + targetId: number; + } + + /** Response to 'goto' request. This is just an acknowledgement, so no body field is required. */ + export interface GotoResponse extends Response { + } + + /** Pause request; value of command field is 'pause'. + The request suspends the debuggee. + The debug adapter first sends the response and then a 'stopped' event (with reason 'pause') after the thread has been paused successfully. + */ + export interface PauseRequest extends Request { + // command: 'pause'; + arguments: PauseArguments; + } + + /** Arguments for 'pause' request. */ + export interface PauseArguments { + /** Pause execution for this thread. */ + threadId: number; + } + + /** Response to 'pause' request. This is just an acknowledgement, so no body field is required. */ + export interface PauseResponse extends Response { + } + + /** StackTrace request; value of command field is 'stackTrace'. + The request returns a stacktrace from the current execution state. + */ + export interface StackTraceRequest extends Request { + // command: 'stackTrace'; + arguments: StackTraceArguments; + } + + /** Arguments for 'stackTrace' request. */ + export interface StackTraceArguments { + /** Retrieve the stacktrace for this thread. */ + threadId: number; + /** The index of the first frame to return; if omitted frames start at 0. */ + startFrame?: number; + /** The maximum number of frames to return. If levels is not specified or 0, all frames are returned. */ + levels?: number; + /** Specifies details on how to format the stack frames. */ + format?: StackFrameFormat; + } + + /** Response to 'stackTrace' request. */ + export interface StackTraceResponse extends Response { + body: { + /** The frames of the stackframe. If the array has length zero, there are no stackframes available. + This means that there is no location information available. + */ + stackFrames: StackFrame[]; + /** The total number of frames available. */ + totalFrames?: number; + }; + } + + /** Scopes request; value of command field is 'scopes'. + The request returns the variable scopes for a given stackframe ID. + */ + export interface ScopesRequest extends Request { + // command: 'scopes'; + arguments: ScopesArguments; + } + + /** Arguments for 'scopes' request. */ + export interface ScopesArguments { + /** Retrieve the scopes for this stackframe. */ + frameId: number; + } + + /** Response to 'scopes' request. */ + export interface ScopesResponse extends Response { + body: { + /** The scopes of the stackframe. If the array has length zero, there are no scopes available. */ + scopes: Scope[]; + }; + } + + /** Variables request; value of command field is 'variables'. + Retrieves all child variables for the given variable reference. + An optional filter can be used to limit the fetched children to either named or indexed children. + */ + export interface VariablesRequest extends Request { + // command: 'variables'; + arguments: VariablesArguments; + } + + /** Arguments for 'variables' request. */ + export interface VariablesArguments { + /** The Variable reference. */ + variablesReference: number; + /** Optional filter to limit the child variables to either named or indexed. If omitted, both types are fetched. */ + filter?: 'indexed' | 'named'; + /** The index of the first variable to return; if omitted children start at 0. */ + start?: number; + /** The number of variables to return. If count is missing or 0, all variables are returned. */ + count?: number; + /** Specifies details on how to format the Variable values. */ + format?: ValueFormat; + } + + /** Response to 'variables' request. */ + export interface VariablesResponse extends Response { + body: { + /** All (or a range) of variables for the given variable reference. */ + variables: Variable[]; + }; + } + + /** SetVariable request; value of command field is 'setVariable'. + Set the variable with the given name in the variable container to a new value. + */ + export interface SetVariableRequest extends Request { + // command: 'setVariable'; + arguments: SetVariableArguments; + } + + /** Arguments for 'setVariable' request. */ + export interface SetVariableArguments { + /** The reference of the variable container. */ + variablesReference: number; + /** The name of the variable in the container. */ + name: string; + /** The value of the variable. */ + value: string; + /** Specifies details on how to format the response value. */ + format?: ValueFormat; + } + + /** Response to 'setVariable' request. */ + export interface SetVariableResponse extends Response { + body: { + /** The new value of the variable. */ + value: string; + /** The type of the new value. Typically shown in the UI when hovering over the value. */ + type?: string; + /** If variablesReference is > 0, the new value is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. The value should be less than or equal to 2147483647 (2^31 - 1). */ + variablesReference?: number; + /** The number of named child variables. + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). + */ + namedVariables?: number; + /** The number of indexed child variables. + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). + */ + indexedVariables?: number; + }; + } + + /** Source request; value of command field is 'source'. + The request retrieves the source code for a given source reference. + */ + export interface SourceRequest extends Request { + // command: 'source'; + arguments: SourceArguments; + } + + /** Arguments for 'source' request. */ + export interface SourceArguments { + /** Specifies the source content to load. Either source.path or source.sourceReference must be specified. */ + source?: Source; + /** The reference to the source. This is the same as source.sourceReference. This is provided for backward compatibility since old backends do not understand the 'source' attribute. */ + sourceReference: number; + } + + /** Response to 'source' request. */ + export interface SourceResponse extends Response { + body: { + /** Content of the source reference. */ + content: string; + /** Optional content type (mime type) of the source. */ + mimeType?: string; + }; + } + + /** Threads request; value of command field is 'threads'. + The request retrieves a list of all threads. + */ + export interface ThreadsRequest extends Request { + // command: 'threads'; + } + + /** Response to 'threads' request. */ + export interface ThreadsResponse extends Response { + body: { + /** All threads. */ + threads: Thread[]; + }; + } + + /** TerminateThreads request; value of command field is 'terminateThreads'. + The request terminates the threads with the given ids. + */ + export interface TerminateThreadsRequest extends Request { + // command: 'terminateThreads'; + arguments: TerminateThreadsArguments; + } + + /** Arguments for 'terminateThreads' request. */ + export interface TerminateThreadsArguments { + /** Ids of threads to be terminated. */ + threadIds?: number[]; + } + + /** Response to 'terminateThreads' request. This is just an acknowledgement, so no body field is required. */ + export interface TerminateThreadsResponse extends Response { + } + + /** Modules request; value of command field is 'modules'. + Modules can be retrieved from the debug adapter with the ModulesRequest which can either return all modules or a range of modules to support paging. + */ + export interface ModulesRequest extends Request { + // command: 'modules'; + arguments: ModulesArguments; + } + + /** Arguments for 'modules' request. */ + export interface ModulesArguments { + /** The index of the first module to return; if omitted modules start at 0. */ + startModule?: number; + /** The number of modules to return. If moduleCount is not specified or 0, all modules are returned. */ + moduleCount?: number; + } + + /** Response to 'modules' request. */ + export interface ModulesResponse extends Response { + body: { + /** All modules or range of modules. */ + modules: Module[]; + /** The total number of modules available. */ + totalModules?: number; + }; + } + + /** LoadedSources request; value of command field is 'loadedSources'. + Retrieves the set of all sources currently loaded by the debugged process. + */ + export interface LoadedSourcesRequest extends Request { + // command: 'loadedSources'; + arguments?: LoadedSourcesArguments; + } + + /** Arguments for 'loadedSources' request. */ + export interface LoadedSourcesArguments { + } + + /** Response to 'loadedSources' request. */ + export interface LoadedSourcesResponse extends Response { + body: { + /** Set of loaded sources. */ + sources: Source[]; + }; + } + + /** Evaluate request; value of command field is 'evaluate'. + Evaluates the given expression in the context of the top most stack frame. + The expression has access to any variables and arguments that are in scope. + */ + export interface EvaluateRequest extends Request { + // command: 'evaluate'; + arguments: EvaluateArguments; + } + + /** Arguments for 'evaluate' request. */ + export interface EvaluateArguments { + /** The expression to evaluate. */ + expression: string; + /** Evaluate the expression in the scope of this stack frame. If not specified, the expression is evaluated in the global scope. */ + frameId?: number; + /** The context in which the evaluate request is run. + Values: + 'watch': evaluate is run in a watch. + 'repl': evaluate is run from REPL console. + 'hover': evaluate is run from a data hover. + etc. + */ + context?: string; + /** Specifies details on how to format the Evaluate result. */ + format?: ValueFormat; + } + + /** Response to 'evaluate' request. */ + export interface EvaluateResponse extends Response { + body: { + /** The result of the evaluate request. */ + result: string; + /** The optional type of the evaluate result. */ + type?: string; + /** Properties of a evaluate result that can be used to determine how to render the result in the UI. */ + presentationHint?: VariablePresentationHint; + /** If variablesReference is > 0, the evaluate result is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. The value should be less than or equal to 2147483647 (2^31 - 1). */ + variablesReference: number; + /** The number of named child variables. + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). + */ + namedVariables?: number; + /** The number of indexed child variables. + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). + */ + indexedVariables?: number; + /** Memory reference to a location appropriate for this result. For pointer type eval results, this is generally a reference to the memory address contained in the pointer. */ + memoryReference?: string; + }; + } + + /** SetExpression request; value of command field is 'setExpression'. + Evaluates the given 'value' expression and assigns it to the 'expression' which must be a modifiable l-value. + The expressions have access to any variables and arguments that are in scope of the specified frame. + */ + export interface SetExpressionRequest extends Request { + // command: 'setExpression'; + arguments: SetExpressionArguments; + } + + /** Arguments for 'setExpression' request. */ + export interface SetExpressionArguments { + /** The l-value expression to assign to. */ + expression: string; + /** The value expression to assign to the l-value expression. */ + value: string; + /** Evaluate the expressions in the scope of this stack frame. If not specified, the expressions are evaluated in the global scope. */ + frameId?: number; + /** Specifies how the resulting value should be formatted. */ + format?: ValueFormat; + } + + /** Response to 'setExpression' request. */ + export interface SetExpressionResponse extends Response { + body: { + /** The new value of the expression. */ + value: string; + /** The optional type of the value. */ + type?: string; + /** Properties of a value that can be used to determine how to render the result in the UI. */ + presentationHint?: VariablePresentationHint; + /** If variablesReference is > 0, the value is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. The value should be less than or equal to 2147483647 (2^31 - 1). */ + variablesReference?: number; + /** The number of named child variables. + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). + */ + namedVariables?: number; + /** The number of indexed child variables. + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). + */ + indexedVariables?: number; + }; + } + + /** StepInTargets request; value of command field is 'stepInTargets'. + This request retrieves the possible stepIn targets for the specified stack frame. + These targets can be used in the 'stepIn' request. + The StepInTargets may only be called if the 'supportsStepInTargetsRequest' capability exists and is true. + */ + export interface StepInTargetsRequest extends Request { + // command: 'stepInTargets'; + arguments: StepInTargetsArguments; + } + + /** Arguments for 'stepInTargets' request. */ + export interface StepInTargetsArguments { + /** The stack frame for which to retrieve the possible stepIn targets. */ + frameId: number; + } + + /** Response to 'stepInTargets' request. */ + export interface StepInTargetsResponse extends Response { + body: { + /** The possible stepIn targets of the specified source location. */ + targets: StepInTarget[]; + }; + } + + /** GotoTargets request; value of command field is 'gotoTargets'. + This request retrieves the possible goto targets for the specified source location. + These targets can be used in the 'goto' request. + The GotoTargets request may only be called if the 'supportsGotoTargetsRequest' capability exists and is true. + */ + export interface GotoTargetsRequest extends Request { + // command: 'gotoTargets'; + arguments: GotoTargetsArguments; + } + + /** Arguments for 'gotoTargets' request. */ + export interface GotoTargetsArguments { + /** The source location for which the goto targets are determined. */ + source: Source; + /** The line location for which the goto targets are determined. */ + line: number; + /** An optional column location for which the goto targets are determined. */ + column?: number; + } + + /** Response to 'gotoTargets' request. */ + export interface GotoTargetsResponse extends Response { + body: { + /** The possible goto targets of the specified location. */ + targets: GotoTarget[]; + }; + } + + /** Completions request; value of command field is 'completions'. + Returns a list of possible completions for a given caret position and text. + The CompletionsRequest may only be called if the 'supportsCompletionsRequest' capability exists and is true. + */ + export interface CompletionsRequest extends Request { + // command: 'completions'; + arguments: CompletionsArguments; + } + + /** Arguments for 'completions' request. */ + export interface CompletionsArguments { + /** Returns completions in the scope of this stack frame. If not specified, the completions are returned for the global scope. */ + frameId?: number; + /** One or more source lines. Typically this is the text a user has typed into the debug console before he asked for completion. */ + text: string; + /** The character position for which to determine the completion proposals. */ + column: number; + /** An optional line for which to determine the completion proposals. If missing the first line of the text is assumed. */ + line?: number; + } + + /** Response to 'completions' request. */ + export interface CompletionsResponse extends Response { + body: { + /** The possible completions for . */ + targets: CompletionItem[]; + }; + } + + /** ExceptionInfo request; value of command field is 'exceptionInfo'. + Retrieves the details of the exception that caused this event to be raised. + */ + export interface ExceptionInfoRequest extends Request { + // command: 'exceptionInfo'; + arguments: ExceptionInfoArguments; + } + + /** Arguments for 'exceptionInfo' request. */ + export interface ExceptionInfoArguments { + /** Thread for which exception information should be retrieved. */ + threadId: number; + } + + /** Response to 'exceptionInfo' request. */ + export interface ExceptionInfoResponse extends Response { + body: { + /** ID of the exception that was thrown. */ + exceptionId: string; + /** Descriptive text for the exception provided by the debug adapter. */ + description?: string; + /** Mode that caused the exception notification to be raised. */ + breakMode: ExceptionBreakMode; + /** Detailed information about the exception. */ + details?: ExceptionDetails; + }; + } + + /** ReadMemory request; value of command field is 'readMemory'. + Reads bytes from memory at the provided location. + */ + export interface ReadMemoryRequest extends Request { + // command: 'readMemory'; + arguments: ReadMemoryArguments; + } + + /** Arguments for 'readMemory' request. */ + export interface ReadMemoryArguments { + /** Memory reference to the base location from which data should be read. */ + memoryReference: string; + /** Optional offset (in bytes) to be applied to the reference location before reading data. Can be negative. */ + offset?: number; + /** Number of bytes to read at the specified location and offset. */ + count: number; + } + + /** Response to 'readMemory' request. */ + export interface ReadMemoryResponse extends Response { + body?: { + /** The address of the first byte of data returned. Treated as a hex value if prefixed with '0x', or as a decimal value otherwise. */ + address: string; + /** The number of unreadable bytes encountered after the last successfully read byte. This can be used to determine the number of bytes that must be skipped before a subsequent 'readMemory' request will succeed. */ + unreadableBytes?: number; + /** The bytes read from memory, encoded using base64. */ + data?: string; + }; + } + + /** Disassemble request; value of command field is 'disassemble'. + Disassembles code stored at the provided location. + */ + export interface DisassembleRequest extends Request { + // command: 'disassemble'; + arguments: DisassembleArguments; + } + + /** Arguments for 'disassemble' request. */ + export interface DisassembleArguments { + /** Memory reference to the base location containing the instructions to disassemble. */ + memoryReference: string; + /** Optional offset (in bytes) to be applied to the reference location before disassembling. Can be negative. */ + offset?: number; + /** Optional offset (in instructions) to be applied after the byte offset (if any) before disassembling. Can be negative. */ + instructionOffset?: number; + /** Number of instructions to disassemble starting at the specified location and offset. An adapter must return exactly this number of instructions - any unavailable instructions should be replaced with an implementation-defined 'invalid instruction' value. */ + instructionCount: number; + /** If true, the adapter should attempt to resolve memory addresses and other values to symbolic names. */ + resolveSymbols?: boolean; + } + + /** Response to 'disassemble' request. */ + export interface DisassembleResponse extends Response { + body?: { + /** The list of disassembled instructions. */ + instructions: DisassembledInstruction[]; + }; + } + + /** Information about the capabilities of a debug adapter. */ + export interface Capabilities { + /** The debug adapter supports the 'configurationDone' request. */ + supportsConfigurationDoneRequest?: boolean; + /** The debug adapter supports function breakpoints. */ + supportsFunctionBreakpoints?: boolean; + /** The debug adapter supports conditional breakpoints. */ + supportsConditionalBreakpoints?: boolean; + /** The debug adapter supports breakpoints that break execution after a specified number of hits. */ + supportsHitConditionalBreakpoints?: boolean; + /** The debug adapter supports a (side effect free) evaluate request for data hovers. */ + supportsEvaluateForHovers?: boolean; + /** Available filters or options for the setExceptionBreakpoints request. */ + exceptionBreakpointFilters?: ExceptionBreakpointsFilter[]; + /** The debug adapter supports stepping back via the 'stepBack' and 'reverseContinue' requests. */ + supportsStepBack?: boolean; + /** The debug adapter supports setting a variable to a value. */ + supportsSetVariable?: boolean; + /** The debug adapter supports restarting a frame. */ + supportsRestartFrame?: boolean; + /** The debug adapter supports the 'gotoTargets' request. */ + supportsGotoTargetsRequest?: boolean; + /** The debug adapter supports the 'stepInTargets' request. */ + supportsStepInTargetsRequest?: boolean; + /** The debug adapter supports the 'completions' request. */ + supportsCompletionsRequest?: boolean; + /** The set of characters that should trigger completion in a REPL. If not specified, the UI should assume the '.' character. */ + completionTriggerCharacters?: string[]; + /** The debug adapter supports the 'modules' request. */ + supportsModulesRequest?: boolean; + /** The set of additional module information exposed by the debug adapter. */ + additionalModuleColumns?: ColumnDescriptor[]; + /** Checksum algorithms supported by the debug adapter. */ + supportedChecksumAlgorithms?: ChecksumAlgorithm[]; + /** The debug adapter supports the 'restart' request. In this case a client should not implement 'restart' by terminating and relaunching the adapter but by calling the RestartRequest. */ + supportsRestartRequest?: boolean; + /** The debug adapter supports 'exceptionOptions' on the setExceptionBreakpoints request. */ + supportsExceptionOptions?: boolean; + /** The debug adapter supports a 'format' attribute on the stackTraceRequest, variablesRequest, and evaluateRequest. */ + supportsValueFormattingOptions?: boolean; + /** The debug adapter supports the 'exceptionInfo' request. */ + supportsExceptionInfoRequest?: boolean; + /** The debug adapter supports the 'terminateDebuggee' attribute on the 'disconnect' request. */ + supportTerminateDebuggee?: boolean; + /** The debug adapter supports the delayed loading of parts of the stack, which requires that both the 'startFrame' and 'levels' arguments and the 'totalFrames' result of the 'StackTrace' request are supported. */ + supportsDelayedStackTraceLoading?: boolean; + /** The debug adapter supports the 'loadedSources' request. */ + supportsLoadedSourcesRequest?: boolean; + /** The debug adapter supports logpoints by interpreting the 'logMessage' attribute of the SourceBreakpoint. */ + supportsLogPoints?: boolean; + /** The debug adapter supports the 'terminateThreads' request. */ + supportsTerminateThreadsRequest?: boolean; + /** The debug adapter supports the 'setExpression' request. */ + supportsSetExpression?: boolean; + /** The debug adapter supports the 'terminate' request. */ + supportsTerminateRequest?: boolean; + /** The debug adapter supports data breakpoints. */ + supportsDataBreakpoints?: boolean; + /** The debug adapter supports the 'readMemory' request. */ + supportsReadMemoryRequest?: boolean; + /** The debug adapter supports the 'disassemble' request. */ + supportsDisassembleRequest?: boolean; + /** The debug adapter supports the 'cancel' request. */ + supportsCancelRequest?: boolean; + /** The debug adapter supports the 'breakpointLocations' request. */ + supportsBreakpointLocationsRequest?: boolean; + } + + /** An ExceptionBreakpointsFilter is shown in the UI as an option for configuring how exceptions are dealt with. */ + export interface ExceptionBreakpointsFilter { + /** The internal ID of the filter. This value is passed to the setExceptionBreakpoints request. */ + filter: string; + /** The name of the filter. This will be shown in the UI. */ + label: string; + /** Initial value of the filter. If not specified a value 'false' is assumed. */ + default?: boolean; + } + + /** A structured message object. Used to return errors from requests. */ + export interface Message { + /** Unique identifier for the message. */ + id: number; + /** A format string for the message. Embedded variables have the form '{name}'. + If variable name starts with an underscore character, the variable does not contain user data (PII) and can be safely used for telemetry purposes. + */ + format: string; + /** An object used as a dictionary for looking up the variables in the format string. */ + variables?: { [key: string]: string; }; + /** If true send to telemetry. */ + sendTelemetry?: boolean; + /** If true show user. */ + showUser?: boolean; + /** An optional url where additional information about this message can be found. */ + url?: string; + /** An optional label that is presented to the user as the UI for opening the url. */ + urlLabel?: string; + } + + /** A Module object represents a row in the modules view. + Two attributes are mandatory: an id identifies a module in the modules view and is used in a ModuleEvent for identifying a module for adding, updating or deleting. + The name is used to minimally render the module in the UI. + + Additional attributes can be added to the module. They will show up in the module View if they have a corresponding ColumnDescriptor. + + To avoid an unnecessary proliferation of additional attributes with similar semantics but different names + we recommend to re-use attributes from the 'recommended' list below first, and only introduce new attributes if nothing appropriate could be found. + */ + export interface Module { + /** Unique identifier for the module. */ + id: number | string; + /** A name of the module. */ + name: string; + /** optional but recommended attributes. + always try to use these first before introducing additional attributes. + + Logical full path to the module. The exact definition is implementation defined, but usually this would be a full path to the on-disk file for the module. + */ + path?: string; + /** True if the module is optimized. */ + isOptimized?: boolean; + /** True if the module is considered 'user code' by a debugger that supports 'Just My Code'. */ + isUserCode?: boolean; + /** Version of Module. */ + version?: string; + /** User understandable description of if symbols were found for the module (ex: 'Symbols Loaded', 'Symbols not found', etc. */ + symbolStatus?: string; + /** Logical full path to the symbol file. The exact definition is implementation defined. */ + symbolFilePath?: string; + /** Module created or modified. */ + dateTimeStamp?: string; + /** Address range covered by this module. */ + addressRange?: string; + } + + /** A ColumnDescriptor specifies what module attribute to show in a column of the ModulesView, how to format it, and what the column's label should be. + It is only used if the underlying UI actually supports this level of customization. + */ + export interface ColumnDescriptor { + /** Name of the attribute rendered in this column. */ + attributeName: string; + /** Header UI label of column. */ + label: string; + /** Format to use for the rendered values in this column. TBD how the format strings looks like. */ + format?: string; + /** Datatype of values in this column. Defaults to 'string' if not specified. */ + type?: 'string' | 'number' | 'boolean' | 'unixTimestampUTC'; + /** Width of this column in characters (hint only). */ + width?: number; + } + + /** The ModulesViewDescriptor is the container for all declarative configuration options of a ModuleView. + For now it only specifies the columns to be shown in the modules view. + */ + export interface ModulesViewDescriptor { + columns: ColumnDescriptor[]; + } + + /** A Thread */ + export interface Thread { + /** Unique identifier for the thread. */ + id: number; + /** A name of the thread. */ + name: string; + } + + /** A Source is a descriptor for source code. It is returned from the debug adapter as part of a StackFrame and it is used by clients when specifying breakpoints. */ + export interface Source { + /** The short name of the source. Every source returned from the debug adapter has a name. When sending a source to the debug adapter this name is optional. */ + name?: string; + /** The path of the source to be shown in the UI. It is only used to locate and load the content of the source if no sourceReference is specified (or its value is 0). */ + path?: string; + /** If sourceReference > 0 the contents of the source must be retrieved through the SourceRequest (even if a path is specified). A sourceReference is only valid for a session, so it must not be used to persist a source. The value should be less than or equal to 2147483647 (2^31 - 1). */ + sourceReference?: number; + /** An optional hint for how to present the source in the UI. A value of 'deemphasize' can be used to indicate that the source is not available or that it is skipped on stepping. */ + presentationHint?: 'normal' | 'emphasize' | 'deemphasize'; + /** The (optional) origin of this source: possible values 'internal module', 'inlined content from source map', etc. */ + origin?: string; + /** An optional list of sources that are related to this source. These may be the source that generated this source. */ + sources?: Source[]; + /** Optional data that a debug adapter might want to loop through the client. The client should leave the data intact and persist it across sessions. The client should not interpret the data. */ + adapterData?: any; + /** The checksums associated with this file. */ + checksums?: Checksum[]; + } + + /** A Stackframe contains the source location. */ + export interface StackFrame { + /** An identifier for the stack frame. It must be unique across all threads. This id can be used to retrieve the scopes of the frame with the 'scopesRequest' or to restart the execution of a stackframe. */ + id: number; + /** The name of the stack frame, typically a method name. */ + name: string; + /** The optional source of the frame. */ + source?: Source; + /** The line within the file of the frame. If source is null or doesn't exist, line is 0 and must be ignored. */ + line: number; + /** The column within the line. If source is null or doesn't exist, column is 0 and must be ignored. */ + column: number; + /** An optional end line of the range covered by the stack frame. */ + endLine?: number; + /** An optional end column of the range covered by the stack frame. */ + endColumn?: number; + /** Optional memory reference for the current instruction pointer in this frame. */ + instructionPointerReference?: string; + /** The module associated with this frame, if any. */ + moduleId?: number | string; + /** An optional hint for how to present this frame in the UI. A value of 'label' can be used to indicate that the frame is an artificial frame that is used as a visual label or separator. A value of 'subtle' can be used to change the appearance of a frame in a 'subtle' way. */ + presentationHint?: 'normal' | 'label' | 'subtle'; + } + + /** A Scope is a named container for variables. Optionally a scope can map to a source or a range within a source. */ + export interface Scope { + /** Name of the scope such as 'Arguments', 'Locals', or 'Registers'. This string is shown in the UI as is and can be translated. */ + name: string; + /** An optional hint for how to present this scope in the UI. If this attribute is missing, the scope is shown with a generic UI. + Values: + 'arguments': Scope contains method arguments. + 'locals': Scope contains local variables. + 'registers': Scope contains registers. Only a single 'registers' scope should be returned from a 'scopes' request. + etc. + */ + presentationHint?: string; + /** The variables of this scope can be retrieved by passing the value of variablesReference to the VariablesRequest. */ + variablesReference: number; + /** The number of named variables in this scope. + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. + */ + namedVariables?: number; + /** The number of indexed variables in this scope. + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. + */ + indexedVariables?: number; + /** If true, the number of variables in this scope is large or expensive to retrieve. */ + expensive: boolean; + /** Optional source for this scope. */ + source?: Source; + /** Optional start line of the range covered by this scope. */ + line?: number; + /** Optional start column of the range covered by this scope. */ + column?: number; + /** Optional end line of the range covered by this scope. */ + endLine?: number; + /** Optional end column of the range covered by this scope. */ + endColumn?: number; + } + + /** A Variable is a name/value pair. + Optionally a variable can have a 'type' that is shown if space permits or when hovering over the variable's name. + An optional 'kind' is used to render additional properties of the variable, e.g. different icons can be used to indicate that a variable is public or private. + If the value is structured (has children), a handle is provided to retrieve the children with the VariablesRequest. + If the number of named or indexed children is large, the numbers should be returned via the optional 'namedVariables' and 'indexedVariables' attributes. + The client can use this optional information to present the children in a paged UI and fetch them in chunks. + */ + export interface Variable { + /** The variable's name. */ + name: string; + /** The variable's value. This can be a multi-line text, e.g. for a function the body of a function. */ + value: string; + /** The type of the variable's value. Typically shown in the UI when hovering over the value. */ + type?: string; + /** Properties of a variable that can be used to determine how to render the variable in the UI. */ + presentationHint?: VariablePresentationHint; + /** Optional evaluatable name of this variable which can be passed to the 'EvaluateRequest' to fetch the variable's value. */ + evaluateName?: string; + /** If variablesReference is > 0, the variable is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. */ + variablesReference: number; + /** The number of named child variables. + The client can use this optional information to present the children in a paged UI and fetch them in chunks. + */ + namedVariables?: number; + /** The number of indexed child variables. + The client can use this optional information to present the children in a paged UI and fetch them in chunks. + */ + indexedVariables?: number; + /** Optional memory reference for the variable if the variable represents executable code, such as a function pointer. */ + memoryReference?: string; + } + + /** Optional properties of a variable that can be used to determine how to render the variable in the UI. */ + export interface VariablePresentationHint { + /** The kind of variable. Before introducing additional values, try to use the listed values. + Values: + 'property': Indicates that the object is a property. + 'method': Indicates that the object is a method. + 'class': Indicates that the object is a class. + 'data': Indicates that the object is data. + 'event': Indicates that the object is an event. + 'baseClass': Indicates that the object is a base class. + 'innerClass': Indicates that the object is an inner class. + 'interface': Indicates that the object is an interface. + 'mostDerivedClass': Indicates that the object is the most derived class. + 'virtual': Indicates that the object is virtual, that means it is a synthetic object introduced by the adapter for rendering purposes, e.g. an index range for large arrays. + 'dataBreakpoint': Indicates that a data breakpoint is registered for the object. + etc. + */ + kind?: string; + /** Set of attributes represented as an array of strings. Before introducing additional values, try to use the listed values. + Values: + 'static': Indicates that the object is static. + 'constant': Indicates that the object is a constant. + 'readOnly': Indicates that the object is read only. + 'rawString': Indicates that the object is a raw string. + 'hasObjectId': Indicates that the object can have an Object ID created for it. + 'canHaveObjectId': Indicates that the object has an Object ID associated with it. + 'hasSideEffects': Indicates that the evaluation had side effects. + etc. + */ + attributes?: string[]; + /** Visibility of variable. Before introducing additional values, try to use the listed values. + Values: 'public', 'private', 'protected', 'internal', 'final', etc. + */ + visibility?: string; + } + + /** Properties of a breakpoint location returned from the 'breakpointLocations' request. */ + export interface BreakpointLocation { + /** Start line of breakpoint location. */ + line: number; + /** Optional start column of breakpoint location. */ + column?: number; + /** Optional end line of breakpoint location if the location covers a range. */ + endLine?: number; + /** Optional end column of breakpoint location if the location covers a range. */ + endColumn?: number; + } + + /** Properties of a breakpoint or logpoint passed to the setBreakpoints request. */ + export interface SourceBreakpoint { + /** The source line of the breakpoint or logpoint. */ + line: number; + /** An optional source column of the breakpoint. */ + column?: number; + /** An optional expression for conditional breakpoints. */ + condition?: string; + /** An optional expression that controls how many hits of the breakpoint are ignored. The backend is expected to interpret the expression as needed. */ + hitCondition?: string; + /** If this attribute exists and is non-empty, the backend must not 'break' (stop) but log the message instead. Expressions within {} are interpolated. */ + logMessage?: string; + } + + /** Properties of a breakpoint passed to the setFunctionBreakpoints request. */ + export interface FunctionBreakpoint { + /** The name of the function. */ + name: string; + /** An optional expression for conditional breakpoints. */ + condition?: string; + /** An optional expression that controls how many hits of the breakpoint are ignored. The backend is expected to interpret the expression as needed. */ + hitCondition?: string; + } + + /** This enumeration defines all possible access types for data breakpoints. */ + export type DataBreakpointAccessType = 'read' | 'write' | 'readWrite'; + + /** Properties of a data breakpoint passed to the setDataBreakpoints request. */ + export interface DataBreakpoint { + /** An id representing the data. This id is returned from the dataBreakpointInfo request. */ + dataId: string; + /** The access type of the data. */ + accessType?: DataBreakpointAccessType; + /** An optional expression for conditional breakpoints. */ + condition?: string; + /** An optional expression that controls how many hits of the breakpoint are ignored. The backend is expected to interpret the expression as needed. */ + hitCondition?: string; + } + + /** Information about a Breakpoint created in setBreakpoints or setFunctionBreakpoints. */ + export interface Breakpoint { + /** An optional identifier for the breakpoint. It is needed if breakpoint events are used to update or remove breakpoints. */ + id?: number; + /** If true breakpoint could be set (but not necessarily at the desired location). */ + verified: boolean; + /** An optional message about the state of the breakpoint. This is shown to the user and can be used to explain why a breakpoint could not be verified. */ + message?: string; + /** The source where the breakpoint is located. */ + source?: Source; + /** The start line of the actual range covered by the breakpoint. */ + line?: number; + /** An optional start column of the actual range covered by the breakpoint. */ + column?: number; + /** An optional end line of the actual range covered by the breakpoint. */ + endLine?: number; + /** An optional end column of the actual range covered by the breakpoint. If no end line is given, then the end column is assumed to be in the start line. */ + endColumn?: number; + } + + /** A StepInTarget can be used in the 'stepIn' request and determines into which single target the stepIn request should step. */ + export interface StepInTarget { + /** Unique identifier for a stepIn target. */ + id: number; + /** The name of the stepIn target (shown in the UI). */ + label: string; + } + + /** A GotoTarget describes a code location that can be used as a target in the 'goto' request. + The possible goto targets can be determined via the 'gotoTargets' request. + */ + export interface GotoTarget { + /** Unique identifier for a goto target. This is used in the goto request. */ + id: number; + /** The name of the goto target (shown in the UI). */ + label: string; + /** The line of the goto target. */ + line: number; + /** An optional column of the goto target. */ + column?: number; + /** An optional end line of the range covered by the goto target. */ + endLine?: number; + /** An optional end column of the range covered by the goto target. */ + endColumn?: number; + /** Optional memory reference for the instruction pointer value represented by this target. */ + instructionPointerReference?: string; + } + + /** CompletionItems are the suggestions returned from the CompletionsRequest. */ + export interface CompletionItem { + /** The label of this completion item. By default this is also the text that is inserted when selecting this completion. */ + label: string; + /** If text is not falsy then it is inserted instead of the label. */ + text?: string; + /** A string that should be used when comparing this item with other items. When `falsy` the label is used. */ + sortText?: string; + /** The item's type. Typically the client uses this information to render the item in the UI with an icon. */ + type?: CompletionItemType; + /** This value determines the location (in the CompletionsRequest's 'text' attribute) where the completion text is added. + If missing the text is added at the location specified by the CompletionsRequest's 'column' attribute. + */ + start?: number; + /** This value determines how many characters are overwritten by the completion text. + If missing the value 0 is assumed which results in the completion text being inserted. + */ + length?: number; + } + + /** Some predefined types for the CompletionItem. Please note that not all clients have specific icons for all of them. */ + export type CompletionItemType = 'method' | 'function' | 'constructor' | 'field' | 'variable' | 'class' | 'interface' | 'module' | 'property' | 'unit' | 'value' | 'enum' | 'keyword' | 'snippet' | 'text' | 'color' | 'file' | 'reference' | 'customcolor'; + + /** Names of checksum algorithms that may be supported by a debug adapter. */ + export type ChecksumAlgorithm = 'MD5' | 'SHA1' | 'SHA256' | 'timestamp'; + + /** The checksum of an item calculated by the specified algorithm. */ + export interface Checksum { + /** The algorithm used to calculate this checksum. */ + algorithm: ChecksumAlgorithm; + /** Value of the checksum. */ + checksum: string; + } + + /** Provides formatting information for a value. */ + export interface ValueFormat { + /** Display the value in hex. */ + hex?: boolean; + } + + /** Provides formatting information for a stack frame. */ + export interface StackFrameFormat extends ValueFormat { + /** Displays parameters for the stack frame. */ + parameters?: boolean; + /** Displays the types of parameters for the stack frame. */ + parameterTypes?: boolean; + /** Displays the names of parameters for the stack frame. */ + parameterNames?: boolean; + /** Displays the values of parameters for the stack frame. */ + parameterValues?: boolean; + /** Displays the line number of the stack frame. */ + line?: boolean; + /** Displays the module of the stack frame. */ + module?: boolean; + /** Includes all stack frames, including those the debug adapter might otherwise hide. */ + includeAll?: boolean; + } + + /** An ExceptionOptions assigns configuration options to a set of exceptions. */ + export interface ExceptionOptions { + /** A path that selects a single or multiple exceptions in a tree. If 'path' is missing, the whole tree is selected. By convention the first segment of the path is a category that is used to group exceptions in the UI. */ + path?: ExceptionPathSegment[]; + /** Condition when a thrown exception should result in a break. */ + breakMode: ExceptionBreakMode; + } + + /** This enumeration defines all possible conditions when a thrown exception should result in a break. + never: never breaks, + always: always breaks, + unhandled: breaks when exception unhandled, + userUnhandled: breaks if the exception is not handled by user code. + */ + export type ExceptionBreakMode = 'never' | 'always' | 'unhandled' | 'userUnhandled'; + + /** An ExceptionPathSegment represents a segment in a path that is used to match leafs or nodes in a tree of exceptions. If a segment consists of more than one name, it matches the names provided if 'negate' is false or missing or it matches anything except the names provided if 'negate' is true. */ + export interface ExceptionPathSegment { + /** If false or missing this segment matches the names provided, otherwise it matches anything except the names provided. */ + negate?: boolean; + /** Depending on the value of 'negate' the names that should match or not match. */ + names: string[]; + } + + /** Detailed information about an exception that has occurred. */ + export interface ExceptionDetails { + /** Message contained in the exception. */ + message?: string; + /** Short type name of the exception object. */ + typeName?: string; + /** Fully-qualified type name of the exception object. */ + fullTypeName?: string; + /** Optional expression that can be evaluated in the current scope to obtain the exception object. */ + evaluateName?: string; + /** Stack trace at the time the exception was thrown. */ + stackTrace?: string; + /** Details of the exception contained by this exception, if any. */ + innerException?: ExceptionDetails[]; + } + + /** Represents a single disassembled instruction. */ + export interface DisassembledInstruction { + /** The address of the instruction. Treated as a hex value if prefixed with '0x', or as a decimal value otherwise. */ + address: string; + /** Optional raw bytes representing the instruction and its operands, in an implementation-defined format. */ + instructionBytes?: string; + /** Text representing the instruction and its operands, in an implementation-defined format. */ + instruction: string; + /** Name of the symbol that corresponds with the location of this instruction, if any. */ + symbol?: string; + /** Source location that corresponds to this instruction, if any. Should always be set (if available) on the first instruction returned, but can be omitted afterwards if this instruction maps to the same source file as the previous instruction. */ + location?: Source; + /** The line within the source location that corresponds to this instruction, if any. */ + line?: number; + /** The column within the line that corresponds to this instruction, if any. */ + column?: number; + /** The end line of the range that corresponds to this instruction, if any. */ + endLine?: number; + /** The end column of the range that corresponds to this instruction, if any. */ + endColumn?: number; + } +} + +//------------------------------------------------------------------------------------------------------------------------------ + +export class Message implements DebugProtocol.ProtocolMessage { + seq: number; + type: string; + + public constructor(type: string) { + this.seq = 0; + this.type = type; + } +} + +export class Response extends Message implements DebugProtocol.Response { + request_seq: number; + success: boolean; + command: string; + + public constructor(request: DebugProtocol.Request, message?: string) { + super('response'); + this.request_seq = request.seq; + this.command = request.command; + if (message) { + this.success = false; + (this).message = message; + } else { + this.success = true; + } + } +} + +export class Event extends Message implements DebugProtocol.Event { + event: string; + + public constructor(event: string, body?: any) { + super('event'); + this.event = event; + if (body) { + (this).body = body; + } + } +} + +//-------------------------------------------------------------------------------------------------------------------------------- + +export interface IDaTransport { + start(cb: (msg: DebugProtocol.ProtocolMessage) => void, errorcb: (event: DebugProtocol.Event) => void): void; + send(message: DebugProtocol.ProtocolMessage): void; + stop(): void; +} + +export class ProtocolServer { + + private close = new vscode.EventEmitter(); + onClose: vscode.Event = this.close.event; + + private error = new vscode.EventEmitter(); + onError: vscode.Event = this.error.event; + + + private _sequence: number = 1; + private _pendingRequests = new Map void>(); + + private _transport?: IDaTransport; + + constructor() { + } + + public __setTransport(transport: IDaTransport): void { + this._sequence = 1; + this._transport = transport; + this._transport.start(msg => { this.dispatch(msg); }, event => { this.error.fire(event); }); + } + + public stop(): void { + if (this._transport) { + this._transport.stop(); + } + } + + public sendEvent(event: DebugProtocol.Event): void { + this._send('event', event); + } + + public sendResponse(response: DebugProtocol.Response): void { + if (response.seq > 0) { + console.error(`attempt to send more than one response for command ${response.command}`); + } else { + this._send('response', response); + } + } + + public sendRequest(command: string, args: any, timeout: number, cb: (response: DebugProtocol.Response) => void): void { + + const request: any = { + command: command + }; + if (args && Object.keys(args).length > 0) { + request.arguments = args; + } + + if (!this._transport) { + this.error.fire(new Event('error', 'sendRequest: no transport')); + return; + } + + this._send('request', request); + + if (cb) { + this._pendingRequests.set(request.seq, cb); + + const timer = setTimeout(() => { + clearTimeout(timer); + const clb = this._pendingRequests.get(request.seq); + if (clb) { + this._pendingRequests.delete(request.seq); + clb(new Response(request, 'timeout')); + } + }, timeout); + } + } + + // ---- protected ---------------------------------------------------------- + + protected dispatchRequest(_request: DebugProtocol.Request): void { + } + + // ---- private ------------------------------------------------------------ + + private dispatch(msg: DebugProtocol.ProtocolMessage) { + if (msg.type === 'request') { + this.dispatchRequest(msg); + } else if (msg.type === 'response') { + const response = msg; + const clb = this._pendingRequests.get(response.request_seq); + if (clb) { + this._pendingRequests.delete(response.request_seq); + clb(response); + } + } + } + + private _send(typ: 'request' | 'response' | 'event', message: DebugProtocol.ProtocolMessage): void { + + message.type = typ; + message.seq = this._sequence++; + + if (this._transport) { + this._transport.send(message); + } + } +} + +//------------------------------------------------------------------------------------------------------------------------------- + +export class Source implements DebugProtocol.Source { + name: string; + path?: string; + sourceReference: number; + + public constructor(name: string, path?: string, id: number = 0, origin?: string, data?: any) { + this.name = name; + this.path = path; + this.sourceReference = id; + if (origin) { + (this).origin = origin; + } + if (data) { + (this).adapterData = data; + } + } +} + +export class Scope implements DebugProtocol.Scope { + name: string; + variablesReference: number; + expensive: boolean; + + public constructor(name: string, reference: number, expensive: boolean = false) { + this.name = name; + this.variablesReference = reference; + this.expensive = expensive; + } +} + +export class StackFrame implements DebugProtocol.StackFrame { + id: number; + source?: Source; + line: number; + column: number; + name: string; + + public constructor(i: number, nm: string, src?: Source, ln: number = 0, col: number = 0) { + this.id = i; + this.source = src; + this.line = ln; + this.column = col; + this.name = nm; + } +} + +export class Thread implements DebugProtocol.Thread { + id: number; + name: string; + + public constructor(id: number, name: string) { + this.id = id; + if (name) { + this.name = name; + } else { + this.name = 'Thread #' + id; + } + } +} + +export class Variable implements DebugProtocol.Variable { + name: string; + value: string; + variablesReference: number; + + public constructor(name: string, value: string, ref: number = 0, indexedVariables?: number, namedVariables?: number) { + this.name = name; + this.value = value; + this.variablesReference = ref; + if (typeof namedVariables === 'number') { + (this).namedVariables = namedVariables; + } + if (typeof indexedVariables === 'number') { + (this).indexedVariables = indexedVariables; + } + } +} + +export class Breakpoint implements DebugProtocol.Breakpoint { + verified: boolean; + + public constructor(verified: boolean, line?: number, column?: number, source?: Source) { + this.verified = verified; + const e: DebugProtocol.Breakpoint = this; + if (typeof line === 'number') { + e.line = line; + } + if (typeof column === 'number') { + e.column = column; + } + if (source) { + e.source = source; + } + } +} + +export class Module implements DebugProtocol.Module { + id: number | string; + name: string; + + public constructor(id: number | string, name: string) { + this.id = id; + this.name = name; + } +} + +export class CompletionItem implements DebugProtocol.CompletionItem { + label: string; + start: number; + length: number; + + public constructor(label: string, start: number, length: number = 0) { + this.label = label; + this.start = start; + this.length = length; + } +} + +export class StoppedEvent extends Event implements DebugProtocol.StoppedEvent { + body: { + reason: string; + }; + + public constructor(reason: string, threadId?: number, exceptionText?: string) { + super('stopped'); + this.body = { + reason: reason + }; + if (typeof threadId === 'number') { + (this as DebugProtocol.StoppedEvent).body.threadId = threadId; + } + if (typeof exceptionText === 'string') { + (this as DebugProtocol.StoppedEvent).body.text = exceptionText; + } + } +} + +export class ContinuedEvent extends Event implements DebugProtocol.ContinuedEvent { + body: { + threadId: number; + }; + + public constructor(threadId: number, allThreadsContinued?: boolean) { + super('continued'); + this.body = { + threadId: threadId + }; + + if (typeof allThreadsContinued === 'boolean') { + (this).body.allThreadsContinued = allThreadsContinued; + } + } +} + +export class InitializedEvent extends Event implements DebugProtocol.InitializedEvent { + public constructor() { + super('initialized'); + } +} + +export class TerminatedEvent extends Event implements DebugProtocol.TerminatedEvent { + public constructor(restart?: any) { + super('terminated'); + if (typeof restart === 'boolean' || restart) { + const e: DebugProtocol.TerminatedEvent = this; + e.body = { + restart: restart + }; + } + } +} + +export class OutputEvent extends Event implements DebugProtocol.OutputEvent { + body: { + category: string, + output: string, + data?: any + }; + + public constructor(output: string, category: string = 'console', data?: any) { + super('output'); + this.body = { + category: category, + output: output + }; + if (data !== undefined) { + this.body.data = data; + } + } +} + +export class ThreadEvent extends Event implements DebugProtocol.ThreadEvent { + body: { + reason: string, + threadId: number + }; + + public constructor(reason: string, threadId: number) { + super('thread'); + this.body = { + reason: reason, + threadId: threadId + }; + } +} + +export class BreakpointEvent extends Event implements DebugProtocol.BreakpointEvent { + body: { + reason: string, + breakpoint: Breakpoint + }; + + public constructor(reason: string, breakpoint: Breakpoint) { + super('breakpoint'); + this.body = { + reason: reason, + breakpoint: breakpoint + }; + } +} + +export class ModuleEvent extends Event implements DebugProtocol.ModuleEvent { + body: { + reason: 'new' | 'changed' | 'removed', + module: Module + }; + + public constructor(reason: 'new' | 'changed' | 'removed', module: Module) { + super('module'); + this.body = { + reason: reason, + module: module + }; + } +} + +export class LoadedSourceEvent extends Event implements DebugProtocol.LoadedSourceEvent { + body: { + reason: 'new' | 'changed' | 'removed', + source: Source + }; + + public constructor(reason: 'new' | 'changed' | 'removed', source: Source) { + super('loadedSource'); + this.body = { + reason: reason, + source: source + }; + } +} + +export class CapabilitiesEvent extends Event implements DebugProtocol.CapabilitiesEvent { + body: { + capabilities: DebugProtocol.Capabilities + }; + + public constructor(capabilities: DebugProtocol.Capabilities) { + super('capabilities'); + this.body = { + capabilities: capabilities + }; + } +} + +export enum ErrorDestination { + User = 1, + Telemetry = 2 +} + +export class DebugSession extends ProtocolServer { + + private _debuggerLinesStartAt1: boolean; + private _debuggerColumnsStartAt1: boolean; + private _debuggerPathsAreURIs: boolean; + + private _clientLinesStartAt1: boolean; + private _clientColumnsStartAt1: boolean; + private _clientPathsAreURIs: boolean; + + protected _isServer: boolean; + + public constructor(obsolete_debuggerLinesAndColumnsStartAt1?: boolean, obsolete_isServer?: boolean) { + super(); + + const linesAndColumnsStartAt1 = typeof obsolete_debuggerLinesAndColumnsStartAt1 === 'boolean' ? obsolete_debuggerLinesAndColumnsStartAt1 : false; + this._debuggerLinesStartAt1 = linesAndColumnsStartAt1; + this._debuggerColumnsStartAt1 = linesAndColumnsStartAt1; + this._debuggerPathsAreURIs = false; + + this._clientLinesStartAt1 = true; + this._clientColumnsStartAt1 = true; + this._clientPathsAreURIs = false; + + this._isServer = typeof obsolete_isServer === 'boolean' ? obsolete_isServer : false; + + this.onClose(() => { + this.shutdown(); + }); + this.onError((_error) => { + this.shutdown(); + }); + } + + public setDebuggerPathFormat(format: string) { + this._debuggerPathsAreURIs = format !== 'path'; + } + + public setDebuggerLinesStartAt1(enable: boolean) { + this._debuggerLinesStartAt1 = enable; + } + + public setDebuggerColumnsStartAt1(enable: boolean) { + this._debuggerColumnsStartAt1 = enable; + } + + public setRunAsServer(enable: boolean) { + this._isServer = enable; + } + + public shutdown(): void { + if (this._isServer) { + // shutdown ignored in server mode + } else { + // TODO@AW + /* + // wait a bit before shutting down + setTimeout(() => { + process.exit(0); + }, 100); + */ + } + } + + protected sendErrorResponse(response: DebugProtocol.Response, codeOrMessage: number | DebugProtocol.Message, format?: string, variables?: any, dest: ErrorDestination = ErrorDestination.User): void { + + let msg: DebugProtocol.Message; + if (typeof codeOrMessage === 'number') { + msg = { + id: codeOrMessage, + format: format + }; + if (variables) { + msg.variables = variables; + } + if (dest & ErrorDestination.User) { + msg.showUser = true; + } + if (dest & ErrorDestination.Telemetry) { + msg.sendTelemetry = true; + } + } else { + msg = codeOrMessage; + } + + response.success = false; + response.message = DebugSession.formatPII(msg.format, true, msg.variables); + if (!response.body) { + response.body = {}; + } + response.body.error = msg; + + this.sendResponse(response); + } + + public runInTerminalRequest(args: DebugProtocol.RunInTerminalRequestArguments, timeout: number, cb: (response: DebugProtocol.Response) => void) { + this.sendRequest('runInTerminal', args, timeout, cb); + } + + protected dispatchRequest(request: DebugProtocol.Request): void { + + const response = new Response(request); + + try { + if (request.command === 'initialize') { + const args = request.arguments; + + if (typeof args.linesStartAt1 === 'boolean') { + this._clientLinesStartAt1 = args.linesStartAt1; + } + if (typeof args.columnsStartAt1 === 'boolean') { + this._clientColumnsStartAt1 = args.columnsStartAt1; + } + + if (args.pathFormat !== 'path') { + this.sendErrorResponse(response, 2018, 'debug adapter only supports native paths', null, ErrorDestination.Telemetry); + } else { + const initializeResponse = response; + initializeResponse.body = {}; + this.initializeRequest(initializeResponse, args); + } + + } else if (request.command === 'launch') { + this.launchRequest(response, request.arguments, request); + + } else if (request.command === 'attach') { + this.attachRequest(response, request.arguments, request); + + } else if (request.command === 'disconnect') { + this.disconnectRequest(response, request.arguments, request); + + } else if (request.command === 'terminate') { + this.terminateRequest(response, request.arguments, request); + + } else if (request.command === 'restart') { + this.restartRequest(response, request.arguments, request); + + } else if (request.command === 'setBreakpoints') { + this.setBreakPointsRequest(response, request.arguments, request); + + } else if (request.command === 'setFunctionBreakpoints') { + this.setFunctionBreakPointsRequest(response, request.arguments, request); + + } else if (request.command === 'setExceptionBreakpoints') { + this.setExceptionBreakPointsRequest(response, request.arguments, request); + + } else if (request.command === 'configurationDone') { + this.configurationDoneRequest(response, request.arguments, request); + + } else if (request.command === 'continue') { + this.continueRequest(response, request.arguments, request); + + } else if (request.command === 'next') { + this.nextRequest(response, request.arguments, request); + + } else if (request.command === 'stepIn') { + this.stepInRequest(response, request.arguments, request); + + } else if (request.command === 'stepOut') { + this.stepOutRequest(response, request.arguments, request); + + } else if (request.command === 'stepBack') { + this.stepBackRequest(response, request.arguments, request); + + } else if (request.command === 'reverseContinue') { + this.reverseContinueRequest(response, request.arguments, request); + + } else if (request.command === 'restartFrame') { + this.restartFrameRequest(response, request.arguments, request); + + } else if (request.command === 'goto') { + this.gotoRequest(response, request.arguments, request); + + } else if (request.command === 'pause') { + this.pauseRequest(response, request.arguments, request); + + } else if (request.command === 'stackTrace') { + this.stackTraceRequest(response, request.arguments, request); + + } else if (request.command === 'scopes') { + this.scopesRequest(response, request.arguments, request); + + } else if (request.command === 'variables') { + this.variablesRequest(response, request.arguments, request); + + } else if (request.command === 'setVariable') { + this.setVariableRequest(response, request.arguments, request); + + } else if (request.command === 'setExpression') { + this.setExpressionRequest(response, request.arguments, request); + + } else if (request.command === 'source') { + this.sourceRequest(response, request.arguments, request); + + } else if (request.command === 'threads') { + this.threadsRequest(response, request); + + } else if (request.command === 'terminateThreads') { + this.terminateThreadsRequest(response, request.arguments, request); + + } else if (request.command === 'evaluate') { + this.evaluateRequest(response, request.arguments, request); + + } else if (request.command === 'stepInTargets') { + this.stepInTargetsRequest(response, request.arguments, request); + + } else if (request.command === 'gotoTargets') { + this.gotoTargetsRequest(response, request.arguments, request); + + } else if (request.command === 'completions') { + this.completionsRequest(response, request.arguments, request); + + } else if (request.command === 'exceptionInfo') { + this.exceptionInfoRequest(response, request.arguments, request); + + } else if (request.command === 'loadedSources') { + this.loadedSourcesRequest(response, request.arguments, request); + + } else if (request.command === 'dataBreakpointInfo') { + this.dataBreakpointInfoRequest(response, request.arguments, request); + + } else if (request.command === 'setDataBreakpoints') { + this.setDataBreakpointsRequest(response, request.arguments, request); + + } else if (request.command === 'readMemory') { + this.readMemoryRequest(response, request.arguments, request); + + } else if (request.command === 'disassemble') { + this.disassembleRequest(response, request.arguments, request); + + } else if (request.command === 'cancel') { + this.cancelRequest(response, request.arguments, request); + + } else if (request.command === 'breakpointLocations') { + this.breakpointLocationsRequest(response, request.arguments, request); + + } else { + this.customRequest(request.command, response, request.arguments, request); + } + } catch (e) { + this.sendErrorResponse(response, 1104, '{_stack}', { _exception: e.message, _stack: e.stack }, ErrorDestination.Telemetry); + } + } + + protected initializeRequest(response: DebugProtocol.InitializeResponse, _args: DebugProtocol.InitializeRequestArguments): void { + + response.body = response.body || {}; + + // This default debug adapter does not support conditional breakpoints. + response.body.supportsConditionalBreakpoints = false; + + // This default debug adapter does not support hit conditional breakpoints. + response.body.supportsHitConditionalBreakpoints = false; + + // This default debug adapter does not support function breakpoints. + response.body.supportsFunctionBreakpoints = false; + + // This default debug adapter implements the 'configurationDone' request. + response.body.supportsConfigurationDoneRequest = true; + + // This default debug adapter does not support hovers based on the 'evaluate' request. + response.body.supportsEvaluateForHovers = false; + + // This default debug adapter does not support the 'stepBack' request. + response.body.supportsStepBack = false; + + // This default debug adapter does not support the 'setVariable' request. + response.body.supportsSetVariable = false; + + // This default debug adapter does not support the 'restartFrame' request. + response.body.supportsRestartFrame = false; + + // This default debug adapter does not support the 'stepInTargets' request. + response.body.supportsStepInTargetsRequest = false; + + // This default debug adapter does not support the 'gotoTargets' request. + response.body.supportsGotoTargetsRequest = false; + + // This default debug adapter does not support the 'completions' request. + response.body.supportsCompletionsRequest = false; + + // This default debug adapter does not support the 'restart' request. + response.body.supportsRestartRequest = false; + + // This default debug adapter does not support the 'exceptionOptions' attribute on the 'setExceptionBreakpoints' request. + response.body.supportsExceptionOptions = false; + + // This default debug adapter does not support the 'format' attribute on the 'variables', 'evaluate', and 'stackTrace' request. + response.body.supportsValueFormattingOptions = false; + + // This debug adapter does not support the 'exceptionInfo' request. + response.body.supportsExceptionInfoRequest = false; + + // This debug adapter does not support the 'TerminateDebuggee' attribute on the 'disconnect' request. + response.body.supportTerminateDebuggee = false; + + // This debug adapter does not support delayed loading of stack frames. + response.body.supportsDelayedStackTraceLoading = false; + + // This debug adapter does not support the 'loadedSources' request. + response.body.supportsLoadedSourcesRequest = false; + + // This debug adapter does not support the 'logMessage' attribute of the SourceBreakpoint. + response.body.supportsLogPoints = false; + + // This debug adapter does not support the 'terminateThreads' request. + response.body.supportsTerminateThreadsRequest = false; + + // This debug adapter does not support the 'setExpression' request. + response.body.supportsSetExpression = false; + + // This debug adapter does not support the 'terminate' request. + response.body.supportsTerminateRequest = false; + + // This debug adapter does not support data breakpoints. + response.body.supportsDataBreakpoints = false; + + /** This debug adapter does not support the 'readMemory' request. */ + response.body.supportsReadMemoryRequest = false; + + /** The debug adapter does not support the 'disassemble' request. */ + response.body.supportsDisassembleRequest = false; + + /** The debug adapter does not support the 'cancel' request. */ + response.body.supportsCancelRequest = false; + + /** The debug adapter does not support the 'breakpointLocations' request. */ + response.body.supportsBreakpointLocationsRequest = false; + + this.sendResponse(response); + } + + protected disconnectRequest(response: DebugProtocol.DisconnectResponse, _args: DebugProtocol.DisconnectArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + this.shutdown(); + } + + protected launchRequest(response: DebugProtocol.LaunchResponse, _args: DebugProtocol.LaunchRequestArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected attachRequest(response: DebugProtocol.AttachResponse, _args: DebugProtocol.AttachRequestArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected terminateRequest(response: DebugProtocol.TerminateResponse, _args: DebugProtocol.TerminateArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected restartRequest(response: DebugProtocol.RestartResponse, _args: DebugProtocol.RestartArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, _args: DebugProtocol.SetBreakpointsArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected setFunctionBreakPointsRequest(response: DebugProtocol.SetFunctionBreakpointsResponse, _args: DebugProtocol.SetFunctionBreakpointsArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected setExceptionBreakPointsRequest(response: DebugProtocol.SetExceptionBreakpointsResponse, _args: DebugProtocol.SetExceptionBreakpointsArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse, _args: DebugProtocol.ConfigurationDoneArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected continueRequest(response: DebugProtocol.ContinueResponse, _args: DebugProtocol.ContinueArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected nextRequest(response: DebugProtocol.NextResponse, _args: DebugProtocol.NextArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected stepInRequest(response: DebugProtocol.StepInResponse, _args: DebugProtocol.StepInArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected stepOutRequest(response: DebugProtocol.StepOutResponse, _args: DebugProtocol.StepOutArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected stepBackRequest(response: DebugProtocol.StepBackResponse, _args: DebugProtocol.StepBackArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, _args: DebugProtocol.ReverseContinueArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected restartFrameRequest(response: DebugProtocol.RestartFrameResponse, _args: DebugProtocol.RestartFrameArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected gotoRequest(response: DebugProtocol.GotoResponse, _args: DebugProtocol.GotoArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected pauseRequest(response: DebugProtocol.PauseResponse, _args: DebugProtocol.PauseArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected sourceRequest(response: DebugProtocol.SourceResponse, _args: DebugProtocol.SourceArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected threadsRequest(response: DebugProtocol.ThreadsResponse, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected terminateThreadsRequest(response: DebugProtocol.TerminateThreadsResponse, _args: DebugProtocol.TerminateThreadsArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, _args: DebugProtocol.StackTraceArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected scopesRequest(response: DebugProtocol.ScopesResponse, _args: DebugProtocol.ScopesArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected variablesRequest(response: DebugProtocol.VariablesResponse, _args: DebugProtocol.VariablesArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected setVariableRequest(response: DebugProtocol.SetVariableResponse, _args: DebugProtocol.SetVariableArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected setExpressionRequest(response: DebugProtocol.SetExpressionResponse, _args: DebugProtocol.SetExpressionArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected evaluateRequest(response: DebugProtocol.EvaluateResponse, _args: DebugProtocol.EvaluateArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected stepInTargetsRequest(response: DebugProtocol.StepInTargetsResponse, _args: DebugProtocol.StepInTargetsArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected gotoTargetsRequest(response: DebugProtocol.GotoTargetsResponse, _args: DebugProtocol.GotoTargetsArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected completionsRequest(response: DebugProtocol.CompletionsResponse, _args: DebugProtocol.CompletionsArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected exceptionInfoRequest(response: DebugProtocol.ExceptionInfoResponse, _args: DebugProtocol.ExceptionInfoArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected loadedSourcesRequest(response: DebugProtocol.LoadedSourcesResponse, _args: DebugProtocol.LoadedSourcesArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected dataBreakpointInfoRequest(response: DebugProtocol.DataBreakpointInfoResponse, _args: DebugProtocol.DataBreakpointInfoArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected setDataBreakpointsRequest(response: DebugProtocol.SetDataBreakpointsResponse, _args: DebugProtocol.SetDataBreakpointsArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected readMemoryRequest(response: DebugProtocol.ReadMemoryResponse, _args: DebugProtocol.ReadMemoryArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected disassembleRequest(response: DebugProtocol.DisassembleResponse, _args: DebugProtocol.DisassembleArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected cancelRequest(response: DebugProtocol.CancelResponse, _args: DebugProtocol.CancelArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected breakpointLocationsRequest(response: DebugProtocol.BreakpointLocationsResponse, _args: DebugProtocol.BreakpointLocationsArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + /** + * Override this hook to implement custom requests. + */ + protected customRequest(_command: string, response: DebugProtocol.Response, _args: any, _request?: DebugProtocol.Request): void { + this.sendErrorResponse(response, 1014, 'unrecognized request', null, ErrorDestination.Telemetry); + } + + //---- protected ------------------------------------------------------------------------------------------------- + + protected convertClientLineToDebugger(line: number): number { + if (this._debuggerLinesStartAt1) { + return this._clientLinesStartAt1 ? line : line + 1; + } + return this._clientLinesStartAt1 ? line - 1 : line; + } + + protected convertDebuggerLineToClient(line: number): number { + if (this._debuggerLinesStartAt1) { + return this._clientLinesStartAt1 ? line : line - 1; + } + return this._clientLinesStartAt1 ? line + 1 : line; + } + + protected convertClientColumnToDebugger(column: number): number { + if (this._debuggerColumnsStartAt1) { + return this._clientColumnsStartAt1 ? column : column + 1; + } + return this._clientColumnsStartAt1 ? column - 1 : column; + } + + protected convertDebuggerColumnToClient(column: number): number { + if (this._debuggerColumnsStartAt1) { + return this._clientColumnsStartAt1 ? column : column - 1; + } + return this._clientColumnsStartAt1 ? column + 1 : column; + } + + protected convertClientPathToDebugger(clientPath: string): string { + if (this._clientPathsAreURIs !== this._debuggerPathsAreURIs) { + if (this._clientPathsAreURIs) { + return DebugSession.uri2path(clientPath); + } else { + return DebugSession.path2uri(clientPath); + } + } + return clientPath; + } + + protected convertDebuggerPathToClient(debuggerPath: string): string { + if (this._debuggerPathsAreURIs !== this._clientPathsAreURIs) { + if (this._debuggerPathsAreURIs) { + return DebugSession.uri2path(debuggerPath); + } else { + return DebugSession.path2uri(debuggerPath); + } + } + return debuggerPath; + } + + //---- private ------------------------------------------------------------------------------- + + private static path2uri(path: string): string { + + path = encodeURI(path); + + let uri = new URL(`file:`); // ignore 'path' for now + uri.pathname = path; // now use 'path' to get the correct percent encoding (see https://url.spec.whatwg.org) + return uri.toString(); + } + + private static uri2path(sourceUri: string): string { + + let uri = new URL(sourceUri); + let s = decodeURIComponent(uri.pathname); + return s; + } + + private static _formatPIIRegexp = /{([^}]+)}/g; + + /* + * If argument starts with '_' it is OK to send its value to telemetry. + */ + private static formatPII(format: string, excludePII: boolean, args?: { [key: string]: string }): string { + return format.replace(DebugSession._formatPIIRegexp, function (match, paramName) { + if (excludePII && paramName.length > 0 && paramName[0] !== '_') { + return match; + } + return args && args[paramName] && args.hasOwnProperty(paramName) ? + args[paramName] : + match; + }); + } +} + +//--------------------------------------------------------------------------- + +export class Handles { + + private START_HANDLE = 1000; + + private _nextHandle: number; + private _handleMap = new Map(); + + public constructor(startHandle?: number) { + this._nextHandle = typeof startHandle === 'number' ? startHandle : this.START_HANDLE; + } + + public reset(): void { + this._nextHandle = this.START_HANDLE; + this._handleMap = new Map(); + } + + public create(value: T): number { + const handle = this._nextHandle++; + this._handleMap.set(handle, value); + return handle; + } + + public get(handle: number, dflt?: T): T | undefined { + return this._handleMap.get(handle) || dflt; + } +} + +//--------------------------------------------------------------------------- + +class MockConfigurationProvider implements vscode.DebugConfigurationProvider { + + /** + * Massage a debug configuration just before a debug session is being launched, + * e.g. add all missing attributes to the debug configuration. + */ + resolveDebugConfiguration(_folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration, _token?: vscode.CancellationToken): vscode.ProviderResult { + + // if launch.json is missing or empty + if (!config.type && !config.request && !config.name) { + const editor = vscode.window.activeTextEditor; + if (editor && editor.document.languageId === 'markdown') { + config.type = 'mock'; + config.name = 'Launch'; + config.request = 'launch'; + config.program = '${file}'; + config.stopOnEntry = true; + } + } + + if (!config.program) { + return vscode.window.showInformationMessage('Cannot find a program to debug').then(_ => { + return undefined; // abort launch + }); + } + + return config; + } +} + +export class MockDebugAdapterDescriptorFactory implements vscode.DebugAdapterDescriptorFactory { + + constructor(private memfs: MemFS) { + } + + createDebugAdapterDescriptor(_session: vscode.DebugSession, _executable: vscode.DebugAdapterExecutable | undefined): vscode.ProviderResult { + // TODO@AW: we need some extension API for connecting a DA implementation with a vscode.DebugAdapterDescriptor + const descriptor = new vscode.DebugAdapterExecutable('dummy'); + const a = descriptor; + a['implementation'] = new MockDebugSession(this.memfs); + return descriptor; + } +} + +function basename(path: string): string { + const pos = path.lastIndexOf('/'); + if (pos >= 0) { + return path.substring(pos + 1); + } + return path; +} + +function timeout(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +/** + * This interface describes the mock-debug specific launch attributes + * (which are not part of the Debug Adapter Protocol). + * The schema for these attributes lives in the package.json of the mock-debug extension. + * The interface should always match this schema. + */ +interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments { + /** An absolute path to the "program" to debug. */ + program: string; + /** Automatically stop target after launch. If not specified, target does not stop. */ + stopOnEntry?: boolean; + /** enable logging the Debug Adapter Protocol */ + trace?: boolean; +} + +export class MockDebugSession extends DebugSession { + + // we don't support multiple threads, so we can use a hardcoded ID for the default thread + private static THREAD_ID = 1; + + // a Mock runtime (or debugger) + private _runtime: MockRuntime; + + private _variableHandles = new Handles(); + + //private _configurationDone = new Subject(); + + private promiseResolve?: () => void; + private _configurationDone = new Promise((r, _e) => { + this.promiseResolve = r; + setTimeout(r, 1000); + }); + + private _cancelationTokens = new Map(); + private _isLongrunning = new Map(); + + /** + * Creates a new debug adapter that is used for one debug session. + * We configure the default implementation of a debug adapter here. + */ + public constructor(memfs: MemFS) { + + super(); + + // this debugger uses zero-based lines and columns + this.setDebuggerLinesStartAt1(false); + this.setDebuggerColumnsStartAt1(false); + + this._runtime = new MockRuntime(memfs); + + // setup event handlers + this._runtime.onStopOnEntry(() => { + this.sendEvent(new StoppedEvent('entry', MockDebugSession.THREAD_ID)); + }); + this._runtime.onStopOnStep(() => { + this.sendEvent(new StoppedEvent('step', MockDebugSession.THREAD_ID)); + }); + this._runtime.onStopOnBreakpoint(() => { + this.sendEvent(new StoppedEvent('breakpoint', MockDebugSession.THREAD_ID)); + }); + this._runtime.onStopOnDataBreakpoint(() => { + this.sendEvent(new StoppedEvent('data breakpoint', MockDebugSession.THREAD_ID)); + }); + this._runtime.onStopOnException(() => { + this.sendEvent(new StoppedEvent('exception', MockDebugSession.THREAD_ID)); + }); + this._runtime.onBreakpointValidated((bp: MockBreakpoint) => { + this.sendEvent(new BreakpointEvent('changed', { verified: bp.verified, id: bp.id })); + }); + this._runtime.onOutput(oe => { + const e: DebugProtocol.OutputEvent = new OutputEvent(`${oe.text}\n`); + e.body.source = this.createSource(oe.filePath); + e.body.line = this.convertDebuggerLineToClient(oe.line); + e.body.column = this.convertDebuggerColumnToClient(oe.column); + this.sendEvent(e); + }); + this._runtime.onEnd(() => { + this.sendEvent(new TerminatedEvent()); + }); + } + + /** + * The 'initialize' request is the first request called by the frontend + * to interrogate the features the debug adapter provides. + */ + protected initializeRequest(response: DebugProtocol.InitializeResponse, _args: DebugProtocol.InitializeRequestArguments): void { + + // build and return the capabilities of this debug adapter: + response.body = response.body || {}; + + // the adapter implements the configurationDoneRequest. + response.body.supportsConfigurationDoneRequest = true; + + // make VS Code to use 'evaluate' when hovering over source + response.body.supportsEvaluateForHovers = true; + + // make VS Code to show a 'step back' button + response.body.supportsStepBack = true; + + // make VS Code to support data breakpoints + response.body.supportsDataBreakpoints = true; + + // make VS Code to support completion in REPL + response.body.supportsCompletionsRequest = true; + response.body.completionTriggerCharacters = ['.', '[']; + + // make VS Code to send cancelRequests + response.body.supportsCancelRequest = true; + + // make VS Code send the breakpointLocations request + response.body.supportsBreakpointLocationsRequest = true; + + this.sendResponse(response); + + // since this debug adapter can accept configuration requests like 'setBreakpoint' at any time, + // we request them early by sending an 'initializeRequest' to the frontend. + // The frontend will end the configuration sequence by calling 'configurationDone' request. + this.sendEvent(new InitializedEvent()); + } + + /** + * Called at the end of the configuration sequence. + * Indicates that all breakpoints etc. have been sent to the DA and that the 'launch' can start. + */ + protected configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse, args: DebugProtocol.ConfigurationDoneArguments): void { + super.configurationDoneRequest(response, args); + + // notify the launchRequest that configuration has finished + //this._configurationDone.notify(); + if (this.promiseResolve) { + this.promiseResolve(); + } + } + + protected async launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments) { + + // make sure to 'Stop' the buffered logging if 'trace' is not set + //logger.setup(args.trace ? Logger.LogLevel.Verbose : Logger.LogLevel.Stop, false); + + // wait until configuration has finished (and configurationDoneRequest has been called) + await this._configurationDone; + + // start the program in the runtime + this._runtime.start(`memfs:${args.program}`, !!args.stopOnEntry); + + this.sendResponse(response); + } + + protected setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void { + + const path = args.source.path; + const clientLines = args.lines || []; + + // clear all breakpoints for this file + this._runtime.clearBreakpoints(path); + + // set and verify breakpoint locations + const actualBreakpoints = clientLines.map(l => { + let { verified, line, id } = this._runtime.setBreakPoint(path, this.convertClientLineToDebugger(l)); + const bp = new Breakpoint(verified, this.convertDebuggerLineToClient(line)); + bp.id = id; + return bp; + }); + + // send back the actual breakpoint positions + response.body = { + breakpoints: actualBreakpoints + }; + this.sendResponse(response); + } + + protected breakpointLocationsRequest(response: DebugProtocol.BreakpointLocationsResponse, args: DebugProtocol.BreakpointLocationsArguments, _request?: DebugProtocol.Request): void { + + if (args.source.path) { + const bps = this._runtime.getBreakpoints(args.source.path, this.convertClientLineToDebugger(args.line)); + response.body = { + breakpoints: bps.map(col => { + return { + line: args.line, + column: this.convertDebuggerColumnToClient(col) + }; + }) + }; + } else { + response.body = { + breakpoints: [] + }; + } + this.sendResponse(response); + } + + protected threadsRequest(response: DebugProtocol.ThreadsResponse): void { + + // runtime supports no threads so just return a default thread. + response.body = { + threads: [ + new Thread(MockDebugSession.THREAD_ID, 'thread 1') + ] + }; + this.sendResponse(response); + } + + protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void { + + const startFrame = typeof args.startFrame === 'number' ? args.startFrame : 0; + const maxLevels = typeof args.levels === 'number' ? args.levels : 1000; + const endFrame = startFrame + maxLevels; + + const stk = this._runtime.stack(startFrame, endFrame); + + response.body = { + stackFrames: stk.frames.map(f => new StackFrame(f.index, f.name, this.createSource(f.file), this.convertDebuggerLineToClient(f.line))), + totalFrames: stk.count + }; + this.sendResponse(response); + } + + protected scopesRequest(response: DebugProtocol.ScopesResponse, _args: DebugProtocol.ScopesArguments): void { + + response.body = { + scopes: [ + new Scope('Local', this._variableHandles.create('local'), false), + new Scope('Global', this._variableHandles.create('global'), true) + ] + }; + this.sendResponse(response); + } + + protected async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments, request?: DebugProtocol.Request) { + + const variables: DebugProtocol.Variable[] = []; + + if (this._isLongrunning.get(args.variablesReference)) { + // long running + + if (request) { + this._cancelationTokens.set(request.seq, false); + } + + for (let i = 0; i < 100; i++) { + await timeout(1000); + variables.push({ + name: `i_${i}`, + type: 'integer', + value: `${i}`, + variablesReference: 0 + }); + if (request && this._cancelationTokens.get(request.seq)) { + break; + } + } + + if (request) { + this._cancelationTokens.delete(request.seq); + } + + } else { + + const id = this._variableHandles.get(args.variablesReference); + + if (id) { + variables.push({ + name: id + '_i', + type: 'integer', + value: '123', + variablesReference: 0 + }); + variables.push({ + name: id + '_f', + type: 'float', + value: '3.14', + variablesReference: 0 + }); + variables.push({ + name: id + '_s', + type: 'string', + value: 'hello world', + variablesReference: 0 + }); + variables.push({ + name: id + '_o', + type: 'object', + value: 'Object', + variablesReference: this._variableHandles.create(id + '_o') + }); + + // cancelation support for long running requests + const nm = id + '_long_running'; + const ref = this._variableHandles.create(id + '_lr'); + variables.push({ + name: nm, + type: 'object', + value: 'Object', + variablesReference: ref + }); + this._isLongrunning.set(ref, true); + } + } + + response.body = { + variables: variables + }; + this.sendResponse(response); + } + + protected continueRequest(response: DebugProtocol.ContinueResponse, _args: DebugProtocol.ContinueArguments): void { + this._runtime.continue(); + this.sendResponse(response); + } + + protected reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, _args: DebugProtocol.ReverseContinueArguments): void { + this._runtime.continue(true); + this.sendResponse(response); + } + + protected nextRequest(response: DebugProtocol.NextResponse, _args: DebugProtocol.NextArguments): void { + this._runtime.step(); + this.sendResponse(response); + } + + protected stepBackRequest(response: DebugProtocol.StepBackResponse, _args: DebugProtocol.StepBackArguments): void { + this._runtime.step(true); + this.sendResponse(response); + } + + protected evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void { + + let reply: string | undefined = undefined; + + if (args.context === 'repl') { + // 'evaluate' supports to create and delete breakpoints from the 'repl': + const matches = /new +([0-9]+)/.exec(args.expression); + if (matches && matches.length === 2) { + if (this._runtime.sourceFile) { + const mbp = this._runtime.setBreakPoint(this._runtime.sourceFile, this.convertClientLineToDebugger(parseInt(matches[1]))); + const bp = new Breakpoint(mbp.verified, this.convertDebuggerLineToClient(mbp.line), undefined, this.createSource(this._runtime.sourceFile)); + bp.id = mbp.id; + this.sendEvent(new BreakpointEvent('new', bp)); + reply = `breakpoint created`; + } + } else { + const matches = /del +([0-9]+)/.exec(args.expression); + if (matches && matches.length === 2) { + const mbp = this._runtime.sourceFile ? this._runtime.clearBreakPoint(this._runtime.sourceFile, this.convertClientLineToDebugger(parseInt(matches[1]))) : undefined; + if (mbp) { + const bp = new Breakpoint(false); + bp.id = mbp.id; + this.sendEvent(new BreakpointEvent('removed', bp)); + reply = `breakpoint deleted`; + } + } + } + } + + response.body = { + result: reply ? reply : `evaluate(context: '${args.context}', '${args.expression}')`, + variablesReference: 0 + }; + this.sendResponse(response); + } + + protected dataBreakpointInfoRequest(response: DebugProtocol.DataBreakpointInfoResponse, args: DebugProtocol.DataBreakpointInfoArguments): void { + + response.body = { + dataId: null, + description: 'cannot break on data access', + accessTypes: undefined, + canPersist: false + }; + + if (args.variablesReference && args.name) { + const id = this._variableHandles.get(args.variablesReference); + if (id && id.startsWith('global_')) { + response.body.dataId = args.name; + response.body.description = args.name; + response.body.accessTypes = ['read']; + response.body.canPersist = false; + } + } + + this.sendResponse(response); + } + + protected setDataBreakpointsRequest(response: DebugProtocol.SetDataBreakpointsResponse, args: DebugProtocol.SetDataBreakpointsArguments): void { + + // clear all data breakpoints + this._runtime.clearAllDataBreakpoints(); + + response.body = { + breakpoints: [] + }; + + for (let dbp of args.breakpoints) { + // assume that id is the "address" to break on + const ok = this._runtime.setDataBreakpoint(dbp.dataId); + response.body.breakpoints.push({ + verified: ok + }); + } + + this.sendResponse(response); + } + + protected completionsRequest(response: DebugProtocol.CompletionsResponse, _args: DebugProtocol.CompletionsArguments): void { + + response.body = { + targets: [ + { + label: 'item 10', + sortText: '10' + }, + { + label: 'item 1', + sortText: '01' + }, + { + label: 'item 2', + sortText: '02' + } + ] + }; + this.sendResponse(response); + } + + protected cancelRequest(_response: DebugProtocol.CancelResponse, args: DebugProtocol.CancelArguments) { + if (args.requestId) { + this._cancelationTokens.set(args.requestId, true); + } + } + + //---- helpers + + private createSource(filePath: string): Source { + return new Source(basename(filePath), this.convertDebuggerPathToClient(filePath), undefined, undefined, 'mock-adapter-data'); + } +} + +//------------------------------------------------------------------------------------------------------------------------------------------ + + +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +export interface MockBreakpoint { + id: number; + line: number; + verified: boolean; +} + +export interface MockOutputEvent { + text: string; + filePath: string; + line: number; + column: number; +} + +/** + * A Mock runtime with minimal debugger functionality. + */ +export class MockRuntime { + + private stopOnEntry = new vscode.EventEmitter(); + onStopOnEntry: vscode.Event = this.stopOnEntry.event; + + private stopOnStep = new vscode.EventEmitter(); + onStopOnStep: vscode.Event = this.stopOnStep.event; + + private stopOnBreakpoint = new vscode.EventEmitter(); + onStopOnBreakpoint: vscode.Event = this.stopOnBreakpoint.event; + + private stopOnDataBreakpoint = new vscode.EventEmitter(); + onStopOnDataBreakpoint: vscode.Event = this.stopOnDataBreakpoint.event; + + private stopOnException = new vscode.EventEmitter(); + onStopOnException: vscode.Event = this.stopOnException.event; + + private breakpointValidated = new vscode.EventEmitter(); + onBreakpointValidated: vscode.Event = this.breakpointValidated.event; + + private output = new vscode.EventEmitter(); + onOutput: vscode.Event = this.output.event; + + private end = new vscode.EventEmitter(); + onEnd: vscode.Event = this.end.event; + + + // the initial (and one and only) file we are 'debugging' + private _sourceFile?: string; + public get sourceFile() { + return this._sourceFile; + } + + // the contents (= lines) of the one and only file + private _sourceLines: string[] = []; + + // This is the next line that will be 'executed' + private _currentLine = 0; + + // maps from sourceFile to array of Mock breakpoints + private _breakPoints = new Map(); + + // since we want to send breakpoint events, we will assign an id to every event + // so that the frontend can match events with breakpoints. + private _breakpointId = 1; + + private _breakAddresses = new Set(); + + constructor(private memfs: MemFS) { + } + + /** + * Start executing the given program. + */ + public start(program: string, stopOnEntry: boolean) { + + this.loadSource(program); + this._currentLine = -1; + + if (this._sourceFile) { + this.verifyBreakpoints(this._sourceFile); + } + + if (stopOnEntry) { + // we step once + this.step(false, this.stopOnEntry); + } else { + // we just start to run until we hit a breakpoint or an exception + this.continue(); + } + } + + /** + * Continue execution to the end/beginning. + */ + public continue(reverse = false) { + this.run(reverse, undefined); + } + + /** + * Step to the next/previous non empty line. + */ + public step(reverse = false, event = this.stopOnStep) { + this.run(reverse, event); + } + + /** + * Returns a fake 'stacktrace' where every 'stackframe' is a word from the current line. + */ + public stack(startFrame: number, endFrame: number): { frames: any[], count: number } { + + const words = this._sourceLines[this._currentLine].trim().split(/\s+/); + + const frames = new Array(); + // every word of the current line becomes a stack frame. + for (let i = startFrame; i < Math.min(endFrame, words.length); i++) { + const name = words[i]; // use a word of the line as the stackframe name + frames.push({ + index: i, + name: `${name}(${i})`, + file: this._sourceFile, + line: this._currentLine + }); + } + return { + frames: frames, + count: words.length + }; + } + + public getBreakpoints(_path: string, line: number): number[] { + + const l = this._sourceLines[line]; + + let sawSpace = true; + const bps: number[] = []; + for (let i = 0; i < l.length; i++) { + if (l[i] !== ' ') { + if (sawSpace) { + bps.push(i); + sawSpace = false; + } + } else { + sawSpace = true; + } + } + + return bps; + } + + /* + * Set breakpoint in file with given line. + */ + public setBreakPoint(path: string, line: number): MockBreakpoint { + + const bp = { verified: false, line, id: this._breakpointId++ }; + let bps = this._breakPoints.get(path); + if (!bps) { + bps = new Array(); + this._breakPoints.set(path, bps); + } + bps.push(bp); + + this.verifyBreakpoints(path); + + return bp; + } + + /* + * Clear breakpoint in file with given line. + */ + public clearBreakPoint(path: string, line: number): MockBreakpoint | undefined { + let bps = this._breakPoints.get(path); + if (bps) { + const index = bps.findIndex(bp => bp.line === line); + if (index >= 0) { + const bp = bps[index]; + bps.splice(index, 1); + return bp; + } + } + return undefined; + } + + /* + * Clear all breakpoints for file. + */ + public clearBreakpoints(path: string): void { + this._breakPoints.delete(path); + } + + /* + * Set data breakpoint. + */ + public setDataBreakpoint(address: string): boolean { + if (address) { + this._breakAddresses.add(address); + return true; + } + return false; + } + + /* + * Clear all data breakpoints. + */ + public clearAllDataBreakpoints(): void { + this._breakAddresses.clear(); + } + + // private methods + + private loadSource(file: string) { + if (this._sourceFile !== file) { + this._sourceFile = file; + + const _textDecoder = new TextDecoder(); + + const uri = vscode.Uri.parse(file); + const content = _textDecoder.decode(this.memfs.readFile(uri)); + this._sourceLines = content.split('\n'); + + //this._sourceLines = readFileSync(this._sourceFile).toString().split('\n'); + } + } + + /** + * Run through the file. + * If stepEvent is specified only run a single step and emit the stepEvent. + */ + private run(reverse = false, stepEvent?: vscode.EventEmitter): void { + if (reverse) { + for (let ln = this._currentLine - 1; ln >= 0; ln--) { + if (this.fireEventsForLine(ln, stepEvent)) { + this._currentLine = ln; + return; + } + } + // no more lines: stop at first line + this._currentLine = 0; + this.stopOnEntry.fire(); + } else { + for (let ln = this._currentLine + 1; ln < this._sourceLines.length; ln++) { + if (this.fireEventsForLine(ln, stepEvent)) { + this._currentLine = ln; + return; + } + } + // no more lines: run to end + this.end.fire(); + } + } + + private verifyBreakpoints(path: string): void { + let bps = this._breakPoints.get(path); + if (bps) { + this.loadSource(path); + bps.forEach(bp => { + if (!bp.verified && bp.line < this._sourceLines.length) { + const srcLine = this._sourceLines[bp.line].trim(); + + // if a line is empty or starts with '+' we don't allow to set a breakpoint but move the breakpoint down + if (srcLine.length === 0 || srcLine.indexOf('+') === 0) { + bp.line++; + } + // if a line starts with '-' we don't allow to set a breakpoint but move the breakpoint up + if (srcLine.indexOf('-') === 0) { + bp.line--; + } + // don't set 'verified' to true if the line contains the word 'lazy' + // in this case the breakpoint will be verified 'lazy' after hitting it once. + if (srcLine.indexOf('lazy') < 0) { + bp.verified = true; + this.breakpointValidated.fire(bp); + } + } + }); + } + } + + /** + * Fire events if line has a breakpoint or the word 'exception' is found. + * Returns true is execution needs to stop. + */ + private fireEventsForLine(ln: number, stepEvent?: vscode.EventEmitter): boolean { + + const line = this._sourceLines[ln].trim(); + + // if 'log(...)' found in source -> send argument to debug console + const matches = /log\((.*)\)/.exec(line); + if (matches && matches.length === 2) { + if (this._sourceFile) { + this.output.fire({ text: matches[1], filePath: this._sourceFile, line: ln, column: matches.index }); + } + } + + // if a word in a line matches a data breakpoint, fire a 'dataBreakpoint' event + const words = line.split(' '); + for (let word of words) { + if (this._breakAddresses.has(word)) { + this.stopOnDataBreakpoint.fire(); + return true; + } + } + + // if word 'exception' found in source -> throw exception + if (line.indexOf('exception') >= 0) { + this.stopOnException.fire(); + return true; + } + + // is there a breakpoint? + const breakpoints = this._sourceFile ? this._breakPoints.get(this._sourceFile) : undefined; + if (breakpoints) { + const bps = breakpoints.filter(bp => bp.line === ln); + if (bps.length > 0) { + + // send 'stopped' event + this.stopOnBreakpoint.fire(); + + // the following shows the use of 'breakpoint' events to update properties of a breakpoint in the UI + // if breakpoint is not yet verified, verify it now and send a 'breakpoint' update event + if (!bps[0].verified) { + bps[0].verified = true; + this.breakpointValidated.fire(bps[0]); + } + return true; + } + } + + // non-empty line + if (stepEvent && line.length > 0) { + stepEvent.fire(); + return true; + } + + // nothing interesting found -> continue + return false; + } +} diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts index 6431bad3635..a45c3188e00 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts @@ -55,7 +55,7 @@ suite('window namespace tests', () => { } terminal.processId.then(id => { try { - ok(id > 0); + ok(id && id > 0); } catch (e) { done(e); } diff --git a/extensions/vscode-colorize-tests/package.json b/extensions/vscode-colorize-tests/package.json index 6045e073d02..c9834b05549 100644 --- a/extensions/vscode-colorize-tests/package.json +++ b/extensions/vscode-colorize-tests/package.json @@ -5,12 +5,20 @@ "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": "^12.11.7", "mocha-junit-reporter": "^1.17.0", diff --git a/extensions/vscode-colorize-tests/src/colorizerTestMain.ts b/extensions/vscode-colorize-tests/src/colorizerTestMain.ts new file mode 100644 index 00000000000..6557111c496 --- /dev/null +++ b/extensions/vscode-colorize-tests/src/colorizerTestMain.ts @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import * as jsoncParser from 'jsonc-parser'; + +export function activate(context: vscode.ExtensionContext): any { + console.log('activates'); + + const tokenModifiers = ['static', 'abstract', 'deprecated']; + const tokenTypes = ['strings', 'types', 'structs', 'classes', 'functions', 'variables']; + const legend = new vscode.SemanticColoringLegend(tokenTypes, tokenModifiers); + + /* + * 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` - `startCharacter`: token start character offset inside the line (inclusive) + * - at index `5*i+2` - `endCharacter`: token end character offset inside the line (exclusive) + * - 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` + */ + + const semanticHighlightProvider: vscode.SemanticColoringProvider = { + provideSemanticColoring(document: vscode.TextDocument): vscode.ProviderResult { + const result: number[] = []; + console.log('provideSemanticColoring'); + + const visitor: jsoncParser.JSONVisitor = { + onObjectProperty: (property: string, _offset: number, length: number, startLine: number, startCharacter: number) => { + result.push(startLine); + result.push(startCharacter); + result.push(startCharacter + length); + + + const [type, ...modifiers] = property.split('.'); + let tokenType = legend.tokenTypes.indexOf(type); + if (tokenType === -1) { + tokenType = 0; + } + result.push(tokenType); + + 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; + } + } + result.push(tokenModifiers); + } + }; + jsoncParser.visit(document.getText(), visitor); + return new vscode.SemanticColoring([new vscode.SemanticColoringArea(0, new Uint32Array(result))]); + } + }; + + + context.subscriptions.push(vscode.languages.registerSemanticColoringProvider({ pattern: '**/color-test.json' }, semanticHighlightProvider, legend)); + +} diff --git a/extensions/vscode-colorize-tests/src/typings/ref.d.ts b/extensions/vscode-colorize-tests/src/typings/ref.d.ts index a45a0c6353f..a17099ac50c 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 MIT License. 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 23ac03a3948..c6b3fdb4313 100644 --- a/extensions/vscode-colorize-tests/yarn.lock +++ b/extensions/vscode-colorize-tests/yarn.lock @@ -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/yarn.lock b/extensions/yarn.lock index 6cae6ac12de..f1a8a66e077 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.20191118: + version "3.7.3-insiders.20191118" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.3-insiders.20191118.tgz#86d3b1de1859d70b0a081b708a223f8f3c6a4d46" + integrity sha512-r06UsJwJzLwgRkf3Or2T5tBnUmp8RD5gOHkC6ax9mLHu5r7voo1WAUpvG09R92GvdEQsgZXxOUhdEJWEoc/5sA== diff --git a/package.json b/package.json index b6f0986df88..1a0b119442e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.41.0", - "distro": "b2b71bcd54560577429f8ee10aa270a41036a09c", + "distro": "531a9666cfe4c178d84b8540af753fa5ee456f33", "author": { "name": "Microsoft Corporation" }, @@ -39,10 +39,10 @@ "jschardet": "2.1.1", "keytar": "^4.11.0", "native-is-elevated": "0.4.1", - "native-keymap": "2.0.0", - "native-watchdog": "1.2.0", + "native-keymap": "2.1.0", + "native-watchdog": "1.3.0", "node-pty": "^0.10.0-beta2", - "onigasm-umd": "^2.2.4", + "onigasm-umd": "2.2.5", "semver-umd": "^5.5.3", "spdlog": "^0.11.1", "sudo-prompt": "9.1.1", @@ -52,7 +52,7 @@ "vscode-proxy-agent": "^0.5.2", "vscode-ripgrep": "^1.5.7", "vscode-sqlite3": "4.0.9", - "vscode-textmate": "^4.3.0", + "vscode-textmate": "4.4.0", "xterm": "4.3.0-beta17", "xterm-addon-search": "0.4.0-beta4", "xterm-addon-web-links": "0.2.1", diff --git a/remote/package.json b/remote/package.json index 1a1c1a0e64a..fa35af0ae93 100644 --- a/remote/package.json +++ b/remote/package.json @@ -10,16 +10,16 @@ "https-proxy-agent": "^2.2.3", "iconv-lite": "0.5.0", "jschardet": "2.1.1", - "native-watchdog": "1.2.0", + "native-watchdog": "1.3.0", "node-pty": "^0.10.0-beta2", - "onigasm-umd": "^2.2.4", + "onigasm-umd": "2.2.5", "semver-umd": "^5.5.3", "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.3.0", + "vscode-textmate": "4.4.0", "xterm": "4.3.0-beta17", "xterm-addon-search": "0.4.0-beta4", "xterm-addon-web-links": "0.2.1", diff --git a/remote/web/package.json b/remote/web/package.json index c4a39c50bb3..6cf60157138 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -2,9 +2,9 @@ "name": "vscode-web", "version": "0.0.0", "dependencies": { - "onigasm-umd": "^2.2.4", + "onigasm-umd": "2.2.5", "semver-umd": "^5.5.3", - "vscode-textmate": "^4.3.0", + "vscode-textmate": "4.4.0", "xterm": "4.3.0-beta17", "xterm-addon-search": "0.4.0-beta4", "xterm-addon-web-links": "0.2.1", diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index d5d1fcdd499..124cf2e3725 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.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/onigasm-umd/-/onigasm-umd-2.2.4.tgz#27ee87f7496c66ad40cebfbc0d418c19bb7db5ec" - integrity sha512-N9VqCUhl9KBuzm47vcK8T/xUnbYylIhMN45Rwltlo1sZc3QUDda6SxIlyVB8r0SJQwURv8JOHjyXjjCriGvzRg== +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,10 +24,10 @@ 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.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-4.3.0.tgz#6e1f0f273d84148cfa1e9c7ed85bd16c974f9f61" - integrity sha512-MhEZ3hvxOVuYGsrRzW/PZLDR2VdtG2+V6TIKPvmE9JT+RAq/OtPlrFd1+ZQwBefoHEhjRNuRJ0OktcFezuxPmg== +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" diff --git a/remote/yarn.lock b/remote/yarn.lock index c2f8da25470..07620b8134f 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -261,10 +261,10 @@ nan@^2.10.0, nan@^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" @@ -283,10 +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== -onigasm-umd@^2.2.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/onigasm-umd/-/onigasm-umd-2.2.4.tgz#27ee87f7496c66ad40cebfbc0d418c19bb7db5ec" - integrity sha512-N9VqCUhl9KBuzm47vcK8T/xUnbYylIhMN45Rwltlo1sZc3QUDda6SxIlyVB8r0SJQwURv8JOHjyXjjCriGvzRg== +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" @@ -399,10 +399,10 @@ 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.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-4.3.0.tgz#6e1f0f273d84148cfa1e9c7ed85bd16c974f9f61" - integrity sha512-MhEZ3hvxOVuYGsrRzW/PZLDR2VdtG2+V6TIKPvmE9JT+RAq/OtPlrFd1+ZQwBefoHEhjRNuRJ0OktcFezuxPmg== +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" diff --git a/scripts/code-web.js b/scripts/code-web.js index 07c8700f7f9..e25efa47b9c 100755 --- a/scripts/code-web.js +++ b/scripts/code-web.js @@ -227,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 diff --git a/src/typings/electron.d.ts b/src/typings/electron.d.ts index e4c3221c6eb..369817dcd12 100644 --- a/src/typings/electron.d.ts +++ b/src/typings/electron.d.ts @@ -1,4 +1,4 @@ -// Type definitions for Electron 6.1.4 +// Type definitions for Electron 6.1.5 // Project: http://electronjs.org/ // Definitions by: The Electron Team // Definitions: https://github.com/electron/electron-typescript-definitions diff --git a/src/typings/native-keymap.d.ts b/src/typings/native-keymap.d.ts deleted file mode 100644 index 716a74e38b8..00000000000 --- a/src/typings/native-keymap.d.ts +++ /dev/null @@ -1,93 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. 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 88119b314c1..00000000000 --- a/src/typings/native-watchdog.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. 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/onigasm-umd.d.ts b/src/typings/onigasm-umd.d.ts deleted file mode 100644 index 151cecebfdd..00000000000 --- a/src/typings/onigasm-umd.d.ts +++ /dev/null @@ -1,33 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. 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/vscode-textmate.d.ts b/src/typings/vscode-textmate.d.ts deleted file mode 100644 index 835b33008ac..00000000000 --- a/src/typings/vscode-textmate.d.ts +++ /dev/null @@ -1,256 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module "vscode-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/vs/base/browser/canIUse.ts b/src/vs/base/browser/canIUse.ts index 9329acfad6a..cf8211e919e 100644 --- a/src/vs/base/browser/canIUse.ts +++ b/src/vs/base/browser/canIUse.ts @@ -19,7 +19,7 @@ export const BrowserFeatures = { clipboard: { writeText: ( platform.isNative - || document.queryCommandSupported('copy') + || (document.queryCommandSupported && document.queryCommandSupported('copy')) || !!(navigator && navigator.clipboard && navigator.clipboard.writeText) ), readText: ( diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 671c8d93d9e..e8a190d2a7d 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -267,10 +267,20 @@ 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); } @@ -502,7 +512,16 @@ export function getClientArea(element: HTMLElement): Dimension { // 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) { - return new Dimension((window).visualViewport.width, (window).visualViewport.height); + 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 @@ -882,6 +901,7 @@ export const EventType = { MOUSE_LEAVE: 'mouseleave', POINTER_UP: 'pointerup', POINTER_DOWN: 'pointerdown', + POINTER_MOVE: 'pointermove', CONTEXT_MENU: 'contextmenu', WHEEL: 'wheel', // Keyboard diff --git a/src/vs/base/browser/touch.ts b/src/vs/base/browser/touch.ts index 912b17e1319..9956bc9112b 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 { @@ -76,6 +77,11 @@ export class Gesture extends Disposable { private activeTouches: { [id: number]: TouchData; }; + private _lastSetTapCountTime: number; + + private static readonly CLEAR_TAP_COUNT_TIME = 400; // ms + + private constructor() { super(); @@ -83,6 +89,7 @@ export class Gesture extends Disposable { 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))); @@ -243,10 +250,27 @@ 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; diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css index dfa5bc44f52..2e259eda4f2 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?34cea5159eda573d2422092d6f8c2c77") format("truetype"); + src: url("./codicon.ttf?c4e66586cd3ad4acc55fc456c0760dec") format("truetype"); } .codicon[class*='codicon-'] { @@ -401,4 +401,8 @@ .codicon-debug-breakpoint-stackframe-focused:before { content: "\eb8b" } .codicon-debug-breakpoint-unsupported:before { content: "\eb8c" } .codicon-symbol-string:before { content: "\eb8d" } -.codicon-debug-step-back:before { content: "\f101" } +.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 9bb5589eef0..a51c284681e 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/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index 28d20f9f371..dd00990ac06 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -171,7 +171,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 }); @@ -305,6 +305,7 @@ export class InputBox extends Widget { } public disable(): void { + this.blur(); this.input.disabled = true; this._hideMessage(); } @@ -529,7 +530,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/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 8280f4e05c4..73a183dfd2b 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -856,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]!; diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 5bc68faa7f1..417998fd379 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -715,7 +715,7 @@ export class DefaultStyleController implements IStyleController { if (styles.listBackground) { if (styles.listBackground.isOpaque()) { content.push(`.monaco-list${suffix} .monaco-list-rows { background: ${styles.listBackground}; }`); - } else { + } 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.`); } } @@ -796,6 +796,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; } `); } diff --git a/src/vs/base/browser/ui/splitview/panelview.css b/src/vs/base/browser/ui/splitview/paneview.css similarity index 50% rename from src/vs/base/browser/ui/splitview/panelview.css rename to src/vs/base/browser/ui/splitview/paneview.css index ff3554c4227..c1397d6c62c 100644 --- a/src/vs/base/browser/ui/splitview/panelview.css +++ b/src/vs/base/browser/ui/splitview/paneview.css @@ -3,12 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-panel-view { +.monaco-pane-view { width: 100%; height: 100%; } -.monaco-panel-view .panel { +.monaco-pane-view .pane { overflow: hidden; width: 100%; height: 100%; @@ -16,7 +16,7 @@ flex-direction: column; } -.monaco-panel-view .panel > .panel-header { +.monaco-pane-view .pane > .pane-header { font-size: 11px; font-weight: bold; text-transform: uppercase; @@ -26,7 +26,7 @@ align-items: center; } -.monaco-panel-view .panel > .panel-header > .twisties { +.monaco-pane-view .pane > .pane-header > .twisties { width: 20px; display: flex; align-items: center; @@ -36,26 +36,26 @@ flex-shrink: 0; } -.monaco-panel-view .panel > .panel-header.expanded > .twisties::before { +.monaco-pane-view .pane > .pane-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 { +/* 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 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 { +/* 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 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 { +/* 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; @@ -69,33 +69,33 @@ } /* 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 { +.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-panel-view .panel > .panel-header.hidden { +.monaco-pane-view .pane > .pane-header.hidden { display: none; } -.monaco-panel-view .panel > .panel-body { +.monaco-pane-view .pane > .pane-body { overflow: hidden; flex: 1; } /* Animation */ -.monaco-panel-view.animated .split-view-view { +.monaco-pane-view.animated .split-view-view { transition-duration: 0.15s; transition-timing-function: ease-out; } -.monaco-panel-view.animated.vertical .split-view-view { +.monaco-pane-view.animated.vertical .split-view-view { transition-property: height; } -.monaco-panel-view.animated.horizontal .split-view-view { +.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 71% rename from src/vs/base/browser/ui/splitview/panelview.ts rename to src/vs/base/browser/ui/splitview/paneview.ts index 85d24c6bc7c..e97fab25404 100644 --- a/src/vs/base/browser/ui/splitview/panelview.ts +++ b/src/vs/base/browser/ui/splitview/paneview.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. 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'; @@ -16,14 +16,14 @@ 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; @@ -31,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 @@ -39,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; @@ -54,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()); @@ -95,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 { @@ -116,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 { @@ -169,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'); @@ -198,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); @@ -211,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) { @@ -242,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; @@ -276,10 +276,10 @@ class PanelDraggable extends Disposable { if (isFirefox) { // Firefox: requires to set a text data transfer to get going - e.dataTransfer?.setData(DataTransfers.TEXT, this.panel.draggableElement.textContent || ''); + e.dataTransfer?.setData(DataTransfers.TEXT, this.pane.draggableElement.textContent || ''); } - const dragImage = append(document.body, $('.monaco-drag-image', {}, this.panel.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); @@ -291,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; } @@ -304,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; } @@ -335,8 +335,8 @@ class PanelDraggable extends Disposable { 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; @@ -346,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; @@ -454,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; @@ -467,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); @@ -490,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/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index addcf81a6ee..52e88cc5c9b 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -336,7 +336,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); diff --git a/src/vs/base/browser/ui/tree/media/panelviewlet.css b/src/vs/base/browser/ui/tree/media/paneviewlet.css similarity index 89% rename from src/vs/base/browser/ui/tree/media/panelviewlet.css rename to src/vs/base/browser/ui/tree/media/paneviewlet.css index 7aba33ef440..eb1e0be70b2 100644 --- a/src/vs/base/browser/ui/tree/media/panelviewlet.css +++ b/src/vs/base/browser/ui/tree/media/paneviewlet.css @@ -3,7 +3,7 @@ * Licensed under the MIT License. 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; diff --git a/src/vs/base/browser/ui/tree/media/tree.css b/src/vs/base/browser/ui/tree/media/tree.css index dc83adc86f4..66cd61d0b87 100644 --- a/src/vs/base/browser/ui/tree/media/tree.css +++ b/src/vs/base/browser/ui/tree/media/tree.css @@ -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/common/event.ts b/src/vs/base/common/event.ts index b85073b6786..f0492026239 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); diff --git a/src/vs/base/node/encoding.ts b/src/vs/base/node/encoding.ts index c4cd563ef8f..3fbae109f00 100644 --- a/src/vs/base/node/encoding.ts +++ b/src/vs/base/node/encoding.ts @@ -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,7 +193,7 @@ 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; diff --git a/src/vs/base/node/watcher.ts b/src/vs/base/node/watcher.ts index b51f73c6eaa..b71a84a63f1 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/test/common/event.test.ts b/src/vs/base/test/common/event.test.ts index 25d47dc6e00..a0e8ca9fcfa 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 MIT License. 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,12 +329,7 @@ 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); }); @@ -372,12 +354,7 @@ suite('AsyncEmitter', function () { e.waitUntil(timeout(10)); }); - await emitter.fireAsync(thenables => ({ - foo: true, - waitUntil(t) { - thenables.push(t); - } - })).then(() => { + await emitter.fireAsync({ foo: true }, CancellationToken.None).then(() => { assert.equal(globalState, 2); }).catch(e => { console.log(e); diff --git a/src/vs/base/test/node/encoding/encoding.test.ts b/src/vs/base/test/node/encoding/encoding.test.ts index eef17fbddb6..a78f2ed6b46 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 () => { diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index 32eaef78ac4..cbb2e5c5269 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -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'); } diff --git a/src/vs/editor/browser/config/charWidthReader.ts b/src/vs/editor/browser/config/charWidthReader.ts index 21a54663412..479a35c7bea 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 94ed9e159cb..cb20bc72ea6 100644 --- a/src/vs/editor/browser/controller/coreCommands.ts +++ b/src/vs/editor/browser/controller/coreCommands.ts @@ -1753,7 +1753,7 @@ registerCommand(new EditorOrNativeTextInputCommand({ kbExpr: null, primary: KeyMod.CtrlCmd | KeyCode.KEY_A }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarSelectionMenu, group: '1_basic', title: nls.localize({ key: 'miSelectAll', comment: ['&& denotes a mnemonic'] }, "&&Select All"), @@ -1771,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"), @@ -1792,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/pointerHandler.ts b/src/vs/editor/browser/controller/pointerHandler.ts index 30ab2b51656..46894f1be15 100644 --- a/src/vs/editor/browser/controller/pointerHandler.ts +++ b/src/vs/editor/browser/controller/pointerHandler.ts @@ -202,7 +202,7 @@ export class PointerEventHandler extends MouseHandler { this._lastPointerType = 'mouse'; - this.viewHelper.linesContentDomNode.addEventListener('pointerdown', (e: any) => { + this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, 'pointerdown', (e: any) => { const pointerType = e.pointerType; if (pointerType === 'mouse') { this._lastPointerType = 'mouse'; @@ -212,7 +212,7 @@ export class PointerEventHandler extends MouseHandler { } else { this._lastPointerType = 'pen'; } - }); + })); // PonterEvents const pointerEvents = new EditorPointerEventFactory(this.viewHelper.viewDomNode); @@ -226,12 +226,30 @@ export class PointerEventHandler extends MouseHandler { } 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.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, + }); } } @@ -242,9 +260,11 @@ export class PointerEventHandler extends MouseHandler { } public _onMouseDown(e: EditorMouseEvent): void { - if (this._lastPointerType !== 'touch') { - super._onMouseDown(e); + if (e.target && this.viewHelper.linesContentDomNode.contains(e.target) && this._lastPointerType === 'touch') { + return; } + + super._onMouseDown(e); } } diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index bcd0995f843..85c44e78f09 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'; @@ -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/editorExtensions.ts b/src/vs/editor/browser/editorExtensions.ts index 2a12d008b5c..18bd6370f27 100644 --- a/src/vs/editor/browser/editorExtensions.ts +++ b/src/vs/editor/browser/editorExtensions.ts @@ -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 { diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 8e50b1e0e2d..4703c176847 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -1109,7 +1109,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 e1136ab6c28..cc8a37fcf6b 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/minimapCharSheet.ts b/src/vs/editor/browser/viewParts/minimap/minimapCharSheet.ts index af7b0eae4f8..cda6dc9e7ee 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/widget/diffReview.ts b/src/vs/editor/browser/widget/diffReview.ts index 8a915a659ff..b0437b6d47f 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/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index ac610aa5626..f17391dbe75 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -1300,15 +1300,20 @@ 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?: GoToLocationValues; + multipleDefinitions?: GoToLocationValues; multipleTypeDefinitions?: GoToLocationValues; multipleDeclarations?: GoToLocationValues; - multipleImplemenations?: GoToLocationValues; + multipleImplementations?: GoToLocationValues; multipleReferences?: GoToLocationValues; + + alternativeDefinitionCommand?: string; + alternativeTypeDefinitionCommand?: string; + alternativeDeclarationCommand?: string; + alternativeImplementationCommand?: string; + alternativeReferenceCommand?: string; } export type GoToLocationOptions = Readonly>; @@ -1321,8 +1326,13 @@ class EditorGoToLocation extends BaseEditorOption(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']), - multipleImplemenations: input.multipleImplemenations ?? EditorStringEnumOption.stringSet(input.multipleImplemenations, '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), }; } } @@ -2353,7 +2393,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. */ @@ -2486,7 +2530,8 @@ class EditorSuggest extends BaseEditorOption 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/modes.ts b/src/vs/editor/common/modes.ts index 4fd62bcd685..5d3f0109194 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -548,6 +548,7 @@ export interface CodeAction { diagnostics?: IMarkerData[]; kind?: string; isPreferred?: boolean; + disabled?: string; } /** diff --git a/src/vs/editor/common/modes/textToHtmlTokenizer.ts b/src/vs/editor/common/modes/textToHtmlTokenizer.ts index e98f2b37f19..c8c4acac430 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/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index 6fbe5274a87..3b9734e0a16 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -14,7 +14,7 @@ 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, IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; -import { LanguageIdentifier, SemanticColoringProviderRegistry, SemanticColoringProvider, SemanticColoring, FontStyle, MetadataConsts } from 'vs/editor/common/modes'; +import { LanguageIdentifier, SemanticColoringProviderRegistry, SemanticColoringProvider, SemanticColoring, SemanticColoringLegend } 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'; @@ -23,6 +23,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur 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(); @@ -118,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; @@ -129,7 +131,7 @@ export class ModelServiceImpl extends Disposable implements IModelService { this._configurationServiceSubscription = this._configurationService.onDidChangeConfiguration(e => this._updateModelOptions()); this._updateModelOptions(); - this._register(new SemanticColoringFeature(this)); + this._register(new SemanticColoringFeature(this, themeService)); } private static _readModelOptions(config: IRawConfig, isForSimpleWidget: boolean): ITextModelCreationOptions { @@ -439,12 +441,14 @@ export interface ILineSequence { class SemanticColoringFeature extends Disposable { private _watchers: Record; + private _semanticStyling: SemanticStyling; - constructor(modelService: IModelService) { + 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); + this._watchers[model.uri.toString()] = new ModelSemanticColoring(model, themeService, this._semanticStyling); })); this._register(modelService.onModelRemoved((model) => { this._watchers[model.uri.toString()].dispose(); @@ -453,25 +457,189 @@ class SemanticColoringFeature extends Disposable { } } +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: SemanticColoringProvider): 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: SemanticColoringLegend, + 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; + } +} + class ModelSemanticColoring extends Disposable { private _isDisposed: boolean; private readonly _model: ITextModel; + private readonly _semanticStyling: SemanticStyling; private readonly _fetchSemanticTokens: RunOnceScheduler; private _currentResponse: SemanticColoring | null; private _currentRequestCancellationTokenSource: CancellationTokenSource | null; - constructor(model: ITextModel) { + 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(SemanticColoringProviderRegistry.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, []); + this._fetchSemanticTokens.schedule(); + })); + } this._fetchSemanticTokens.schedule(0); } @@ -504,21 +672,22 @@ class ModelSemanticColoring extends Disposable { pendingChanges.push(e); }); + const styling = this._semanticStyling.get(provider); const request = Promise.resolve(provider.provideSemanticColoring(this._model, this._currentRequestCancellationTokenSource.token)); request.then((res) => { this._currentRequestCancellationTokenSource = null; contentChangeListener.dispose(); - this._setSemanticTokens(res || null, pendingChanges); + this._setSemanticTokens(res || null, styling, pendingChanges); }, (err) => { errors.onUnexpectedError(err); this._currentRequestCancellationTokenSource = null; contentChangeListener.dispose(); - this._setSemanticTokens(null, pendingChanges); + this._setSemanticTokens(null, styling, pendingChanges); }); } - private _setSemanticTokens(tokens: SemanticColoring | null, pendingChanges: IModelContentChangedEvent[]): void { + private _setSemanticTokens(tokens: SemanticColoring | null, styling: SemanticColoringProviderStyling | null, pendingChanges: IModelContentChangedEvent[]): void { if (this._currentResponse) { this._currentResponse.dispose(); this._currentResponse = null; @@ -531,7 +700,7 @@ class ModelSemanticColoring extends Disposable { return; } this._currentResponse = tokens; - if (!this._currentResponse) { + if (!this._currentResponse || !styling) { this._model.setSemanticTokens(null); return; } @@ -540,30 +709,28 @@ class ModelSemanticColoring extends Disposable { for (const area of this._currentResponse.areas) { const srcTokens = area.data; const tokenCount = srcTokens.length / 5; - const destTokens = new Uint32Array(4 * tokenCount); + let destTokens = new Uint32Array(4 * tokenCount); + let destOffset = 0; for (let i = 0; i < tokenCount; i++) { const srcOffset = 5 * i; const deltaLine = srcTokens[srcOffset]; const startCharacter = srcTokens[srcOffset + 1]; const endCharacter = srcTokens[srcOffset + 2]; - // const tokenType = srcTokens[srcOffset + 3]; - // const tokenModifiers = srcTokens[srcOffset + 4]; - // TODO@semantic: map here tokenType and tokenModifiers to metadata - - const fontStyle = FontStyle.Italic | FontStyle.Bold | FontStyle.Underline; - const foregroundColorId = 3; - const metadata = ( - (fontStyle << MetadataConsts.FONT_STYLE_OFFSET) - | (foregroundColorId << MetadataConsts.FOREGROUND_OFFSET) - ) >>> 0; - - const destOffset = 4 * i; - destTokens[destOffset] = deltaLine; - destTokens[destOffset + 1] = startCharacter; - destTokens[destOffset + 2] = endCharacter; - destTokens[destOffset + 3] = metadata; + const tokenTypeIndex = srcTokens[srcOffset + 3]; + const tokenModifierSet = srcTokens[srcOffset + 4]; + const metadata = styling.getMetadata(tokenTypeIndex, tokenModifierSet); + if (metadata !== Constants.NO_STYLING) { + destTokens[destOffset] = deltaLine; + destTokens[destOffset + 1] = startCharacter; + destTokens[destOffset + 2] = endCharacter; + destTokens[destOffset + 3] = metadata; + destOffset += 4; + } } + if (destOffset !== destTokens.length) { + destTokens = destTokens.subarray(0, destOffset); + } const tokens = new MultilineTokens2(area.line, new SparseEncodedTokens(destTokens)); result.push(tokens); } diff --git a/src/vs/editor/common/services/resolverService.ts b/src/vs/editor/common/services/resolverService.ts index b425ebb8fde..77719683a23 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/contrib/clipboard/clipboard.ts b/src/vs/editor/contrib/clipboard/clipboard.ts index df241dc8643..cb106b53b82 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 2cfc82d83cc..c4850175c63 100644 --- a/src/vs/editor/contrib/codeAction/codeAction.ts +++ b/src/vs/editor/contrib/codeAction/codeAction.ts @@ -24,7 +24,8 @@ 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; } @@ -44,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); } } @@ -150,5 +153,5 @@ registerLanguageCommand('_executeCodeActionProvider', async function (accessor, 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 c555ceec6d6..8185eca6b17 100644 --- a/src/vs/editor/contrib/codeAction/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/codeActionCommands.ts @@ -106,7 +106,7 @@ export class QuickFixController extends Disposable implements IEditorContributio } } } - }, contextMenuService, keybindingService)) + }, this._instantiationService)) ); } @@ -115,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( @@ -265,7 +265,7 @@ export class RefactorAction extends EditorAction { }, weight: KeybindingWeight.EditorContrib }, - menuOpts: { + contextMenuOpts: { group: '1_modification', order: 2, when: ContextKeyExpr.and( @@ -308,7 +308,7 @@ export class SourceAction extends EditorAction { 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( diff --git a/src/vs/editor/contrib/codeAction/codeActionWidget.ts b/src/vs/editor/contrib/codeAction/codeActionMenu.ts similarity index 91% rename from src/vs/editor/contrib/codeAction/codeActionWidget.ts rename to src/vs/editor/contrib/codeAction/codeActionMenu.ts index a40ffdfa45c..c2cdc9c4f0f 100644 --- a/src/vs/editor/contrib/codeAction/codeActionWidget.ts +++ b/src/vs/editor/contrib/codeAction/codeActionMenu.ts @@ -35,11 +35,15 @@ class CodeActionAction extends Action { public readonly action: CodeAction, callback: () => Promise, ) { - super(action.command ? action.command.id : action.title, action.title, undefined, true, callback); + super(action.command ? action.command.id : action.title, action.title, undefined, !action.disabled, callback); } } -export class CodeActionWidget extends Disposable { +export interface CodeActionShowOptions { + readonly includeDisabledActions: boolean; +} + +export class CodeActionMenu extends Disposable { private _visible: boolean = false; private readonly _showingActions = this._register(new MutableDisposable()); @@ -48,9 +52,9 @@ export class CodeActionWidget extends Disposable { constructor( private readonly _editor: ICodeEditor, - private readonly _contextMenuService: IContextMenuService, - keybindingService: IKeybindingService, private readonly _delegate: CodeActionWidgetDelegate, + @IContextMenuService private readonly _contextMenuService: IContextMenuService, + @IKeybindingService keybindingService: IKeybindingService, ) { super(); @@ -63,8 +67,9 @@ export class CodeActionWidget extends Disposable { return this._visible; } - public async show(codeActions: CodeActionSet, at: IAnchor | IPosition): Promise { - if (!codeActions.actions.length) { + 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; } @@ -78,7 +83,7 @@ export class CodeActionWidget extends Disposable { this._visible = true; this._showingActions.value = codeActions; - const actions = codeActions.actions.map(action => + 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 }; @@ -86,7 +91,7 @@ export class CodeActionWidget extends Disposable { this._contextMenuService.showContextMenu({ getAnchor: () => anchor, - getActions: () => actions, + getActions: () => menuActions, onHide: () => { this._visible = false; this._editor.focus(); diff --git a/src/vs/editor/contrib/codeAction/codeActionModel.ts b/src/vs/editor/contrib/codeAction/codeActionModel.ts index 39faf3722d1..e5743489005 100644 --- a/src/vs/editor/contrib/codeAction/codeActionModel.ts +++ b/src/vs/editor/contrib/codeAction/codeActionModel.ts @@ -73,8 +73,9 @@ 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); } } diff --git a/src/vs/editor/contrib/codeAction/codeActionUi.ts b/src/vs/editor/contrib/codeAction/codeActionUi.ts index 5dfd9228ff0..5c34b32fe7f 100644 --- a/src/vs/editor/contrib/codeAction/codeActionUi.ts +++ b/src/vs/editor/contrib/codeAction/codeActionUi.ts @@ -3,25 +3,25 @@ * Licensed under the MIT License. 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 './types'; -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()); @@ -32,13 +32,12 @@ export class CodeActionUi extends Disposable { private readonly delegate: { 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, keybindingService, { + return this._register(instantiationService.createInstance(CodeActionMenu, this._editor, { onSelectCodeAction: async (action) => { this.delegate.applyCodeAction(action, /* retrigger */ true); } @@ -46,8 +45,8 @@ export class CodeActionUi extends Disposable { }); this._lightBulbWidget = new Lazy(() => { - const widget = this._register(new LightBulbWidget(this._editor, quickFixActionId, preferredFixActionId, keybindingService)); - this._register(widget.onClick(e => this.showCodeActionList(e.actions, e))); + 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; }); } @@ -68,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.include) { - // 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) { @@ -102,7 +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 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/lightBulbWidget.ts b/src/vs/editor/contrib/codeAction/lightBulbWidget.ts index 0fc03b3a8ba..e01343ff2b5 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 { @@ -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 730d96fc9a5..0b7fb8ba4a6 100644 --- a/src/vs/editor/contrib/codeAction/test/codeAction.test.ts +++ b/src/vs/editor/contrib/codeAction/test/codeAction.test.ts @@ -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: { include: 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: { include: 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: { include: 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: { include: 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,13 +186,13 @@ 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: { include: 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'); } @@ -208,7 +208,7 @@ 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: { include: CodeActionKind.Source.append('test'), excludes: [CodeActionKind.Source], @@ -233,7 +233,7 @@ 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: { include: CodeActionKind.QuickFix diff --git a/src/vs/editor/contrib/codeAction/test/codeActionKeybindingResolver.test.ts b/src/vs/editor/contrib/codeAction/test/codeActionKeybindingResolver.test.ts index 44314a90860..9a939303ece 100644 --- a/src/vs/editor/contrib/codeAction/test/codeActionKeybindingResolver.test.ts +++ b/src/vs/editor/contrib/codeAction/test/codeActionKeybindingResolver.test.ts @@ -8,7 +8,7 @@ import { ChordKeybinding, KeyCode, SimpleKeybinding } from 'vs/base/common/keyCo 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/codeActionWidget'; +import { CodeActionKeybindingResolver } from 'vs/editor/contrib/codeAction/codeActionMenu'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; diff --git a/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts b/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts index e394f6838c1..c4a7cba5173 100644 --- a/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts +++ b/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts @@ -64,7 +64,7 @@ suite('CodeActionModel', () => { e.actions.then(fixes => { model.dispose(); - assert.equal(fixes.actions.length, 1); + assert.equal(fixes.validActions.length, 1); done(); }, done); })); @@ -104,7 +104,7 @@ suite('CodeActionModel', () => { 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); })); diff --git a/src/vs/editor/contrib/codelens/codelensController.ts b/src/vs/editor/contrib/codelens/codelensController.ts index 852571ac070..40fca0ab3bd 100644 --- a/src/vs/editor/contrib/codelens/codelensController.ts +++ b/src/vs/editor/contrib/codelens/codelensController.ts @@ -367,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); } }); diff --git a/src/vs/editor/contrib/codelens/codelensWidget.ts b/src/vs/editor/contrib/codelens/codelensWidget.ts index c046279ce16..a0bde211451 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.ts +++ b/src/vs/editor/contrib/codelens/codelensWidget.ts @@ -68,7 +68,7 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget { line: number, ) { this._editor = editor; - this._id = (CodeLensContentWidget._idPool++).toString(); + this._id = `codelens.widget-${(CodeLensContentWidget._idPool++)}`; this.updatePosition(line); @@ -96,7 +96,7 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget { innerHtml += `${title}`; } if (i + 1 < lenses.length) { - innerHtml += ' | '; + innerHtml += ' | '; } } } @@ -108,7 +108,7 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget { } else { // symbols and commands if (!innerHtml) { - innerHtml = ' '; + innerHtml = ' '; } this._domNode.innerHTML = innerHtml; if (this._isEmpty && animate) { @@ -188,6 +188,7 @@ export class CodeLensWidget { private _contentWidget?: CodeLensContentWidget; private _decorationIds: string[]; private _data: CodeLensItem[]; + private _isDisposed: boolean = false; constructor( data: CodeLensItem[], @@ -250,7 +251,13 @@ export class CodeLensWidget { } if (this._contentWidget) { this._editor.removeContentWidget(this._contentWidget); + this._contentWidget = undefined; } + this._isDisposed = true; + } + + isDisposed(): boolean { + return this._isDisposed; } isValid(): boolean { @@ -315,7 +322,7 @@ export class CodeLensWidget { } 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; diff --git a/src/vs/editor/contrib/comment/comment.ts b/src/vs/editor/contrib/comment/comment.ts index 5c6118a3f6f..301b00677c4 100644 --- a/src/vs/editor/contrib/comment/comment.ts +++ b/src/vs/editor/contrib/comment/comment.ts @@ -56,7 +56,7 @@ class ToggleCommentLineAction extends CommentLineAction { primary: KeyMod.CtrlCmd | KeyCode.US_SLASH, weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarEditMenu, group: '5_insert', title: nls.localize({ key: 'miToggleLineComment', comment: ['&& denotes a mnemonic'] }, "&&Toggle Line Comment"), @@ -112,7 +112,7 @@ class BlockCommentAction extends EditorAction { linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_A }, weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarEditMenu, group: '5_insert', title: nls.localize({ key: 'miToggleBlockComment', comment: ['&& denotes a mnemonic'] }, "Toggle &&Block Comment"), diff --git a/src/vs/editor/contrib/contextmenu/contextmenu.ts b/src/vs/editor/contrib/contextmenu/contextmenu.ts index ea1d5f26b17..6a301ba93e4 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/find/findController.ts b/src/vs/editor/contrib/find/findController.ts index ae362600757..fbd9bf5500b 100644 --- a/src/vs/editor/contrib/find/findController.ts +++ b/src/vs/editor/contrib/find/findController.ts @@ -455,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"), @@ -701,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/format/formatActions.ts b/src/vs/editor/contrib/format/formatActions.ts index daded6e3e9d..879000c9140 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/gotoSymbol/goToCommands.ts b/src/vs/editor/contrib/gotoSymbol/goToCommands.ts index ed7fe586b9c..cdcad1d5df4 100644 --- a/src/vs/editor/contrib/gotoSymbol/goToCommands.ts +++ b/src/vs/editor/contrib/gotoSymbol/goToCommands.ts @@ -21,7 +21,7 @@ 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 } from 'vs/platform/actions/common/actions'; +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'; @@ -34,9 +34,17 @@ import { EditorOption, GoToLocationValues } from 'vs/editor/common/config/editor import { isStandalone } from 'vs/base/browser/browser'; import { URI } from 'vs/base/common/uri'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ScrollType } from 'vs/editor/common/editorCommon'; +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; @@ -74,8 +82,15 @@ abstract class SymbolNavigationAction extends EditorAction { 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; - const altAction = references.referenceAt(model.uri, pos) && editor.getAction(this._getAlternativeCommand()); if (referenceCount === 0) { // no result -> show message @@ -107,7 +122,7 @@ abstract class SymbolNavigationAction extends EditorAction { protected abstract _getNoResultFoundMessage(info: IWordAtPosition | null): string; - protected abstract _getAlternativeCommand(): string; + protected abstract _getAlternativeCommand(editor: IActiveCodeEditor): string; protected abstract _getGoToPreference(editor: IActiveCodeEditor): GoToLocationValues; @@ -195,8 +210,8 @@ export class DefinitionAction extends SymbolNavigationAction { : nls.localize('generic.noResults', "No definition found"); } - protected _getAlternativeCommand(): string { - return 'editor.action.goToReferences'; + protected _getAlternativeCommand(editor: IActiveCodeEditor): string { + return editor.getOption(EditorOption.gotoLocation).alternativeDefinitionCommand; } protected _getGoToPreference(editor: IActiveCodeEditor): GoToLocationValues { @@ -229,11 +244,11 @@ registerEditorAction(class GoToDefinitionAction extends DefinitionAction { primary: goToDefinitionKb, weight: KeybindingWeight.EditorContrib }, - menuOpts: { + contextMenuOpts: { group: 'navigation', order: 1.1 }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarGoMenu, group: '4_symbol_nav', order: 2, @@ -293,6 +308,11 @@ registerEditorAction(class PeekDefinitionAction extends DefinitionAction { 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); @@ -315,8 +335,8 @@ class DeclarationAction extends SymbolNavigationAction { : nls.localize('decl.generic.noResults', "No declaration found"); } - protected _getAlternativeCommand(): string { - return 'editor.action.goToReferences'; + protected _getAlternativeCommand(editor: IActiveCodeEditor): string { + return editor.getOption(EditorOption.gotoLocation).alternativeDeclarationCommand; } protected _getGoToPreference(editor: IActiveCodeEditor): GoToLocationValues { @@ -341,11 +361,11 @@ registerEditorAction(class GoToDeclarationAction extends DeclarationAction { EditorContextKeys.hasDeclarationProvider, EditorContextKeys.isInEmbeddedEditor.toNegated() ), - menuOpts: { + contextMenuOpts: { group: 'navigation', order: 1.3 }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarGoMenu, group: '4_symbol_nav', order: 3, @@ -376,6 +396,11 @@ registerEditorAction(class PeekDeclarationAction extends DeclarationAction { PeekContext.notInPeekEditor, EditorContextKeys.isInEmbeddedEditor.toNegated() ), + contextMenuOpts: { + menuId: MenuId.EditorContextPeek, + group: 'peek', + order: 3 + } }); } }); @@ -396,8 +421,8 @@ class TypeDefinitionAction extends SymbolNavigationAction { : nls.localize('goToTypeDefinition.generic.noResults', "No type definition found"); } - protected _getAlternativeCommand(): string { - return 'editor.action.goToReferences'; + protected _getAlternativeCommand(editor: IActiveCodeEditor): string { + return editor.getOption(EditorOption.gotoLocation).alternativeTypeDefinitionCommand; } protected _getGoToPreference(editor: IActiveCodeEditor): GoToLocationValues { @@ -426,11 +451,11 @@ registerEditorAction(class GoToTypeDefinitionAction extends TypeDefinitionAction primary: 0, weight: KeybindingWeight.EditorContrib }, - menuOpts: { + contextMenuOpts: { group: 'navigation', order: 1.4 }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarGoMenu, group: '4_symbol_nav', order: 3, @@ -457,7 +482,12 @@ registerEditorAction(class PeekTypeDefinitionAction extends TypeDefinitionAction EditorContextKeys.hasTypeDefinitionProvider, PeekContext.notInPeekEditor, EditorContextKeys.isInEmbeddedEditor.toNegated() - ) + ), + contextMenuOpts: { + menuId: MenuId.EditorContextPeek, + group: 'peek', + order: 4 + } }); } }); @@ -478,12 +508,12 @@ class ImplementationAction extends SymbolNavigationAction { : nls.localize('goToImplementation.generic.noResults', "No implementation found"); } - protected _getAlternativeCommand(): string { - return ''; + protected _getAlternativeCommand(editor: IActiveCodeEditor): string { + return editor.getOption(EditorOption.gotoLocation).alternativeImplementationCommand; } protected _getGoToPreference(editor: IActiveCodeEditor): GoToLocationValues { - return editor.getOption(EditorOption.gotoLocation).multipleImplemenations; + return editor.getOption(EditorOption.gotoLocation).multipleImplementations; } } @@ -508,13 +538,13 @@ registerEditorAction(class GoToImplementationAction extends ImplementationAction primary: KeyMod.CtrlCmd | KeyCode.F12, weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarGoMenu, group: '4_symbol_nav', order: 4, title: nls.localize({ key: 'miGotoImplementation', comment: ['&& denotes a mnemonic'] }, "Go to &&Implementations") }, - menuOpts: { + contextMenuOpts: { group: 'navigation', order: 1.45 } @@ -544,6 +574,11 @@ registerEditorAction(class PeekImplementationAction extends ImplementationAction kbExpr: EditorContextKeys.editorTextFocus, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.F12, weight: KeybindingWeight.EditorContrib + }, + contextMenuOpts: { + menuId: MenuId.EditorContextPeek, + group: 'peek', + order: 5 } }); } @@ -565,8 +600,8 @@ class ReferencesAction extends SymbolNavigationAction { : nls.localize('references.noGeneric', "No references found"); } - protected _getAlternativeCommand(): string { - return ''; + protected _getAlternativeCommand(editor: IActiveCodeEditor): string { + return editor.getOption(EditorOption.gotoLocation).alternativeReferenceCommand; } protected _getGoToPreference(editor: IActiveCodeEditor): GoToLocationValues { @@ -595,11 +630,11 @@ registerEditorAction(class GoToReferencesAction extends ReferencesAction { primary: KeyMod.Shift | KeyCode.F12, weight: KeybindingWeight.EditorContrib }, - menuOpts: { + contextMenuOpts: { group: 'navigation', order: 1.45 }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarGoMenu, group: '4_symbol_nav', order: 5, @@ -624,7 +659,12 @@ registerEditorAction(class PeekReferencesAction extends ReferencesAction { EditorContextKeys.hasReferenceProvider, PeekContext.notInPeekEditor, EditorContextKeys.isInEmbeddedEditor.toNegated() - ) + ), + contextMenuOpts: { + menuId: MenuId.EditorContextPeek, + group: 'peek', + order: 6 + } }); } }); diff --git a/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts index 33813997edb..48a83e6d40e 100644 --- a/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts @@ -6,6 +6,7 @@ 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'; @@ -364,8 +365,11 @@ export class ReferenceWidget extends peekView.PeekViewWidget { 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 (e.browserEvent instanceof KeyboardEvent || (e.browserEvent instanceof MouseEvent && e.browserEvent.detail === 2)) { - // keybinding (list service command) OR double click -> close widget and goto target + } 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 diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index ca710263b24..de0e08e7099 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -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; diff --git a/src/vs/editor/contrib/linesOperations/linesOperations.ts b/src/vs/editor/contrib/linesOperations/linesOperations.ts index b615da8257b..d351f6af863 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/multicursor/multicursor.ts b/src/vs/editor/contrib/multicursor/multicursor.ts index 4434040595a..a5341c5896b 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"), @@ -662,7 +662,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"), @@ -682,7 +682,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"), @@ -740,7 +740,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"), @@ -759,13 +759,13 @@ export class CompatChangeAll extends MultiCursorSelectionControllerAction { id: 'editor.action.changeAll', label: nls.localize('changeAll.label', "Change All Occurrences"), alias: 'Change All Occurrences', - precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.editorTextFocus, EditorContextKeys.hasRenameProvider.toNegated()), + precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.editorTextFocus), kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyMod.CtrlCmd | KeyCode.F2, weight: KeybindingWeight.EditorContrib }, - menuOpts: { + contextMenuOpts: { group: '1_modification', order: 1.2 } diff --git a/src/vs/editor/contrib/rename/rename.ts b/src/vs/editor/contrib/rename/rename.ts index 8eb67d9fe04..86cd28d8d01 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 81b60e73d91..a98044f0ada 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 f0eedb8324d..b161849905b 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/suggest/media/suggest.css b/src/vs/editor/contrib/suggest/media/suggest.css index a58882341b8..922fa8cb6e5 100644 --- a/src/vs/editor/contrib/suggest/media/suggest.css +++ b/src/vs/editor/contrib/suggest/media/suggest.css @@ -276,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/suggestController.ts b/src/vs/editor/contrib/suggest/suggestController.ts index 007b89d7203..8757b59eb27 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 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,73 +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 { item } = event; - const { completion: suggestion, position } = item; - const columnDelta = this._editor.getPosition().column - position.column; + 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(), item); + this._memoryService.memorize(model, this.editor.getPosition(), item); let { insertText } = suggestion; if (!(suggestion.insertTextRules! & CompletionItemInsertTextRule.InsertAsSnippet)) { insertText = SnippetParser.escape(insertText); } - const overwriteConfig = flags & InsertFlags.AlternativeOverwriteConfig - ? !this._editor.getOption(EditorOption.suggest).overwriteOnAccept - : this._editor.getOption(EditorOption.suggest).overwriteOnAccept; + const info = this.getOverwriteInfo(item, Boolean(flags & InsertFlags.AlternativeOverwriteConfig)); - const overwriteBefore = position.column - item.editStart.column; - const overwriteAfter = (overwriteConfig ? item.editReplaceEnd.column : item.editInsertEnd.column) - position.column; - const suffixDelta = this._lineSuffix.value ? this._lineSuffix.value.delta(this._editor.getPosition()) : 0; - - 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) { @@ -321,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); @@ -329,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); } }; @@ -354,14 +374,14 @@ export class SuggestController implements IEditorContribution { // snippet, other editor -> makes edit return true; } - const position = this._editor.getPosition()!; + 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, @@ -371,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; @@ -425,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(); } } diff --git a/src/vs/editor/contrib/suggest/suggestRangeHighlighter.ts b/src/vs/editor/contrib/suggest/suggestRangeHighlighter.ts new file mode 100644 index 00000000000..0056a622743 --- /dev/null +++ b/src/vs/editor/contrib/suggest/suggestRangeHighlighter.ts @@ -0,0 +1,125 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. 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/test/completionModel.test.ts b/src/vs/editor/contrib/suggest/test/completionModel.test.ts index 0f4d43d9902..6a26c5fd584 100644 --- a/src/vs/editor/contrib/suggest/test/completionModel.test.ts +++ b/src/vs/editor/contrib/suggest/test/completionModel.test.ts @@ -8,7 +8,7 @@ import * as modes from 'vs/editor/common/modes'; import { CompletionModel } from 'vs/editor/contrib/suggest/completionModel'; import { CompletionItem, getSuggestionComparator, SnippetSortOrder } from 'vs/editor/contrib/suggest/suggest'; import { WordDistance } from 'vs/editor/contrib/suggest/wordDistance'; -import { EditorOptions } from 'vs/editor/common/config/editorOptions'; +import { EditorOptions, InternalSuggestOptions } from 'vs/editor/common/config/editorOptions'; export function createSuggestItem(label: string, overwriteBefore: number, kind = modes.CompletionItemKind.Property, incomplete: boolean = false, position: IPosition = { lineNumber: 1, column: 1 }, sortText?: string, filterText?: string): CompletionItem { const suggestion: modes.CompletionItem = { @@ -33,8 +33,8 @@ export function createSuggestItem(label: string, overwriteBefore: number, kind = } suite('CompletionModel', function () { - let defaultOptions = { - overwriteOnAccept: false, + let defaultOptions = { + insertMode: 'insert', snippetsPreventQuickSuggestions: true, filterGraceful: true, localityBonus: false, diff --git a/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts b/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts index b5d2715c808..d00c36ea973 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 8bd72a304a2..22ab5a56c83 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/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index c2ed588baf1..d9701b08ae0 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -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; } diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index 82b29c68eb8..87b5de2618b 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -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); } diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index f03b1e1415e..41fadf14389 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -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()); diff --git a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts index 61cf4274a0d..445bf8a2528 100644 --- a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts +++ b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts @@ -14,7 +14,6 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { Registry } from 'vs/platform/registry/common/platform'; import { ColorIdentifier, Extensions, IColorRegistry } from 'vs/platform/theme/common/colorRegistry'; import { Extensions as ThemingExtensions, ICssStyleCollector, IIconTheme, IThemingRegistry } from 'vs/platform/theme/common/themeService'; -import { TokenStyle, TokenClassification, ProbeScope } from 'vs/platform/theme/common/tokenClassificationRegistry'; const VS_THEME_NAME = 'vs'; const VS_DARK_THEME_NAME = 'vs-dark'; @@ -131,12 +130,12 @@ class StandaloneTheme implements IStandaloneTheme { return this._tokenTheme; } - getTokenStyle(classification: TokenClassification, useDefault?: boolean | undefined): TokenStyle | undefined { + public getTokenStyleMetadata(type: string, modifiers: string[]): number | undefined { return undefined; } - resolveScopes(scopes: ProbeScope[]): TokenStyle | undefined { - return undefined; + public get tokenColorMap(): string[] { + return []; } } diff --git a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts index 3cb4e474b1d..12bfb828dff 100644 --- a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts +++ b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts @@ -56,11 +56,14 @@ suite('TokenizationSupport2Adapter', () => { throw new Error('Not implemented'); }, - getTokenStyle: () => undefined, - resolveScopes: () => undefined + getTokenStyleMetadata: (type: string, modifiers: string[]): number | undefined => { + return undefined; + }, + tokenColorMap: [] }; } + public getIconTheme(): IIconTheme { return { hasFileIcons: false, diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index cabab834266..454ce0ac3c9 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -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/view/minimapCharRenderer.test.ts b/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts index f45f5fa1162..56281b80706 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 9ddd334797a..00000000000 --- 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 b064114f722..00000000000 --- 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 MIT License. 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/modes/textToHtmlTokenizer.test.ts b/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts index 77f96fc20af..fb4c4028beb 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/modelService.test.ts b/src/vs/editor/test/common/services/modelService.test.ts index 0a28a285b38..8b72a3493c3 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/monaco.d.ts b/src/vs/monaco.d.ts index b1d411f7aef..996495a26d5 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3066,15 +3066,17 @@ declare namespace monaco.editor { * Configuration options for go to location */ export interface IGotoLocationOptions { - /** - * Control how goto-command work when having multiple results. - */ multiple?: GoToLocationValues; multipleDefinitions?: GoToLocationValues; multipleTypeDefinitions?: GoToLocationValues; multipleDeclarations?: GoToLocationValues; - multipleImplemenations?: GoToLocationValues; + multipleImplementations?: GoToLocationValues; multipleReferences?: GoToLocationValues; + alternativeDefinitionCommand?: string; + alternativeTypeDefinitionCommand?: string; + alternativeDeclarationCommand?: string; + alternativeImplementationCommand?: string; + alternativeReferenceCommand?: string; } export type GoToLocationOptions = Readonly>; @@ -3396,7 +3398,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. */ @@ -4233,6 +4239,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 { @@ -4973,6 +4983,7 @@ declare namespace monaco.languages { diagnostics?: editor.IMarkerData[]; kind?: string; isPreferred?: boolean; + disabled?: string; } export interface CodeActionList extends IDisposable { diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index 05efd4bd424..77d930aa7fd 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -147,7 +147,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.iconLocation || _action.item.iconClassName), label: !_action.class && !_action.item.iconLocation && !_action.item.iconClassName }); this._altKey = AlternativeKeyEmitter.getInstance(_contextMenuService); } diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index cc7b367fd5d..99a84647d28 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -65,6 +65,7 @@ export const enum MenuId { DebugWatchContext, DebugToolBar, EditorContext, + EditorContextPeek, EditorTitle, EditorTitleContext, EmptyEditorGroupContext, diff --git a/src/vs/platform/actions/common/menuService.ts b/src/vs/platform/actions/common/menuService.ts index 651e6fe27f4..29787fb379f 100644 --- a/src/vs/platform/actions/common/menuService.ts +++ b/src/vs/platform/actions/common/menuService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { IMenu, IMenuActionOptions, IMenuItem, IMenuService, isIMenuItem, ISubmenuItem, MenuId, MenuItemAction, MenuRegistry, SubmenuItemAction } 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); } } diff --git a/src/vs/platform/auth/common/auth.css b/src/vs/platform/auth/common/auth.css new file mode 100644 index 00000000000..e87a6372763 --- /dev/null +++ b/src/vs/platform/auth/common/auth.css @@ -0,0 +1,100 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +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 00000000000..8fe3e50e7b7 --- /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 390bc6d64da..81af0bbf0a4 100644 --- a/src/vs/platform/auth/common/auth.ts +++ b/src/vs/platform/auth/common/auth.ts @@ -8,10 +8,11 @@ import { Event, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; export const enum AuthTokenStatus { - Inactive = 'Inactive', - Active = 'Active', + Initializing = 'Initializing', + SignedOut = 'SignedOut', + SignedIn = 'SignedIn', SigningIn = 'SigningIn', - Refreshing = 'Refreshing' + RefreshingToken = 'RefreshingToken' } export const IAuthTokenService = createDecorator('IAuthTokenService'); @@ -25,6 +26,6 @@ export interface IAuthTokenService { getToken(): Promise; refreshToken(): Promise; - login(callbackUri?: URI): Promise; + login(): Promise; logout(): Promise; } diff --git a/src/vs/platform/auth/common/authTokenIpc.ts b/src/vs/platform/auth/common/authTokenIpc.ts index 99a2111750c..eff088c1114 100644 --- a/src/vs/platform/auth/common/authTokenIpc.ts +++ b/src/vs/platform/auth/common/authTokenIpc.ts @@ -22,11 +22,8 @@ export class AuthTokenChannel implements IServerChannel { switch (command) { case '_getInitialStatus': return Promise.resolve(this.service.status); case 'getToken': return this.service.getToken(); - case 'exchangeCodeForToken': - this.service._onDidGetCallback.fire(args); - return Promise.resolve(); case 'refreshToken': return this.service.refreshToken(); - case 'login': return this.service.login(args); + 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 00000000000..b2f227114fd --- /dev/null +++ b/src/vs/platform/auth/electron-browser/authServer.ts @@ -0,0 +1,162 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. 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'; + +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(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); + } 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 index 7438d2708af..971e8f7a967 100644 --- a/src/vs/platform/auth/electron-browser/authTokenService.ts +++ b/src/vs/platform/auth/electron-browser/authTokenService.ts @@ -8,29 +8,18 @@ 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, IDisposable } from 'vs/base/common/lifecycle'; +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 redirectUrlAAD = 'https://vscode-redirect.azurewebsites.net/'; -const activeDirectoryEndpointUrl = 'https://login.microsoftonline.com/'; const activeDirectoryResourceId = 'https://management.core.windows.net/'; -const clientId = 'aebc6443-996d-45c2-90f0-388ff96faa56'; -const tenantId = 'common'; - -function parseQuery(uri: URI) { - return uri.query.split('&').reduce((prev: any, current) => { - const queryString = current.split('='); - prev[queryString[0]] = queryString[1]; - return prev; - }, {}); -} - function toQuery(obj: any): string { return Object.keys(obj).map(key => `${key}=${obj[key]}`).join('&'); } @@ -49,7 +38,7 @@ export interface IToken { export class AuthTokenService extends Disposable implements IAuthTokenService { _serviceBrand: undefined; - private _status: AuthTokenStatus = AuthTokenStatus.Refreshing; + private _status: AuthTokenStatus = AuthTokenStatus.Initializing; get status(): AuthTokenStatus { return this._status; } private _onDidChangeStatus: Emitter = this._register(new Emitter()); readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; @@ -61,45 +50,82 @@ export class AuthTokenService extends Disposable implements IAuthTokenService { 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.Inactive); + this.setStatus(AuthTokenStatus.SignedOut); } }); } - public async login(callbackUri: URI): Promise { + public async login(): Promise { + if (!this.productService.auth) { + throw new Error('Authentication is not configured.'); + } + this.setStatus(AuthTokenStatus.SigningIn); + const nonce = generateUuid(); - const port = (callbackUri.authority.match(/:([0-9]*)$/) || [])[1] || (callbackUri.scheme === 'https' || callbackUri.scheme === 'http' ? 443 : 80); - const state = `${callbackUri.scheme},${port},${encodeURIComponent(nonce)},${encodeURIComponent(callbackUri.query)}`; - const signInUrl = `${activeDirectoryEndpointUrl}${tenantId}/oauth2/authorize`; + const { server, redirectPromise, codePromise } = createServer(nonce); - const codeVerifier = toBase64UrlEncoding(crypto.randomBytes(32).toString('base64')); - const codeChallenge = toBase64UrlEncoding(crypto.createHash('sha256').update(codeVerifier).digest('base64')); + try { + const port = await startServer(server); + shell.openExternal(`http://localhost:${port}/signin?nonce=${encodeURIComponent(nonce)}`); - let uri = URI.parse(signInUrl); - uri = uri.with({ - query: `response_type=code&client_id=${encodeURIComponent(clientId)}&redirect_uri=${redirectUrlAAD}&state=${encodeURIComponent(state)}&resource=${activeDirectoryResourceId}&prompt=select_account&code_challenge_method=S256&code_challenge=${codeChallenge}` - }); + 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; + } - await shell.openExternal(uri.toString(true)); + 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 timeoutPromise = new Promise((resolve: (value: IToken) => void, reject) => { - const wait = setTimeout(() => { - this.setStatus(AuthTokenStatus.Inactive); - clearTimeout(wait); - reject('Login timed out.'); - }, 1000 * 60 * 5); - }); + 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); + } - return Promise.race([this.exchangeCodeForToken(clientId, tenantId, codeVerifier, state), timeoutPromise]).then(token => { - this.setToken(token); - }); } public getToken(): Promise { @@ -117,90 +143,86 @@ export class AuthTokenService extends Disposable implements IAuthTokenService { private setToken(token: IToken) { this._activeToken = token; this.credentialsService.setPassword(SERVICE_NAME, ACCOUNT, token.refreshToken); - this.setStatus(AuthTokenStatus.Active); + this.setStatus(AuthTokenStatus.SignedIn); } - private async exchangeCodeForToken(clientId: string, tenantId: string, codeVerifier: string, state: string): Promise { - let uriEventListener: IDisposable; + private exchangeCodeForToken(code: string, codeVerifier: string): Promise { return new Promise((resolve: (value: IToken) => void, reject) => { - uriEventListener = this.onDidGetCallback(async (uri: URI) => { - try { - const query = parseQuery(uri); - const code = query.code; - - if (query.state !== state) { - return; - } - - const postData = toQuery({ - grant_type: 'authorization_code', - code: code, - client_id: clientId, - code_verifier: codeVerifier, - redirect_uri: redirectUrlAAD - }); - - const post = https.request({ - host: 'login.microsoftonline.com', - path: `/${tenantId}/oauth2/token`, - 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); + try { + if (!this.productService.auth) { + throw new Error('Authentication is not configured.'); } - }); - }).then(result => { - uriEventListener.dispose(); - return result; - }).catch(err => { - uriEventListener.dispose(); - throw err; + + 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) => { - this.setStatus(AuthTokenStatus.Refreshing); + if (!this.productService.auth) { + throw new Error('Authentication is not configured.'); + } + + this.setStatus(AuthTokenStatus.RefreshingToken); const postData = toQuery({ refresh_token: refreshToken, - client_id: clientId, + 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: 'login.microsoftonline.com', - path: `/${tenantId}/oauth2/token`, + host: tokenUrl.authority, + path: tokenUrl.path, method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', @@ -231,7 +253,7 @@ export class AuthTokenService extends Disposable implements IAuthTokenService { post.end(); post.on('error', err => { - this.setStatus(AuthTokenStatus.Inactive); + this.setStatus(AuthTokenStatus.SignedOut); reject(err); }); }); @@ -240,7 +262,7 @@ export class AuthTokenService extends Disposable implements IAuthTokenService { async logout(): Promise { await this.credentialsService.deletePassword(SERVICE_NAME, ACCOUNT); this._activeToken = undefined; - this.setStatus(AuthTokenStatus.Inactive); + this.setStatus(AuthTokenStatus.SignedOut); } private setStatus(status: AuthTokenStatus): void { diff --git a/src/vs/platform/dialogs/common/dialogs.ts b/src/vs/platform/dialogs/common/dialogs.ts index 64f783471ba..81615b1c9e3 100644 --- a/src/vs/platform/dialogs/common/dialogs.ts +++ b/src/vs/platform/dialogs/common/dialogs.ts @@ -242,7 +242,7 @@ export interface IFileDialogService { /** * Shows a confirm dialog for saving 1-N files. */ - showSaveConfirm(fileNameOrResources: string | URI[]): Promise; + showSaveConfirm(fileNamesOrResources: (string | URI)[]): Promise; /** * Shows a open file dialog and returns the chosen file URI. @@ -257,16 +257,16 @@ export const enum ConfirmResult { } 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/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index a3c9d9b927d..f3fb5ecca84 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -580,7 +580,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); } @@ -590,7 +590,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); } diff --git a/src/vs/platform/files/common/fileService.ts b/src/vs/platform/files/common/fileService.ts index d914b9c5810..ecf261d3610 100644 --- a/src/vs/platform/files/common/fileService.ts +++ b/src/vs/platform/files/common/fileService.ts @@ -220,7 +220,6 @@ export class FileService extends Disposable implements IFileService { 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, @@ -683,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.")); } } @@ -693,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 @@ -701,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.")); } } } diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 15f2e20910b..658998bd7c2 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -615,11 +615,6 @@ interface IBaseStat { * it is optional. */ etag?: string; - - /** - * The resource is readonly. - */ - isReadonly?: boolean; } export interface IBaseStatWithMetadata extends IBaseStat { diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts index e905ecfa297..6db9725704d 100644 --- a/src/vs/platform/product/common/productService.ts +++ b/src/vs/platform/product/common/productService.ts @@ -103,7 +103,12 @@ export interface IProductConfiguration { 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/storage/node/storageIpc.ts b/src/vs/platform/storage/node/storageIpc.ts index b0a2b3752a5..2d04f0a77f7 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/theme/common/themeService.ts b/src/vs/platform/theme/common/themeService.ts index 8117fd486a3..f2b0f08a256 100644 --- a/src/vs/platform/theme/common/themeService.ts +++ b/src/vs/platform/theme/common/themeService.ts @@ -10,7 +10,6 @@ import * as platform from 'vs/platform/registry/common/platform'; import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; import { Event, Emitter } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { TokenStyle, TokenClassification, ProbeScope } from 'vs/platform/theme/common/tokenClassificationRegistry'; export const IThemeService = createDecorator('themeService'); @@ -61,9 +60,15 @@ export interface ITheme { */ defines(color: ColorIdentifier): boolean; - getTokenStyle(classification: TokenClassification, useDefault?: boolean): TokenStyle | undefined; + /** + * Returns the token style for a given classification. The result uses the MetadataConsts format + */ + getTokenStyleMetadata(type: string, modifiers: string[]): number | undefined; - resolveScopes(scopes: ProbeScope[]): TokenStyle | 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 index 2ec489ae46f..3fa3e0c9c46 100644 --- a/src/vs/platform/theme/common/tokenClassificationRegistry.ts +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -109,7 +109,6 @@ export interface ITokenClassificationRegistry { */ registerTokenModifier(id: string, description: string): void; - getTokenClassificationFromString(str: TokenClassificationString): TokenClassification | undefined; getTokenClassification(type: string, modifiers: string[]): TokenClassification | undefined; getTokenStylingRule(classification: TokenClassification, value: TokenStyle): TokenStylingRule; @@ -142,9 +141,9 @@ export interface ITokenClassificationRegistry { getTokenModifiers(): TokenTypeOrModifierContribution[]; /** - * Resolves a token classification against the given rules and default rules from the registry. + * The styling rules to used when a schema does not define any styling rules. */ - resolveTokenStyle(classification: TokenClassification, themingRules: TokenStylingRule[] | undefined, customThemingRules: TokenStylingRule[], theme: ITheme): TokenStyle | undefined; + getTokenStylingDefaultRules(): TokenStylingDefaultRule[]; /** * JSON schema for an object to assign styling to token classifications @@ -168,6 +167,7 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { private tokenStylingSchema: IJSONSchema & { properties: IJSONSchemaMap } = { type: 'object', properties: {}, + additionalProperties: getStylingSchemeEntry(), definitions: { style: { type: 'object', @@ -236,15 +236,6 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { return { type: tokenTypeDesc.num, modifiers: allModifierBits }; } - public getTokenClassificationFromString(str: TokenClassificationString): TokenClassification | undefined { - const parts = str.split('.'); - const type = parts.shift(); - if (type) { - return this.getTokenClassification(type, parts); - } - return undefined; - } - public getTokenStylingRule(classification: TokenClassification, value: TokenStyle): TokenStylingRule { return { classification, matchScore: getTokenStylingScore(classification), value }; } @@ -271,87 +262,13 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { return Object.keys(this.tokenModifierById).map(id => this.tokenModifierById[id]); } - public resolveTokenStyle(classification: TokenClassification, themingRules: TokenStylingRule[] | undefined, customThemingRules: TokenStylingRule[], theme: ITheme): 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 (themingRules === undefined) { - for (const rule of this.tokenStylingDefaultRules) { - const matchScore = match(rule, classification); - if (matchScore >= 0) { - let style = theme.resolveScopes(rule.defaults.scopesToProbe); - if (!style) { - style = this.resolveTokenStyleValue(rule.defaults[theme.type], theme); - } - if (style) { - _processStyle(matchScore, style); - } - } - } - } else { - for (const rule of themingRules) { - const matchScore = match(rule, classification); - if (matchScore >= 0) { - _processStyle(matchScore, rule.value); - } - } - } - for (const rule of customThemingRules) { - const matchScore = match(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, theme: ITheme): TokenStyle | undefined { - if (tokenStyleValue === null) { - return undefined; - } else if (typeof tokenStyleValue === 'string') { - const classification = this.getTokenClassificationFromString(tokenStyleValue); - if (classification) { - return theme.getTokenStyle(classification); - } - } else if (typeof tokenStyleValue === 'object') { - return tokenStyleValue; - } - return undefined; - } - public getTokenStylingSchema(): IJSONSchema { return this.tokenStylingSchema; } + public getTokenStylingDefaultRules(): TokenStylingDefaultRule[] { + return this.tokenStylingDefaultRules; + } public toString() { let sorter = (a: string, b: string) => { @@ -368,7 +285,7 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { } -function match(themeSelector: TokenStylingRule | TokenStylingDefaultRule, classification: TokenClassification): number { +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; @@ -448,7 +365,7 @@ function getTokenStylingScore(classification: TokenClassification) { return bitCount(classification.modifiers) + ((classification.type !== TOKEN_TYPE_WILDCARD_NUM) ? 1 : 0); } -function getStylingSchemeEntry(description: string, deprecationMessage?: string): IJSONSchema { +function getStylingSchemeEntry(description?: string, deprecationMessage?: string): IJSONSchema { return { description, deprecationMessage, diff --git a/src/vs/platform/theme/test/common/testThemeService.ts b/src/vs/platform/theme/test/common/testThemeService.ts index b9279dc39ee..8df0303f8bb 100644 --- a/src/vs/platform/theme/test/common/testThemeService.ts +++ b/src/vs/platform/theme/test/common/testThemeService.ts @@ -6,7 +6,6 @@ import { Event, Emitter } from 'vs/base/common/event'; import { IThemeService, ITheme, DARK, IIconTheme } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; -import { TokenStyle, TokenClassification, ProbeScope } from 'vs/platform/theme/common/tokenClassificationRegistry'; export class TestTheme implements ITheme { @@ -25,12 +24,12 @@ export class TestTheme implements ITheme { throw new Error('Method not implemented.'); } - getTokenStyle(classification: TokenClassification, useDefault?: boolean | undefined): TokenStyle | undefined { - throw new Error('Method not implemented.'); + getTokenStyleMetadata(type: string, modifiers: string[]): number | undefined { + return undefined; } - resolveScopes(scopes: ProbeScope[]): TokenStyle | undefined { - throw new Error('Method not implemented.'); + get tokenColorMap(): string[] { + return []; } } diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 54fcb05a56c..720d0d86ccc 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,7 +162,7 @@ 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.'); diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 03d97cc1418..1a757524dd6 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; } @@ -252,10 +252,10 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { if (settingsContent) { const setting = parse(settingsContent); if (setting) { - value = setting['configurationSync.settingsToIgnore']; + value = setting['sync.ignoredSettings']; } } else { - value = this.configurationService.getValue('configurationSync.settingsToIgnore'); + value = this.configurationService.getValue('sync.ignoredSettings'); } const added: string[] = [], removed: string[] = []; if (Array.isArray(value)) { diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 7f8d907a421..5771808a1c9 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -14,50 +14,54 @@ 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'; + +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, synchronizes configuration that includes Settings and Extensions."), + 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 synchronized 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 synchronized while synchronizing configuration."), + description: localize('sync.enableExtensions', "Enable synchronizing extensions."), default: true, scope: ConfigurationScope.APPLICATION, }, - 'configurationSync.extensionsToIgnore': { + '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, @@ -98,12 +102,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; diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 85b8a9ee152..91eed22298e 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -43,16 +43,20 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this.extensionsSynchroniser = this._register(this.instantiationService.createInstance(ExtensionsSynchroniser)); this.synchronisers = [this.settingsSynchroniser, 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) { + if (this.authTokenService.status === AuthTokenStatus.SignedOut) { throw new Error('Not Authenticated. Please sign in to start sync.'); } for (const synchroniser of this.synchronisers) { @@ -64,7 +68,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 +93,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)) { @@ -112,7 +116,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } private onDidChangeAuthTokenStatus(): void { - if (this.authTokenService.status === AuthTokenStatus.Inactive) { + if (this.authTokenService.status === AuthTokenStatus.SignedOut) { this.stop(); } } @@ -133,7 +137,7 @@ export class UserDataAutoSync extends Disposable { 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))); + this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('sync.enable'))(() => this.updateEnablement(true))); // Sync immediately if there is a local change. this._register(Event.debounce(this.userDataSyncService.onDidChangeLocal, () => undefined, 500)(() => this.sync(false))); @@ -174,9 +178,9 @@ export class UserDataAutoSync extends Disposable { } private isSyncEnabled(): boolean { - return this.configurationService.getValue('configurationSync.enable') + return this.configurationService.getValue('sync.enable') && this.userDataSyncService.status !== SyncStatus.Uninitialized - && this.authTokenService.status !== AuthTokenStatus.Inactive; + && this.authTokenService.status === AuthTokenStatus.SignedIn; } } diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index faf1ec0c400..bc499a7c48a 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 } 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) { + 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) { + 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; diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 4eed2e45d76..555187ec1d0 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -3015,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. @@ -4843,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 @@ -7284,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. diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 2b5b8549455..ce948daac9b 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -826,6 +826,53 @@ declare module 'vscode' { //#region mjbvz,joh: https://github.com/Microsoft/vscode/issues/43768 + /** + * 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; + } + + /** + * An event that is fired after files are created. + */ export interface FileCreateEvent { /** @@ -834,17 +881,53 @@ declare module 'vscode' { readonly files: ReadonlyArray; } - export interface FileWillCreateEvent { + /** + * 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 created. + * 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 { /** @@ -853,17 +936,53 @@ declare module 'vscode' { readonly files: ReadonlyArray; } - export interface FileWillDeleteEvent { + /** + * 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 deleted. + * The files that are going to be renamed. */ - readonly files: ReadonlyArray; + 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 { /** @@ -872,51 +991,76 @@ declare module 'vscode' { readonly files: ReadonlyArray<{ oldUri: Uri, newUri: Uri }>; } - export interface FileWillRenameEvent { - - /** - * The files that are going to be renamed. - */ - readonly files: ReadonlyArray<{ oldUri: Uri, newUri: Uri }>; - - waitUntil(thenable: Thenable): void; - waitUntil(thenable: Thenable): void; - } - export namespace workspace { /** * An event that is emitted when files are being created. * - * *Note* that this event is triggered by user gestures, like creating a file from the + * *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 are being deleted. + * An event that is emitted when files have been created. * - * *Note* that this event is triggered by user gestures, like deleting a file from the - * explorer, or from the [`workspace.applyEdit`](#workspace.applyEdit)-api. This event is *not* fired when + * *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 are being renamed. + * An event that is emitted when files have been deleted. * - * *Note* that this event is triggered by user gestures, like renaming a file from the - * explorer, and from the [`workspace.applyEdit`](#workspace.applyEdit)-api. This event is *not* fired when + * *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; - export const onDidCreateFiles: Event; - export const onDidDeleteFiles: 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 @@ -932,14 +1076,6 @@ declare module 'vscode' { //#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; - } - /** * Label describing the [Tree item](#TreeItem) */ @@ -1073,7 +1209,7 @@ declare module 'vscode' { //#region Custom editors, mjbvz /** - * + * Defines how a webview editor interacts with VS Code. */ interface WebviewEditorCapabilities { /** @@ -1088,26 +1224,36 @@ declare module 'vscode' { * * @return Thenable that signals the save is complete. */ - rename?(newResource: Uri): Thenable; + // 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; /** - * Called when the editor exits. - */ - hotExit(hotExitPath: Uri): Thenable; - - /** - * Signal to VS Code that an edit has occurred. + * Event triggered by extensions to signal to VS Code that an edit has occurred. * - * Edits must be a json serilizable object. + * The edit must be a json serializable object. */ readonly onEdit: Event; @@ -1133,14 +1279,16 @@ declare module 'vscode' { export interface WebviewEditorProvider { /** - * Fills out a `WebviewEditor` for a given resource. + * 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. - * **❗️Note**: `WebviewEditorCapabilities` is not actually implemented... yet! */ resolveWebviewEditor( input: { @@ -1151,6 +1299,15 @@ declare module 'vscode' { } 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, @@ -1190,4 +1347,18 @@ declare module 'vscode' { } //#endregion + + //#region mjbvz - 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/mainThreadFileSystemEventService.ts b/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts index 295bd0d8c9e..38f3b5fdca9 100644 --- a/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts +++ b/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts @@ -10,6 +10,11 @@ import { ExtHostContext, FileSystemEvents, IExtHostContext } from '../common/ext 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 { @@ -20,7 +25,9 @@ export class MainThreadFileSystemEventService { extHostContext: IExtHostContext, @IFileService fileService: IFileService, @ITextFileService textFileService: ITextFileService, - @IProgressService progressService: IProgressService + @IProgressService progressService: IProgressService, + @IConfigurationService configService: IConfigurationService, + @ILogService logService: ILogService, ) { const proxy = extHostContext.getProxy(ExtHostContext.ExtHostFileSystemEventService); @@ -59,16 +66,33 @@ export class MainThreadFileSystemEventService { 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 + } + const p = progressService.withProgress({ location: ProgressLocation.Window }, progress => { progress.report({ message: messages.get(e.operation) }); - const p1 = proxy.$onWillRunFileOperation(e.operation, e.target, e.source); - const p2 = new Promise((_resolve, reject) => { - setTimeout(() => reject(new Error('timeout')), 5000); + 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)); }); - return Promise.race([p1, p2]); + }); e.waitUntil(p); @@ -82,3 +106,15 @@ export class MainThreadFileSystemEventService { 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/mainThreadSaveParticipant.ts b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts index 023e413f8cc..bf7d5c6660d 100644 --- a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts @@ -307,7 +307,7 @@ class CodeActionOnSaveParticipant implements ISaveParticipant { for (const codeActionKind of codeActionsOnSave) { 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 { diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 691cd725136..3fad85d1949 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'; @@ -422,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/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 93d36038334..c35de421c57 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -116,7 +116,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I 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)); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 35dbdf8eafe..800b3ee2f15 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -923,7 +923,7 @@ export interface FileSystemEvents { export interface ExtHostFileSystemEventServiceShape { $onFileEvent(events: FileSystemEvents): void; - $onWillRunFileOperation(operation: files.FileOperation, target: UriComponents, source: UriComponents | undefined): 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; } @@ -1086,6 +1086,7 @@ export interface ICodeActionDto { command?: ICommandDto; kind?: string; isPreferred?: boolean; + disabled?: string; } export interface ICodeActionListDto { diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index a80504d6c0f..75bc3851448 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -3,11 +3,35 @@ * Licensed under the MIT License. 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 } 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, IDebugAdapterImplementation } 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'); @@ -33,3 +57,1064 @@ export interface IExtHostDebugService extends ExtHostDebugServiceShape { asDebugSourceUri(source: vscode.DebugSource, 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.DebugSource, 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) { + + const a = x; + if (a['implementation']) { + return { + type: 'implementation', + implementation: a['implementation'] + }; + } + + 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)); + } + + 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); + } +} + +interface IDapTransport { + start(cb: (msg: DebugProtocol.ProtocolMessage) => void, errorcb: (event: DebugProtocol.Event) => void): void; + send(message: DebugProtocol.ProtocolMessage): void; + stop(): void; +} + +/* + * experimental: call directly into a debug adapter implementation + */ +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.'); + } +} + + +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/extHostDocumentData.ts b/src/vs/workbench/api/common/extHostDocumentData.ts index 064cbbb0426..28e76f2a267 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 { @@ -255,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/extHostExtensionActivator.ts b/src/vs/workbench/api/common/extHostExtensionActivator.ts index bc2edc0c2af..efa750e4b65 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 b5ce835e074..a3b5ed0057d 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/extHostFileSystemEventService.ts b/src/vs/workbench/api/common/extHostFileSystemEventService.ts index f39a22d203e..39bc81f6f8c 100644 --- a/src/vs/workbench/api/common/extHostFileSystemEventService.ts +++ b/src/vs/workbench/api/common/extHostFileSystemEventService.ts @@ -14,6 +14,8 @@ 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 { @@ -120,6 +122,7 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ constructor( mainContext: IMainContext, + private readonly _logService: ILogService, private readonly _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors, private readonly _mainThreadTextEditors: MainThreadTextEditorsShape = mainContext.getProxy(MainContext.MainThreadTextEditors) ) { @@ -176,57 +179,52 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ }; } - async $onWillRunFileOperation(operation: FileOperation, target: UriComponents, source: UriComponents | undefined): Promise { + 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) }], }); + 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)] }); + await this._fireWillEvent(this._onWillDeleteFile, { files: [URI.revive(target)] }, timeout, token); break; case FileOperation.CREATE: - await this._fireWillEvent(this._onWillCreateFile, { files: [URI.revive(target)] }); + await this._fireWillEvent(this._onWillCreateFile, { files: [URI.revive(target)] }, timeout, token); break; default: //ignore, dont send } } - private async _fireWillEvent(emitter: AsyncEmitter, data: Omit): Promise { + private async _fireWillEvent(emitter: AsyncEmitter, data: Omit, timeout: number, token: CancellationToken): Promise { const edits: WorkspaceEdit[] = []; - await Promise.resolve(emitter.fireAsync(bucket => { - return { - ...data, - ...{ - waitUntil: (thenable: Promise): void => { - if (Object.isFrozen(bucket)) { - throw new TypeError('waitUntil cannot be called async'); - } - const promise = Promise.resolve(thenable).then(result => { - // ignore all results except for WorkspaceEdits. Those - // are stored in a spare array - if (result instanceof WorkspaceEdit) { - edits.push(result); - } - }); - bucket.push(promise); - } - } - }; - })); - 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; } - // flatten all WorkspaceEdits collected via waitUntil-call - // and apply them in one go. - const allEdits = new Array>(); - for (let edit of edits) { - let { edits } = typeConverter.WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors); - allEdits.push(edits); + 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) { + let { edits } = typeConverter.WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors); + allEdits.push(edits); + } + return this._mainThreadTextEditors.$tryApplyWorkspaceEdit({ edits: flatten(allEdits) }); } - 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 30c3818861f..88464ef0ead 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -389,6 +389,7 @@ class CodeActionAdapter { edit: candidate.edit && typeConvert.WorkspaceEdit.from(candidate.edit), kind: candidate.kind && candidate.kind.value, isPreferred: candidate.isPreferred, + disabled: candidate.disabled }); } } diff --git a/src/vs/workbench/api/common/extHostQuickOpen.ts b/src/vs/workbench/api/common/extHostQuickOpen.ts index 3a85192f04a..8d089783698 100644 --- a/src/vs/workbench/api/common/extHostQuickOpen.ts +++ b/src/vs/workbench/api/common/extHostQuickOpen.ts @@ -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; } diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 74f6b5d98a4..50d9901b68d 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -268,8 +268,8 @@ export class ExtHostPseudoterminal implements ITerminalChildProcess { // Attach the listeners this._pty.onDidWrite(e => this._onProcessData.fire(e)); if (this._pty.onDidClose) { - this._pty.onDidClose((e: number | undefined = undefined) => { - this._onProcessExit.fire(e); + this._pty.onDidClose((e: number | void = undefined) => { + this._onProcessExit.fire(e === void 0 ? undefined : e); }); } if (this._pty.onDidOverrideDimensions) { @@ -288,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; } @@ -462,17 +463,13 @@ 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 { @@ -487,6 +484,12 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ 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 { @@ -525,6 +528,7 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ // Remove process reference delete this._terminalProcesses[id]; + delete this._extensionTerminalAwaitingStart[id]; // Send exit event to main side this._proxy.$sendProcessExit(id, exitCode); diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index 15e80858930..54cc945d6ae 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -93,12 +93,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 => { diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index 2f48b4e01ea..3cf879271d3 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -243,7 +243,7 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa this._capabilities = capabilities; if (capabilities.editingCapability) { this._register(capabilities.editingCapability.onEdit(edit => { - this._proxy.$onEdit(this._handle, JSON.stringify(edit)); + this._proxy.$onEdit(this._handle, edit); })); } } @@ -449,18 +449,12 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { $undoEdits(handle: WebviewPanelHandle, edits: string[]): void { const panel = this.getWebviewPanel(handle); - if (!panel) { - return; - } - panel._undoEdits(edits); + panel?._undoEdits(edits); } $redoEdits(handle: WebviewPanelHandle, edits: string[]): void { const panel = this.getWebviewPanel(handle); - if (!panel) { - return; - } - panel._redoEdits(edits); + panel?._redoEdits(edits); } async $onSave(handle: WebviewPanelHandle): Promise { diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index 81e40b7f893..ac05c037e00 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -3,357 +3,70 @@ * Licensed under the MIT License. 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 { withNullAsUndefined } from 'vs/base/common/types'; +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); } - public asDebugSourceUri(src: vscode.DebugSource, 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.`); + 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); } + return super.createDebugAdapter(adapter, session); } - 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 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); } - - this._debugServiceProxy.$registerDebugTypes(debugTypes); + return undefined; } - // extension debug API - - get onDidChangeBreakpoints(): Event { - return this._onDidChangeBreakpoints.event; + protected createSignService(): ISignService | undefined { + return new SignService(); } - 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 { if (args.kind === 'integrated') { @@ -404,731 +117,11 @@ export class ExtHostDebugService implements IExtHostDebugService, ExtHostDebugSe 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 | 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 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/extHostTask.ts b/src/vs/workbench/api/node/extHostTask.ts index 6e1a647067a..d6d9a1ee84c 100644 --- a/src/vs/workbench/api/node/extHostTask.ts +++ b/src/vs/workbench/api/node/extHostTask.ts @@ -12,7 +12,7 @@ import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import * as vscode from 'vscode'; import * as tasks from '../common/shared/tasks'; import * as Objects from 'vs/base/common/objects'; -import { ExtHostVariableResolverService } from 'vs/workbench/api/node/extHostDebugService'; +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'; @@ -23,6 +23,7 @@ import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitData 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; @@ -102,7 +103,7 @@ export class ExtHostTask extends ExtHostTaskBase { 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); + this._variableResolver = new ExtHostVariableResolverService(workspaceFolders, this._editorService, configProvider, process.env as IProcessEnvironment); } return this._variableResolver; } diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index 64d1a0005b7..bac43635693 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'; @@ -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/composite.ts b/src/vs/workbench/browser/composite.ts index 6414496a6d3..3a1eb2c4e6e 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 66d6d8138a6..2726a5c7d68 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, EditorAreaVisibleContext, DirtyWorkingCopiesContext } 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'; @@ -53,7 +53,7 @@ export class WorkbenchContextKeysHandler extends Disposable { private dirtyWorkingCopiesContext: IContextKey; private activeEditorContext: IContextKey; - private activeEditorIsSaveable: IContextKey; + private activeEditorIsReadonly: IContextKey; private activeEditorGroupEmpty: IContextKey; private activeEditorGroupIndex: IContextKey; @@ -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); @@ -222,10 +222,10 @@ export class WorkbenchContextKeysHandler extends Disposable { if (activeControl) { this.activeEditorContext.set(activeControl.getId()); - this.activeEditorIsSaveable.set(!activeControl.input.isReadonly()); + this.activeEditorIsReadonly.set(activeControl.input.isReadonly()); } else { this.activeEditorContext.reset(); - this.activeEditorIsSaveable.reset(); + this.activeEditorIsReadonly.reset(); } } diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 70b956eb862..94382d7ceb4 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, Dimension, toggleClass } 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'; @@ -905,14 +905,16 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } getClientArea(): Dimension { - const dim = getClientArea(this.parent); - return this.state.windowBorder ? new Dimension(dim.width - 2, dim.height - 2) : dim; + return getClientArea(this.parent); } layout(): void { if (!this.disposed) { this._dimension = this.getClientArea(); + position(this.container, 0, 0, 0, 0, 'relative'); + size(this.container, this._dimension.width, this._dimension.height); + // Layout the grid widget this.workbenchGrid.layout(this._dimension.width, this._dimension.height); diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css index d087cd74997..f6c7bd7b620 100644 --- a/src/vs/workbench/browser/media/style.css +++ b/src/vs/workbench/browser/media/style.css @@ -56,8 +56,6 @@ body.web { position: relative; z-index: 1; overflow: hidden; - height: 100vh; - width: 100vw; } .monaco-workbench.border:not(.fullscreen) { diff --git a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css index 28c43d3ab7a..b15991bd888 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css @@ -91,6 +91,10 @@ 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: 24px; diff --git a/src/vs/workbench/browser/parts/editor/baseEditor.ts b/src/vs/workbench/browser/parts/editor/baseEditor.ts index 669ee10f294..583bdb727a7 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(); } /** diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 7dee5c30a3a..86fe94a3f75 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -52,8 +52,8 @@ 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'; // Register String Editor Registry.as(EditorExtensions.Editors).registerEditor( @@ -224,6 +224,9 @@ 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(SyncActionDescriptor.create(ChangeModeAction, ChangeModeAction.ID, ChangeModeAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_M) }), 'Change Language Mode'); @@ -452,13 +455,14 @@ 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; iconDark?: URI; iconLight?: URI; iconClassName?: string; } function appendEditorToolItem(primary: IEditorToolItem, when: ContextKeyExpr | undefined, order: number, alternative?: IEditorToolItem): void { const item: IMenuItem = { command: { id: primary.id, title: primary.title, + iconClassName: primary.iconClassName, iconLocation: { dark: primary.iconDark, light: primary.iconLight @@ -473,6 +477,7 @@ function appendEditorToolItem(primary: IEditorToolItem, when: ContextKeyExpr | u item.alt = { id: alternative.id, title: alternative.title, + iconClassName: alternative.iconClassName, iconLocation: { dark: alternative.iconDark, light: alternative.iconLight @@ -483,26 +488,19 @@ function appendEditorToolItem(primary: IEditorToolItem, when: ContextKeyExpr | u 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 + iconClassName: '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 + iconClassName: 'codicon-split-vertical' } ); @@ -510,37 +508,30 @@ appendEditorToolItem( { id: SplitEditorAction.ID, title: nls.localize('splitEditorDown', "Split Editor Down"), - iconDark: SPLIT_EDITOR_VERTICAL_DARK_ICON, - iconLight: SPLIT_EDITOR_VERTICAL_LIGHT_ICON + iconClassName: '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 + iconClassName: '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')) + iconClassName: '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 + iconClassName: 'codicon-close-all' } ); @@ -548,16 +539,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')) + iconClassName: '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 + iconClassName: 'codicon-close-all' } ); @@ -566,8 +555,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')) + iconClassName: 'codicon-arrow-up' }, TextCompareEditorActiveContext, 10 @@ -578,8 +566,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')) + iconClassName: 'codicon-arrow-down' }, TextCompareEditorActiveContext, 11 @@ -590,8 +577,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')) + iconClassName: 'codicon-whitespace' }, ContextKeyExpr.and(TextCompareEditorActiveContext, ContextKeyExpr.notEquals('config.diffEditor.ignoreTrimWhitespace', true)), 20 @@ -602,8 +588,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')) + iconClassName: 'codicon-whitespace disabled' }, ContextKeyExpr.and(TextCompareEditorActiveContext, ContextKeyExpr.notEquals('config.diffEditor.ignoreTrimWhitespace', false)), 20 diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 8dc609269f7..70b32efb074 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -23,6 +23,7 @@ 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 { @@ -639,14 +640,30 @@ export abstract class BaseCloseAllAction extends Action { return undefined; })); - const confirm = await this.fileDialogService.showSaveConfirm(this.workingCopyService.getDirty().map(copy => copy.resource)); + 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) { - saveOrRevert = await this.editorService.revertAll({ soft: true }); + saveOrRevert = await this.editorService.revertAll({ soft: true, includeUntitled: true }); } else { saveOrRevert = await this.editorService.saveAll({ reason: SaveReason.EXPLICIT, includeUntitled: 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 00000000000..b4eb3a41f04 --- /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 MIT License. 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/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 0b0fcb36fc7..c98f797eefc 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -1297,7 +1297,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { await this.openEditor(editor); const editorResource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); - const res = await this.fileDialogService.showSaveConfirm(editorResource ? [editorResource] : editor.getName()); + 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. diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 7cb0562675b..b1e47014aa9 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>()); 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 d69cc9c8351..00000000000 --- 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 9fcf77fe72e..00000000000 --- 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 44ece771f45..00000000000 --- 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 51946be5bb7..00000000000 --- 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 fb91225b968..00000000000 --- 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 742fcae4ae7..00000000000 --- 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/next-diff-dark.svg b/src/vs/workbench/browser/parts/editor/media/next-diff-dark.svg deleted file mode 100644 index 455532ddb7a..00000000000 --- 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 a443086f358..00000000000 --- 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/paragraph-dark.svg b/src/vs/workbench/browser/parts/editor/media/paragraph-dark.svg deleted file mode 100644 index 24708ab31f9..00000000000 --- 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 3c174668fdc..00000000000 --- 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 5edb9e63eb7..00000000000 --- 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 734fe619208..00000000000 --- 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 5ca3526019f..00000000000 --- 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 87e179a7f3c..00000000000 --- 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 8c22a7c5bfe..00000000000 --- 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 82c19d0c8fc..00000000000 --- 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 400952cdda8..00000000000 --- 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 419c21be4f6..00000000000 --- 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 7565fd3c168..00000000000 --- 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 405291c14dd..00000000000 --- 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/titlecontrol.css b/src/vs/workbench/browser/parts/editor/media/titlecontrol.css index 2c77ffb461d..9d825abeb16 100644 --- a/src/vs/workbench/browser/parts/editor/media/titlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/titlecontrol.css @@ -30,10 +30,11 @@ .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; @@ -53,6 +54,11 @@ 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 645159a3399..115bfb381ce 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); } } diff --git a/src/vs/workbench/browser/parts/editor/resourceViewer.ts b/src/vs/workbench/browser/parts/editor/resourceViewer.ts index c87b88479de..1879da12053 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 9d8ae67d35b..c60834c5721 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 d42242fe112..eb3e326f378 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -106,6 +106,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)); @@ -173,13 +174,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.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) => { @@ -600,16 +615,22 @@ export class TabsTitleControl extends TitleControl { })); // Double click: either pin or toggle maximized - disposables.add(addDisposableListener(tab, EventType.DBLCLICK, (e: MouseEvent) => { - EventHelper.stop(e); + [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 + } - const editor = this.group.getEditorByIndex(index); - if (editor && this.group.isPinned(editor)) { - this.accessor.arrangeGroups(GroupsArrangement.TOGGLE, this.group); - } else { - this.group.pinEditor(editor); - } - })); + 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) => { diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index ecbc295c0da..0ad7afc0e07 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,10 +29,8 @@ 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'; -import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; /** * The text editor that leverages the diff text editor for the editing experience. @@ -53,12 +50,9 @@ 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, - @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService + @IClipboardService private clipboardService: IClipboardService ) { - super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, hostService, filesConfigurationService); + super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorService, editorGroupService); } protected getEditorMemento(editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento { @@ -74,7 +68,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { } createEditorControl(parent: HTMLElement, configuration: ICodeEditorOptions): IDiffEditor { - return this.instantiationService.createInstance(DiffEditorWidget, parent, configuration, this._clipboardService); + return this.instantiationService.createInstance(DiffEditorWidget, parent, configuration, this.clipboardService); } async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { @@ -124,8 +118,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. @@ -137,14 +138,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); @@ -211,7 +204,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; @@ -219,8 +213,9 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { protected getAriaLabel(): string { let ariaLabel: string; + const inputName = this.input?.getName(); - if (this.isReadOnly()) { + 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."); @@ -229,17 +224,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 4dfe5dd897f..738e69b193b 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -6,25 +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, withNullAsUndefined } 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, SaveReason } 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 } 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'; -import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; const TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'textEditorViewState'; @@ -39,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; @@ -51,11 +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, - @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService + @IEditorGroupsService protected editorGroupService: IEditorGroupsService ) { super(id, telemetryService, themeService, storageService); @@ -76,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); @@ -121,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.filesConfigurationService.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())); } } @@ -200,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 @@ -303,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 78c0c9ab54b..a5433f5cd75 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -6,7 +6,6 @@ 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'; @@ -17,14 +16,11 @@ 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'; -import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; /** * An editor implementation that is capable of showing the contents of resource inputs. Uses @@ -40,12 +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, - @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService + @IEditorService editorService: IEditorService ) { - super(id, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, hostService, filesConfigurationService); + super(id, telemetryService, instantiationService, storageService, configurationService, themeService, editorService, editorGroupService); } getTitle(): string | undefined { @@ -91,6 +84,13 @@ 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) { @@ -102,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 UntitledTextEditorInput); // all resource editors are readonly except for the untitled one; - - return options; - } - protected getAriaLabel(): string { - const input = this.input; - const isReadonly = !(this.input instanceof UntitledTextEditorInput); - 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."); @@ -204,12 +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, - @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService + @IEditorGroupsService editorGroupService: IEditorGroupsService ) { - super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorGroupService, textFileService, editorService, hostService, filesConfigurationService); + super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorGroupService, editorService); } } diff --git a/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css b/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css index 3f07c0123ab..f1ab3c2cf1c 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; - transform: translate3d(0px, 0px, 0px); /* force a separate layer for the toast to speed things up */ } .monaco-workbench > .notifications-toasts .notification-toast-container > .notification-toast.notification-fade-in { diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.ts index bc24b962426..fb9eca271bb 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.ts @@ -1528,7 +1528,7 @@ export class QuickInputService extends Component implements IQuickInputService { style.marginLeft = '-' + (width / 2) + 'px'; this.ui.inputBox.layout(); - this.ui.list.layout(this.dimension && this.dimension.height * 0.6); + this.ui.list.layout(this.dimension && this.dimension.height * 0.4); } } diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index c48f38e7118..68b13c2d377 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -29,7 +29,7 @@ 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 { 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()); diff --git a/src/vs/workbench/browser/parts/views/media/panelviewlet.css b/src/vs/workbench/browser/parts/views/media/paneviewlet.css similarity index 72% rename from src/vs/workbench/browser/parts/views/media/panelviewlet.css rename to src/vs/workbench/browser/parts/views/media/paneviewlet.css index e022f97beb8..fc7221b9eaa 100644 --- a/src/vs/workbench/browser/parts/views/media/panelviewlet.css +++ b/src/vs/workbench/browser/parts/views/media/paneviewlet.css @@ -3,15 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-panel-view .split-view-view:first-of-type > .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 fb062a7a3af..43fa0eb55da 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; } 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 b4fd837aaa6..9125018139a 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 MIT License. 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'; @@ -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(); } @@ -225,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( @@ -267,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; } } @@ -294,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; @@ -303,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 []; @@ -311,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 []; @@ -319,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); @@ -328,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; } } @@ -341,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(); @@ -366,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()) { @@ -403,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 abab26cccde..2b35bf2b70c 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -429,23 +429,26 @@ export class ContributableViewsModel extends Disposable { } } + viewDescriptors = viewDescriptors.sort(this.compareViewDescriptors.bind(this)); + const toRemove: { index: number, viewDescriptor: IViewDescriptor; }[] = []; - const toAdd: { index: number, viewDescriptor: IViewDescriptor, size?: number, collapsed: boolean; }[] = []; - - const previous = this._viewDescriptors; - this._viewDescriptors = viewDescriptors.sort(this.compareViewDescriptors.bind(this)); - - for (let index = 0; index < previous.length; index++) { - if (this._viewDescriptors.every(viewDescriptor => viewDescriptor.id !== previous[index].id)) { - toRemove.push({ index, viewDescriptor: previous[index] }); + 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 }); } } - for (let index = 0; index < this._viewDescriptors.length; index++) { - const viewDescriptor = this._viewDescriptors[index]; - if (previous.every(previousViewDescriptor => previousViewDescriptor.id !== viewDescriptor.id)) { - const state = this.viewStates.get(viewDescriptor.id)!; - toAdd.push({ index, viewDescriptor, size: state.size, collapsed: !!state.collapsed }); + 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 }); } } diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index 806dadb4443..1399797b257 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(); } @@ -127,7 +127,7 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView 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)); } @@ -147,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); } @@ -170,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; } @@ -188,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, @@ -207,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 { @@ -281,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)); } } } @@ -292,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); } } } @@ -318,7 +318,7 @@ 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(); @@ -418,18 +418,18 @@ export abstract class FilterViewContainerViewlet extends ViewContainerViewlet { return views; } - onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPanel[] { - const panels: ViewletPanel[] = super.onDidAddViews(added); + 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)) { - panels[i].setExpanded(false); + panes[i].setExpanded(false); } } // Check that allViews is ready if (this.allViews.size === 0) { this.updateAllViews(this.viewsModel.viewDescriptors); } - return panels; + return panes; } abstract getTitle(): string; diff --git a/src/vs/workbench/browser/style.ts b/src/vs/workbench/browser/style.ts index d38931efc44..ea118c2511a 100644 --- a/src/vs/workbench/browser/style.ts +++ b/src/vs/workbench/browser/style.ts @@ -8,9 +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 } from 'vs/base/browser/browser'; +import { isSafari, isStandalone } from 'vs/base/browser/browser'; registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { @@ -184,4 +184,9 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { } `); } + + // 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/common/composite.ts b/src/vs/workbench/common/composite.ts index b6ea6f2ca73..8b48a7f4de8 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/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 0fe32907c4e..c2f430cfefe 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -23,10 +23,11 @@ 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); @@ -54,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. @@ -96,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. */ @@ -334,6 +325,9 @@ export interface IRevertOptions { /** * 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; } diff --git a/src/vs/workbench/common/editor/binaryEditorModel.ts b/src/vs/workbench/common/editor/binaryEditorModel.ts index a211169a6b7..edf4d54b15e 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 } from 'vs/base/common/resources'; import { MIME_BINARY } from 'vs/base/common/mime'; /** @@ -28,18 +26,6 @@ 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; - } - } } /** diff --git a/src/vs/workbench/common/editor/dataUriEditorInput.ts b/src/vs/workbench/common/editor/dataUriEditorInput.ts deleted file mode 100644 index 94911e9c9db..00000000000 --- a/src/vs/workbench/common/editor/dataUriEditorInput.ts +++ /dev/null @@ -1,79 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { 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, basename } 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'; - - private readonly name: string; - private readonly description: string | undefined; - - constructor( - name: string | undefined, - description: string | undefined, - private readonly resource: URI, - @IInstantiationService private readonly instantiationService: IInstantiationService - ) { - super(); - - if (!name || !description) { - const metadata = DataUri.parseMetaData(this.resource); - - if (!name) { - name = metadata.get(DataUri.META_DATA_LABEL) || basename(resource); - } - - if (!description) { - description = metadata.get(DataUri.META_DATA_DESCRIPTION); - } - } - - this.name = name; - this.description = description; - } - - getResource(): URI { - return this.resource; - } - - getTypeId(): string { - return DataUriEditorInput.ID; - } - - getName(): string { - 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/resourceEditorModel.ts b/src/vs/workbench/common/editor/resourceEditorModel.ts index f6bc4b1555b..e8e58c3f9b2 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 e95e0d87bae..bbc5ce958ea 100644 --- a/src/vs/workbench/common/editor/textEditorModel.ts +++ b/src/vs/workbench/common/editor/textEditorModel.ts @@ -16,7 +16,7 @@ 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; @@ -61,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()) { diff --git a/src/vs/workbench/common/editor/untitledTextEditorInput.ts b/src/vs/workbench/common/editor/untitledTextEditorInput.ts index 8be428a4931..cc44661e542 100644 --- a/src/vs/workbench/common/editor/untitledTextEditorInput.ts +++ b/src/vs/workbench/common/editor/untitledTextEditorInput.ts @@ -185,14 +185,14 @@ export class UntitledTextEditorInput extends TextEditorInput implements IEncodin return this.doSaveAs(group, () => this.textFileService.saveAs(this.resource, undefined, options), true /* replace editor across all groups */); } - revert(options?: IRevertOptions): Promise { + async revert(options?: IRevertOptions): Promise { if (this.cachedModel) { this.cachedModel.revert(); } this.dispose(); // a reverted untitled text editor is no longer valid, so we dispose it - return Promise.resolve(true); + return true; } suggestFileName(): string { diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts index aae136b9a2c..57b75c64bf1 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts @@ -160,9 +160,9 @@ registerEditorAction(class extends EditorAction { id: 'editor.showCallHierarchy', label: localize('title', "Peek Call Hierarchy"), alias: 'Peek Call Hierarchy', - menuOpts: { + contextMenuOpts: { group: 'navigation', - order: 1.48 + order: 1000 }, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.ts index aecdba4bd23..19fa1197cad 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/suggestEnabledInput/suggestEnabledInput.css b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.css index c9512232cd3..d79f11d4ce1 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 b7f1355fdad..b9731888148 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts @@ -224,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; @@ -254,7 +255,7 @@ export class SuggestEnabledInput extends Widget implements IThemable { 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 { @@ -286,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/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 72eb83fb5b8..e9f233496aa 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -27,7 +27,7 @@ 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'; @@ -260,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 }); } diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index c499a52051e..ba8510c41f4 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -20,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'; @@ -200,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/simpleCommentEditor.ts b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts index 7cd39297216..d57b989bede 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,9 +46,9 @@ export class SimpleCommentEditor extends CodeEditorWidget { @INotificationService notificationService: INotificationService, @IAccessibilityService accessibilityService: IAccessibilityService ) { - const codeEditorWidgetOptions = { + const codeEditorWidgetOptions: ICodeEditorWidgetOptions = { isSimpleWidget: true, - contributions: [ + 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/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index e7a2e3f5c31..9d3653b9d8f 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -6,18 +6,16 @@ import { memoize } from 'vs/base/common/decorators'; 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 { URI } from 'vs/base/common/uri'; +import { IEditorModel } from 'vs/platform/editor/common/editor'; import { ILabelService } from 'vs/platform/label/common/label'; 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 { CustomEditorModel } from '../common/customEditorModel'; -import { IEditorModel } from 'vs/platform/editor/common/editor'; export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { @@ -50,25 +48,11 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { @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(); } @@ -85,17 +69,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()); } @@ -111,14 +89,6 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { } } - public setModel(model: CustomEditorModel) { - if (this._model) { - throw new Error('Model is already set'); - } - this._model = model; - this._register(model.onDidChangeDirty(() => this._onDidChangeDirty.fire())); - } - public isReadonly(): boolean { return false; } @@ -142,6 +112,7 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { public async resolve(): Promise { this._model = await this.customEditorService.models.loadOrCreate(this.getResource(), this.viewType); + this._register(this._model.onDidChangeDirty(() => this._onDidChangeDirty.fire())); return await super.resolve(); } } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts index e4509a59b02..8739a05780a 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts @@ -12,7 +12,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; diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 56c6b0320a9..814c4d6b301 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -7,8 +7,7 @@ import { coalesce, distinct, find, mergeSort } from 'vs/base/common/arrays'; import * as glob from 'vs/base/common/glob'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable, UnownedDisposable } from 'vs/base/common/lifecycle'; -import { Schemas } from 'vs/base/common/network'; -import { basename, DataUri, isEqual } from 'vs/base/common/resources'; +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'; @@ -31,7 +30,6 @@ import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsSe import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { CustomFileEditorInput } from './customEditorInput'; - const defaultEditorId = 'default'; const defaultEditorInfo: CustomEditorInfo = { @@ -408,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 5e169d1b2c4..2a126dabefe 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 cfd19134b04..cdb3dc55fc9 100644 --- a/src/vs/workbench/contrib/customEditor/browser/webviewEditor.contribution.ts +++ b/src/vs/workbench/contrib/customEditor/browser/webviewEditor.contribution.ts @@ -13,7 +13,7 @@ import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } fro 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'; @@ -35,8 +35,8 @@ Registry.as(EditorExtensions.Editors).registerEditor( ]); Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory( - CustomEditoInputFactory.ID, - CustomEditoInputFactory); + CustomEditorInputFactory.ID, + CustomEditorInputFactory); Registry.as(ConfigurationExtensions.Configuration) .registerConfiguration({ diff --git a/src/vs/workbench/contrib/customEditor/common/customEditor.ts b/src/vs/workbench/contrib/customEditor/common/customEditor.ts index bfdf0c5177c..f8a50cdb9d1 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditor.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditor.ts @@ -70,7 +70,6 @@ export const enum CustomEditorPriority { export interface CustomEditorSelector { readonly filenamePattern?: string; - readonly mime?: string; } export interface CustomEditorInfo { diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 789df7edfd6..d9711706037 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -28,10 +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.$; @@ -39,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; @@ -61,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())); diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 667f578a5f1..20029b0ec3b 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -18,7 +18,7 @@ import { IAction, Action } from 'vs/base/common/actions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -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 { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; @@ -40,7 +40,7 @@ const $ = dom.$; type CallStackItem = IStackFrame | IThread | IDebugSession | string | ThreadAndSessionIds | IStackFrame[]; -export class CallStackView extends ViewletPanel { +export class CallStackView extends ViewletPane { private pauseMessage!: HTMLSpanElement; private pauseMessageLabel!: HTMLSpanElement; @@ -66,7 +66,7 @@ export class CallStackView extends ViewletPanel { @IMenuService menuService: IMenuService, @IContextKeyService readonly contextKeyService: IContextKeyService, ) { - super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('callstackSection', "Call Stack Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewletPaneOptions), ariaHeaderLabel: nls.localize('callstackSection', "Call Stack Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); this.callStackItemType = CONTEXT_CALLSTACK_ITEM_TYPE.bindTo(contextKeyService); this.contributedContextMenu = menuService.createMenu(MenuId.DebugCallStackContext, contextKeyService); @@ -385,6 +385,7 @@ interface IStackFrameTemplateData { fileName: HTMLElement; lineNumber: HTMLElement; label: HighlightedLabel; + actionBar: ActionBar; } class SessionsRenderer implements ITreeRenderer { @@ -481,8 +482,9 @@ class StackFramesRenderer implements ITreeRenderer, index: number, data: IStackFrameTemplateData): void { @@ -490,6 +492,8 @@ class StackFramesRenderer implements ITreeRenderer { + return stackFrame.restart(); + }); + data.actionBar.push(action, { icon: true, label: false }); + } } disposeTemplate(templateData: IStackFrameTemplateData): void { - // noop + templateData.actionBar.dispose(); } } diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 479de2952de..67ba00c4cd6 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -83,7 +83,7 @@ Registry.as(ViewletExtensions.Viewlets).registerViewlet(Viewlet DebugViewlet, VIEWLET_ID, nls.localize('debug', "Debug"), - 'codicon-debug', + 'codicon-debug-alt', 3 )); @@ -304,7 +304,7 @@ registerDebugToolBarItem(STEP_INTO_ID, STEP_INTO_LABEL, 30, 'codicon-debug-step- registerDebugToolBarItem(STEP_OUT_ID, STEP_OUT_LABEL, 40, 'codicon-debug-step-out', undefined, undefined, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); registerDebugToolBarItem(RESTART_SESSION_ID, RESTART_LABEL, 60, 'codicon-debug-restart', undefined, undefined); registerDebugToolBarItem(STEP_BACK_ID, nls.localize('stepBackDebug', "Step Back"), 50, 'codicon-debug-step-back', undefined, undefined, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); -registerDebugToolBarItem(REVERSE_CONTINUE_ID, nls.localize('reverseContinue', "Reverse"), 60, 'codicon-debug-continue', undefined, undefined, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); +registerDebugToolBarItem(REVERSE_CONTINUE_ID, nls.localize('reverseContinue', "Reverse"), 60, 'codicon-debug-reverse-continue', undefined, undefined, 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') => { diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index 0c4a83d2cb4..eb1eb008da0 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -13,7 +13,6 @@ import * as resources from 'vs/base/common/resources'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { ITextModel } from 'vs/editor/common/model'; import { IEditor } from 'vs/workbench/common/editor'; -import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -35,6 +34,7 @@ import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/c import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { CancellationToken } from 'vs/base/common/cancellation'; import { withUndefinedAsNull } from 'vs/base/common/types'; +import { sequence } from 'vs/base/common/async'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { first } from 'vs/base/common/arrays'; @@ -65,7 +65,6 @@ export class ConfigurationManager implements IConfigurationManager { @IInstantiationService private readonly instantiationService: IInstantiationService, @ICommandService private readonly commandService: ICommandService, @IStorageService private readonly storageService: IStorageService, - @ILifecycleService lifecycleService: ILifecycleService, @IExtensionService private readonly extensionService: IExtensionService, @IContextKeyService contextKeyService: IContextKeyService, @IHistoryService historyService: IHistoryService @@ -75,7 +74,7 @@ export class ConfigurationManager implements IConfigurationManager { this.debuggers = []; this.toDispose = []; this.initLaunches(); - this.registerListeners(lifecycleService); + this.registerListeners(); const previousSelectedRoot = this.storageService.get(DEBUG_SELECTED_ROOT, StorageScope.WORKSPACE); const previousSelectedLaunch = this.launches.filter(l => l.uri.toString() === previousSelectedRoot).pop(); this.debugConfigurationTypeContext = CONTEXT_DEBUG_CONFIGURATION_TYPE.bindTo(contextKeyService); @@ -191,31 +190,31 @@ export class ConfigurationManager implements IConfigurationManager { return providers.length > 0; } - resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: IConfig, token: CancellationToken): Promise { - return this.activateDebuggers('onDebugResolve', type).then(() => { - // pipe the config through the promises sequentially. Append at the end the '*' types - const providers = this.configProviders.filter(p => p.type === type && p.resolveDebugConfiguration) - .concat(this.configProviders.filter(p => p.type === '*' && p.resolveDebugConfiguration)); + async resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, config: IConfig, token: CancellationToken): Promise { + await this.activateDebuggers('onDebugResolve', type); + // pipe the config through the promises sequentially. Append at the end the '*' types + const providers = this.configProviders.filter(p => p.type === type && p.resolveDebugConfiguration) + .concat(this.configProviders.filter(p => p.type === '*' && p.resolveDebugConfiguration)); - return providers.reduce((promise, provider) => { - return promise.then(config => { - if (config) { - return provider.resolveDebugConfiguration!(folderUri, config, token); - } else { - return Promise.resolve(config); - } - }); - }, Promise.resolve(debugConfiguration)); - }); + let result: IConfig | null | undefined = config; + await sequence(providers.map(provider => async () => { + // 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 result; } - provideDebugConfigurations(folderUri: uri | undefined, type: string, token: CancellationToken): Promise { - return this.activateDebuggers('onDebugInitialConfigurations') - .then(() => Promise.all(this.configProviders.filter(p => p.type === type && p.provideDebugConfigurations).map(p => p.provideDebugConfigurations!(folderUri, token))) - .then(results => results.reduce((first, second) => first.concat(second), []))); + async provideDebugConfigurations(folderUri: uri | undefined, type: string, token: CancellationToken): Promise { + await this.activateDebuggers('onDebugInitialConfigurations'); + const results = await Promise.all(this.configProviders.filter(p => p.type === type && p.provideDebugConfigurations).map(p => p.provideDebugConfigurations!(folderUri, token))); + + return results.reduce((first, second) => first.concat(second), []); } - private registerListeners(lifecycleService: ILifecycleService): void { + private registerListeners(): void { debuggersExtPoint.setHandler((extensions, delta) => { delta.added.forEach(added => { added.value.forEach(rawAdapter => { @@ -389,57 +388,54 @@ export class ConfigurationManager implements IConfigurationManager { return this.debuggers.filter(dbg => strings.equalsIgnoreCase(dbg.type, type)).pop(); } - guessDebugger(type?: string): Promise { + async guessDebugger(type?: string): Promise { if (type) { const adapter = this.getDebugger(type); return Promise.resolve(adapter); } const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - let candidates: Promise | undefined; + let candidates: Debugger[] | undefined; if (isCodeEditor(activeTextEditorWidget)) { const model = activeTextEditorWidget.getModel(); const language = model ? model.getLanguageIdentifier().language : undefined; const adapters = this.debuggers.filter(a => language && a.languages && a.languages.indexOf(language) >= 0); if (adapters.length === 1) { - return Promise.resolve(adapters[0]); + return adapters[0]; } if (adapters.length > 1) { - candidates = Promise.resolve(adapters); + candidates = adapters; } } if (!candidates) { - candidates = this.activateDebuggers('onDebugInitialConfigurations').then(() => this.debuggers.filter(dbg => dbg.hasInitialConfiguration() || dbg.hasConfigurationProvider())); + await this.activateDebuggers('onDebugInitialConfigurations'); + candidates = this.debuggers.filter(dbg => dbg.hasInitialConfiguration() || dbg.hasConfigurationProvider()); } - return candidates.then(debuggers => { - debuggers.sort((first, second) => first.label.localeCompare(second.label)); - const picks = debuggers.map(c => ({ label: c.label, debugger: c })); - 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; - }); - }); + candidates.sort((first, second) => first.label.localeCompare(second.label)); + const picks = candidates.map(c => ({ label: c.label, debugger: c })); + 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; + }); } - activateDebuggers(activationEvent: string, debugType?: string): Promise { - const thenables: Promise[] = [ + async activateDebuggers(activationEvent: string, debugType?: string): Promise { + const promises: Promise[] = [ this.extensionService.activateByEvent(activationEvent), this.extensionService.activateByEvent('onDebug') ]; if (debugType) { - thenables.push(this.extensionService.activateByEvent(`${activationEvent}:${debugType}`)); + promises.push(this.extensionService.activateByEvent(`${activationEvent}:${debugType}`)); } - return Promise.all(thenables).then(_ => { - return undefined; - }); + await Promise.all(promises); } private setSelectedLaunchName(selectedName: string | undefined): void { @@ -536,54 +532,57 @@ class Launch extends AbstractLaunch implements ILaunch { return this.configurationService.inspect('launch', { resource: this.workspace.uri }).workspaceFolder; } - openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditor | null, created: boolean }> { + async openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditor | null, created: boolean }> { const resource = this.uri; let created = false; - - return this.fileService.readFile(resource).then(content => content.value, err => { + let content = ''; + try { + const fileContent = await this.fileService.readFile(resource); + content = fileContent.value.toString(); + } catch { // launch.json not found: create one by collecting launch configs from debugConfigProviders - return this.configurationManager.guessDebugger(type).then(adapter => { - if (adapter) { - return this.configurationManager.provideDebugConfigurations(this.workspace.uri, adapter.type, token || CancellationToken.None).then(initialConfigs => { - return adapter.getInitialConfigurationContent(initialConfigs); - }); - } else { - return ''; - } - }).then(content => { - - if (!content) { - return ''; - } + const adapter = await this.configurationManager.guessDebugger(type); + if (adapter) { + const initialConfigs = await this.configurationManager.provideDebugConfigurations(this.workspace.uri, adapter.type, token || CancellationToken.None); + content = await adapter.getInitialConfigurationContent(initialConfigs); + } + if (content) { created = true; // pin only if config file is created #8727 - return this.textFileService.write(resource, content).then(() => content); - }); - }).then(content => { - if (!content) { - return { editor: null, created: false }; - } - const contentValue = content.toString(); - const index = contentValue.indexOf(`"${this.configurationManager.selectedConfiguration.name}"`); - let startLineNumber = 1; - for (let i = 0; i < index; i++) { - if (contentValue.charAt(i) === '\n') { - startLineNumber++; + try { + await this.textFileService.write(resource, content); + } catch (error) { + throw new Error(nls.localize('DebugConfig.failed', "Unable to create 'launch.json' file inside the '.vscode' folder ({0}).", error.message)); } } - const selection = startLineNumber > 1 ? { startLineNumber, startColumn: 4 } : undefined; + } - return Promise.resolve(this.editorService.openEditor({ - resource, - options: { - selection, - preserveFocus, - pinned: created, - revealIfVisible: true - }, - }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => ({ editor: withUndefinedAsNull(editor), created }))); - }, (error: Error) => { - throw new Error(nls.localize('DebugConfig.failed', "Unable to create 'launch.json' file inside the '.vscode' folder ({0}).", error.message)); + if (content === '') { + return { editor: null, created: false }; + } + + const index = content.indexOf(`"${this.configurationManager.selectedConfiguration.name}"`); + let startLineNumber = 1; + for (let i = 0; i < index; i++) { + if (content.charAt(i) === '\n') { + startLineNumber++; + } + } + const selection = startLineNumber > 1 ? { startLineNumber, startColumn: 4 } : undefined; + + const editor = await this.editorService.openEditor({ + resource, + options: { + selection, + preserveFocus, + pinned: created, + revealIfVisible: true + }, + }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + + return ({ + editor: withUndefinedAsNull(editor), + created }); } } @@ -613,11 +612,17 @@ class WorkspaceLaunch extends AbstractLaunch implements ILaunch { return this.configurationService.inspect('launch').workspace; } - openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string): Promise<{ editor: IEditor | null, created: boolean }> { - return this.editorService.openEditor({ + async openConfigFile(sideBySide: boolean, preserveFocus: boolean): Promise<{ editor: IEditor | null, created: boolean }> { + + const editor = await this.editorService.openEditor({ resource: this.contextService.getWorkspace().configuration!, options: { preserveFocus } - }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => ({ editor: withUndefinedAsNull(editor), created: false })); + }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + + return ({ + editor: withUndefinedAsNull(editor), + created: false + }); } } @@ -650,7 +655,11 @@ class UserLaunch extends AbstractLaunch implements ILaunch { return this.configurationService.inspect('launch').user; } - openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string): Promise<{ editor: IEditor | null, created: boolean }> { - return this.preferencesService.openGlobalSettings(false, { preserveFocus }).then(editor => ({ editor: withUndefinedAsNull(editor), created: false })); + async openConfigFile(_: boolean, preserveFocus: boolean): Promise<{ editor: IEditor | null, created: boolean }> { + const editor = await this.preferencesService.openGlobalSettings(false, { preserveFocus }); + return ({ + editor: withUndefinedAsNull(editor), + created: false + }); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index d053a9d481a..80306b110a5 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/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 89f0cb1e6d2..86287b627a5 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 e939c92fd67..4d8c423dbce 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -208,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 @@ -218,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(); @@ -229,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'); @@ -377,7 +377,7 @@ registerThemingParticipant((theme, collector) => { const debugIconRestartColor = theme.getColor(debugIconRestartForeground); if (debugIconRestartColor) { - collector.addRule(`.monaco-workbench .codicon-debug-restart { color: ${debugIconRestartColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-debug-restart, .monaco-workbench .codicon-debug-restart-frame { color: ${debugIconRestartColor} !important; }`); } const debugIconStepOverColor = theme.getColor(debugIconStepOverForeground); @@ -397,7 +397,7 @@ registerThemingParticipant((theme, collector) => { const debugIconContinueColor = theme.getColor(debugIconContinueForeground); if (debugIconContinueColor) { - collector.addRule(`.monaco-workbench .codicon-debug-continue { color: ${debugIconContinueColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-debug-continue,.monaco-workbench .codicon-debug-reverse-continue { color: ${debugIconContinueColor} !important; }`); } const debugIconStepBackColor = theme.getColor(debugIconStepBackForeground); diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index b3eb22bde17..c48c0ecf957 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -26,7 +26,7 @@ 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'; @@ -38,8 +38,8 @@ 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; @@ -181,32 +181,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; } } diff --git a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index 28c7604d2a0..c825a856bdd 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'; @@ -388,7 +388,7 @@ class SessionTreeItem extends BaseTreeItem { } } -export class LoadedScriptsView extends ViewletPanel { +export class LoadedScriptsView extends ViewletPane { private treeContainer!: HTMLElement; private loadedScriptsItemType: IContextKey; @@ -411,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); } 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 b7129400782..47fdbe45c60 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css @@ -11,11 +11,19 @@ opacity: .4 !important; } +.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 { @@ -31,7 +39,7 @@ } .monaco-editor .inline-breakpoint-widget.line-start { - left: -0.45em !important; + left: -8px !important; } .monaco-editor .debug-breakpoint-placeholder::before, diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index 185b8187d41..d4e91e65cd1 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -148,6 +148,11 @@ display: none; } +.debug-viewlet .debug-call-stack .monaco-list-row:hover .stack-frame.has-actions .file, +.debug-viewlet .debug-call-stack .monaco-list-row.focused .stack-frame.has-actions .file { + display: none; +} + .debug-viewlet .debug-call-stack .monaco-list-row .monaco-action-bar { display: none; } diff --git a/src/vs/workbench/contrib/debug/browser/media/repl.css b/src/vs/workbench/contrib/debug/browser/media/repl.css index 9b6b7dccecc..68c1f96bacc 100644 --- a/src/vs/workbench/contrib/debug/browser/media/repl.css +++ b/src/vs/workbench/contrib/debug/browser/media/repl.css @@ -14,6 +14,7 @@ .repl .repl-tree .monaco-tl-contents { user-select: text; -webkit-user-select: text; + white-space: pre; } .repl .repl-tree.word-wrap .monaco-tl-contents { diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index b0394820606..4ff31849ccd 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -809,13 +809,13 @@ class ReplDelegate extends CachedListVirtualDelegate { 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); @@ -825,7 +825,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/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index a6104ac4e9b..81012b401ae 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'; @@ -37,7 +37,7 @@ let forgetScopes = true; export const variableSetEmitter = new Emitter(); -export class VariablesView extends ViewletPanel { +export class VariablesView extends ViewletPane { private onFocusStackFrameScheduler: RunOnceScheduler; private needsRefresh = false; @@ -54,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 () => { diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 2a601f78e9d..23928662699 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'; @@ -34,7 +34,7 @@ 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; @@ -49,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; @@ -86,11 +86,14 @@ export class WatchExpressionsView extends ViewletPanel { 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/replModel.ts b/src/vs/workbench/contrib/debug/common/replModel.ts index a02e8305f8d..bc0f77b86d2 100644 --- a/src/vs/workbench/contrib/debug/common/replModel.ts +++ b/src/vs/workbench/contrib/debug/common/replModel.ts @@ -136,6 +136,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/node/terminals.ts b/src/vs/workbench/contrib/debug/node/terminals.ts index 7142a363971..e4e0abd2280 100644 --- a/src/vs/workbench/contrib/debug/node/terminals.ts +++ b/src/vs/workbench/contrib/debug/node/terminals.ts @@ -47,7 +47,7 @@ function spawnAsPromised(command: string, args: string[]): Promise { }); } -export function hasChildProcesses(processId: number): Promise { +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) { diff --git a/src/vs/workbench/contrib/emmet/browser/actions/expandAbbreviation.ts b/src/vs/workbench/contrib/emmet/browser/actions/expandAbbreviation.ts index 18aa8533f44..b6129b8a6ae 100644 --- a/src/vs/workbench/contrib/emmet/browser/actions/expandAbbreviation.ts +++ b/src/vs/workbench/contrib/emmet/browser/actions/expandAbbreviation.ts @@ -29,7 +29,7 @@ class ExpandAbbreviationAction extends EmmetEditorAction { ), weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarEditMenu, group: '5_insert', title: nls.localize({ key: 'miEmmetExpandAbbreviation', comment: ['&& denotes a mnemonic'] }, "Emmet: E&&xpand Abbreviation"), diff --git a/src/vs/workbench/contrib/emmet/browser/actions/showEmmetCommands.ts b/src/vs/workbench/contrib/emmet/browser/actions/showEmmetCommands.ts index 1845bcd4239..1fe29bd1d93 100644 --- a/src/vs/workbench/contrib/emmet/browser/actions/showEmmetCommands.ts +++ b/src/vs/workbench/contrib/emmet/browser/actions/showEmmetCommands.ts @@ -21,7 +21,7 @@ class ShowEmmetCommandsAction extends EditorAction { label: nls.localize('showEmmetCommands', "Show Emmet Commands"), alias: 'Show Emmet Commands', precondition: EditorContextKeys.writable, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarEditMenu, group: '5_insert', title: nls.localize({ key: 'miShowEmmetCommands', comment: ['&& denotes a mnemonic'] }, "E&&mmet..."), diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 250084615b9..4ac7d34337e 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -46,7 +46,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views'; -import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { ViewletPane } from 'vs/workbench/browser/parts/views/paneViewlet'; import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery'; import { SuggestEnabledInput, attachSuggestEnabledInputBoxStyler } from 'vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput'; import { alert } from 'vs/base/browser/ui/aria/aria'; @@ -526,13 +526,13 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio this.nonEmptyWorkspaceContextKey.set(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY); this.defaultViewsContextKey.set(!value); - return this.progress(Promise.all(this.panels.map(view => + 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()) @@ -563,12 +563,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 530fd637d9a..1c3e99c9a7e 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 } from 'vs/base/common/arrays'; import { IExperimentService, IExperiment, ExperimentActionType } from 'vs/workbench/contrib/experiments/common/experimentService'; @@ -72,7 +72,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 +105,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; } diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts index cfb489c9d7c..d36942793a8 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'; @@ -32,7 +31,6 @@ import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; import { MutableDisposable } from 'vs/base/common/lifecycle'; import { EditorActivation, IEditorOptions } from 'vs/platform/editor/common/editor'; -import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; /** * An implementation of editor for file system resources. @@ -55,12 +53,10 @@ export class TextFileEditor extends BaseTextEditor { @IEditorService editorService: IEditorService, @IThemeService themeService: IThemeService, @IEditorGroupsService editorGroupService: IEditorGroupsService, - @ITextFileService textFileService: ITextFileService, - @IHostService hostService: IHostService, - @IExplorerService private readonly explorerService: IExplorerService, - @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService + @ITextFileService private readonly textFileService: ITextFileService, + @IExplorerService private readonly explorerService: IExplorerService ) { - super(TextFileEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, hostService, filesConfigurationService); + super(TextFileEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorService, editorGroupService); this.updateRestoreViewStateConfiguration(); @@ -117,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 @@ -164,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); @@ -243,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 85cecc75dfa..fe6dd9eafc1 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 diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index 32abad22c0f..aabb8afd4af 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -10,7 +10,7 @@ import { revertLocalChangesCommand, acceptLocalChangesCommand, CONFLICT_RESOLUTI 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, SaveableEditorContext } 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'; @@ -26,7 +26,7 @@ import { Schemas } from 'vs/base/common/network'; 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, DirtyWorkingCopiesContext, ActiveEditorContext } 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'; @@ -77,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 @@ -89,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 @@ -100,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 @@ -274,7 +274,7 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { // Not: editor groups OpenEditorsGroupContext.toNegated(), // Not: readonly editors - SaveableEditorContext, + ReadonlyEditorContext.toNegated(), // Not: auto save after short delay AutoSaveAfterShortDelayContext.toNegated() ) @@ -293,7 +293,7 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { // Not: editor groups OpenEditorsGroupContext.toNegated(), // Not: readonly editors - SaveableEditorContext, + ReadonlyEditorContext.toNegated(), // Not: untitled editors (revert closes them) ResourceContextKey.Scheme.notEqualsTo(Schemas.untitled), // Not: auto save after short delay @@ -580,7 +580,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 }); @@ -659,7 +659,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { command: { id: REVERT_FILE_COMMAND_ID, title: nls.localize({ key: 'miRevert', comment: ['&& denotes a mnemonic'] }, "Re&&vert File"), - precondition: ContextKeyExpr.or(ActiveEditorIsSaveableContext, ContextKeyExpr.and(ExplorerViewletVisibleContext, SidebarFocusContext)) + 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 72d5dc4669a..0e1d4b4801a 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -937,17 +937,21 @@ export const renameHandler = (accessor: ServicesAccessor) => { }); }; -export const moveFileToTrashHandler = (accessor: ServicesAccessor) => { +export const moveFileToTrashHandler = async (accessor: ServicesAccessor) => { const explorerService = accessor.get(IExplorerService); - const stats = explorerService.getContext(true); - return deleteFiles(accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), accessor.get(IFileService), stats, true); + const stats = explorerService.getContext(true).filter(s => !s.isRoot); + if (stats.length) { + await deleteFiles(accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), accessor.get(IFileService), stats, true); + } }; -export const deleteFileHandler = (accessor: ServicesAccessor) => { +export const deleteFileHandler = async (accessor: ServicesAccessor) => { const explorerService = accessor.get(IExplorerService); - const stats = explorerService.getContext(true); + 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), accessor.get(IFileService), stats, false); + } }; let pasteShouldMove = false; @@ -1046,7 +1050,7 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => { 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)))); + 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; } })); diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index 962a8183061..f09033db891 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; -import { toResource, IEditorCommandsContext, SideBySideEditor, IEditorIdentifier, SaveReason } 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'; @@ -23,7 +23,7 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co 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, getMultiSelectedEditors } 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'; @@ -37,6 +37,9 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { UNTITLED_WORKSPACE_NAME } from 'vs/platform/workspaces/common/workspaces'; 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 @@ -67,7 +70,7 @@ 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 SaveableEditorContext = new RawContextKey('saveableEditor', false); +export const ReadonlyEditorContext = new RawContextKey('readonlyEditor', false); export const ResourceSelectedForCompareContext = new RawContextKey('resourceSelectedForCompare', false); export const REMOVE_ROOT_FOLDER_COMMAND_ID = 'removeRootFolder'; @@ -310,24 +313,68 @@ CommandsRegistry.registerCommand({ // Save / Save As / Save All / Revert -function saveSelectedEditors(accessor: ServicesAccessor, options?: ISaveEditorsOptions): Promise { +async function saveSelectedEditors(accessor: ServicesAccessor, options?: ISaveEditorsOptions): Promise { const listService = accessor.get(IListService); - const editorGroupsService = accessor.get(IEditorGroupsService); + const editorGroupService = accessor.get(IEditorGroupsService); + const codeEditorService = accessor.get(ICodeEditorService); + const textFileService = accessor.get(ITextFileService); - return doSaveEditors(accessor, getMultiSelectedEditors(listService, editorGroupsService), options); -} + // Retrieve selected or active editor + let editors = getOpenEditorsViewMultiSelection(listService, editorGroupService); + if (!editors) { + const activeGroup = editorGroupService.activeGroup; + if (activeGroup.activeEditor) { + editors = []; -function saveDirtyEditorsOfGroups(accessor: ServicesAccessor, groups: ReadonlyArray, options?: ISaveEditorsOptions): Promise { - const saveableEditors: IEditorIdentifier[] = []; - for (const group of groups) { - for (const editor of group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)) { - if (editor.isDirty()) { - saveableEditors.push({ groupId: group.id, editor }); + // 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 }); } } } - return doSaveEditors(accessor, saveableEditors, options); + 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 { @@ -411,25 +458,27 @@ CommandsRegistry.registerCommand({ handler: async accessor => { const notificationService = accessor.get(INotificationService); const listService = accessor.get(IListService); - const editorGroupsService = accessor.get(IEditorGroupsService); + const editorGroupService = accessor.get(IEditorGroupsService); + const editorService = accessor.get(IEditorService); - const editors = getMultiSelectedEditors(listService, editorGroupsService); - if (editors.length) { - try { - await Promise.all(editors.map(async ({ groupId, editor }) => { - if (editor.isUntitled()) { - return; // we do not allow to revert untitled editors - } - - // Use revert as a hint to pin the editor - editorGroupsService.getGroup(groupId)?.pinEditor(editor); - - return editor.revert({ force: true }); - })); - } catch (error) { - notificationService.error(nls.localize('genericRevertError', "Failed to revert '{0}': {1}", editors.map(({ editor }) => editor.getName()).join(', '), toErrorMessage(error, false))); + // 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))); + } } }); diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index e8727ebc798..a3ffb18b605 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -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'; @@ -107,8 +106,7 @@ Registry.as(EditorExtensions.Editors).registerEditor( nls.localize('binaryFileEditor', "Binary File Editor") ), [ - new SyncDescriptor(FileEditorInput), - new SyncDescriptor(DataUriEditorInput) + new SyncDescriptor(FileEditorInput) ] ); diff --git a/src/vs/workbench/contrib/files/browser/files.ts b/src/vs/workbench/contrib/files/browser/files.ts index 6b99f39e922..7746773311f 100644 --- a/src/vs/workbench/contrib/files/browser/files.ts +++ b/src/vs/workbench/contrib/files/browser/files.ts @@ -53,17 +53,6 @@ export function getResourceForCommand(resource: URI | object | undefined, listSe return editorService.activeEditor ? toResource(editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }) : undefined; } -export function getEditorForCommand(listService: IListService, editorGroupService: IEditorGroupsService): IEditorIdentifier | undefined { - const focus = getFocus(listService); - if (focus instanceof OpenEditor) { - return focus; - } - - const activeGroup = editorGroupService.activeGroup; - - return activeGroup.activeEditor ? { groupId: activeGroup.id, editor: activeGroup.activeEditor } : undefined; -} - export function getMultiSelectedResources(resource: URI | object | undefined, listService: IListService, editorService: IEditorService): Array { const list = listService.lastFocusedList; if (list?.getHTMLElement() === document.activeElement) { @@ -103,7 +92,7 @@ export function getMultiSelectedResources(resource: URI | object | undefined, li return !!result ? [result] : []; } -export function getMultiSelectedEditors(listService: IListService, editorGroupsService: IEditorGroupsService): Array { +export function getOpenEditorsViewMultiSelection(listService: IListService, editorGroupService: IEditorGroupsService): Array | undefined { const list = listService.lastFocusedList; if (list?.getHTMLElement() === document.activeElement) { // Open editors view @@ -122,6 +111,5 @@ export function getMultiSelectedEditors(listService: IListService, editorGroupsS } } - const result = getEditorForCommand(listService, editorGroupsService); - return !!result ? [result] : []; + return undefined; } diff --git a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css index 40510378cfe..8933103c4d3 100644 --- a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css +++ b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css @@ -61,14 +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; } diff --git a/src/vs/workbench/contrib/files/browser/views/emptyView.ts b/src/vs/workbench/contrib/files/browser/views/emptyView.ts index 7d9fbe1c034..d5d9984613c 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())); } @@ -135,12 +135,7 @@ export class EmptyView extends ViewletPanel { } focus(): void { - this.focusBody(); + this.button.element.focus(); } - focusBody(): void { - if (this.button) { - 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 010f7e545d3..5020df769b7 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -27,9 +27,9 @@ import { IDecorationsService } from 'vs/workbench/services/decorations/browser/d 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, ICompressedNavigationController, FilesFilter, FileSorter, FileDragAndDrop, ExplorerCompressionDelegate } from 'vs/workbench/contrib/files/browser/views/explorerViewer'; +import { ExplorerDelegate, ExplorerAccessibilityProvider, 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'; @@ -64,7 +64,7 @@ interface IExplorerViewStyles { listDropBackground?: Color; } -export class ExplorerView extends ViewletPanel { +export class ExplorerView extends ViewletPane { static readonly ID: string = 'workbench.explorer.fileView'; static readonly TREE_VIEW_STATE_STORAGE_KEY: string = 'workbench.explorer.treeViewState'; @@ -92,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, @@ -112,7 +112,7 @@ 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); @@ -293,10 +293,6 @@ export class ExplorerView extends ViewletPanel { focusedStat = focus.length ? focus[0] : undefined; } - if (!focusedStat) { - return []; - } - const selectedStats: ExplorerItem[] = []; for (const stat of this.tree.getSelection()) { @@ -308,6 +304,13 @@ export class ExplorerView extends ViewletPanel { selectedStats.push(stat); } } + if (!focusedStat) { + if (respectMultiSelection) { + return selectedStats; + } else { + return []; + } + } if (respectMultiSelection && selectedStats.indexOf(focusedStat) >= 0) { return selectedStats; @@ -475,7 +478,11 @@ export class ExplorerView extends ViewletPanel { const controller = this.renderer.getCompressedNavigationController(stat); if (controller) { - anchor = controller.labels[controller.index]; + if (isCompressedFolderName(e.browserEvent.target)) { + anchor = controller.labels[controller.index]; + } else { + controller.last(); + } } } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 8bb84ec2795..7e1d56a9a1e 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -1096,6 +1096,10 @@ function getIconLabelNameFromHTMLElement(target: HTMLElement | EventTarget | Ele return null; } +export function isCompressedFolderName(target: HTMLElement | EventTarget | Element | null): boolean { + return !!getIconLabelNameFromHTMLElement(target); +} + export class ExplorerCompressionDelegate implements ITreeCompressionDelegate { isIncompressible(stat: ExplorerItem): boolean { diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 45abe43d33b..de1f8bf059b 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -30,10 +30,10 @@ 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, SaveableEditorContext } 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, containsDragType } from 'vs/workbench/browser/dnd'; -import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; +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'; @@ -46,7 +46,7 @@ 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'; @@ -62,7 +62,7 @@ export class OpenEditorsView extends ViewletPanel { private resourceContext!: ResourceContextKey; private groupFocusedContext!: IContextKey; private dirtyEditorFocusedContext!: IContextKey; - private saveableEditorFocusedContext!: IContextKey; + private readonlyEditorFocusedContext!: IContextKey; constructor( options: IViewletViewOptions, @@ -79,7 +79,7 @@ export class OpenEditorsView extends ViewletPanel { @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); @@ -236,19 +236,19 @@ export class OpenEditorsView extends ViewletPanel { this._register(this.resourceContext); this.groupFocusedContext = OpenEditorsGroupContext.bindTo(this.contextKeyService); this.dirtyEditorFocusedContext = DirtyEditorContext.bindTo(this.contextKeyService); - this.saveableEditorFocusedContext = SaveableEditorContext.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.saveableEditorFocusedContext.reset(); + this.readonlyEditorFocusedContext.reset(); const element = e.elements.length ? e.elements[0] : undefined; if (element instanceof OpenEditor) { const resource = element.getResource(); this.dirtyEditorFocusedContext.set(element.editor.isDirty()); - this.saveableEditorFocusedContext.set(!element.editor.isReadonly()); + this.readonlyEditorFocusedContext.set(element.editor.isReadonly()); this.resourceContext.set(withUndefinedAsNull(resource)); } else if (!!element) { this.groupFocusedContext.set(true); diff --git a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts index 387acd584b5..ea568d71791 100644 --- a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts @@ -216,7 +216,7 @@ export class FileEditorInput extends TextEditorInput implements IFileEditorInput return localize('orphanedFile', "{0} (deleted)", label); } - if (model?.isReadonly()) { + if (this.isReadonly()) { return localize('readonlyFile', "{0} (read-only)", label); } diff --git a/src/vs/workbench/contrib/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts index fd2f87c5430..cd74eea7b4e 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'; @@ -154,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) { @@ -170,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); } } @@ -260,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; diff --git a/src/vs/workbench/contrib/files/common/explorerService.ts b/src/vs/workbench/contrib/files/common/explorerService.ts index f9afb180963..cc9d46ab545 100644 --- a/src/vs/workbench/contrib/files/common/explorerService.ts +++ b/src/vs/workbench/contrib/files/common/explorerService.ts @@ -186,7 +186,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); @@ -230,11 +230,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 5eb63e01d92..d900202310e 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -18,7 +18,6 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile 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 { 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'; @@ -256,10 +255,6 @@ export class OpenEditor implements IEditorIdentifier { return this._group.previewEditor === this.editor; } - isUntitled(): boolean { - return !!toResource(this.editor, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: Schemas.untitled }); - } - isDirty(): boolean { return this.editor.isDirty(); } diff --git a/src/vs/workbench/contrib/files/electron-browser/textFileEditor.ts b/src/vs/workbench/contrib/files/electron-browser/textFileEditor.ts index 2afebd9cbe2..d34fc54cc67 100644 --- a/src/vs/workbench/contrib/files/electron-browser/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/electron-browser/textFileEditor.ts @@ -21,11 +21,9 @@ 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'; -import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; /** * An implementation of editor for file system resources. @@ -46,11 +44,9 @@ export class NativeTextFileEditor extends TextFileEditor { @ITextFileService textFileService: ITextFileService, @IElectronService private readonly electronService: IElectronService, @IPreferencesService private readonly preferencesService: IPreferencesService, - @IHostService hostService: IHostService, - @IExplorerService explorerService: IExplorerService, - @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService + @IExplorerService explorerService: IExplorerService ) { - super(telemetryService, fileService, viewletService, instantiationService, contextService, storageService, configurationService, editorService, themeService, editorGroupService, textFileService, hostService, explorerService, filesConfigurationService); + super(telemetryService, fileService, viewletService, instantiationService, contextService, storageService, configurationService, editorService, themeService, editorGroupService, textFileService, explorerService); } protected handleSetInputError(error: Error, input: FileEditorInput, options: EditorOptions | undefined): void { 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 35e56e70a5f..932c54fa018 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts @@ -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 887d6a87d2f..4b171f9aa4c 100644 --- a/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts +++ b/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts @@ -253,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 } @@ -284,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/logs/common/logs.contribution.ts b/src/vs/workbench/contrib/logs/common/logs.contribution.ts index 0479108f91c..7a1a767f67a 100644 --- a/src/vs/workbench/contrib/logs/common/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/common/logs.contribution.ts @@ -22,7 +22,6 @@ 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"); @@ -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', "Configuration Sync"), this.environmentService.userDataSyncLogResource); this.registerLogChannel(Constants.rendererLogChannelId, nls.localize('rendererLog', "Window"), this.environmentService.logFile); } diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index d8f74092225..19fac10efdc 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -564,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/outline/browser/outline.contribution.ts b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts index b99786acfc2..0affdee3013 100644 --- a/src/vs/workbench/contrib/outline/browser/outline.contribution.ts +++ b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts @@ -5,7 +5,7 @@ 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'; @@ -16,7 +16,7 @@ import './outlineNavigation'; const _outlineDesc = { id: OutlineViewId, name: localize('name', "Outline"), - ctorDescriptor: { ctor: OutlinePanel }, + ctorDescriptor: { ctor: OutlinePane }, canToggleVisibility: true, hideByDefault: false, collapsed: 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 950fb41088b..4f443d9f29e 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 MIT License. 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 98% rename from src/vs/workbench/contrib/outline/browser/outlinePanel.ts rename to src/vs/workbench/contrib/outline/browser/outlinePane.ts index 9d563e905c0..068c3471ed5 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'; @@ -233,7 +233,7 @@ class OutlineViewState { } } -export class OutlinePanel extends ViewletPanel { +export class OutlinePane extends ViewletPane { private _disposables = new Array(); @@ -297,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'); @@ -318,7 +318,7 @@ export class OutlinePanel extends ViewletPanel { this._treeFilter = this._instantiationService.createInstance(OutlineFilter, 'outline'); this._tree = this._instantiationService.createInstance( WorkbenchDataTree, - 'OutlinePanel', + 'OutlinePane', treeContainer, new OutlineVirtualDelegate(), [new OutlineGroupRenderer(), this._treeRenderer], @@ -482,7 +482,7 @@ export class OutlinePanel extends ViewletPanel { 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 4f192347a5d..e1f3a1fd3b4 100644 --- a/src/vs/workbench/contrib/output/browser/logViewer.ts +++ b/src/vs/workbench/contrib/output/browser/logViewer.ts @@ -11,16 +11,12 @@ 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'; -import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; export class LogViewerInput extends ResourceEditorInput { @@ -49,16 +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, - @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService + @IEditorService editorService: IEditorService ) { - super(LogViewer.LOG_VIEWER_EDITOR_ID, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorGroupService, textFileService, editorService, hostService, filesConfigurationService); + 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/outputPanel.ts b/src/vs/workbench/contrib/output/browser/outputPanel.ts index 9ecc2c1d47c..3cf422ed07f 100644 --- a/src/vs/workbench/contrib/output/browser/outputPanel.ts +++ b/src/vs/workbench/contrib/output/browser/outputPanel.ts @@ -19,14 +19,11 @@ 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'; -import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; export class OutputPanel extends AbstractTextResourceEditor { private actions: IAction[] | undefined; @@ -43,12 +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, - @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService + @IEditorService editorService: IEditorService ) { - super(OUTPUT_PANEL_ID, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorGroupService, textFileService, editorService, hostService, filesConfigurationService); + super(OUTPUT_PANEL_ID, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorGroupService, editorService); this.scopedInstantiationService = instantiationService; } diff --git a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts index cd52141866d..6d38111b94b 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts @@ -39,7 +39,6 @@ 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( EditorDescriptor.create( @@ -368,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( @@ -384,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 - } + iconClassName: 'codicon-go-to-file' }, when: ResourceContextKey.Resource.isEqualTo(environmentService.keybindingsResource.toString()), group: 'navigation', @@ -400,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 - } + iconClassName: 'codicon-go-to-file' }, when: ResourceContextKey.Resource.isEqualTo(environmentService.settingsResource.toString()), group: 'navigation', @@ -441,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 - } + iconClassName: 'codicon-go-to-file' }, when: ContextKeyExpr.and(ResourceContextKey.Resource.isEqualTo(this.preferencesService.workspaceSettingsResource!.toString()), WorkbenchStateContext.isEqualTo('workspace')), group: 'navigation', @@ -469,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 - } + iconClassName: 'codicon-go-to-file' }, when: ContextKeyExpr.and(ResourceContextKey.Resource.isEqualTo(this.preferencesService.getFolderSettingsResource(folder.uri)!.toString())), group: 'navigation', @@ -536,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 - } + iconClassName: 'codicon-go-to-file' }, when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR), group: 'navigation', @@ -820,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 - } + iconClassName: '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 77284a59676..29e80ca36b1 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts @@ -52,10 +52,7 @@ 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'; -import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; export class PreferencesEditor extends BaseEditor { @@ -979,13 +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, - @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService + @IEditorService editorService: IEditorService ) { - super(DefaultPreferencesEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, hostService, filesConfigurationService); + super(DefaultPreferencesEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorService, editorGroupService); } private static _getContributions(): IEditorContributionDescription[] { diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index fff4ef6068b..fa530078f92 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -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 f732f7672dd..592b3194e54 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -36,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'; @@ -1517,20 +1517,21 @@ export class SettingsTree extends ObjectTree { })); 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/tocTree.ts b/src/vs/workbench/contrib/preferences/browser/tocTree.ts index 301eaf966c5..cb7b19f19f2 100644 --- a/src/vs/workbench/contrib/preferences/browser/tocTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/tocTree.ts @@ -209,6 +209,7 @@ export class TOCTree extends ObjectTree { options); this.disposables.add(attachStyler(themeService, { + listBackground: editorBackground, listActiveSelectionBackground: editorBackground, listActiveSelectionForeground: settingsHeaderForeground, listFocusAndSelectionBackground: editorBackground, diff --git a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts index 66b5f574bdb..9c0dbb1757c 100644 --- a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts @@ -214,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 } diff --git a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts index d5447f83cfc..a152b50423d 100644 --- a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts @@ -26,7 +26,6 @@ interface IConfiguration extends IWindowsConfiguration { telemetry: { enableCrashReporter: boolean }; workbench: { list: { horizontalScrolling: boolean } }; debug: { console: { wordWrap: boolean } }; - configurationSync: { enableAuth: boolean }; } export class SettingsChangeRelauncher extends Disposable implements IWorkbenchContribution { 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 2673902c684..00000000000 --- 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 e8dc8205bab..00000000000 --- 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 4a3009baeee..00000000000 --- 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 5d99408934e..00000000000 --- 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 941430e9dd6..00000000000 --- 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 72437202b72..00000000000 --- 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 0ea65d83198..00000000000 --- 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 5bb05d3d8c5..00000000000 --- 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 46cde7f7cc0..00000000000 --- 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 0117ceb7ded..00000000000 --- 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 b0c521b7dc6..00000000000 --- 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 5da9322b6a9..00000000000 --- 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 21eec9cbcb8..00000000000 --- 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 94013ea52ae..00000000000 --- 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 826d0eefbf4..00000000000 --- a/src/vs/workbench/contrib/remote/browser/help-review-issues-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index e4d5e233034..312efc0e9e3 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -58,7 +58,7 @@ class HelpModel { if (getStarted.length) { helpItems.push(new HelpItem( ['getStarted'], - nls.localize('remote.help.getStarted', "Get Started"), + nls.localize('remote.help.getStarted', "$(star) Get Started"), getStarted.map((info: HelpInformation) => ({ extensionDescription: info.extensionDescription, url: info.getStarted! @@ -73,7 +73,7 @@ class HelpModel { if (documentation.length) { helpItems.push(new HelpItem( ['documentation'], - nls.localize('remote.help.documentation', "Read Documentation"), + nls.localize('remote.help.documentation', "$(book) Read Documentation"), documentation.map((info: HelpInformation) => ({ extensionDescription: info.extensionDescription, url: info.documentation! @@ -88,7 +88,7 @@ class HelpModel { if (feedback.length) { helpItems.push(new HelpItem( ['feedback'], - nls.localize('remote.help.feedback', "Provide Feedback"), + nls.localize('remote.help.feedback', "$(twitter) Provide Feedback"), feedback.map((info: HelpInformation) => ({ extensionDescription: info.extensionDescription, url: info.feedback! @@ -103,7 +103,7 @@ class HelpModel { if (issues.length) { helpItems.push(new HelpItem( ['issues'], - nls.localize('remote.help.issues', "Review Issues"), + nls.localize('remote.help.issues', "$(issues) Review Issues"), issues.map((info: HelpInformation) => ({ extensionDescription: info.extensionDescription, url: info.issues! @@ -116,7 +116,7 @@ class HelpModel { if (helpItems.length) { helpItems.push(new IssueReporterItem( ['issueReporter'], - nls.localize('remote.help.report', "Report Issue"), + nls.localize('remote.help.report', "$(comment) Report Issue"), remoteExplorerService.helpInformation.map(info => info.extensionDescription), quickInputService, commandService diff --git a/src/vs/workbench/contrib/remote/browser/remoteViewlet.css b/src/vs/workbench/contrib/remote/browser/remoteViewlet.css index d95c32bbf0d..39b5cdec500 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteViewlet.css +++ b/src/vs/workbench/contrib/remote/browser/remoteViewlet.css @@ -19,66 +19,6 @@ width: 0px !important; } -.vs .remote-help-tree-node-item-icon.getStarted { - background-image: url('help-getting-started-light.svg') -} - -.vs .remote-help-tree-node-item-icon.documentation { - background-image: url('help-documentation-light.svg') -} - -.vs .remote-help-tree-node-item-icon.feedback { - background-image: url('help-feedback-light.svg') -} - -.vs .remote-help-tree-node-item-icon.issues { - background-image: url('help-review-issues-light.svg') -} - -.vs .remote-help-tree-node-item-icon.issueReporter { - background-image: url('help-report-issue-light.svg') -} - -.vs-dark .remote-help-tree-node-item-icon.getStarted { - background-image: url('help-getting-started-dark.svg') -} - -.vs-dark .remote-help-tree-node-item-icon.documentation { - background-image: url('help-documentation-dark.svg') -} - -.vs-dark .remote-help-tree-node-item-icon.feedback { - background-image: url('help-feedback-dark.svg') -} - -.vs-dark .remote-help-tree-node-item-icon.issues { - background-image: url('help-review-issues-dark.svg') -} - -.vs-dark .remote-help-tree-node-item-icon.issueReporter { - background-image: url('help-report-issue-dark.svg') -} - -.hc-black .remote-help-tree-node-item-icon.getStarted { - background-image: url('help-getting-started-hc.svg') -} - -.hc-black .remote-help-tree-node-item-icon.documentation { - background-image: url('help-documentation-hc.svg') -} - -.hc-black .remote-help-tree-node-item-icon.feedback { - background-image: url('help-feedback-hc.svg') -} - -.hc-black .remote-help-tree-node-item-icon.issues { - background-image: url('help-review-issues-hc.svg') -} - -.hc-black .remote-help-tree-node-item-icon.issueReporter { - background-image: url('help-report-issue-hc.svg') -} - .monaco-workbench .part > .title > .title-actions .switch-remote { display: flex; align-items: center; diff --git a/src/vs/workbench/contrib/scm/browser/mainPanel.ts b/src/vs/workbench/contrib/scm/browser/mainPane.ts similarity index 95% rename from src/vs/workbench/contrib/scm/browser/mainPanel.ts rename to src/vs/workbench/contrib/scm/browser/mainPane.ts index d45bdb1b57c..02a3e45afd2 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, @@ -195,7 +196,10 @@ export class MainPanel extends ViewletPanel { 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/scmViewlet.css b/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css index 0d5b50909f6..1a56521f66e 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css +++ b/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css @@ -15,7 +15,7 @@ } .scm-viewlet:not(.empty) .empty-message, -.scm-viewlet.empty .monaco-panel-view { +.scm-viewlet.empty .monaco-pane-view { display: none; } @@ -47,6 +47,7 @@ .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 { diff --git a/src/vs/workbench/contrib/scm/browser/repositoryPanel.ts b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts similarity index 97% rename from src/vs/workbench/contrib/scm/browser/repositoryPanel.ts rename to src/vs/workbench/contrib/scm/browser/repositoryPane.ts index 3c026cf10a3..e4fa1369aed 100644 --- a/src/vs/workbench/contrib/scm/browser/repositoryPanel.ts +++ b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts @@ -8,7 +8,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; 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'; @@ -52,6 +52,8 @@ 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, @@ -584,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; @@ -601,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, @@ -744,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) @@ -959,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/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts index a4207212fd4..2593b355533 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/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 22f4f3467de..7cf58147ab2 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -43,7 +43,7 @@ import { OpenSymbolHandler } from 'vs/workbench/contrib/search/browser/openSymbo 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, ToggleSearchOnTypeAction } 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'; @@ -542,7 +542,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); } diff --git a/src/vs/workbench/contrib/search/browser/searchPanel.ts b/src/vs/workbench/contrib/search/browser/searchPanel.ts index 0c50a100a55..559f023dee7 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"), actionRunner: this.getActionRunner() })); + 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 bd2d3c226e4..2fc338d2dab 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -56,11 +56,12 @@ import { IPreferencesService, ISettingsEditorOptions } from 'vs/workbench/servic 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 { SIDE_BAR_BACKGROUND, PANEL_BACKGROUND } from 'vs/workbench/common/theme'; const $ = dom.$; @@ -70,8 +71,13 @@ enum SearchUIState { SlowSearch } +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 ViewletPanel { +export class SearchView extends ViewletPane { private static readonly MAX_TEXT_RESULTS = 10000; @@ -132,7 +138,8 @@ export class SearchView extends ViewletPanel { 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, @@ -156,7 +163,7 @@ export class SearchView extends ViewletPanel { @IStorageService storageService: IStorageService, @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); @@ -651,7 +658,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))); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 8ddb38770fc..18dd357cc65 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -589,7 +589,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; @@ -599,6 +602,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) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalQuickOpen.ts b/src/vs/workbench/contrib/terminal/browser/terminalQuickOpen.ts index 0a17ee6d236..17c61bc9c11 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalQuickOpen.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalQuickOpen.ts @@ -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/test/electron-browser/terminalColorRegistry.test.ts b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalColorRegistry.test.ts index 10cad170f83..65fcaa47fe2 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 @@ -20,8 +20,8 @@ function getMockTheme(type: ThemeType): ITheme { type: type, getColor: (colorId: ColorIdentifier): Color | undefined => themingRegistry.resolveDefaultColor(colorId, theme), defines: () => true, - getTokenStyle: () => undefined, - resolveScopes: () => undefined + getTokenStyleMetadata: () => undefined, + tokenColorMap: [] }; return theme; 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 392488ad4e7..d9e0970f7ca 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/userDataSync/browser/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts index 3fe61e9a7e5..367dd0b6fb3 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 MIT License. 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 fbac49c504c..4e4b029b044 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 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, DisposableStore } from 'vs/base/common/lifecycle'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -27,22 +27,26 @@ 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 { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; 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/platform/userDataSync/common/userDataSyncService'; -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 = 'configurationSync.enable'; + 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()); private readonly conflictsWarningDisposable = this._register(new MutableDisposable()); private readonly signInNotificationDisposable = this._register(new MutableDisposable()); + private previousAuthStatus: AuthTokenStatus | undefined; constructor( @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, @@ -56,27 +60,40 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo @IHistoryService private readonly historyService: IHistoryService, @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, @IDialogService private readonly dialogService: IDialogService, - @IStorageService private readonly storageService: IStorageService, @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(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING))(() => this.onDidChangeEnablement())); - 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(); + + if (isWeb) { + this._register(instantiationService.createInstance(UserDataAutoSync)); + } + } } private onDidChangeAuthTokenStatus(status: AuthTokenStatus) { this.authTokenContext.set(status); - if (status === AuthTokenStatus.Active) { + if (status === AuthTokenStatus.SignedIn) { this.signInNotificationDisposable.clear(); + + if (this.previousAuthStatus === AuthTokenStatus.SigningIn) { + this.notificationService.info(localize('signedIn', "Successfully signed in.")); + } } this.updateBadge(); + this.previousAuthStatus = status; } private onDidChangeSyncStatus(status: SyncStatus) { @@ -109,8 +126,8 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this.updateBadge(); const enabled = this.configurationService.getValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING); if (enabled) { - if (this.authTokenService.status === AuthTokenStatus.Inactive) { - const handle = this.notificationService.prompt(Severity.Info, localize('ask to sign in', "Please sign in with your '{0}' account to sync configuration", "{ACCOUNT_NAME}"), + 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"), @@ -131,7 +148,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo let badge: IBadge | undefined = undefined; let clazz: string | undefined; - if (this.userDataSyncService.status !== SyncStatus.Uninitialized && this.configurationService.getValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING) && this.authTokenService.status === AuthTokenStatus.Inactive) { + 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...")); @@ -149,11 +166,11 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } private async turnOn(): Promise { - if (this.authTokenService.status === AuthTokenStatus.Inactive) { + if (this.authTokenService.status === AuthTokenStatus.SignedOut) { const result = await this.dialogService.confirm({ type: 'info', - message: localize('sign in to account', "Sign in to {0}", "{ACCOUNT_NAME}"), - detail: localize('ask to sign in', "Please sign in with your '{0}' account to sync configuration", "{ACCOUNT_NAME}"), + 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) { @@ -167,32 +184,24 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } private async configureSyncOptions(): Promise { - if (this.storageService.getBoolean('userDataSync.configureOptions.donotAsk', StorageScope.GLOBAL, false)) { - return; - } 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: 'configurationSync.enableSettings', + id: 'sync.enableSettings', label: localize('user settings', "User Settings") }, { - id: 'configurationSync.enableExtensions', + id: 'sync.enableExtensions', label: localize('extensions', "Extensions") }]; quickPick.items = items; quickPick.selectedItems = items.filter(item => this.configurationService.getValue(item.id)); - quickPick.customButton = true; - quickPick.customLabel = localize('do not ask', "Don't Ask Again"); - disposables.add(quickPick.onDidCustom(() => { - this.storageService.store('userDataSync.configureOptions.donotAsk', true, StorageScope.GLOBAL); - quickPick.hide(); - })); - disposables.add(quickPick.onDidAccept(() => quickPick.hide())); - disposables.add(quickPick.onDidHide(() => { + 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]; @@ -200,6 +209,9 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this.configurationService.updateValue(item.id!, isEnabled); } } + quickPick.hide(); + })); + disposables.add(quickPick.onDidHide(() => { disposables.dispose(); c(); })); @@ -288,7 +300,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo MenuRegistry.appendMenuItem(MenuId.CommandPalette, startSyncMenuItem); 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.Inactive)); + 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', @@ -318,18 +330,18 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo when: CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.SigningIn) }); - const stopSycCommand = { + const stopSyncCommand = { id: 'workbench.userData.actions.stopSync', title: localize('stop sync', "Sync: Turn Off") }; - CommandsRegistry.registerCommand(stopSycCommand.id, () => this.turnOff()); + CommandsRegistry.registerCommand(stopSyncCommand.id, () => this.turnOff()); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '5_sync', - command: stopSycCommand, - when: ContextKeyExpr.and(ContextKeyExpr.has(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`), CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.Active), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.HasConflicts)) + 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: stopSycCommand, + command: stopSyncCommand, when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.has(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`)), }); @@ -381,7 +393,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo id: 'workbench.userData.actions.signout', title: localize('sign out', "Sign Out") }, - when: ContextKeyExpr.and(CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.Active)), + 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/services/authToken/browser/authTokenService.ts b/src/vs/workbench/services/authToken/browser/authTokenService.ts index 844cf7f97b5..ef7af7506a2 100644 --- a/src/vs/workbench/services/authToken/browser/authTokenService.ts +++ b/src/vs/workbench/services/authToken/browser/authTokenService.ts @@ -17,7 +17,7 @@ const ACCOUNT = 'MyAccount'; export class AuthTokenService extends Disposable implements IAuthTokenService { _serviceBrand: undefined; - private _status: AuthTokenStatus = AuthTokenStatus.Refreshing; + private _status: AuthTokenStatus = AuthTokenStatus.Initializing; get status(): AuthTokenStatus { return this._status; } private _onDidChangeStatus: Emitter = this._register(new Emitter()); readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; @@ -31,9 +31,9 @@ export class AuthTokenService extends Disposable implements IAuthTokenService { super(); this.getToken().then(token => { if (token) { - this.setStatus(AuthTokenStatus.Active); + this.setStatus(AuthTokenStatus.SignedIn); } else { - this.setStatus(AuthTokenStatus.Inactive); + this.setStatus(AuthTokenStatus.SignedOut); } }); } @@ -51,7 +51,7 @@ export class AuthTokenService extends Disposable implements IAuthTokenService { 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.Active); + this.setStatus(AuthTokenStatus.SignedIn); } } @@ -61,7 +61,7 @@ export class AuthTokenService extends Disposable implements IAuthTokenService { async logout(): Promise { await this.credentialsService.deletePassword(SERVICE_NAME, ACCOUNT); - this.setStatus(AuthTokenStatus.Inactive); + this.setStatus(AuthTokenStatus.SignedOut); } private setStatus(status: AuthTokenStatus): void { diff --git a/src/vs/workbench/services/authToken/electron-browser/authTokenService.ts b/src/vs/workbench/services/authToken/electron-browser/authTokenService.ts index e29e1727aff..ad7abd58826 100644 --- a/src/vs/workbench/services/authToken/electron-browser/authTokenService.ts +++ b/src/vs/workbench/services/authToken/electron-browser/authTokenService.ts @@ -9,7 +9,6 @@ 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 { IURLService } from 'vs/platform/url/common/url'; import { URI } from 'vs/base/common/uri'; export class AuthTokenService extends Disposable implements IAuthTokenService { @@ -18,7 +17,7 @@ export class AuthTokenService extends Disposable implements IAuthTokenService { private readonly channel: IChannel; - private _status: AuthTokenStatus = AuthTokenStatus.Inactive; + private _status: AuthTokenStatus = AuthTokenStatus.Initializing; get status(): AuthTokenStatus { return this._status; } private _onDidChangeStatus: Emitter = this._register(new Emitter()); readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; @@ -27,25 +26,11 @@ export class AuthTokenService extends Disposable implements IAuthTokenService { constructor( @ISharedProcessService sharedProcessService: ISharedProcessService, - @IURLService private readonly urlService: IURLService ) { 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.urlService.registerHandler(this); - } - - handleURL(uri: URI) { - if (uri.authority === 'vscode.login') { - this.channel.call('exchangeCodeForToken', uri); - return Promise.resolve(true); - } else { - return Promise.resolve(false); - } + this._register(this.channel.listen('onDidChangeStatus')(status => this.updateStatus(status))); + this.channel.call('_getInitialStatus').then(status => this.updateStatus(status)); } getToken(): Promise { @@ -53,8 +38,7 @@ export class AuthTokenService extends Disposable implements IAuthTokenService { } login(): Promise { - const callbackUri = this.urlService.create({ authority: 'vscode.login ' }); - return this.channel.call('login', callbackUri); + return this.channel.call('login'); } refreshToken(): Promise { @@ -66,8 +50,10 @@ export class AuthTokenService extends Disposable implements IAuthTokenService { } 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/configurationResolver/common/variableResolver.ts b/src/vs/workbench/services/configurationResolver/common/variableResolver.ts index c6f302d1b3c..5947f5e4e96 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/dialogs/browser/abstractFileDialogService.ts b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts index cead0c626ba..9fdab42186d 100644 --- a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts @@ -80,24 +80,24 @@ export abstract class AbstractFileDialogService implements IFileDialogService { return this.defaultFilePath(schemeFilter); } - async showSaveConfirm(fileNameOrResources: string | URI[]): Promise { + 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 (Array.isArray(fileNameOrResources) && fileNameOrResources.length === 0) { + if (fileNamesOrResources.length === 0) { return ConfirmResult.DONT_SAVE; } let message: string; - if (typeof fileNameOrResources === 'string' || fileNameOrResources.length === 1) { - message = nls.localize('saveChangesMessage', "Do you want to save the changes you made to {0}?", typeof fileNameOrResources === 'string' ? fileNameOrResources : resources.basename(fileNameOrResources[0])); + 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?", fileNameOrResources.length), fileNameOrResources); + message = getConfirmMessage(nls.localize('saveChangesMessages', "Do you want to save the changes to the following {0} files?", fileNamesOrResources.length), fileNamesOrResources); } const buttons: string[] = [ - Array.isArray(fileNameOrResources) && fileNameOrResources.length > 1 ? nls.localize({ key: 'saveAll', comment: ['&& denotes a mnemonic'] }, "&&Save All") : nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save"), + 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") ]; diff --git a/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts index f4452c6dc6f..af8287287a9 100644 --- a/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts @@ -184,14 +184,14 @@ export class FileDialogService extends AbstractFileDialogService implements IFil return schema === Schemas.untitled ? [Schemas.file] : (schema !== Schemas.file ? [schema, Schemas.file] : [schema]); } - async showSaveConfirm(fileNameOrResources: string | URI[]): Promise { + 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(fileNameOrResources); + return super.showSaveConfirm(fileNamesOrResources); } } diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index 4562afe63e5..9e01064847f 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -7,7 +7,6 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati import { IResourceInput, ITextEditorOptions, IEditorOptions, EditorActivation } from 'vs/platform/editor/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 { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; @@ -19,7 +18,7 @@ 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, EditorsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IResourceEditor, SIDE_GROUP, IResourceEditorReplacement, IOpenEditorOverrideHandler, IVisibleEditor, IEditorService, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE, ISaveEditorsOptions, ISaveAllEditorsOptions } from 'vs/workbench/services/editor/common/editorService'; +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,36 +28,36 @@ 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, @@ -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(); } @@ -580,8 +583,8 @@ export class EditorService extends Disposable implements EditorServiceImpl { 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; @@ -605,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); } @@ -624,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 || basename(resource), description, resource); - } - // Resource else { input = instantiationService.createInstance(ResourceEditorInput, label, description, resource, mode); @@ -658,7 +656,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#endregion - //#region save + //#region save/revert async save(editors: IEditorIdentifier | IEditorIdentifier[], options?: ISaveEditorsOptions): Promise { @@ -712,21 +710,37 @@ export class EditorService extends Disposable implements EditorServiceImpl { } saveAll(options?: ISaveAllEditorsOptions): Promise { - return this.save(this.getAllDirtyEditors(!!options?.includeUntitled), options); + return this.save(this.getAllDirtyEditors(options), options); } - async revertAll(options?: IRevertOptions): Promise { - const result = await Promise.all(this.getAllDirtyEditors(true /* include untitled */).map(async ({ editor }) => editor.revert(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); } - private getAllDirtyEditors(includeUntitled: boolean): IEditorIdentifier[] { + 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() || includeUntitled)) { + if (editor.isDirty() && (!editor.isUntitled() || !!options?.includeUntitled)) { editors.push({ groupId: group.id, editor }); } } diff --git a/src/vs/workbench/services/editor/common/editorService.ts b/src/vs/workbench/services/editor/common/editorService.ts index 6bb7691e0b6..af64d9b7a1a 100644 --- a/src/vs/workbench/services/editor/common/editorService.ts +++ b/src/vs/workbench/services/editor/common/editorService.ts @@ -52,7 +52,7 @@ export interface ISaveEditorsOptions extends ISaveOptions { saveAs?: boolean; } -export interface ISaveAllEditorsOptions extends ISaveEditorsOptions { +export interface IBaseSaveRevertAllEditorOptions { /** * Wether to include untitled editors as well. @@ -60,6 +60,10 @@ export interface ISaveAllEditorsOptions extends ISaveEditorsOptions { includeUntitled?: boolean; } +export interface ISaveAllEditorsOptions extends ISaveEditorsOptions, IBaseSaveRevertAllEditorOptions { } + +export interface IRevertAllEditorsOptions extends IRevertOptions, IBaseSaveRevertAllEditorOptions { } + export interface IEditorService { _serviceBrand: undefined; @@ -212,8 +216,13 @@ export interface IEditorService { */ saveAll(options?: ISaveAllEditorsOptions): Promise; + /** + * Reverts the provided list of editors. + */ + revert(editors: IEditorIdentifier | IEditorIdentifier[], options?: IRevertOptions): Promise; + /** * Reverts all editors. */ - revertAll(options?: IRevertOptions): Promise; + revertAll(options?: IRevertAllEditorsOptions): Promise; } diff --git a/src/vs/workbench/services/extensions/common/extensionsUtil.ts b/src/vs/workbench/services/extensions/common/extensionsUtil.ts index 83fdf37fb67..6a232c9616b 100644 --- a/src/vs/workbench/services/extensions/common/extensionsUtil.ts +++ b/src/vs/workbench/services/extensions/common/extensionsUtil.ts @@ -32,14 +32,14 @@ export function getExtensionKind(manifest: IExtensionManifest, productService: I return toArray(result); } - // check the manifest itself - result = manifest.extensionKind; + // check product.json + result = getProductExtensionKind(manifest, productService); if (typeof result !== 'undefined') { return toArray(result); } - // check product.json - result = getProductExtensionKind(manifest, productService); + // check the manifest itself + result = manifest.extensionKind; if (typeof result !== 'undefined') { return toArray(result); } diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index 87de5701125..ce56c37a418 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -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 diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index bdcbe53192d..f4665c503b9 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -514,13 +514,9 @@ export class ExtensionService extends AbstractExtensionService implements IExten const pickRunningLocation = (extension: IExtensionDescription): RunningLocation => { for (const extensionKind of getExtensionKind(extension, this._productService, this._configurationService)) { if (extensionKind === 'ui') { - // a ui extension can run on both sides for now... if (isInstalledLocally.has(ExtensionIdentifier.toKey(extension.identifier))) { return RunningLocation.Local; } - if (isInstalledRemotely.has(ExtensionIdentifier.toKey(extension.identifier))) { - return RunningLocation.Remote; - } } else if (extensionKind === 'workspace') { if (isInstalledRemotely.has(ExtensionIdentifier.toKey(extension.identifier))) { return RunningLocation.Remote; diff --git a/src/vs/workbench/services/extensions/worker/extHost.services.ts b/src/vs/workbench/services/extensions/worker/extHost.services.ts index ed2e8767cf1..8a65101aa4e 100644 --- a/src/vs/workbench/services/extensions/worker/extHost.services.ts +++ b/src/vs/workbench/services/extensions/worker/extHost.services.ts @@ -12,7 +12,7 @@ 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 { 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'; @@ -51,7 +51,7 @@ function NotImplementedProxy(name: ServiceIdentifier): { new(): T } { } registerSingleton(IExtHostTerminalService, WorkerExtHostTerminalService); registerSingleton(IExtHostTask, WorkerExtHostTask); -registerSingleton(IExtHostDebugService, class extends NotImplementedProxy(IExtHostDebugService) { }); +registerSingleton(IExtHostDebugService, WorkerExtHostDebugService); registerSingleton(IExtensionStoragePaths, class extends NotImplementedProxy(IExtensionStoragePaths) { whenReady = Promise.resolve(); }); diff --git a/src/vs/workbench/services/history/browser/history.ts b/src/vs/workbench/services/history/browser/history.ts index 5c3bb1e5f2e..6e061b1dcc1 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. @@ -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/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index 3867576370d..2c0becf3008 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -193,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 @@ -229,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}" ] } diff --git a/src/vs/workbench/services/progress/test/progressIndicator.test.ts b/src/vs/workbench/services/progress/test/progressIndicator.test.ts index 85a8e2f7275..f50511e22d8 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/textMate/browser/abstractTextMateService.ts b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts index dc9222e4b02..a782f0cd7ac 100644 --- a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts +++ b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts @@ -10,6 +10,7 @@ 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'; @@ -44,6 +45,7 @@ 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, @@ -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; @@ -218,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]) { @@ -242,16 +248,17 @@ 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); @@ -314,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/common/TMGrammarFactory.ts b/src/vs/workbench/services/textMate/common/TMGrammarFactory.ts index 1ea68789895..0a6c9abffe9 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 2b61bb56cb8..0ff72dadff2 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 6a4a4d4d2e2..f33593da728 100644 --- a/src/vs/workbench/services/textMate/electron-browser/textMateService.ts +++ b/src/vs/workbench/services/textMate/electron-browser/textMateService.ts @@ -206,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 6f78679f1fd..6f2725ba9c6 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/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 400006f193f..d0fb5b46abd 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -36,6 +36,7 @@ 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. @@ -238,7 +239,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex 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 } @@ -344,7 +345,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex async create(resource: URI, value?: string | ITextSnapshot, options?: ICreateFileOptions): Promise { // before event - await this._onWillRunOperation.fireAsync(promises => new FileOperationWillRunEvent(promises, FileOperation.CREATE, resource)); + await this._onWillRunOperation.fireAsync({ operation: FileOperation.CREATE, target: resource }, CancellationToken.None); const stat = await this.doCreate(resource, value, options); @@ -374,7 +375,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex async delete(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Promise { // before event - await this._onWillRunOperation.fireAsync(promises => new FileOperationWillRunEvent(promises, FileOperation.DELETE, resource)); + 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 }); @@ -388,7 +389,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex async move(source: URI, target: URI, overwrite?: boolean): Promise { // before event - await this._onWillRunOperation.fireAsync(promises => new FileOperationWillRunEvent(promises, FileOperation.MOVE, target, source)); + await this._onWillRunOperation.fireAsync({ operation: FileOperation.MOVE, 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[] = []; @@ -491,9 +492,7 @@ 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); } saveAll(includeUntitled?: boolean, options?: ITextFileSaveOptions): Promise; @@ -559,7 +558,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 }); })); @@ -647,10 +646,11 @@ export abstract class AbstractTextFileService extends Disposable implements ITex await Promise.all(dirtyFileModels.map(async model => { await model.save(options); - if (!model.isDirty()) { + // 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; } } })); @@ -837,9 +837,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 { @@ -849,7 +847,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // Revert untitled const untitledReverted = this.untitledTextEditorService.revertAll(resources); - untitledReverted.forEach(untitled => revertOperationResult.results.push({ source: untitled, success: true })); + untitledReverted.forEach(untitled => revertOperationResult.results.push({ source: untitled })); return revertOperationResult; } @@ -868,20 +866,18 @@ export abstract class AbstractTextFileService extends Disposable implements ITex try { await model.revert(options); - if (!model.isDirty()) { + // 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.resource); - if (result) { - result.success = true; - } + return; } // Otherwise bubble up the error diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index f125c5235e5..8ab10dc191a 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -15,7 +15,7 @@ import { ITextFileService, ModelState, ITextFileEditorModel, ISaveErrorHandler, 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'; @@ -340,8 +340,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil 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 @@ -426,8 +425,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil etag: content.etag, isFile: true, isDirectory: false, - isSymbolicLink: false, - isReadonly: content.isReadonly + isSymbolicLink: false }); // Keep the original encoding to not loose it when saving @@ -1030,7 +1028,7 @@ 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 { diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 51338221b40..f2eef06db52 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -131,21 +131,10 @@ export interface ITextFileService extends IDisposable { move(source: URI, target: URI, overwrite?: boolean): Promise; } -export class FileOperationWillRunEvent implements IWaitUntil { - - constructor( - private _thenables: Promise[], - readonly operation: FileOperation, - readonly target: URI, - readonly source?: URI | undefined - ) { } - - waitUntil(thenable: Promise): void { - if (Object.isFrozen(this._thenables)) { - throw new Error('waitUntil cannot be used aync'); - } - this._thenables.push(thenable); - } +export interface FileOperationWillRunEvent extends IWaitUntil { + operation: FileOperation; + target: URI; + source?: URI; } export class FileOperationDidRunEvent { @@ -320,7 +309,7 @@ export interface ITextFileOperationResult { export interface IResult { source: URI; target?: URI; - success?: boolean; + error?: boolean; } export const enum LoadReason { diff --git a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts index e105588731a..9b748aef08b 100644 --- a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts @@ -375,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) { @@ -400,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 @@ -409,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/textFileService.io.test.ts b/src/vs/workbench/services/textfile/test/textFileService.io.test.ts index 79eedaa0e67..eae7fcd7217 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.io.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.io.test.ts @@ -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 db7e2bd8033..e0bb57aac5b 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.test.ts @@ -205,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); diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 046764e60c3..c0172a4cdd8 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -359,14 +359,17 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { const themeData = data; 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 => { @@ -379,6 +382,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { 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); } @@ -415,6 +419,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)); diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index 4a1b760b4e2..9639f59e0d1 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -19,9 +19,10 @@ import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; import { URI } from 'vs/base/common/uri'; import { parse as parsePList } from 'vs/workbench/services/themes/common/plistParser'; import { startsWith } from 'vs/base/common/strings'; -import { TokenStyle, TokenClassification, ProbeScope, TokenStylingRule, getTokenClassificationRegistry } from 'vs/platform/theme/common/tokenClassificationRegistry'; +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(ColorRegistryExtensions.ColorContribution); @@ -54,12 +55,15 @@ export class ColorThemeData implements IColorTheme { private colorMap: IColorMap = {}; private customColorMap: IColorMap = {}; - private tokenStylingRules: TokenStylingRule[] | undefined = undefined; + 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; @@ -68,38 +72,41 @@ export class ColorThemeData implements IColorTheme { } get tokenColors(): ITextMateThemingRule[] { - const result: 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: ITextMateThemingRule) { - 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 { @@ -114,19 +121,142 @@ export class ColorThemeData implements IColorTheme { return color; } - public getTokenStyle(tokenClassification: TokenClassification, useDefault?: boolean): TokenStyle | undefined { - // todo: cache results - return tokenClassificationRegistry.resolveTokenStyle(tokenClassification, this.tokenStylingRules, this.customTokenStylingRules, this); + 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 getDefaultTokenStyle(tokenClassification: TokenClassification): TokenStyle | undefined { - return tokenClassificationRegistry.resolveTokenStyle(tokenClassification, undefined, [], this); - } - public resolveScopes(scopes: ProbeScope[]): TokenStyle | undefined { if (!this.themeTokenScopeMatchers) { @@ -177,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) { @@ -190,7 +324,7 @@ export class ColorThemeData implements IColorTheme { public setCustomTokenColors(customTokenColors: ITokenColorCustomizations) { this.customTokenColors = []; - this.customTokenScopeMatchers = undefined; + // first add the non-theme specific settings this.addCustomTokenColors(customTokenColors); @@ -199,16 +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.tokenStylingRules = []; - readCustomTokenStyleRules(tokenStylingRules, this.tokenStylingRules); + this.customTokenStylingRules = []; + readCustomTokenStyleRules(tokenStylingRules, this.customTokenStylingRules); const themeSpecificColors = tokenStylingRules[`[${this.settingsId}]`] as IExperimentalTokenStyleCustomizations; if (types.isObject(themeSpecificColors)) { - readCustomTokenStyleRules(themeSpecificColors, this.tokenStylingRules); + readCustomTokenStyleRules(themeSpecificColors, this.customTokenStylingRules); } + + this.tokenColorIndex = undefined; + this.textMateThemingRules = undefined; } private addCustomTokenColors(customTokenColors: ITokenColorCustomizations) { @@ -249,7 +390,7 @@ export class ColorThemeData implements IColorTheme { return Promise.resolve(undefined); } this.themeTokenColors = []; - this.themeTokenScopeMatchers = undefined; + this.clearCaches(); const result = { colors: {}, @@ -264,6 +405,13 @@ export class ColorThemeData implements IColorTheme { }); } + 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) { @@ -566,7 +714,8 @@ function getTokenStyle(foreground: string | undefined, fontStyle: string | undef function readCustomTokenStyleRules(tokenStylingRuleSection: IExperimentalTokenStyleCustomizations, result: TokenStylingRule[] = []) { for (let key in tokenStylingRuleSection) { if (key[0] !== '[') { - const classification = tokenClassificationRegistry.getTokenClassificationFromString(key); + const [type, ...modifiers] = key.split('.'); + const classification = tokenClassificationRegistry.getTokenClassification(type, modifiers); if (classification) { const settings = tokenStylingRuleSection[key]; let style: TokenStyle | undefined; @@ -587,3 +736,74 @@ function readCustomTokenStyleRules(tokenStylingRuleSection: IExperimentalTokenSt 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/test/electron-browser/tokenStyleResolving.test.ts b/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts index ab41c917cb8..b256164b7b4 100644 --- a/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts +++ b/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts @@ -16,6 +16,7 @@ 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(); @@ -48,13 +49,42 @@ function assertTokenStyle(actual: TokenStyle | undefined | null, expected: Token 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 classification = tokenClassificationRegistry.getTokenClassificationFromString(qualifiedClassifier); + const [type, ...modifiers] = qualifiedClassifier.split('.'); + + const classification = tokenClassificationRegistry.getTokenClassification(type, modifiers); assert.ok(classification, 'Classification not found'); const tokenStyle = themeData.getTokenStyle(classification!); - assertTokenStyle(tokenStyle, expected[qualifiedClassifier], qualifiedClassifier); + const expectedTokenStyle = expected[qualifiedClassifier]; + assertTokenStyle(tokenStyle, expectedTokenStyle, qualifiedClassifier); + + const tokenStyleMetaData = themeData.getTokenStyleMetadata(type, modifiers); + assertTokenStyleMetaData(colorIndex, tokenStyleMetaData, expectedTokenStyle); } } diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts index fd4da1c641f..ae74659bc3f 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts @@ -51,8 +51,6 @@ export interface IWorkingCopyService { isDirty(resource: URI): boolean; - getDirty(...resources: URI[]): IWorkingCopy[]; - //#endregion @@ -72,34 +70,6 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic private readonly _onDidChangeDirty = this._register(new Emitter()); readonly onDidChangeDirty = this._onDidChangeDirty.event; - getDirty(...resources: URI[]): IWorkingCopy[] { - const dirtyWorkingCopies: IWorkingCopy[] = []; - - // Specific resource(s) - if (resources.length > 0) { - for (const resource of resources) { - this.fillDirty(this.mapResourceToWorkingCopy.get(resource.toString()), dirtyWorkingCopies); - } - } - - // All resources - else { - this.fillDirty(this.workingCopies, dirtyWorkingCopies); - } - - return dirtyWorkingCopies; - } - - private fillDirty(workingCopies: Set | undefined, target: IWorkingCopy[]): void { - if (workingCopies) { - for (const workingCopy of workingCopies) { - if (workingCopy.isDirty()) { - target.push(workingCopy); - } - } - } - } - isDirty(resource: URI): boolean { const workingCopies = this.mapResourceToWorkingCopy.get(resource.toString()); if (workingCopies) { diff --git a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts index 7110cd28582..a7797845a17 100644 --- a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts @@ -56,8 +56,6 @@ suite('WorkingCopyService', () => { assert.equal(service.hasDirty, false); assert.equal(service.dirtyCount, 0); - assert.equal(service.getDirty().length, 0); - assert.equal(service.getDirty(URI.file('/'), URI.file('/some')).length, 0); assert.equal(service.isDirty(URI.file('/')), false); // resource 1 @@ -68,8 +66,6 @@ suite('WorkingCopyService', () => { assert.equal(service.dirtyCount, 0); assert.equal(service.isDirty(resource1), false); assert.equal(service.hasDirty, false); - assert.equal(service.getDirty(resource1).length, 0); - assert.equal(service.getDirty().length, 0); copy1.setDirty(true); @@ -78,8 +74,6 @@ suite('WorkingCopyService', () => { assert.equal(service.hasDirty, true); assert.equal(onDidChangeDirty.length, 1); assert.equal(onDidChangeDirty[0], copy1); - assert.equal(service.getDirty(resource1).length, 1); - assert.equal(service.getDirty().length, 1); copy1.setDirty(false); @@ -88,8 +82,6 @@ suite('WorkingCopyService', () => { assert.equal(service.hasDirty, false); assert.equal(onDidChangeDirty.length, 2); assert.equal(onDidChangeDirty[1], copy1); - assert.equal(service.getDirty(resource1).length, 0); - assert.equal(service.getDirty().length, 0); unregister1.dispose(); @@ -101,8 +93,6 @@ suite('WorkingCopyService', () => { assert.equal(service.dirtyCount, 1); assert.equal(service.isDirty(resource2), true); assert.equal(service.hasDirty, true); - assert.equal(service.getDirty(resource1, resource2).length, 1); - assert.equal(service.getDirty().length, 1); assert.equal(onDidChangeDirty.length, 3); assert.equal(onDidChangeDirty[2], copy2); @@ -112,8 +102,6 @@ suite('WorkingCopyService', () => { assert.equal(service.hasDirty, false); assert.equal(onDidChangeDirty.length, 4); assert.equal(onDidChangeDirty[3], copy2); - assert.equal(service.getDirty(resource1, resource2).length, 0); - assert.equal(service.getDirty().length, 0); }); test('registry - multiple copies on same resource', () => { @@ -135,31 +123,23 @@ suite('WorkingCopyService', () => { assert.equal(service.dirtyCount, 1); assert.equal(onDidChangeDirty.length, 1); assert.equal(service.isDirty(resource), true); - assert.equal(service.getDirty(resource).length, 1); - assert.equal(service.getDirty().length, 1); copy2.setDirty(true); assert.equal(service.dirtyCount, 2); assert.equal(onDidChangeDirty.length, 2); assert.equal(service.isDirty(resource), true); - assert.equal(service.getDirty(resource).length, 2); - assert.equal(service.getDirty().length, 2); unregister1.dispose(); assert.equal(service.dirtyCount, 1); assert.equal(onDidChangeDirty.length, 3); assert.equal(service.isDirty(resource), true); - assert.equal(service.getDirty(resource).length, 1); - assert.equal(service.getDirty().length, 1); unregister2.dispose(); assert.equal(service.dirtyCount, 0); assert.equal(onDidChangeDirty.length, 4); assert.equal(service.isDirty(resource), false); - assert.equal(service.getDirty(resource).length, 0); - assert.equal(service.getDirty().length, 0); }); }); 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 9ae0966307c..dbfe6851bae 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/common/editor/dataUriEditorInput.test.ts b/src/vs/workbench/test/common/editor/dataUriEditorInput.test.ts deleted file mode 100644 index 44d111a1f81..00000000000 --- 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 MIT License. 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'); - }); - }); -}); diff --git a/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts b/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts index 78cb84d606e..f9f9f5eb20e 100644 --- a/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts +++ b/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts @@ -98,9 +98,13 @@ suite('Workbench untitled text editors', () => { assert.ok(!workingCopyService.isDirty(input2.getResource())); assert.equal(workingCopyService.dirtyCount, 0); - input2.dispose(); + assert.ok(input1.revert()); + assert.ok(input1.isDisposed()); + assert.ok(!service.exists(input1.getResource())); + input2.dispose(); assert.ok(!service.exists(input2.getResource())); + done(); }); 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 43e7087b3be..374f21e78d0 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostDiagnostics.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostDiagnostics.test.ts @@ -97,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[]; 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 d8b1cbadc9b..c20cee41ce1 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 3793608226f..891f0358784 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts @@ -589,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'); @@ -613,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'); @@ -636,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); }); @@ -654,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/mainThreadDocumentsAndEditors.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts index 483d634e6ea..f8d30bbaf7f 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 35fde4c61de..eba10c94c0d 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', () => { @@ -41,7 +42,7 @@ suite('MainThreadEditors', () => { 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(); 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 e8161804b96..bd24a27bebe 100644 --- a/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts @@ -30,6 +30,7 @@ import { LocalSearchService } from 'vs/workbench/services/search/node/searchServ 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,7 +74,7 @@ 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()], 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 76aa1c7f8f2..d4eb6ee2728 100644 --- a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts @@ -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,7 +64,7 @@ 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()], diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 861d7bc9a7c..531bfd37b51 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -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, ISaveEditorsOptions } 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'; @@ -444,7 +444,7 @@ export class TestFileDialogService implements IFileDialogService { public setConfirmResult(result: ConfirmResult): void { this.confirmResult = result; } - public showSaveConfirm(resources: string | URI[]): Promise { + public showSaveConfirm(fileNamesOrResources: (string | URI)[]): Promise { return Promise.resolve(this.confirmResult); } } @@ -930,7 +930,11 @@ export class TestEditorService implements EditorServiceImpl { throw new Error('Method not implemented.'); } - revertAll(options?: IRevertOptions): Promise { + revert(editors: IEditorIdentifier[], options?: IRevertOptions): Promise { + throw new Error('Method not implemented.'); + } + + revertAll(options?: IRevertAllEditorsOptions): Promise { throw new Error('Method not implemented.'); } } diff --git a/test/automation/src/editor.ts b/test/automation/src/editor.ts index 907f614d245..97a8b5214a2 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/smoke/src/areas/editor/editor.test.ts b/test/smoke/src/areas/editor/editor.test.ts index 469d5a179b1..8832f070225 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/yarn.lock b/yarn.lock index d9bdce0cd9f..31726b4dfef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5706,15 +5706,15 @@ native-is-elevated@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" @@ -6068,10 +6068,10 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" -onigasm-umd@^2.2.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/onigasm-umd/-/onigasm-umd-2.2.4.tgz#27ee87f7496c66ad40cebfbc0d418c19bb7db5ec" - integrity sha512-N9VqCUhl9KBuzm47vcK8T/xUnbYylIhMN45Rwltlo1sZc3QUDda6SxIlyVB8r0SJQwURv8JOHjyXjjCriGvzRg== +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" @@ -9072,10 +9072,10 @@ vscode-sqlite3@4.0.9: dependencies: nan "^2.14.0" -vscode-textmate@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-4.3.0.tgz#6e1f0f273d84148cfa1e9c7ed85bd16c974f9f61" - integrity sha512-MhEZ3hvxOVuYGsrRzW/PZLDR2VdtG2+V6TIKPvmE9JT+RAq/OtPlrFd1+ZQwBefoHEhjRNuRJ0OktcFezuxPmg== +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"