diff --git a/.github/classifier.yml b/.github/classifier.yml index e7a79f7b64c..29f9fa41f52 100644 --- a/.github/classifier.yml +++ b/.github/classifier.yml @@ -18,40 +18,127 @@ assignees: [ weinand ], assignLabel: false }, - diff-editor: [], - dropdown: [], - editor: { + diff-editor: : { + assignees: [], + assignLabel: false + }, + dropdown: [], + editor: : { + assignees: [], + assignLabel: false + }, + editor-1000-limit: : { + assignees: [], + assignLabel: false + }, + editor-autoclosing: : { + assignees: [], + assignLabel: false + }, + editor-autoindent: : { + assignees: [], + assignLabel: false + }, + editor-brackets: : { + assignees: [], + assignLabel: false + }, + editor-clipboard: : { + assignees: [], + assignLabel: false + }, + editor-code-actions: : { + assignees: [], + assignLabel: false + }, + editor-code-lens: : { + assignees: [], + assignLabel: false + }, + editor-color-picker: : { + assignees: [], + assignLabel: false + }, + editor-colors: : { + assignees: [], + assignLabel: false + }, + editor-columnselect: : { + assignees: [], + assignLabel: false + }, + editor-commands: : { + assignees: [], + assignLabel: false + }, + editor-contrib: : { + assignees: [], + assignLabel: false + }, + editor-drag-and-drop: : { + assignees: [], + assignLabel: false + }, + editor-find: : { + assignees: [], + assignLabel: false + }, + editor-folding: : { + assignees: [], + assignLabel: false + }, + editor-hover: : { + assignees: [], + assignLabel: false + }, + editor-ime: : { + assignees: [], + assignLabel: false + }, + editor-input: : { + assignees: [], + assignLabel: false + }, + editor-ligatures: : { + assignees: [], + assignLabel: false + }, + editor-links: : { + assignees: [], + assignLabel: false + }, + editor-minimap: : { + assignees: [], + assignLabel: false + }, + editor-multicursor: : { + assignees: [], + assignLabel: false + }, + editor-parameter-hints: : { + assignees: [], + assignLabel: false + }, + editor-rendering: : { + assignees: [], + assignLabel: false + }, + editor-smooth: : { + assignees: [], + assignLabel: false + }, + editor-symbols: : { + assignees: [], + assignLabel: false + }, + editor-textbuffer: : { + assignees: [], + assignLabel: false + }, + editor-wrapping: : { assignees: [], assignLabel: false }, - editor-1000-limit: [], - editor-autoclosing: [], - editor-autoindent: [], - editor-brackets: [], - editor-clipboard: [], - editor-code-actions: [], - editor-code-lens: [], - editor-color-picker: [], - editor-colors: [], - editor-columnselect: [], - editor-commands: [], - editor-contrib: [], - editor-drag-and-drop: [], - editor-find: [], - editor-folding: [], - editor-hover: [], - editor-ime: [], - editor-input: [], - editor-ligatures: [], - editor-links: [], - editor-minimap: [], - editor-multicursor: [], - editor-parameter-hints: [], - editor-rendering: [], - editor-smooth: [], - editor-symbols: [], - editor-textbuffer: [], - editor-wrapping: [], emmet: [ octref ], error-list: [], explorer-custom: [], @@ -87,8 +174,14 @@ issue-reporter: [ RMacfarlane ], javascript: [ mjbvz ], json: [], - keyboard-layout: [], - keybindings: [], + keyboard-layout: : { + assignees: [], + assignLabel: false + }, + keybindings: : { + assignees: [], + assignLabel: false + }, keybindings-editor: [], lang-diagnostics: [], languages basic: [], diff --git a/.vscode/settings.json b/.vscode/settings.json index 3fa04b1ee95..1a760bdda6b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -59,5 +59,6 @@ "git.ignoreLimitWarning": true, "remote.extensionKind": { "msjsdiag.debugger-for-chrome": "workspace" - } -} + }, + "files.insertFinalNewline": true +} \ No newline at end of file diff --git a/build/azure-pipelines/distro-build.yml b/build/azure-pipelines/distro-build.yml index 4057894f025..62ee67ad1c6 100644 --- a/build/azure-pipelines/distro-build.yml +++ b/build/azure-pipelines/distro-build.yml @@ -31,12 +31,12 @@ steps: git remote add distro "https://github.com/$VSCODE_MIXIN_REPO.git" git fetch distro - # Push master branch into master and oss/master - git push distro origin/master:refs/heads/master origin/master:refs/heads/oss/master + # Push master branch into oss/master + git push distro origin/master:refs/heads/oss/master # Push every release branch into oss/release git for-each-ref --format="%(refname:short)" refs/remotes/origin/release/* | sed 's/^origin\/\(.*\)$/\0:refs\/heads\/oss\/\1/' | xargs git push distro git merge $(node -p "require('./package.json').distro") - displayName: Sync & Merge Distro \ No newline at end of file + displayName: Sync & Merge Distro diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index f044a0e5384..a3483f451e2 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -31,7 +31,7 @@ }, { "name": "ms-vscode.references-view", - "version": "0.0.28", + "version": "0.0.29", "repo": "https://github.com/Microsoft/vscode-reference-view", "metadata": { "id": "dc489f46-520d-4556-ae85-1f9eab3c412d", diff --git a/build/gulpfile.reh.js b/build/gulpfile.reh.js index 8784602db3e..7085c4f8f50 100644 --- a/build/gulpfile.reh.js +++ b/build/gulpfile.reh.js @@ -17,7 +17,7 @@ const gunzip = require('gulp-gunzip'); const untar = require('gulp-untar'); const File = require('vinyl'); const fs = require('fs'); -const remote = require('gulp-remote-src'); +const remote = require('gulp-remote-retry-src'); const rename = require('gulp-rename'); const filter = require('gulp-filter'); const cp = require('child_process'); diff --git a/build/lib/extensions.js b/build/lib/extensions.js index a73f8cf5864..73d2c7acef3 100644 --- a/build/lib/extensions.js +++ b/build/lib/extensions.js @@ -13,7 +13,7 @@ const File = require("vinyl"); const vsce = require("vsce"); const stats_1 = require("./stats"); const util2 = require("./util"); -const remote = require("gulp-remote-src"); +const remote = require("gulp-remote-retry-src"); const vzip = require('gulp-vinyl-zip'); const filter = require("gulp-filter"); const rename = require("gulp-rename"); diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts index 94bc81c15df..4b185aff681 100644 --- a/build/lib/extensions.ts +++ b/build/lib/extensions.ts @@ -13,7 +13,7 @@ import * as File from 'vinyl'; import * as vsce from 'vsce'; import { createStatsStream } from './stats'; import * as util2 from './util'; -import remote = require('gulp-remote-src'); +import remote = require('gulp-remote-retry-src'); const vzip = require('gulp-vinyl-zip'); import filter = require('gulp-filter'); import rename = require('gulp-rename'); diff --git a/build/lib/typings/gulp-remote-src.d.ts b/build/lib/typings/gulp-remote-src.d.ts index 6ea57f84fe5..ff9026b79bb 100644 --- a/build/lib/typings/gulp-remote-src.d.ts +++ b/build/lib/typings/gulp-remote-src.d.ts @@ -1,4 +1,4 @@ -declare module 'gulp-remote-src' { +declare module 'gulp-remote-retry-src' { import stream = require("stream"); @@ -20,4 +20,4 @@ declare module 'gulp-remote-src' { } export = remote; -} \ No newline at end of file +} diff --git a/build/monaco/monaco.usage.recipe b/build/monaco/monaco.usage.recipe index 13290a7abb5..f1a74a25e30 100644 --- a/build/monaco/monaco.usage.recipe +++ b/build/monaco/monaco.usage.recipe @@ -30,7 +30,7 @@ import * as editorAPI from './vs/editor/editor.api'; a = (>b).type; a = (b).start; a = (b).end; - a = (>b).getProxyObject; // IWorkerClient + a = (>b).getProxyObject; // IWorkerClient a = create1; a = create2; a = (b).extensionId; diff --git a/build/package.json b/build/package.json index e292f7a7e41..2a1c775c736 100644 --- a/build/package.json +++ b/build/package.json @@ -42,7 +42,7 @@ "tslint": "^5.9.1", "typescript": "3.5.2", "vsce": "1.48.0", - "vscode-telemetry-extractor": "1.4.3", + "vscode-telemetry-extractor": "^1.5.1", "xml2js": "^0.4.17" }, "scripts": { @@ -51,4 +51,4 @@ "postinstall": "npm run compile", "npmCheckJs": "tsc --noEmit" } -} \ No newline at end of file +} diff --git a/build/yarn.lock b/build/yarn.lock index 8f675d1fef0..7d00ac18872 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -1257,7 +1257,12 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= @@ -2119,7 +2124,7 @@ tough-cookie@~2.3.3: dependencies: punycode "^1.4.1" -ts-morph@^3.1.2: +ts-morph@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/ts-morph/-/ts-morph-3.1.3.tgz#bbfa1d14481ee23bdd1c030340ccf4a243cfc844" integrity sha512-CwjgyJTtd3f8vBi7Vr0IOgdOY6Wi/Tq0MhieXOE2B5ns5WWRD7BwMNHtv+ZufKI/S2U/lMrh+Q3bOauE4tsv2g== @@ -2308,19 +2313,19 @@ vsce@1.48.0: yauzl "^2.3.1" yazl "^2.2.2" -vscode-ripgrep@^1.4.0: +vscode-ripgrep@^1.5.5: version "1.5.5" resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.5.5.tgz#24c0e9cb356cf889c98e15ecb58f9cf654a1d961" integrity sha512-OrPrAmcun4+uZAuNcQvE6CCPskh+5AsjANod/Q3zRcJcGNxgoOSGlQN9RPtatkUNmkN8Nn8mZBnS1jMylu/dKg== -vscode-telemetry-extractor@1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/vscode-telemetry-extractor/-/vscode-telemetry-extractor-1.4.3.tgz#e4af380beb4e2a63d6e4fa819b25ba7ef6dc4a10" - integrity sha512-OFklPErZnUBjrKte3hg+irQXue5rzgz4qnvE8kdaOnW1E/wynHUEXW9t5vF5q0hu2lodpGMkybwLkSjONZVzvg== +vscode-telemetry-extractor@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/vscode-telemetry-extractor/-/vscode-telemetry-extractor-1.5.1.tgz#67249e4ca9c65a21800ca53880732f8cef98d0fa" + integrity sha512-B5SnEdRiDrI4o6NMG9iHmengoaW1rxUQmS/sCaripgnchm+P79JURmKxhfXr5eRo4Mr1QSenFT/SDNaEop7aoQ== dependencies: command-line-args "^5.1.1" - ts-morph "^3.1.2" - vscode-ripgrep "^1.4.0" + ts-morph "^3.1.3" + vscode-ripgrep "^1.5.5" vso-node-api@6.1.2-preview: version "6.1.2-preview" diff --git a/extensions/css-language-features/server/package.json b/extensions/css-language-features/server/package.json index d1913a26acf..c6e8600d2ae 100644 --- a/extensions/css-language-features/server/package.json +++ b/extensions/css-language-features/server/package.json @@ -9,7 +9,7 @@ }, "main": "./out/cssServerMain", "dependencies": { - "vscode-css-languageservice": "^4.0.3-next.0", + "vscode-css-languageservice": "^4.0.3-next.1", "vscode-languageserver": "^5.3.0-next.8" }, "devDependencies": { diff --git a/extensions/css-language-features/server/src/cssServerMain.ts b/extensions/css-language-features/server/src/cssServerMain.ts index 884306d299a..0bfd886c310 100644 --- a/extensions/css-language-features/server/src/cssServerMain.ts +++ b/extensions/css-language-features/server/src/cssServerMain.ts @@ -121,9 +121,9 @@ connection.onInitialize((params: InitializeParams): InitializeResult => { scopedSettingsSupport = !!getClientCapability('workspace.configuration', false); foldingRangeLimit = getClientCapability('textDocument.foldingRange.rangeLimit', Number.MAX_VALUE); - languageServices.css = getCSSLanguageService({ customDataProviders, fileSystemProvider }); - languageServices.scss = getSCSSLanguageService({ customDataProviders, fileSystemProvider }); - languageServices.less = getLESSLanguageService({ customDataProviders, fileSystemProvider }); + languageServices.css = getCSSLanguageService({ customDataProviders, fileSystemProvider, clientCapabilities: params.capabilities }); + languageServices.scss = getSCSSLanguageService({ customDataProviders, fileSystemProvider, clientCapabilities: params.capabilities }); + languageServices.less = getLESSLanguageService({ customDataProviders, fileSystemProvider, clientCapabilities: params.capabilities }); const capabilities: ServerCapabilities = { // Tell the client that the server works in FULL text document sync mode @@ -140,7 +140,8 @@ connection.onInitialize((params: InitializeParams): InitializeResult => { codeActionProvider: true, renameProvider: true, colorProvider: {}, - foldingRangeProvider: true + foldingRangeProvider: true, + selectionRangeProvider: true }; return { capabilities }; }); diff --git a/extensions/css-language-features/server/yarn.lock b/extensions/css-language-features/server/yarn.lock index 3537a3d2ecd..d93eec392c7 100644 --- a/extensions/css-language-features/server/yarn.lock +++ b/extensions/css-language-features/server/yarn.lock @@ -781,10 +781,10 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -vscode-css-languageservice@^4.0.3-next.0: - version "4.0.3-next.0" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.0.3-next.0.tgz#ba96894cf2a0c86c744a1274590f27e55ea60f58" - integrity sha512-ku58Y5jDFNfDicv2AAhgu1edgfGcRZPwlKu6EBK2ck/O/Vco7Zy64FDoClJghcYBhJiDs7sy2q/UtQD0IoGbRw== +vscode-css-languageservice@^4.0.3-next.1: + version "4.0.3-next.1" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.0.3-next.1.tgz#e89d01ce0d79b3e6c2642f5e3ad73cb8160d38d9" + integrity sha512-Zrm5TeraVUJ8vRikWhFt259dQu+WK+Ie3K5UA8BB4kqcanoM+1mcnIt8fPkTXlZLbiEWElrkJ9yuYbDNkufeBg== dependencies: vscode-languageserver-types "^3.15.0-next.2" vscode-nls "^4.1.1" diff --git a/extensions/git/src/api/api1.ts b/extensions/git/src/api/api1.ts index 25742babc15..a4fd677db27 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -5,7 +5,7 @@ import { Model } from '../model'; import { Repository as BaseRepository, Resource } from '../repository'; -import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions } from './git'; +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'; @@ -214,6 +214,14 @@ export class ApiImpl implements API { readonly git = new ApiGit(this._model); + get state(): APIState { + return this._model.state; + } + + get onDidChangeState(): Event { + return this._model.onDidChangeState; + } + get onDidOpenRepository(): Event { return mapEvent(this._model.onDidOpenRepository, r => new ApiRepository(r)); } diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index 9444ea1fada..21195974fc1 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -176,7 +176,11 @@ export interface Repository { log(options?: LogOptions): Promise; } +export type APIState = 'uninitialized' | 'initialized'; + export interface API { + readonly state: APIState; + readonly onDidChangeState: Event; readonly git: Git; readonly repositories: Repository[]; readonly onDidOpenRepository: Event; diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index a04dc33c8be..da6a940428e 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -12,7 +12,7 @@ import * as path from 'path'; import * as fs from 'fs'; import * as nls from 'vscode-nls'; import { fromGitUri } from './uri'; -import { GitErrorCodes } from './api/git'; +import { GitErrorCodes, APIState as State } from './api/git'; const localize = nls.loadMessageBundle(); @@ -63,15 +63,22 @@ export class Model { private possibleGitRepositoryPaths = new Set(); + private _onDidChangeState = new EventEmitter(); + readonly onDidChangeState = this._onDidChangeState.event; + + private _state: State = 'uninitialized'; + get state(): State { return this._state; } + + setState(state: State): void { + this._state = state; + this._onDidChangeState.fire(state); + } + private disposables: Disposable[] = []; constructor(readonly git: Git, private globalState: Memento, private outputChannel: OutputChannel) { workspace.onDidChangeWorkspaceFolders(this.onDidChangeWorkspaceFolders, this, this.disposables); - this.onDidChangeWorkspaceFolders({ added: workspace.workspaceFolders || [], removed: [] }); - window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, this.disposables); - this.onDidChangeVisibleTextEditors(window.visibleTextEditors); - workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this, this.disposables); const fsWatcher = workspace.createFileSystemWatcher('**'); @@ -82,7 +89,15 @@ export class Model { const onPossibleGitRepositoryChange = filterEvent(onGitRepositoryChange, uri => !this.getRepository(uri)); onPossibleGitRepositoryChange(this.onPossibleGitRepositoryChange, this, this.disposables); - this.scanWorkspaceFolders(); + this.doInitialScan().finally(() => this.setState('initialized')); + } + + private async doInitialScan(): Promise { + await Promise.all([ + this.onDidChangeWorkspaceFolders({ added: workspace.workspaceFolders || [], removed: [] }), + this.onDidChangeVisibleTextEditors(window.visibleTextEditors), + this.scanWorkspaceFolders() + ]); } /** @@ -157,8 +172,8 @@ export class Model { .filter(r => !activeRepositories.has(r!.repository)) .filter(r => !(workspace.workspaceFolders || []).some(f => isDescendant(f.uri.fsPath, r!.repository.root))) as OpenRepository[]; - possibleRepositoryFolders.forEach(p => this.openRepository(p.uri.fsPath)); openRepositoriesToDispose.forEach(r => r.dispose()); + await Promise.all(possibleRepositoryFolders.map(p => this.openRepository(p.uri.fsPath))); } private onDidChangeConfiguration(): void { @@ -175,7 +190,7 @@ export class Model { openRepositoriesToDispose.forEach(r => r.dispose()); } - private onDidChangeVisibleTextEditors(editors: TextEditor[]): void { + private async onDidChangeVisibleTextEditors(editors: TextEditor[]): Promise { const config = workspace.getConfiguration('git'); const autoRepositoryDetection = config.get('autoRepositoryDetection'); @@ -183,7 +198,7 @@ export class Model { return; } - editors.forEach(editor => { + await Promise.all(editors.map(async editor => { const uri = editor.document.uri; if (uri.scheme !== 'file') { @@ -196,8 +211,8 @@ export class Model { return; } - this.openRepository(path.dirname(uri.fsPath)); - }); + await this.openRepository(path.dirname(uri.fsPath)); + })); } @sequentialize @@ -236,6 +251,7 @@ export class Model { const repository = new Repository(this.git.open(repositoryRoot, dotGit), this.globalState, this.outputChannel); this.open(repository); + await repository.status(); } catch (err) { if (err.gitErrorCode === GitErrorCodes.NotAGitRepository) { return; @@ -421,4 +437,4 @@ export class Model { this.possibleGitRepositoryPaths.clear(); this.disposables = dispose(this.disposables); } -} \ No newline at end of file +} diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index a581f9f5fd4..196a1bddade 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -519,8 +519,8 @@ class DotGitWatcher implements IFileWatcher { this.transientDisposables.push(upstreamWatcher); upstreamWatcher.event(this.emitter.fire, this.emitter, this.transientDisposables); } catch (err) { - if (env.logLevel <= LogLevel.Info) { - this.outputChannel.appendLine(`Failed to watch ref '${upstreamPath}'. Ref is most likely packed.`); + if (env.logLevel <= LogLevel.Error) { + this.outputChannel.appendLine(`Failed to watch ref '${upstreamPath}', is most likely packed.\n${err.stack || err}`); } } } @@ -651,19 +651,30 @@ export class Repository implements Disposable { const onWorkspaceRepositoryFileChange = filterEvent(onWorkspaceFileChange, uri => isDescendant(repository.root, uri.fsPath)); const onWorkspaceWorkingTreeFileChange = filterEvent(onWorkspaceRepositoryFileChange, uri => !/\/\.git($|\/)/.test(uri.path)); - const dotGitFileWatcher = new DotGitWatcher(this, outputChannel); - this.disposables.push(dotGitFileWatcher); + let onDotGitFileChange: Event; + + try { + const dotGitFileWatcher = new DotGitWatcher(this, outputChannel); + onDotGitFileChange = dotGitFileWatcher.event; + this.disposables.push(dotGitFileWatcher); + } catch (err) { + if (env.logLevel <= LogLevel.Error) { + outputChannel.appendLine(`Failed to watch '${this.dotGit}', reverting to legacy API file watched. Some events might be lost.\n${err.stack || err}`); + } + + onDotGitFileChange = filterEvent(onWorkspaceRepositoryFileChange, uri => /\/\.git($|\/)/.test(uri.path)); + } // FS changes should trigger `git status`: // - any change inside the repository working tree // - any change whithin the first level of the `.git` folder, except the folder itself and `index.lock` - const onFileChange = anyEvent(onWorkspaceWorkingTreeFileChange, dotGitFileWatcher.event); + const onFileChange = anyEvent(onWorkspaceWorkingTreeFileChange, onDotGitFileChange); onFileChange(this.onFileChange, this, this.disposables); // Relevate repository changes should trigger virtual document change events - dotGitFileWatcher.event(this._onDidChangeRepository.fire, this._onDidChangeRepository, this.disposables); + onDotGitFileChange(this._onDidChangeRepository.fire, this._onDidChangeRepository, this.disposables); - this.disposables.push(new FileEventLogger(onWorkspaceWorkingTreeFileChange, dotGitFileWatcher.event, outputChannel)); + this.disposables.push(new FileEventLogger(onWorkspaceWorkingTreeFileChange, onDotGitFileChange, outputChannel)); const root = Uri.file(repository.root); this._sourceControl = scm.createSourceControl('git', 'Git', root); @@ -713,7 +724,6 @@ export class Repository implements Disposable { this.disposables.push(progressManager); this.updateCommitTemplate(); - this.status(); } validateInput(text: string, position: number): SourceControlInputBoxValidation | undefined { diff --git a/extensions/html-language-features/server/package.json b/extensions/html-language-features/server/package.json index e97a42ab0a6..95a97f1110f 100644 --- a/extensions/html-language-features/server/package.json +++ b/extensions/html-language-features/server/package.json @@ -9,12 +9,12 @@ }, "main": "./out/htmlServerMain", "dependencies": { - "vscode-css-languageservice": "^4.0.2", - "vscode-html-languageservice": "^3.0.0", + "vscode-css-languageservice": "^4.0.3-next.1", + "vscode-html-languageservice": "^3.0.4-next.0", "vscode-languageserver": "^5.3.0-next.8", "vscode-languageserver-types": "3.15.0-next.2", "vscode-nls": "^4.1.1", - "vscode-uri": "^2.0.1" + "vscode-uri": "^2.0.3" }, "devDependencies": { "@types/mocha": "2.2.33", diff --git a/extensions/html-language-features/server/src/htmlServerMain.ts b/extensions/html-language-features/server/src/htmlServerMain.ts index 6eb8fc298fe..01b43d9a9ba 100644 --- a/extensions/html-language-features/server/src/htmlServerMain.ts +++ b/extensions/html-language-features/server/src/htmlServerMain.ts @@ -96,7 +96,7 @@ connection.onInitialize((params: InitializeParams): InitializeResult => { get folders() { return workspaceFolders; } }; - languageModes = getLanguageModes(initializationOptions ? initializationOptions.embeddedLanguages : { css: true, javascript: true }, workspace, providers); + languageModes = getLanguageModes(initializationOptions ? initializationOptions.embeddedLanguages : { css: true, javascript: true }, workspace, params.capabilities, providers); documents.onDidClose(e => { languageModes.onDocumentRemoved(e.document); @@ -135,7 +135,8 @@ connection.onInitialize((params: InitializeParams): InitializeResult => { signatureHelpProvider: { triggerCharacters: ['('] }, referencesProvider: true, colorProvider: {}, - foldingRangeProvider: true + foldingRangeProvider: true, + selectionRangeProvider: true }; return { capabilities }; }); diff --git a/extensions/html-language-features/server/src/modes/cssMode.ts b/extensions/html-language-features/server/src/modes/cssMode.ts index 168c7ceffa0..6e60c32d87c 100644 --- a/extensions/html-language-features/server/src/modes/cssMode.ts +++ b/extensions/html-language-features/server/src/modes/cssMode.ts @@ -5,13 +5,12 @@ import { LanguageModelCache, getLanguageModelCache } from '../languageModelCache'; import { TextDocument, Position, Range, CompletionList } from 'vscode-languageserver-types'; -import { getCSSLanguageService, Stylesheet, FoldingRange } from 'vscode-css-languageservice'; +import { Stylesheet, FoldingRange, LanguageService as CSSLanguageService } from 'vscode-css-languageservice'; import { LanguageMode, Workspace } from './languageModes'; import { HTMLDocumentRegions, CSS_STYLE_RULE } from './embeddedSupport'; import { Color } from 'vscode-languageserver'; -export function getCSSMode(documentRegions: LanguageModelCache, workspace: Workspace): LanguageMode { - let cssLanguageService = getCSSLanguageService(); +export function getCSSMode(cssLanguageService: CSSLanguageService, documentRegions: LanguageModelCache, workspace: Workspace): LanguageMode { let embeddedCSSDocuments = getLanguageModelCache(10, 60, document => documentRegions.get(document).getEmbeddedDocument('css')); let cssStylesheets = getLanguageModelCache(10, 60, document => cssLanguageService.parseStylesheet(document)); diff --git a/extensions/html-language-features/server/src/modes/languageModes.ts b/extensions/html-language-features/server/src/modes/languageModes.ts index b44b2442420..d07e0bd80f6 100644 --- a/extensions/html-language-features/server/src/modes/languageModes.ts +++ b/extensions/html-language-features/server/src/modes/languageModes.ts @@ -3,18 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getLanguageService as getHTMLLanguageService, DocumentContext, IHTMLDataProvider, SelectionRange } from 'vscode-html-languageservice'; -import { - CompletionItem, Location, SignatureHelp, Definition, TextEdit, TextDocument, Diagnostic, DocumentLink, Range, - Hover, DocumentHighlight, CompletionList, Position, FormattingOptions, SymbolInformation, FoldingRange -} from 'vscode-languageserver-types'; -import { ColorInformation, ColorPresentation, Color, WorkspaceFolder } from 'vscode-languageserver'; - +import { getCSSLanguageService } from 'vscode-css-languageservice'; +import { ClientCapabilities, DocumentContext, getLanguageService as getHTMLLanguageService, IHTMLDataProvider, SelectionRange } from 'vscode-html-languageservice'; +import { Color, ColorInformation, ColorPresentation, WorkspaceFolder } from 'vscode-languageserver'; +import { CompletionItem, CompletionList, Definition, Diagnostic, DocumentHighlight, DocumentLink, FoldingRange, FormattingOptions, Hover, Location, Position, Range, SignatureHelp, SymbolInformation, TextDocument, TextEdit } from 'vscode-languageserver-types'; import { getLanguageModelCache, LanguageModelCache } from '../languageModelCache'; -import { getDocumentRegions, HTMLDocumentRegions } from './embeddedSupport'; import { getCSSMode } from './cssMode'; -import { getJavaScriptMode } from './javascriptMode'; +import { getDocumentRegions, HTMLDocumentRegions } from './embeddedSupport'; import { getHTMLMode } from './htmlMode'; +import { getJavaScriptMode } from './javascriptMode'; export { ColorInformation, ColorPresentation, Color }; @@ -66,8 +63,9 @@ export interface LanguageModeRange extends Range { attributeValue?: boolean; } -export function getLanguageModes(supportedLanguages: { [languageId: string]: boolean; }, workspace: Workspace, customDataProviders?: IHTMLDataProvider[]): LanguageModes { - const htmlLanguageService = getHTMLLanguageService({ customDataProviders }); +export function getLanguageModes(supportedLanguages: { [languageId: string]: boolean; }, workspace: Workspace, clientCapabilities: ClientCapabilities, customDataProviders?: IHTMLDataProvider[]): LanguageModes { + const htmlLanguageService = getHTMLLanguageService({ customDataProviders, clientCapabilities }); + const cssLanguageService = getCSSLanguageService({ clientCapabilities }); let documentRegions = getLanguageModelCache(10, 60, document => getDocumentRegions(htmlLanguageService, document)); @@ -77,7 +75,7 @@ export function getLanguageModes(supportedLanguages: { [languageId: string]: boo let modes = Object.create(null); modes['html'] = getHTMLMode(htmlLanguageService, workspace); if (supportedLanguages['css']) { - modes['css'] = getCSSMode(documentRegions, workspace); + modes['css'] = getCSSMode(cssLanguageService, documentRegions, workspace); } if (supportedLanguages['javascript']) { modes['javascript'] = getJavaScriptMode(documentRegions); diff --git a/extensions/html-language-features/server/src/test/completions.test.ts b/extensions/html-language-features/server/src/test/completions.test.ts index de056ed3c1e..aaba72add6d 100644 --- a/extensions/html-language-features/server/src/test/completions.test.ts +++ b/extensions/html-language-features/server/src/test/completions.test.ts @@ -9,6 +9,7 @@ import { URI } from 'vscode-uri'; import { TextDocument, CompletionList, CompletionItemKind } from 'vscode-languageserver-types'; import { getLanguageModes } from '../modes/languageModes'; import { WorkspaceFolder } from 'vscode-languageserver'; +import { ClientCapabilities } from 'vscode-html-languageservice'; export interface ItemDescription { label: string; @@ -58,7 +59,7 @@ export function testCompletionFor(value: string, expected: { count?: number, ite let document = TextDocument.create(uri, 'html', 0, value); let position = document.positionAt(offset); - const languageModes = getLanguageModes({ css: true, javascript: true }, workspace); + const languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST); const mode = languageModes.getModeAtPosition(document, position)!; let list = mode.doComplete!(document, position); diff --git a/extensions/html-language-features/server/src/test/folding.test.ts b/extensions/html-language-features/server/src/test/folding.test.ts index 3ce2816185b..e19555d568d 100644 --- a/extensions/html-language-features/server/src/test/folding.test.ts +++ b/extensions/html-language-features/server/src/test/folding.test.ts @@ -8,6 +8,7 @@ import * as assert from 'assert'; import { TextDocument } from 'vscode-languageserver'; import { getFoldingRanges } from '../modes/htmlFolding'; import { getLanguageModes } from '../modes/languageModes'; +import { ClientCapabilities } from 'vscode-css-languageservice'; interface ExpectedIndentRange { startLine: number; @@ -21,7 +22,7 @@ function assertRanges(lines: string[], expected: ExpectedIndentRange[], message? settings: {}, folders: [{ name: 'foo', uri: 'test://foo' }] }; - let languageModes = getLanguageModes({ css: true, javascript: true }, workspace); + let languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST); let actual = getFoldingRanges(languageModes, document, nRanges, null); let actualRanges = []; diff --git a/extensions/html-language-features/server/src/test/formatting.test.ts b/extensions/html-language-features/server/src/test/formatting.test.ts index 702aa76da6c..8c59c75e017 100644 --- a/extensions/html-language-features/server/src/test/formatting.test.ts +++ b/extensions/html-language-features/server/src/test/formatting.test.ts @@ -11,6 +11,7 @@ import { getLanguageModes } from '../modes/languageModes'; import { TextDocument, Range, FormattingOptions } from 'vscode-languageserver-types'; import { format } from '../modes/formatting'; +import { ClientCapabilities } from 'vscode-html-languageservice'; suite('HTML Embedded Formatting', () => { @@ -19,7 +20,7 @@ suite('HTML Embedded Formatting', () => { settings: options, folders: [{ name: 'foo', uri: 'test://foo' }] }; - var languageModes = getLanguageModes({ css: true, javascript: true }, workspace); + var languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST); let rangeStartOffset = value.indexOf('|'); let rangeEndOffset; diff --git a/extensions/html-language-features/server/src/utils/documentContext.ts b/extensions/html-language-features/server/src/utils/documentContext.ts index bfd26d1aa8d..6c391064fae 100644 --- a/extensions/html-language-features/server/src/utils/documentContext.ts +++ b/extensions/html-language-features/server/src/utils/documentContext.ts @@ -32,7 +32,11 @@ export function getDocumentContext(documentUri: string, workspaceFolders: Worksp } } } - return url.resolve(base, ref); + try { + return url.resolve(base, ref); + } catch { + return ''; + } }, }; } diff --git a/extensions/html-language-features/server/yarn.lock b/extensions/html-language-features/server/yarn.lock index 8c532e3136f..9ea59b6077c 100644 --- a/extensions/html-language-features/server/yarn.lock +++ b/extensions/html-language-features/server/yarn.lock @@ -229,22 +229,23 @@ supports-color@5.4.0: dependencies: has-flag "^3.0.0" -vscode-css-languageservice@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.0.2.tgz#7496e538b0c151feac16d5888cc0b1b104f4c736" - integrity sha512-pTnfXbsME3pl+yDfhUp/mtvPyIJk0Le4zqJxDn56s9GY9LqY0RmkSEh0oHH6D0HXR3Ni6wKosIaqu8a2G0+jdw== +vscode-css-languageservice@^4.0.3-next.1: + version "4.0.3-next.1" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.0.3-next.1.tgz#e89d01ce0d79b3e6c2642f5e3ad73cb8160d38d9" + integrity sha512-Zrm5TeraVUJ8vRikWhFt259dQu+WK+Ie3K5UA8BB4kqcanoM+1mcnIt8fPkTXlZLbiEWElrkJ9yuYbDNkufeBg== dependencies: vscode-languageserver-types "^3.15.0-next.2" vscode-nls "^4.1.1" + vscode-uri "^2.0.3" -vscode-html-languageservice@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-3.0.0.tgz#b9649aa0713d68665d7546bd3772dd10e4dbe200" - integrity sha512-AgNyjaYrmgundh5gXP0bqCLeLdfUTyvNafF1moNwYdqeNh6DIpMG6RjwYwgtNChXSsVGXnaHiwGMtAUwMxkQUQ== +vscode-html-languageservice@^3.0.4-next.0: + version "3.0.4-next.0" + resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-3.0.4-next.0.tgz#d4f5a103b94753a19b374158212fe734dbe670e8" + integrity sha512-5Z5ITtokWt/zuPKemKEXfC+4XHoQryBAZVAcTwpAel2qqueUwGqjd5ZrVy/2x5GZAdZAipl0BvsTTMkOBS1BFQ== dependencies: vscode-languageserver-types "^3.15.0-next.2" vscode-nls "^4.1.1" - vscode-uri "^2.0.1" + vscode-uri "^2.0.3" vscode-jsonrpc@^4.1.0-next.2: version "4.1.0-next.2" @@ -288,10 +289,10 @@ vscode-uri@^1.0.6: resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-1.0.6.tgz#6b8f141b0bbc44ad7b07e94f82f168ac7608ad4d" integrity sha512-sLI2L0uGov3wKVb9EB+vIQBl9tVP90nqRvxSoJ35vI3NjxE8jfsE5DSOhWgSunHSZmKS4OCi2jrtfxK7uyp2ww== -vscode-uri@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-2.0.1.tgz#5448e4f77d21d93ffa34b96f84c6c5e09e3f5a9b" - integrity sha512-s/k0zsYr6y+tsocFyxT/+G5aq8mEdpDZuph3LZ+UmCs7LNhx/xomiCy5kyP+jOAKC7RMCUvb6JbPD1/TgAvq0g== +vscode-uri@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-2.0.3.tgz#25e5f37f552fbee3cec7e5f80cef8469cefc6543" + integrity sha512-4D3DI3F4uRy09WNtDGD93H9q034OHImxiIcSq664Hq1Y1AScehlP3qqZyTkX/RWxeu0MRMHGkrxYqm2qlDF/aw== wrappy@1: version "1.0.2" diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index 061cb69249a..68e6a005537 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -17,7 +17,7 @@ "vscode-json-languageservice": "^3.3.0", "vscode-languageserver": "^5.3.0-next.8", "vscode-nls": "^4.1.1", - "vscode-uri": "^2.0.1" + "vscode-uri": "^2.0.3" }, "devDependencies": { "@types/mocha": "2.2.33", diff --git a/extensions/json-language-features/server/src/jsonServerMain.ts b/extensions/json-language-features/server/src/jsonServerMain.ts index 699e73a9c91..27e29a96764 100644 --- a/extensions/json-language-features/server/src/jsonServerMain.ts +++ b/extensions/json-language-features/server/src/jsonServerMain.ts @@ -154,7 +154,8 @@ connection.onInitialize((params: InitializeParams): InitializeResult => { documentSymbolProvider: true, documentRangeFormattingProvider: false, colorProvider: {}, - foldingRangeProvider: true + foldingRangeProvider: true, + selectionRangeProvider: true }; return { capabilities }; diff --git a/extensions/json-language-features/server/yarn.lock b/extensions/json-language-features/server/yarn.lock index 1bb198a5210..13ab8c59ab0 100644 --- a/extensions/json-language-features/server/yarn.lock +++ b/extensions/json-language-features/server/yarn.lock @@ -134,3 +134,8 @@ vscode-uri@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-2.0.1.tgz#5448e4f77d21d93ffa34b96f84c6c5e09e3f5a9b" integrity sha512-s/k0zsYr6y+tsocFyxT/+G5aq8mEdpDZuph3LZ+UmCs7LNhx/xomiCy5kyP+jOAKC7RMCUvb6JbPD1/TgAvq0g== + +vscode-uri@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-2.0.3.tgz#25e5f37f552fbee3cec7e5f80cef8469cefc6543" + integrity sha512-4D3DI3F4uRy09WNtDGD93H9q034OHImxiIcSq664Hq1Y1AScehlP3qqZyTkX/RWxeu0MRMHGkrxYqm2qlDF/aw== diff --git a/extensions/typescript-language-features/src/features/quickFix.ts b/extensions/typescript-language-features/src/features/quickFix.ts index 8972df52185..6ce9f199704 100644 --- a/extensions/typescript-language-features/src/features/quickFix.ts +++ b/extensions/typescript-language-features/src/features/quickFix.ts @@ -314,6 +314,7 @@ const preferredFixes = new Set([ 'forgottenThisPropertyAccess', 'spelling', 'unusedIdentifier', + 'addMissingAwait', ]); function isPreferredFix(tsAction: Proto.CodeFixAction): boolean { return preferredFixes.has(tsAction.fixName); 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 13b9666dbfe..dc281a06fd5 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { window, Terminal, TerminalVirtualProcess, EventEmitter, TerminalDimensions, workspace, ConfigurationTarget } from 'vscode'; +import { window, Terminal, Pseudoterminal, EventEmitter, TerminalDimensions, workspace, ConfigurationTarget } from 'vscode'; import { doesNotThrow, equal, ok } from 'assert'; suite('window namespace tests', () => { @@ -264,10 +264,12 @@ suite('window namespace tests', () => { }); term.dispose(); }); - const virtualProcess: TerminalVirtualProcess = { - onDidWrite: new EventEmitter().event + const pty: Pseudoterminal = { + onDidWrite: new EventEmitter().event, + open: () => {}, + close: () => {} }; - window.createTerminal({ name: 'c', virtualProcess }); + window.createTerminal({ name: 'c', pty }); }); test('should fire Terminal.onData on write', (done) => { @@ -289,11 +291,12 @@ suite('window namespace tests', () => { let startResolve: () => void; const startPromise: Promise = new Promise(r => startResolve = r); const writeEmitter = new EventEmitter(); - const virtualProcess: TerminalVirtualProcess = { + const pty: Pseudoterminal = { onDidWrite: writeEmitter.event, - start: () => startResolve() + open: () => startResolve(), + close: () => {} }; - const terminal = window.createTerminal({ name: 'foo', virtualProcess }); + const terminal = window.createTerminal({ name: 'foo', pty }); }); test('should fire provide dimensions on start as the terminal has been shown', (done) => { @@ -301,9 +304,9 @@ suite('window namespace tests', () => { equal(terminal, term); reg1.dispose(); }); - const virtualProcess: TerminalVirtualProcess = { + const pty: Pseudoterminal = { onDidWrite: new EventEmitter().event, - start: (dimensions) => { + open: (dimensions) => { ok(dimensions!.columns > 0); ok(dimensions!.rows > 0); const reg3 = window.onDidCloseTerminal(() => { @@ -311,9 +314,10 @@ suite('window namespace tests', () => { done(); }); terminal.dispose(); - } + }, + close: () => {} }; - const terminal = window.createTerminal({ name: 'foo', virtualProcess }); + const terminal = window.createTerminal({ name: 'foo', pty }); }); test('should respect dimension overrides', (done) => { @@ -336,11 +340,13 @@ suite('window namespace tests', () => { }); const writeEmitter = new EventEmitter(); const overrideDimensionsEmitter = new EventEmitter(); - const virtualProcess: TerminalVirtualProcess = { + const pty: Pseudoterminal = { onDidWrite: writeEmitter.event, - onDidOverrideDimensions: overrideDimensionsEmitter.event + onDidOverrideDimensions: overrideDimensionsEmitter.event, + open: () => {}, + close: () => {} }; - const terminal = window.createTerminal({ name: 'foo', virtualProcess }); + const terminal = window.createTerminal({ name: 'foo', pty }); }); }); }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts index 43470d24e19..daef120762c 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import * as vscode from 'vscode'; -suite.only('workspace-namespace', () => { +suite('workspace-namespace', () => { suite('Tasks', () => { @@ -35,17 +35,18 @@ suite.only('workspace-namespace', () => { customProp1: 'testing task one' }; const writeEmitter = new vscode.EventEmitter(); - const execution = new vscode.CustomExecution2((): Thenable => { - return Promise.resolve({ + const execution = new vscode.CustomExecution2((): Thenable => { + const pty: vscode.Pseudoterminal = { onDidWrite: writeEmitter.event, - start: () => { + open: () => { writeEmitter.fire('testing\r\n'); }, - shutdown: () => { + close: () => { taskProvider.dispose(); done(); } - }); + }; + return Promise.resolve(pty); }); const task = new vscode.Task2(kind, vscode.TaskScope.Workspace, taskName, taskType, execution); result.push(task); diff --git a/package.json b/package.json index 859546881b5..ca74e4fb961 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.37.0", - "distro": "13a988e218df227b97e810eb49a1a0982bfed00d", + "distro": "7224ab1f6e8484664148ab7362432b49c653d047", "author": { "name": "Microsoft Corporation" }, @@ -52,8 +52,8 @@ "vscode-ripgrep": "^1.5.5", "vscode-sqlite3": "4.0.8", "vscode-textmate": "^4.2.2", - "xterm": "3.15.0-beta87", - "xterm-addon-search": "0.2.0-beta2", + "xterm": "3.15.0-beta89", + "xterm-addon-search": "0.2.0-beta3", "xterm-addon-web-links": "0.1.0-beta10", "yauzl": "^2.9.2", "yazl": "^2.4.3" @@ -96,7 +96,7 @@ "gulp-gunzip": "^1.0.0", "gulp-json-editor": "^2.5.0", "gulp-plumber": "^1.2.0", - "gulp-remote-src": "^0.4.4", + "gulp-remote-retry-src": "^0.6.0", "gulp-rename": "^1.2.0", "gulp-replace": "^0.5.4", "gulp-shell": "^0.6.5", diff --git a/remote/package.json b/remote/package.json index 081eb5c6076..34ffee1a4be 100644 --- a/remote/package.json +++ b/remote/package.json @@ -20,8 +20,8 @@ "vscode-proxy-agent": "0.4.0", "vscode-ripgrep": "^1.5.5", "vscode-textmate": "^4.2.2", - "xterm": "3.15.0-beta71", - "xterm-addon-search": "0.2.0-beta2", + "xterm": "3.15.0-beta89", + "xterm-addon-search": "0.2.0-beta3", "xterm-addon-web-links": "0.1.0-beta10", "yauzl": "^2.9.2", "yazl": "^2.4.3" diff --git a/remote/yarn.lock b/remote/yarn.lock index 3ed8f02de3c..1c4e700f495 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -1149,20 +1149,20 @@ vscode-windows-registry@1.0.1: dependencies: nan "^2.12.1" -xterm-addon-search@0.2.0-beta2: - version "0.2.0-beta2" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.2.0-beta2.tgz#c3173f0a6f207ee9f1848849174ee5d6b6ce8262" - integrity sha512-XEcwi2TeFGk2MuIFjiI/OpVXSNO5dGQBvHH3o+9KzqG3ooVqhhDqzwxs092QGNcNCGh8hGn/PWZiczaBBnKm/g== +xterm-addon-search@0.2.0-beta3: + version "0.2.0-beta3" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.2.0-beta3.tgz#710ce14658e269c5a4791f5a9e2f520883a2e62b" + integrity sha512-KzVdkEtGbKJe9ER2TmrI7XjF/wUq1lir9U63vPJi0t2ymQvIECl1V63f9QtOp1vvpdhbZiXBxO+vGTj+y0tRow== xterm-addon-web-links@0.1.0-beta10: version "0.1.0-beta10" resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.1.0-beta10.tgz#610fa9773a2a5ccd41c1c83ba0e2dd2c9eb66a23" integrity sha512-xfpjy0V6bB4BR44qIgZQPoCMVakxb65gMscPkHpO//QxvUxKzabV3dxOsIbeZRFkUGsWTFlvz2OoaBLoNtv5gg== -xterm@3.15.0-beta71: - version "3.15.0-beta71" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.15.0-beta71.tgz#2728c9800ca3b08423e835e9504bd1f4b5de6253" - integrity sha512-8M/cLaxZ+iDopRxLPdPfKuDGaNNyYTdCeytdxjMSH0N7dZzbx6fbaEygQdCrV5pO9cGnT92MefSjVPGRXRiBLA== +xterm@3.15.0-beta89: + version "3.15.0-beta89" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.15.0-beta89.tgz#255962e2595deefb42b8c0043001256526163a3f" + integrity sha512-rNaoUamacPRg+ejbKDGRDNqR3SZ3Uf/pUW0mO+FF25/lIgdLq8x7RgZVBgFweCZ/dijPjxoyMcgfNDTH9h8LOg== yauzl@^2.9.2: version "2.10.0" diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index b447e0b68ec..6d4436c23eb 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -122,7 +122,7 @@ export const isSafari = (!isChrome && (userAgent.indexOf('Safari') >= 0)); export const isWebkitWebView = (!isChrome && !isSafari && isWebKit); export const isIPad = (userAgent.indexOf('iPad') >= 0); export const isEdgeWebView = isEdge && (userAgent.indexOf('WebView/') >= 0); -export const isStandalone = (window.matchMedia('(display-mode: standalone)').matches); +export const isStandalone = (window.matchMedia && window.matchMedia('(display-mode: standalone)').matches); export function hasClipboardSupport() { if (isIE) { diff --git a/src/vs/base/browser/mouseEvent.ts b/src/vs/base/browser/mouseEvent.ts index dee572ce892..4c7295e3b9b 100644 --- a/src/vs/base/browser/mouseEvent.ts +++ b/src/vs/base/browser/mouseEvent.ts @@ -160,6 +160,8 @@ export class StandardWheelEvent { this.deltaY = e1.wheelDeltaY / 120; } else if (typeof e2.VERTICAL_AXIS !== 'undefined' && e2.axis === e2.VERTICAL_AXIS) { this.deltaY = -e2.detail / 3; + } else { + this.deltaY = -e.deltaY / 40; } // horizontal delta scroll @@ -171,6 +173,8 @@ export class StandardWheelEvent { } } else if (typeof e2.HORIZONTAL_AXIS !== 'undefined' && e2.axis === e2.HORIZONTAL_AXIS) { this.deltaX = -e.detail / 3; + } else { + this.deltaX = -e.deltaX / 40; } // Assume a vertical scroll if nothing else worked @@ -195,4 +199,4 @@ export class StandardWheelEvent { } } } -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/centered/centeredViewLayout.ts b/src/vs/base/browser/ui/centered/centeredViewLayout.ts index b40384018f1..299b6534873 100644 --- a/src/vs/base/browser/ui/centered/centeredViewLayout.ts +++ b/src/vs/base/browser/ui/centered/centeredViewLayout.ts @@ -6,7 +6,7 @@ import { SplitView, Orientation, ISplitViewStyles, IView as ISplitViewView } from 'vs/base/browser/ui/splitview/splitview'; import { $ } from 'vs/base/browser/dom'; import { Event } from 'vs/base/common/event'; -import { IView, IViewSize } from 'vs/base/browser/ui/grid/gridview'; +import { IView, IViewSize } from 'vs/base/browser/ui/grid/grid'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Color } from 'vs/base/common/color'; diff --git a/src/vs/base/browser/ui/grid/grid.ts b/src/vs/base/browser/ui/grid/grid.ts index 0fc6d389e81..d2cc4ee6ff5 100644 --- a/src/vs/base/browser/ui/grid/grid.ts +++ b/src/vs/base/browser/ui/grid/grid.ts @@ -7,10 +7,11 @@ import 'vs/css!./gridview'; import { Orientation } from 'vs/base/browser/ui/sash/sash'; import { Disposable } from 'vs/base/common/lifecycle'; import { tail2 as tail, equals } from 'vs/base/common/arrays'; -import { orthogonal, IView, GridView, Sizing as GridViewSizing, Box, IGridViewStyles, IViewSize } from './gridview'; +import { orthogonal, IView as IGridViewView, GridView, Sizing as GridViewSizing, Box, IGridViewStyles, IViewSize } from './gridview'; import { Event } from 'vs/base/common/event'; +import { InvisibleSizing } from 'vs/base/browser/ui/splitview/splitview'; -export { Orientation, Sizing as GridViewSizing } from './gridview'; +export { Orientation, Sizing as GridViewSizing, IViewSize, orthogonal, LayoutPriority } from './gridview'; export const enum Direction { Up, @@ -28,9 +29,15 @@ function oppositeDirection(direction: Direction): Direction { } } +export interface IView extends IGridViewView { + readonly preferredHeight?: number; + readonly preferredWidth?: number; +} + export interface GridLeafNode { readonly view: T; readonly box: Box; + readonly cachedVisibleSize: number | undefined; } export interface GridBranchNode { @@ -173,16 +180,23 @@ function getGridLocation(element: HTMLElement): number[] { return [...getGridLocation(ancestor), index]; } -export const enum Sizing { - Distribute = 'distribute', - Split = 'split' +export type DistributeSizing = { type: 'distribute' }; +export type SplitSizing = { type: 'split' }; +export type InvisibleSizing = { type: 'invisible', cachedVisibleSize: number }; +export type Sizing = DistributeSizing | SplitSizing | InvisibleSizing; + +export namespace Sizing { + export const Distribute: DistributeSizing = { type: 'distribute' }; + export const Split: SplitSizing = { type: 'split' }; + export function Invisible(cachedVisibleSize: number): InvisibleSizing { return { type: 'invisible', cachedVisibleSize }; } } export interface IGridStyles extends IGridViewStyles { } export interface IGridOptions { - styles?: IGridStyles; - proportionalLayout?: boolean; + readonly styles?: IGridStyles; + readonly proportionalLayout?: boolean; + readonly firstViewVisibleCachedSize?: number; } export class Grid extends Disposable { @@ -208,9 +222,13 @@ export class Grid extends Disposable { this.gridview = new GridView(options); this._register(this.gridview); - this._register(this.gridview.onDidSashReset(this.doResetViewSize, this)); + this._register(this.gridview.onDidSashReset(this.onDidSashReset, this)); - this._addView(view, 0, [0]); + const size: number | GridViewSizing = typeof options.firstViewVisibleCachedSize === 'number' + ? GridViewSizing.Invisible(options.firstViewVisibleCachedSize) + : 0; + + this._addView(view, size, [0]); } style(styles: IGridStyles): void { @@ -241,10 +259,12 @@ export class Grid extends Disposable { let viewSize: number | GridViewSizing; - if (size === Sizing.Split) { + if (typeof size === 'number') { + viewSize = size; + } else if (size.type === 'split') { const [, index] = tail(referenceLocation); viewSize = GridViewSizing.Split(index); - } else if (size === Sizing.Distribute) { + } else if (size.type === 'distribute') { viewSize = GridViewSizing.Distribute; } else { viewSize = size; @@ -264,7 +284,7 @@ export class Grid extends Disposable { } const location = this.getViewLocation(view); - this.gridview.removeView(location, sizing === Sizing.Distribute ? GridViewSizing.Distribute : undefined); + this.gridview.removeView(location, (sizing && sizing.type === 'distribute') ? GridViewSizing.Distribute : undefined); this.views.delete(view); } @@ -320,7 +340,7 @@ export class Grid extends Disposable { } getViews(): GridBranchNode { - return this.gridview.getViews() as GridBranchNode; + return this.gridview.getView() as GridBranchNode; } getNeighborViews(view: T, direction: Direction, wrap: boolean = false): T[] { @@ -355,8 +375,36 @@ export class Grid extends Disposable { return getGridLocation(element); } - private doResetViewSize(location: number[]): void { - const [parentLocation,] = tail(location); + private onDidSashReset(location: number[]): void { + const resizeToPreferredSize = (location: number[]): boolean => { + const node = this.gridview.getView(location) as GridNode; + + if (isGridBranchNode(node)) { + return false; + } + + const direction = getLocationOrientation(this.orientation, location); + const size = direction === Orientation.HORIZONTAL ? node.view.preferredWidth : node.view.preferredHeight; + + if (typeof size !== 'number') { + return false; + } + + const viewSize = direction === Orientation.HORIZONTAL ? { width: Math.round(size) } : { height: Math.round(size) }; + this.gridview.resizeView(location, viewSize); + return true; + }; + + if (resizeToPreferredSize(location)) { + return; + } + + const [parentLocation, index] = tail(location); + + if (resizeToPreferredSize([...parentLocation, index + 1])) { + return; + } + this.gridview.distributeViewSizes(parentLocation); } } @@ -379,6 +427,7 @@ export interface ISerializedLeafNode { type: 'leaf'; data: object | null; size: number; + visible?: boolean; } export interface ISerializedBranchNode { @@ -402,6 +451,10 @@ export class SerializableGrid extends Grid { const size = orientation === Orientation.VERTICAL ? node.box.width : node.box.height; if (!isGridBranchNode(node)) { + if (typeof node.cachedVisibleSize === 'number') { + return { type: 'leaf', data: node.view.toJSON(), size: node.cachedVisibleSize, visible: false }; + } + return { type: 'leaf', data: node.view.toJSON(), size }; } @@ -426,25 +479,26 @@ export class SerializableGrid extends Grid { throw new Error('Invalid JSON: \'size\' property of node must be a number.'); } + const childSize = child.type === 'leaf' && child.visible === false ? 0 : child.size; const childBox: Box = orientation === Orientation.HORIZONTAL - ? { top: box.top, left: box.left + offset, width: child.size, height: box.height } - : { top: box.top + offset, left: box.left, width: box.width, height: child.size }; + ? { top: box.top, left: box.left + offset, width: childSize, height: box.height } + : { top: box.top + offset, left: box.left, width: box.width, height: childSize }; children.push(SerializableGrid.deserializeNode(child, orthogonal(orientation), childBox, deserializer)); - offset += child.size; + offset += childSize; } return { children, box }; } else if (json.type === 'leaf') { const view: T = deserializer.fromJSON(json.data); - return { view, box }; + return { view, box, cachedVisibleSize: json.visible === false ? json.size : undefined }; } throw new Error('Invalid JSON: \'type\' property must be either \'branch\' or \'leaf\'.'); } - private static getFirstLeaf(node: GridNode): GridLeafNode | undefined { + private static getFirstLeaf(node: GridNode): GridLeafNode { if (!isGridBranchNode(node)) { return node; } @@ -473,6 +527,10 @@ export class SerializableGrid extends Grid { throw new Error('Invalid serialized state, first leaf not found'); } + if (typeof firstLeaf.cachedVisibleSize === 'number') { + options = { ...options, firstViewVisibleCachedSize: firstLeaf.cachedVisibleSize }; + } + const result = new SerializableGrid(firstLeaf.view, options); result.orientation = orientation; result.restoreViews(firstLeaf.view, orientation, root); @@ -522,13 +580,16 @@ export class SerializableGrid extends Grid { const firstLeaves = node.children.map(c => SerializableGrid.getFirstLeaf(c)); for (let i = 1; i < firstLeaves.length; i++) { - const size = orientation === Orientation.VERTICAL ? firstLeaves[i]!.box.height : firstLeaves[i]!.box.width; - this.addView(firstLeaves[i]!.view, size, referenceView, direction); - referenceView = firstLeaves[i]!.view; + const node = firstLeaves[i]; + const size: number | InvisibleSizing = typeof node.cachedVisibleSize === 'number' + ? GridViewSizing.Invisible(node.cachedVisibleSize) + : (orientation === Orientation.VERTICAL ? node.box.height : node.box.width); + this.addView(node.view, size, referenceView, direction); + referenceView = node.view; } for (let i = 0; i < node.children.length; i++) { - this.restoreViews(firstLeaves[i]!.view, orthogonal(orientation), node.children[i]); + this.restoreViews(firstLeaves[i].view, orthogonal(orientation), node.children[i]); } } diff --git a/src/vs/base/browser/ui/grid/gridview.ts b/src/vs/base/browser/ui/grid/gridview.ts index 8ce22799e56..f6e65de3a14 100644 --- a/src/vs/base/browser/ui/grid/gridview.ts +++ b/src/vs/base/browser/ui/grid/gridview.ts @@ -47,6 +47,7 @@ export interface Box { export interface GridLeafNode { readonly view: IView; readonly box: Box; + readonly cachedVisibleSize: number | undefined; } export interface GridBranchNode { @@ -343,6 +344,14 @@ class BranchNode implements ISplitView, IDisposable { this.splitview.setViewVisible(index, visible); } + getChildCachedVisibleSize(index: number): number | undefined { + if (index < 0 || index >= this.children.length) { + throw new Error('Invalid index'); + } + + return this.splitview.getViewCachedVisibleSize(index); + } + private onDidChildrenChange(): void { const onDidChildrenChange = Event.map(Event.any(...this.children.map(c => c.onDidChange)), () => undefined); this.childrenChangeDisposable.dispose(); @@ -424,6 +433,9 @@ class LeafNode implements ISplitView, IDisposable { private _size: number = 0; get size(): number { return this._size; } + private _cachedVisibleSize: number | undefined; + get cachedVisibleSize(): number | undefined { return this._cachedVisibleSize; } + private _orthogonalSize: number; get orthogonalSize(): number { return this._orthogonalSize; } @@ -528,6 +540,12 @@ class LeafNode implements ISplitView, IDisposable { } setVisible(visible: boolean): void { + if (visible) { + this._cachedVisibleSize = undefined; + } else { + this._cachedVisibleSize = this._size; + } + if (this.view.setVisible) { this.view.setVisible(visible); } @@ -658,6 +676,14 @@ export class GridView implements IDisposable { } else { const [, grandParent] = tail(pathToParent); const [, parentIndex] = tail(rest); + + let newSiblingSize: number | Sizing = 0; + + const newSiblingCachedVisibleSize = grandParent.getChildCachedVisibleSize(parentIndex); + if (typeof newSiblingCachedVisibleSize === 'number') { + newSiblingSize = Sizing.Invisible(newSiblingCachedVisibleSize); + } + grandParent.removeChild(parentIndex); const newParent = new BranchNode(parent.orientation, this.styles, this.proportionalLayout, parent.size, parent.orthogonalSize); @@ -665,7 +691,7 @@ export class GridView implements IDisposable { newParent.orthogonalLayout(parent.orthogonalSize); const newSibling = new LeafNode(parent.view, grandParent.orientation, parent.size); - newParent.addChild(newSibling, 0, 0); + newParent.addChild(newSibling, newSiblingSize, 0); if (typeof size !== 'number' && size.type === 'split') { size = Sizing.Split(0); @@ -877,13 +903,16 @@ export class GridView implements IDisposable { parent.setChildVisible(index, visible); } - getViews(): GridBranchNode { - return this._getViews(this.root, this.orientation, { top: 0, left: 0, width: this.width, height: this.height }) as GridBranchNode; + getView(): GridBranchNode; + getView(location?: number[]): GridNode; + getView(location?: number[]): GridNode { + const node = location ? this.getNode(location)[1] : this._root; + return this._getViews(node, this.orientation, { top: 0, left: 0, width: this.width, height: this.height }); } private _getViews(node: Node, orientation: Orientation, box: Box): GridNode { if (node instanceof LeafNode) { - return { view: node.view, box }; + return { view: node.view, box, cachedVisibleSize: node.cachedVisibleSize }; } const children: GridNode[] = []; diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts index fbf8d5dcd13..1c1ded9eb2c 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/scrollbars'; +import { isEdgeOrIE } from 'vs/base/browser/browser'; import * as dom from 'vs/base/browser/dom'; import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; import { IMouseEvent, StandardWheelEvent, IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; @@ -312,7 +313,7 @@ export abstract class AbstractScrollableElement extends Widget { this._onMouseWheel(new StandardWheelEvent(browserEvent)); }; - this._mouseWheelToDispose.push(dom.addDisposableListener(this._listenOnDomNode, 'mousewheel', onMouseWheel)); + this._mouseWheelToDispose.push(dom.addDisposableListener(this._listenOnDomNode, isEdgeOrIE ? 'mousewheel' : 'wheel', onMouseWheel)); } } diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index 7599cc6808d..6f031dac570 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -59,8 +59,11 @@ interface ISashEvent { alt: boolean; } +type ViewItemSize = number | { cachedVisibleSize: number }; + abstract class ViewItem { + private _size: number; set size(size: number) { this._size = size; } @@ -69,10 +72,11 @@ abstract class ViewItem { return this._size; } - private cachedSize: number | undefined = undefined; + private _cachedVisibleSize: number | undefined = undefined; + get cachedVisibleSize(): number | undefined { return this._cachedVisibleSize; } get visible(): boolean { - return typeof this.cachedSize === 'undefined'; + return typeof this._cachedVisibleSize === 'undefined'; } set visible(visible: boolean) { @@ -81,10 +85,10 @@ abstract class ViewItem { } if (visible) { - this.size = this.cachedSize!; - this.cachedSize = undefined; + this.size = clamp(this._cachedVisibleSize!, this.viewMinimumSize, this.viewMaximumSize); + this._cachedVisibleSize = undefined; } else { - this.cachedSize = this.size; + this._cachedVisibleSize = this.size; this.size = 0; } @@ -104,7 +108,20 @@ abstract class ViewItem { get priority(): LayoutPriority | undefined { return this.view.priority; } get snap(): boolean { return !!this.view.snap; } - constructor(protected container: HTMLElement, private view: IView, private _size: number, private disposable: IDisposable) { + constructor( + protected container: HTMLElement, + private view: IView, + size: ViewItemSize, + private disposable: IDisposable + ) { + if (typeof size === 'number') { + this._size = size; + this._cachedVisibleSize = undefined; + } else { + this._size = 0; + this._cachedVisibleSize = size.cachedVisibleSize; + } + dom.addClass(container, 'visible'); } @@ -166,11 +183,13 @@ enum State { export type DistributeSizing = { type: 'distribute' }; export type SplitSizing = { type: 'split', index: number }; -export type Sizing = DistributeSizing | SplitSizing; +export type InvisibleSizing = { type: 'invisible', cachedVisibleSize: number }; +export type Sizing = DistributeSizing | SplitSizing | InvisibleSizing; export namespace Sizing { export const Distribute: DistributeSizing = { type: 'distribute' }; export function Split(index: number): SplitSizing { return { type: 'split', index }; } + export function Invisible(cachedVisibleSize: number): InvisibleSizing { return { type: 'invisible', cachedVisibleSize }; } } export class SplitView extends Disposable { @@ -279,12 +298,14 @@ export class SplitView extends Disposable { const containerDisposable = toDisposable(() => this.viewContainer.removeChild(container)); const disposable = combinedDisposable(onChangeDisposable, containerDisposable); - let viewSize: number; + let viewSize: ViewItemSize; if (typeof size === 'number') { viewSize = size; } else if (size.type === 'split') { viewSize = this.getViewSize(size.index) / 2; + } else if (size.type === 'invisible') { + viewSize = { cachedVisibleSize: size.cachedVisibleSize }; } else { viewSize = view.minimumSize; } @@ -315,7 +336,24 @@ export class SplitView extends Disposable { const onChangeDisposable = onChange(this.onSashChange, this); const onEnd = Event.map(sash.onDidEnd, () => firstIndex(this.sashItems, item => item.sash === sash)); const onEndDisposable = onEnd(this.onSashEnd, this); - const onDidResetDisposable = sash.onDidReset(() => this._onDidSashReset.fire(firstIndex(this.sashItems, item => item.sash === sash))); + + const onDidResetDisposable = sash.onDidReset(() => { + const index = firstIndex(this.sashItems, item => item.sash === sash); + const upIndexes = range(index, -1); + const downIndexes = range(index + 1, this.viewItems.length); + const snapBeforeIndex = this.findFirstSnapIndex(upIndexes); + const snapAfterIndex = this.findFirstSnapIndex(downIndexes); + + if (typeof snapBeforeIndex === 'number' && !this.viewItems[snapBeforeIndex].visible) { + return; + } + + if (typeof snapAfterIndex === 'number' && !this.viewItems[snapAfterIndex].visible) { + return; + } + + this._onDidSashReset.fire(index); + }); const disposable = combinedDisposable(onStartDisposable, onChangeDisposable, onEndDisposable, onDidResetDisposable, sash); const sashItem: ISashItem = { sash, disposable }; @@ -420,6 +458,15 @@ export class SplitView extends Disposable { this.layoutViews(); } + getViewCachedVisibleSize(index: number): number | undefined { + if (index < 0 || index >= this.viewItems.length) { + throw new Error('Index out of bounds'); + } + + const viewItem = this.viewItems[index]; + return viewItem.cachedVisibleSize; + } + layout(size: number): void { const previousSize = Math.max(this.size, this.contentSize); this.size = size; @@ -600,10 +647,19 @@ export class SplitView extends Disposable { } distributeViewSizes(): void { - const size = Math.floor(this.size / this.viewItems.length); + const flexibleViewItems: ViewItem[] = []; + let flexibleSize = 0; - for (let i = 0; i < this.viewItems.length; i++) { - const item = this.viewItems[i]; + for (const item of this.viewItems) { + if (item.maximumSize - item.minimumSize > 0) { + flexibleViewItems.push(item); + flexibleSize += item.size; + } + } + + const size = Math.floor(flexibleSize / flexibleViewItems.length); + + for (const item of flexibleViewItems) { item.size = clamp(size, item.minimumSize, item.maximumSize); } diff --git a/src/vs/base/common/filters.ts b/src/vs/base/common/filters.ts index 645937c3375..d01c32668ca 100644 --- a/src/vs/base/common/filters.ts +++ b/src/vs/base/common/filters.ts @@ -414,7 +414,7 @@ export function createMatches(score: undefined | FuzzyScore): IMatch[] { return res; } -const _maxLen = 53; +const _maxLen = 128; function initTable() { const table: number[][] = []; diff --git a/src/vs/base/test/browser/ui/grid/grid.test.ts b/src/vs/base/test/browser/ui/grid/grid.test.ts index 7385b3ca4d6..092f3007082 100644 --- a/src/vs/base/test/browser/ui/grid/grid.test.ts +++ b/src/vs/base/test/browser/ui/grid/grid.test.ts @@ -8,6 +8,15 @@ import { Direction, getRelativeLocation, Orientation, SerializableGrid, ISeriali import { TestView, nodesToArrays } from './util'; import { deepClone } from 'vs/base/common/objects'; +// Simple example: +// +-----+---------------+ +// | 4 | 2 | +// +-----+---------+-----+ +// | 1 | | +// +---------------+ 3 | +// | 5 | | +// +---------------+-----+ + suite('Grid', function () { let container: HTMLElement; @@ -798,4 +807,222 @@ suite('SerializableGrid', function () { height: 1 }); }); + + test('serialize should store visibility and previous size', function () { + const view1 = new TestSerializableView('view1', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const grid = new SerializableGrid(view1); + container.appendChild(grid.element); + grid.layout(800, 600); + + const view2 = new TestSerializableView('view2', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + grid.addView(view2, 200, view1, Direction.Up); + + const view3 = new TestSerializableView('view3', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + grid.addView(view3, 200, view1, Direction.Right); + + const view4 = new TestSerializableView('view4', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + grid.addView(view4, 200, view2, Direction.Left); + + const view5 = new TestSerializableView('view5', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + grid.addView(view5, 100, view1, Direction.Down); + + assert.deepEqual(view1.size, [600, 300]); + assert.deepEqual(view2.size, [600, 200]); + assert.deepEqual(view3.size, [200, 400]); + assert.deepEqual(view4.size, [200, 200]); + assert.deepEqual(view5.size, [600, 100]); + + grid.setViewVisible(view5, false); + + assert.deepEqual(view1.size, [600, 400]); + assert.deepEqual(view2.size, [600, 200]); + assert.deepEqual(view3.size, [200, 400]); + assert.deepEqual(view4.size, [200, 200]); + assert.deepEqual(view5.size, [600, 0]); + + const json = grid.serialize(); + assert.deepEqual(json, { + orientation: 0, + width: 800, + height: 600, + root: { + type: 'branch', + data: [ + { + type: 'branch', + data: [ + { type: 'leaf', data: { name: 'view4' }, size: 200 }, + { type: 'leaf', data: { name: 'view2' }, size: 600 } + ], + size: 200 + }, + { + type: 'branch', + data: [ + { + type: 'branch', + data: [ + { type: 'leaf', data: { name: 'view1' }, size: 400 }, + { type: 'leaf', data: { name: 'view5' }, size: 100, visible: false } + ], + size: 600 + }, + { type: 'leaf', data: { name: 'view3' }, size: 200 } + ], + size: 400 + } + ], + size: 800 + } + }); + + grid.dispose(); + + const deserializer = new TestViewDeserializer(); + const grid2 = SerializableGrid.deserialize(json, deserializer); + + const view1Copy = deserializer.getView('view1'); + const view2Copy = deserializer.getView('view2'); + const view3Copy = deserializer.getView('view3'); + const view4Copy = deserializer.getView('view4'); + const view5Copy = deserializer.getView('view5'); + + assert.deepEqual(nodesToArrays(grid2.getViews()), [[view4Copy, view2Copy], [[view1Copy, view5Copy], view3Copy]]); + + grid2.layout(800, 600); + assert.deepEqual(view1Copy.size, [600, 400]); + assert.deepEqual(view2Copy.size, [600, 200]); + assert.deepEqual(view3Copy.size, [200, 400]); + assert.deepEqual(view4Copy.size, [200, 200]); + assert.deepEqual(view5Copy.size, [600, 0]); + + assert.deepEqual(grid2.isViewVisible(view1Copy), true); + assert.deepEqual(grid2.isViewVisible(view2Copy), true); + assert.deepEqual(grid2.isViewVisible(view3Copy), true); + assert.deepEqual(grid2.isViewVisible(view4Copy), true); + assert.deepEqual(grid2.isViewVisible(view5Copy), false); + + grid2.setViewVisible(view5Copy, true); + + assert.deepEqual(view1Copy.size, [600, 300]); + assert.deepEqual(view2Copy.size, [600, 200]); + assert.deepEqual(view3Copy.size, [200, 400]); + assert.deepEqual(view4Copy.size, [200, 200]); + assert.deepEqual(view5Copy.size, [600, 100]); + + assert.deepEqual(grid2.isViewVisible(view1Copy), true); + assert.deepEqual(grid2.isViewVisible(view2Copy), true); + assert.deepEqual(grid2.isViewVisible(view3Copy), true); + assert.deepEqual(grid2.isViewVisible(view4Copy), true); + assert.deepEqual(grid2.isViewVisible(view5Copy), true); + }); + + test('serialize should store visibility and previous size even for first leaf', function () { + const view1 = new TestSerializableView('view1', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const grid = new SerializableGrid(view1); + container.appendChild(grid.element); + grid.layout(800, 600); + + const view2 = new TestSerializableView('view2', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + grid.addView(view2, 200, view1, Direction.Up); + + const view3 = new TestSerializableView('view3', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + grid.addView(view3, 200, view1, Direction.Right); + + const view4 = new TestSerializableView('view4', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + grid.addView(view4, 200, view2, Direction.Left); + + const view5 = new TestSerializableView('view5', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + grid.addView(view5, 100, view1, Direction.Down); + + assert.deepEqual(view1.size, [600, 300]); + assert.deepEqual(view2.size, [600, 200]); + assert.deepEqual(view3.size, [200, 400]); + assert.deepEqual(view4.size, [200, 200]); + assert.deepEqual(view5.size, [600, 100]); + + grid.setViewVisible(view4, false); + + assert.deepEqual(view1.size, [600, 300]); + assert.deepEqual(view2.size, [800, 200]); + assert.deepEqual(view3.size, [200, 400]); + assert.deepEqual(view4.size, [0, 200]); + assert.deepEqual(view5.size, [600, 100]); + + const json = grid.serialize(); + assert.deepEqual(json, { + orientation: 0, + width: 800, + height: 600, + root: { + type: 'branch', + data: [ + { + type: 'branch', + data: [ + { type: 'leaf', data: { name: 'view4' }, size: 200, visible: false }, + { type: 'leaf', data: { name: 'view2' }, size: 800 } + ], + size: 200 + }, + { + type: 'branch', + data: [ + { + type: 'branch', + data: [ + { type: 'leaf', data: { name: 'view1' }, size: 300 }, + { type: 'leaf', data: { name: 'view5' }, size: 100 } + ], + size: 600 + }, + { type: 'leaf', data: { name: 'view3' }, size: 200 } + ], + size: 400 + } + ], + size: 800 + } + }); + + grid.dispose(); + + const deserializer = new TestViewDeserializer(); + const grid2 = SerializableGrid.deserialize(json, deserializer); + + const view1Copy = deserializer.getView('view1'); + const view2Copy = deserializer.getView('view2'); + const view3Copy = deserializer.getView('view3'); + const view4Copy = deserializer.getView('view4'); + const view5Copy = deserializer.getView('view5'); + + assert.deepEqual(nodesToArrays(grid2.getViews()), [[view4Copy, view2Copy], [[view1Copy, view5Copy], view3Copy]]); + + grid2.layout(800, 600); + assert.deepEqual(view1Copy.size, [600, 300]); + assert.deepEqual(view2Copy.size, [800, 200]); + assert.deepEqual(view3Copy.size, [200, 400]); + assert.deepEqual(view4Copy.size, [0, 200]); + assert.deepEqual(view5Copy.size, [600, 100]); + + assert.deepEqual(grid2.isViewVisible(view1Copy), true); + assert.deepEqual(grid2.isViewVisible(view2Copy), true); + assert.deepEqual(grid2.isViewVisible(view3Copy), true); + assert.deepEqual(grid2.isViewVisible(view4Copy), false); + assert.deepEqual(grid2.isViewVisible(view5Copy), true); + + grid2.setViewVisible(view4Copy, true); + + assert.deepEqual(view1Copy.size, [600, 300]); + assert.deepEqual(view2Copy.size, [600, 200]); + assert.deepEqual(view3Copy.size, [200, 400]); + assert.deepEqual(view4Copy.size, [200, 200]); + assert.deepEqual(view5Copy.size, [600, 100]); + + assert.deepEqual(grid2.isViewVisible(view1Copy), true); + assert.deepEqual(grid2.isViewVisible(view2Copy), true); + assert.deepEqual(grid2.isViewVisible(view3Copy), true); + assert.deepEqual(grid2.isViewVisible(view4Copy), true); + assert.deepEqual(grid2.isViewVisible(view5Copy), true); + }); }); diff --git a/src/vs/base/test/browser/ui/grid/gridview.test.ts b/src/vs/base/test/browser/ui/grid/gridview.test.ts index 20b7626b184..78cc44cc76a 100644 --- a/src/vs/base/test/browser/ui/grid/gridview.test.ts +++ b/src/vs/base/test/browser/ui/grid/gridview.test.ts @@ -22,7 +22,7 @@ suite('Gridview', function () { }); test('empty gridview is empty', function () { - assert.deepEqual(nodesToArrays(gridview.getViews()), []); + assert.deepEqual(nodesToArrays(gridview.getView()), []); gridview.dispose(); }); @@ -43,7 +43,7 @@ suite('Gridview', function () { gridview.addView(views[1], 200, [1]); gridview.addView(views[2], 200, [2]); - assert.deepEqual(nodesToArrays(gridview.getViews()), views); + assert.deepEqual(nodesToArrays(gridview.getView()), views); gridview.dispose(); }); @@ -62,7 +62,7 @@ suite('Gridview', function () { gridview.addView((views[1] as TestView[])[0] as IView, 200, [1]); gridview.addView((views[1] as TestView[])[1] as IView, 200, [1, 1]); - assert.deepEqual(nodesToArrays(gridview.getViews()), views); + assert.deepEqual(nodesToArrays(gridview.getView()), views); gridview.dispose(); }); @@ -71,35 +71,35 @@ suite('Gridview', function () { const view1 = new TestView(20, 20, 20, 20); gridview.addView(view1 as IView, 200, [0]); - assert.deepEqual(nodesToArrays(gridview.getViews()), [view1]); + assert.deepEqual(nodesToArrays(gridview.getView()), [view1]); const view2 = new TestView(20, 20, 20, 20); gridview.addView(view2 as IView, 200, [1]); - assert.deepEqual(nodesToArrays(gridview.getViews()), [view1, view2]); + assert.deepEqual(nodesToArrays(gridview.getView()), [view1, view2]); const view3 = new TestView(20, 20, 20, 20); gridview.addView(view3 as IView, 200, [1, 0]); - assert.deepEqual(nodesToArrays(gridview.getViews()), [view1, [view3, view2]]); + assert.deepEqual(nodesToArrays(gridview.getView()), [view1, [view3, view2]]); const view4 = new TestView(20, 20, 20, 20); gridview.addView(view4 as IView, 200, [1, 0, 0]); - assert.deepEqual(nodesToArrays(gridview.getViews()), [view1, [[view4, view3], view2]]); + assert.deepEqual(nodesToArrays(gridview.getView()), [view1, [[view4, view3], view2]]); const view5 = new TestView(20, 20, 20, 20); gridview.addView(view5 as IView, 200, [1, 0]); - assert.deepEqual(nodesToArrays(gridview.getViews()), [view1, [view5, [view4, view3], view2]]); + assert.deepEqual(nodesToArrays(gridview.getView()), [view1, [view5, [view4, view3], view2]]); const view6 = new TestView(20, 20, 20, 20); gridview.addView(view6 as IView, 200, [2]); - assert.deepEqual(nodesToArrays(gridview.getViews()), [view1, [view5, [view4, view3], view2], view6]); + assert.deepEqual(nodesToArrays(gridview.getView()), [view1, [view5, [view4, view3], view2], view6]); const view7 = new TestView(20, 20, 20, 20); gridview.addView(view7 as IView, 200, [1, 1]); - assert.deepEqual(nodesToArrays(gridview.getViews()), [view1, [view5, view7, [view4, view3], view2], view6]); + assert.deepEqual(nodesToArrays(gridview.getView()), [view1, [view5, view7, [view4, view3], view2], view6]); const view8 = new TestView(20, 20, 20, 20); gridview.addView(view8 as IView, 200, [1, 1, 0]); - assert.deepEqual(nodesToArrays(gridview.getViews()), [view1, [view5, [view8, view7], [view4, view3], view2], view6]); + assert.deepEqual(nodesToArrays(gridview.getView()), [view1, [view5, [view8, view7], [view4, view3], view2], view6]); gridview.dispose(); }); diff --git a/src/vs/base/test/browser/ui/grid/util.ts b/src/vs/base/test/browser/ui/grid/util.ts index 0efdc44851a..39a35736ddd 100644 --- a/src/vs/base/test/browser/ui/grid/util.ts +++ b/src/vs/base/test/browser/ui/grid/util.ts @@ -5,7 +5,8 @@ import * as assert from 'assert'; import { Emitter, Event } from 'vs/base/common/event'; -import { IView, GridNode, isGridBranchNode, } from 'vs/base/browser/ui/grid/gridview'; +import { GridNode, isGridBranchNode } from 'vs/base/browser/ui/grid/gridview'; +import { IView } from 'vs/base/browser/ui/grid/grid'; export class TestView implements IView { @@ -78,4 +79,4 @@ export function nodesToArrays(node: GridNode): any { } else { return node.view; } -} \ No newline at end of file +} diff --git a/src/vs/base/test/common/filters.test.ts b/src/vs/base/test/common/filters.test.ts index b93e58cfc0a..25cc50d5780 100644 --- a/src/vs/base/test/common/filters.test.ts +++ b/src/vs/base/test/common/filters.test.ts @@ -470,4 +470,19 @@ suite('Filters', () => { test('List highlight filter: Not all characters from match are highlighterd #66923', () => { assertMatches('foo', 'barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar_foo', 'barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar_^f^o^o', fuzzyScore); }); + + test('Autocompletion is matched against truncated filterText to 54 characters #74133', () => { + assertMatches( + 'foo', + 'ffffffffffffffffffffffffffffbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar_foo', + 'ffffffffffffffffffffffffffffbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar_^f^o^o', + fuzzyScore + ); + assertMatches( + 'foo', + 'Gffffffffffffffffffffffffffffbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar_foo', + undefined, + fuzzyScore + ); + }); }); diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index 4a2dc7661c9..06d4b2f5307 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -123,7 +123,7 @@ export class MouseHandler extends ViewEventHandler { e.stopPropagation(); } }; - this._register(dom.addDisposableListener(this.viewHelper.viewDomNode, 'mousewheel', onMouseWheel, true)); + this._register(dom.addDisposableListener(this.viewHelper.viewDomNode, browser.isEdgeOrIE ? 'mousewheel' : 'wheel', onMouseWheel, true)); this._context.addEventHandler(this); } diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index 8d69b3dffbf..9a37d512035 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -407,7 +407,12 @@ class HitTestRequest extends BareHitTestRequest { } public fulfill(type: MouseTargetType, position: Position | null = null, range: EditorRange | null = null, detail: any = null): MouseTarget { - return new MouseTarget(this.target, type, this.mouseColumn, position, range, detail); + let mouseColumn = this.mouseColumn; + if (position && position.column < this._ctx.model.getLineMaxColumn(position.lineNumber)) { + // Most likely, the line contains foreign decorations... + mouseColumn = position.column; + } + return new MouseTarget(this.target, type, mouseColumn, position, range, detail); } public withTarget(target: Element | null): HitTestRequest { diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index e571b75dd3b..c3be18fe35d 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -1848,4 +1848,6 @@ registerThemingParticipant((theme, collector) => { if (unnecessaryBorder) { collector.addRule(`.${SHOW_UNUSED_ENABLED_CLASS} .monaco-editor .${ClassName.EditorUnnecessaryDecoration} { border-bottom: 2px dashed ${unnecessaryBorder}; }`); } + + collector.addRule(`.monaco-editor .${ClassName.EditorDeprecatedInlineDecoration} { text-decoration: line-through; }`); }); diff --git a/src/vs/editor/browser/widget/media/editor.css b/src/vs/editor/browser/widget/media/editor.css index 3266ae96364..fb9e5f5e8b8 100644 --- a/src/vs/editor/browser/widget/media/editor.css +++ b/src/vs/editor/browser/widget/media/editor.css @@ -39,4 +39,10 @@ .monaco-editor .view-overlays { position: absolute; top: 0; -} \ No newline at end of file +} + +/* +.monaco-editor .auto-closed-character { + opacity: 0.3; +} +*/ diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index 889384a1748..5d1818712b0 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -696,7 +696,7 @@ const editorConfiguration: IConfigurationNode = { }, 'editor.suggest.filteredTypes': { type: 'object', - default: { keyword: true }, + default: { keyword: true, snippet: true }, markdownDescription: nls.localize('suggest.filtered', "Controls whether some suggestion types should be filtered from IntelliSense. A list of suggestion types can be found here: https://code.visualstudio.com/docs/editor/intellisense#_types-of-completions."), properties: { method: { diff --git a/src/vs/editor/common/controller/cursor.ts b/src/vs/editor/common/controller/cursor.ts index add9f3444a9..789d3a97b02 100644 --- a/src/vs/editor/common/controller/cursor.ts +++ b/src/vs/editor/common/controller/cursor.ts @@ -10,15 +10,16 @@ import { CursorCollection } from 'vs/editor/common/controller/cursorCollection'; import { CursorColumns, CursorConfiguration, CursorContext, CursorState, EditOperationResult, EditOperationType, IColumnSelectData, ICursors, PartialCursorState, RevealTarget } from 'vs/editor/common/controller/cursorCommon'; import { DeleteOperations } from 'vs/editor/common/controller/cursorDeleteOperations'; import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents'; -import { TypeOperations } from 'vs/editor/common/controller/cursorTypeOperations'; +import { TypeOperations, TypeWithAutoClosingCommand } from 'vs/editor/common/controller/cursorTypeOperations'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { ISelection, Selection, SelectionDirection } from 'vs/editor/common/core/selection'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { IIdentifiedSingleEditOperation, ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { IIdentifiedSingleEditOperation, ITextModel, TrackedRangeStickiness, IModelDeltaDecoration } from 'vs/editor/common/model'; import { RawContentChangedType } from 'vs/editor/common/model/textModelEvents'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { IViewModel } from 'vs/editor/common/viewModel/viewModel'; +import { dispose } from 'vs/base/common/lifecycle'; function containsLineMappingChanged(events: viewEvents.ViewEvent[]): boolean { for (let i = 0, len = events.length; i < len; i++) { @@ -83,6 +84,64 @@ export class CursorModelState { } } +class AutoClosedAction { + + private readonly _model: ITextModel; + + private _autoClosedCharactersDecorations: string[]; + private _autoClosedEnclosingDecorations: string[]; + + constructor(model: ITextModel, autoClosedCharactersDecorations: string[], autoClosedEnclosingDecorations: string[]) { + this._model = model; + this._autoClosedCharactersDecorations = autoClosedCharactersDecorations; + this._autoClosedEnclosingDecorations = autoClosedEnclosingDecorations; + } + + public dispose(): void { + this._autoClosedCharactersDecorations = this._model.deltaDecorations(this._autoClosedCharactersDecorations, []); + this._autoClosedEnclosingDecorations = this._model.deltaDecorations(this._autoClosedEnclosingDecorations, []); + } + + public getAutoClosedCharactersRanges(): Range[] { + let result: Range[] = []; + for (let i = 0; i < this._autoClosedCharactersDecorations.length; i++) { + const decorationRange = this._model.getDecorationRange(this._autoClosedCharactersDecorations[i]); + if (decorationRange) { + result.push(decorationRange); + } + } + return result; + } + + public isValid(selections: Range[]): boolean { + let enclosingRanges: Range[] = []; + for (let i = 0; i < this._autoClosedEnclosingDecorations.length; i++) { + const decorationRange = this._model.getDecorationRange(this._autoClosedEnclosingDecorations[i]); + if (decorationRange) { + enclosingRanges.push(decorationRange); + if (decorationRange.startLineNumber !== decorationRange.endLineNumber) { + // Stop tracking if the range becomes multiline... + return false; + } + } + } + enclosingRanges.sort(Range.compareRangesUsingStarts); + + selections.sort(Range.compareRangesUsingStarts); + + for (let i = 0; i < selections.length; i++) { + if (i >= enclosingRanges.length) { + return false; + } + if (!enclosingRanges[i].strictContainsRange(selections[i])) { + return false; + } + } + + return true; + } +} + export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { public static MAX_CURSOR_COUNT = 10000; @@ -106,6 +165,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { private _isHandling: boolean; private _isDoingComposition: boolean; private _columnSelectData: IColumnSelectData | null; + private _autoClosedActions: AutoClosedAction[]; private _prevEditOperationType: EditOperationType; constructor(configuration: editorCommon.IConfiguration, model: ITextModel, viewModel: IViewModel) { @@ -120,6 +180,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { this._isHandling = false; this._isDoingComposition = false; this._columnSelectData = null; + this._autoClosedActions = []; this._prevEditOperationType = EditOperationType.Other; this._register(this._model.onDidChangeRawContent((e) => { @@ -173,9 +234,24 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { public dispose(): void { this._cursors.dispose(); + this._autoClosedActions = dispose(this._autoClosedActions); super.dispose(); } + private _validateAutoClosedActions(): void { + if (this._autoClosedActions.length > 0) { + let selections: Range[] = this._cursors.getSelections(); + for (let i = 0; i < this._autoClosedActions.length; i++) { + const autoClosedAction = this._autoClosedActions[i]; + if (!autoClosedAction.isValid(selections)) { + autoClosedAction.dispose(); + this._autoClosedActions.splice(i, 1); + i--; + } + } + } + } + // ------ some getters/setters public getPrimaryCursor(): CursorState { @@ -202,6 +278,8 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { this._cursors.normalize(); this._columnSelectData = null; + this._validateAutoClosedActions(); + this._emitStateChangedIfNecessary(source, reason, oldState); } @@ -296,7 +374,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { // a model.setValue() was called this._cursors.dispose(); this._cursors = new CursorCollection(this.context); - + this._validateAutoClosedActions(); this._emitStateChangedIfNecessary('model', CursorChangeReason.ContentFlush, null); } else { const selectionsFromMarkers = this._cursors.readSelectionFromMarkers(); @@ -367,6 +445,35 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { // The commands were applied correctly this._interpretCommandResult(result); + // Check for auto-closing closed characters + let autoClosedCharactersRanges: IModelDeltaDecoration[] = []; + let autoClosedEnclosingRanges: IModelDeltaDecoration[] = []; + + for (let i = 0; i < opResult.commands.length; i++) { + const command = opResult.commands[i]; + if (command instanceof TypeWithAutoClosingCommand && command.enclosingRange && command.closeCharacterRange) { + autoClosedCharactersRanges.push({ + range: command.closeCharacterRange, + options: { + inlineClassName: 'auto-closed-character', + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges + } + }); + autoClosedEnclosingRanges.push({ + range: command.enclosingRange, + options: { + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges + } + }); + } + } + + if (autoClosedCharactersRanges.length > 0) { + const autoClosedCharactersDecorations = this._model.deltaDecorations([], autoClosedCharactersRanges); + const autoClosedEnclosingDecorations = this._model.deltaDecorations([], autoClosedEnclosingRanges); + this._autoClosedActions.push(new AutoClosedAction(this._model, autoClosedCharactersDecorations, autoClosedEnclosingDecorations)); + } + this._prevEditOperationType = opResult.type; } @@ -540,6 +647,8 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { this._cursors.startTrackingSelections(); } + this._validateAutoClosedActions(); + if (this._emitStateChangedIfNecessary(source, cursorChangeReason, oldState)) { this._revealRange(RevealTarget.Primary, viewEvents.VerticalRevealType.Simple, true, editorCommon.ScrollType.Smooth); } @@ -566,8 +675,15 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { chr = text.charAt(i); } + let autoClosedCharacters: Range[] = []; + if (this._autoClosedActions.length > 0) { + for (let i = 0, len = this._autoClosedActions.length; i < len; i++) { + autoClosedCharacters = autoClosedCharacters.concat(this._autoClosedActions[i].getAutoClosedCharactersRanges()); + } + } + // Here we must interpret each typed character individually, that's why we create a new context - this._executeEditOperation(TypeOperations.typeWithInterceptors(this._prevEditOperationType, this.context.config, this.context.model, this.getSelections(), chr)); + this._executeEditOperation(TypeOperations.typeWithInterceptors(this._prevEditOperationType, this.context.config, this.context.model, this.getSelections(), autoClosedCharacters, chr)); } } else { diff --git a/src/vs/editor/common/controller/cursorTypeOperations.ts b/src/vs/editor/common/controller/cursorTypeOperations.ts index c18d081379d..73d984cfb62 100644 --- a/src/vs/editor/common/controller/cursorTypeOperations.ts +++ b/src/vs/editor/common/controller/cursorTypeOperations.ts @@ -13,7 +13,7 @@ import { CursorColumns, CursorConfiguration, EditOperationResult, EditOperationT import { WordCharacterClass, getMapForWordSeparators } from 'vs/editor/common/controller/wordCharacterClassifier'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; -import { ICommand } from 'vs/editor/common/editorCommon'; +import { ICommand, ICursorStateComputerData } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import { EnterAction, IndentAction } from 'vs/editor/common/modes/languageConfiguration'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; @@ -430,7 +430,7 @@ export class TypeOperations { return null; } - private static _isAutoClosingCloseCharType(config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string): boolean { + private static _isAutoClosingCloseCharType(config: CursorConfiguration, model: ITextModel, selections: Selection[], autoClosedCharacters: Range[], ch: string): boolean { const autoCloseConfig = isQuote(ch) ? config.autoClosingQuotes : config.autoClosingBrackets; if (autoCloseConfig === 'never' || !config.autoClosingPairsClose.hasOwnProperty(ch)) { @@ -461,6 +461,19 @@ export class TypeOperations { return false; } } + + // Must over-type a closing character typed by the editor + let found = false; + for (let j = 0, lenJ = autoClosedCharacters.length; j < lenJ; j++) { + const autoClosedCharacter = autoClosedCharacters[j]; + if (position.lineNumber === autoClosedCharacter.startLineNumber && position.column === autoClosedCharacter.startColumn) { + found = true; + break; + } + } + if (!found) { + return false; + } } return true; @@ -573,7 +586,7 @@ export class TypeOperations { for (let i = 0, len = selections.length; i < len; i++) { const selection = selections[i]; const closeCharacter = config.autoClosingPairsOpen[ch]; - commands[i] = new ReplaceCommandWithOffsetCursorState(selection, ch + closeCharacter, 0, -closeCharacter.length); + commands[i] = new TypeWithAutoClosingCommand(selection, ch, closeCharacter); } return new EditOperationResult(EditOperationType.Typing, commands, { shouldPushStackElementBefore: true, @@ -802,7 +815,7 @@ export class TypeOperations { }); } - public static typeWithInterceptors(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string): EditOperationResult { + public static typeWithInterceptors(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], autoClosedCharacters: Range[], ch: string): EditOperationResult { if (ch === '\n') { let commands: ICommand[] = []; @@ -833,7 +846,7 @@ export class TypeOperations { } } - if (this._isAutoClosingCloseCharType(config, model, selections, ch)) { + if (this._isAutoClosingCloseCharType(config, model, selections, autoClosedCharacters, ch)) { return this._runAutoClosingCloseCharType(prevEditOperationType, config, model, selections, ch); } @@ -923,3 +936,24 @@ export class TypeOperations { return commands; } } + +export class TypeWithAutoClosingCommand extends ReplaceCommandWithOffsetCursorState { + + private _closeCharacter: string; + public closeCharacterRange: Range | null; + public enclosingRange: Range | null; + + constructor(selection: Selection, openCharacter: string, closeCharacter: string) { + super(selection, openCharacter + closeCharacter, 0, -closeCharacter.length); + this._closeCharacter = closeCharacter; + this.closeCharacterRange = null; + } + + public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { + let inverseEditOperations = helper.getInverseEditOperations(); + let range = inverseEditOperations[0].range; + this.closeCharacterRange = new Range(range.startLineNumber, range.endColumn - this._closeCharacter.length, range.endLineNumber, range.endColumn); + this.enclosingRange = range; + return super.computeCursorState(model, helper); + } +} diff --git a/src/vs/editor/common/core/range.ts b/src/vs/editor/common/core/range.ts index c84b5df9d8a..e212e757dce 100644 --- a/src/vs/editor/common/core/range.ts +++ b/src/vs/editor/common/core/range.ts @@ -126,6 +126,32 @@ export class Range { return true; } + /** + * Test if `range` is strictly in this range. `range` must start after and end before this range for the result to be true. + */ + public strictContainsRange(range: IRange): boolean { + return Range.strictContainsRange(this, range); + } + + /** + * Test if `otherRange` is strinctly in `range` (must start after, and end before). If the ranges are equal, will return false. + */ + public static strictContainsRange(range: IRange, otherRange: IRange): boolean { + if (otherRange.startLineNumber < range.startLineNumber || otherRange.endLineNumber < range.startLineNumber) { + return false; + } + if (otherRange.startLineNumber > range.endLineNumber || otherRange.endLineNumber > range.endLineNumber) { + return false; + } + if (otherRange.startLineNumber === range.startLineNumber && otherRange.startColumn <= range.startColumn) { + return false; + } + if (otherRange.endLineNumber === range.endLineNumber && otherRange.endColumn >= range.endColumn) { + return false; + } + return true; + } + /** * A reunion of the two ranges. * The smallest position will be used as the start point, and the largest one as the end point. diff --git a/src/vs/editor/common/model/intervalTree.ts b/src/vs/editor/common/model/intervalTree.ts index 496e77b9ef4..d400024e7c5 100644 --- a/src/vs/editor/common/model/intervalTree.ts +++ b/src/vs/editor/common/model/intervalTree.ts @@ -17,7 +17,8 @@ export const enum ClassName { EditorWarningDecoration = 'squiggly-warning', EditorErrorDecoration = 'squiggly-error', EditorUnnecessaryDecoration = 'squiggly-unnecessary', - EditorUnnecessaryInlineDecoration = 'squiggly-inline-unnecessary' + EditorUnnecessaryInlineDecoration = 'squiggly-inline-unnecessary', + EditorDeprecatedInlineDecoration = 'squiggly-inline-deprecated' } export const enum NodeColor { diff --git a/src/vs/editor/common/model/textModelSearch.ts b/src/vs/editor/common/model/textModelSearch.ts index 1b63ef93f9f..ec2bb71ec30 100644 --- a/src/vs/editor/common/model/textModelSearch.ts +++ b/src/vs/editor/common/model/textModelSearch.ts @@ -545,6 +545,12 @@ export class Searcher { const matchStartIndex = m.index; const matchLength = m[0].length; if (matchStartIndex === this._prevMatchStartIndex && matchLength === this._prevMatchLength) { + if (matchLength === 0) { + // the search result is an empty string and won't advance `regex.lastIndex`, so `regex.exec` will stuck here + // we attempt to recover from that by advancing by one + this._searchRegex.lastIndex += 1; + continue; + } // Exit early if the regex matches the same range twice return null; } diff --git a/src/vs/editor/common/services/markerDecorationsServiceImpl.ts b/src/vs/editor/common/services/markerDecorationsServiceImpl.ts index a319e8004b6..3da62fed16f 100644 --- a/src/vs/editor/common/services/markerDecorationsServiceImpl.ts +++ b/src/vs/editor/common/services/markerDecorationsServiceImpl.ts @@ -221,6 +221,9 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor if (marker.tags.indexOf(MarkerTag.Unnecessary) !== -1) { inlineClassName = ClassName.EditorUnnecessaryInlineDecoration; } + if (marker.tags.indexOf(MarkerTag.Deprecated) !== -1) { + inlineClassName = ClassName.EditorDeprecatedInlineDecoration; + } } return { diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index df80861b2a8..46d295d2398 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -7,7 +7,8 @@ export enum MarkerTag { - Unnecessary = 1 + Unnecessary = 1, + Deprecated = 2 } export enum MarkerSeverity { diff --git a/src/vs/editor/contrib/clipboard/clipboard.ts b/src/vs/editor/contrib/clipboard/clipboard.ts index 102f1b019dc..c0929f47d00 100644 --- a/src/vs/editor/contrib/clipboard/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/clipboard.ts @@ -148,12 +148,6 @@ class ExecCommandCopyAction extends ExecCommandAction { if (!emptySelectionClipboard && editor.getSelection().isEmpty()) { return; } - // Prevent copying an empty line by accident - if (editor.getSelections().length === 1 && editor.getSelection().isEmpty()) { - if (editor.getModel().getLineFirstNonWhitespaceColumn(editor.getSelection().positionLineNumber) === 0) { - return; - } - } super.run(accessor, editor); } diff --git a/src/vs/editor/contrib/find/findController.ts b/src/vs/editor/contrib/find/findController.ts index 3b12e6e1c5d..739cde26fef 100644 --- a/src/vs/editor/contrib/find/findController.ts +++ b/src/vs/editor/contrib/find/findController.ts @@ -112,7 +112,8 @@ export class CommonFindController extends Disposable implements editorCommon.IEd searchScope: null, matchCase: this._storageService.getBoolean('editor.matchCase', StorageScope.WORKSPACE, false), wholeWord: this._storageService.getBoolean('editor.wholeWord', StorageScope.WORKSPACE, false), - isRegex: this._storageService.getBoolean('editor.isRegex', StorageScope.WORKSPACE, false) + isRegex: this._storageService.getBoolean('editor.isRegex', StorageScope.WORKSPACE, false), + preserveCase: this._storageService.getBoolean('editor.preserveCase', StorageScope.WORKSPACE, false) }, false); if (shouldRestartFind) { @@ -170,13 +171,17 @@ export class CommonFindController extends Disposable implements editorCommon.IEd if (e.matchCase) { this._storageService.store('editor.matchCase', this._state.actualMatchCase, StorageScope.WORKSPACE); } + if (e.preserveCase) { + this._storageService.store('editor.preserveCase', this._state.actualPreserveCase, StorageScope.WORKSPACE); + } } private loadQueryState() { this._state.change({ matchCase: this._storageService.getBoolean('editor.matchCase', StorageScope.WORKSPACE, this._state.matchCase), wholeWord: this._storageService.getBoolean('editor.wholeWord', StorageScope.WORKSPACE, this._state.wholeWord), - isRegex: this._storageService.getBoolean('editor.isRegex', StorageScope.WORKSPACE, this._state.isRegex) + isRegex: this._storageService.getBoolean('editor.isRegex', StorageScope.WORKSPACE, this._state.isRegex), + preserveCase: this._storageService.getBoolean('editor.preserveCase', StorageScope.WORKSPACE, this._state.preserveCase) }, false); } @@ -217,6 +222,11 @@ export class CommonFindController extends Disposable implements editorCommon.IEd } } + public togglePreserveCase(): void { + this._state.change({ preserveCase: !this._state.preserveCase }, false); + this.highlightFindOptions(); + } + public toggleSearchScope(): void { if (this._state.searchScope) { this._state.change({ searchScope: null }, true); diff --git a/src/vs/editor/contrib/find/findModel.ts b/src/vs/editor/contrib/find/findModel.ts index 31a0d470be3..1600655f6c3 100644 --- a/src/vs/editor/contrib/find/findModel.ts +++ b/src/vs/editor/contrib/find/findModel.ts @@ -59,6 +59,7 @@ export const FIND_IDS = { ToggleWholeWordCommand: 'toggleFindWholeWord', ToggleRegexCommand: 'toggleFindRegex', ToggleSearchScopeCommand: 'toggleFindInSelection', + TogglePreserveCaseCommand: 'togglePreserveCase', ReplaceOneAction: 'editor.action.replaceOne', ReplaceAllAction: 'editor.action.replaceAll', SelectAllMatchesAction: 'editor.action.selectAllMatches' @@ -416,11 +417,11 @@ export class FindModelBoundToEditorModel { let replacePattern = this._getReplacePattern(); let selection = this._editor.getSelection(); - let nextMatch = this._getNextMatch(selection.getStartPosition(), replacePattern.hasReplacementPatterns, false); + let nextMatch = this._getNextMatch(selection.getStartPosition(), true, false); if (nextMatch) { if (selection.equalsRange(nextMatch.range)) { // selection sits on a find match => replace it! - let replaceString = replacePattern.buildReplaceString(nextMatch.matches); + let replaceString = replacePattern.buildReplaceString(nextMatch.matches, this._state.preserveCase); let command = new ReplaceCommand(selection, replaceString); @@ -482,12 +483,14 @@ export class FindModelBoundToEditorModel { const replacePattern = this._getReplacePattern(); let resultText: string; + const preserveCase = this._state.preserveCase; + if (replacePattern.hasReplacementPatterns) { resultText = modelText.replace(searchRegex, function () { - return replacePattern.buildReplaceString(arguments); + return replacePattern.buildReplaceString(arguments, preserveCase); }); } else { - resultText = modelText.replace(searchRegex, replacePattern.buildReplaceString(null)); + resultText = modelText.replace(searchRegex, replacePattern.buildReplaceString(null, preserveCase)); } let command = new ReplaceCommandThatPreservesSelection(fullModelRange, resultText, this._editor.getSelection()); @@ -501,7 +504,7 @@ export class FindModelBoundToEditorModel { let replaceStrings: string[] = []; for (let i = 0, len = matches.length; i < len; i++) { - replaceStrings[i] = replacePattern.buildReplaceString(matches[i].matches); + replaceStrings[i] = replacePattern.buildReplaceString(matches[i].matches, this._state.preserveCase); } let command = new ReplaceAllCommand(this._editor.getSelection(), matches.map(m => m.range), replaceStrings); diff --git a/src/vs/editor/contrib/find/findState.ts b/src/vs/editor/contrib/find/findState.ts index 96f1078f130..777e273148d 100644 --- a/src/vs/editor/contrib/find/findState.ts +++ b/src/vs/editor/contrib/find/findState.ts @@ -18,6 +18,7 @@ export interface FindReplaceStateChangedEvent { isRegex: boolean; wholeWord: boolean; matchCase: boolean; + preserveCase: boolean; searchScope: boolean; matchesPosition: boolean; matchesCount: boolean; @@ -41,6 +42,8 @@ export interface INewFindReplaceState { wholeWordOverride?: FindOptionOverride; matchCase?: boolean; matchCaseOverride?: FindOptionOverride; + preserveCase?: boolean; + preserveCaseOverride?: FindOptionOverride; searchScope?: Range | null; } @@ -65,6 +68,8 @@ export class FindReplaceState implements IDisposable { private _wholeWordOverride: FindOptionOverride; private _matchCase: boolean; private _matchCaseOverride: FindOptionOverride; + private _preserveCase: boolean; + private _preserveCaseOverride: FindOptionOverride; private _searchScope: Range | null; private _matchesPosition: number; private _matchesCount: number; @@ -78,10 +83,12 @@ export class FindReplaceState implements IDisposable { public get isRegex(): boolean { return effectiveOptionValue(this._isRegexOverride, this._isRegex); } public get wholeWord(): boolean { return effectiveOptionValue(this._wholeWordOverride, this._wholeWord); } public get matchCase(): boolean { return effectiveOptionValue(this._matchCaseOverride, this._matchCase); } + public get preserveCase(): boolean { return effectiveOptionValue(this._preserveCaseOverride, this._preserveCase); } public get actualIsRegex(): boolean { return this._isRegex; } public get actualWholeWord(): boolean { return this._wholeWord; } public get actualMatchCase(): boolean { return this._matchCase; } + public get actualPreserveCase(): boolean { return this._preserveCase; } public get searchScope(): Range | null { return this._searchScope; } public get matchesPosition(): number { return this._matchesPosition; } @@ -100,6 +107,8 @@ export class FindReplaceState implements IDisposable { this._wholeWordOverride = FindOptionOverride.NotSet; this._matchCase = false; this._matchCaseOverride = FindOptionOverride.NotSet; + this._preserveCase = false; + this._preserveCaseOverride = FindOptionOverride.NotSet; this._searchScope = null; this._matchesPosition = 0; this._matchesCount = 0; @@ -120,6 +129,7 @@ export class FindReplaceState implements IDisposable { isRegex: false, wholeWord: false, matchCase: false, + preserveCase: false, searchScope: false, matchesPosition: false, matchesCount: false, @@ -169,6 +179,7 @@ export class FindReplaceState implements IDisposable { isRegex: false, wholeWord: false, matchCase: false, + preserveCase: false, searchScope: false, matchesPosition: false, matchesCount: false, @@ -179,6 +190,7 @@ export class FindReplaceState implements IDisposable { const oldEffectiveIsRegex = this.isRegex; const oldEffectiveWholeWords = this.wholeWord; const oldEffectiveMatchCase = this.matchCase; + const oldEffectivePreserveCase = this.preserveCase; if (typeof newState.searchString !== 'undefined') { if (this._searchString !== newState.searchString) { @@ -217,6 +229,9 @@ export class FindReplaceState implements IDisposable { if (typeof newState.matchCase !== 'undefined') { this._matchCase = newState.matchCase; } + if (typeof newState.preserveCase !== 'undefined') { + this._preserveCase = newState.preserveCase; + } if (typeof newState.searchScope !== 'undefined') { if (!Range.equalsRange(this._searchScope, newState.searchScope)) { this._searchScope = newState.searchScope; @@ -229,6 +244,7 @@ export class FindReplaceState implements IDisposable { this._isRegexOverride = (typeof newState.isRegexOverride !== 'undefined' ? newState.isRegexOverride : FindOptionOverride.NotSet); this._wholeWordOverride = (typeof newState.wholeWordOverride !== 'undefined' ? newState.wholeWordOverride : FindOptionOverride.NotSet); this._matchCaseOverride = (typeof newState.matchCaseOverride !== 'undefined' ? newState.matchCaseOverride : FindOptionOverride.NotSet); + this._preserveCaseOverride = (typeof newState.preserveCaseOverride !== 'undefined' ? newState.preserveCaseOverride : FindOptionOverride.NotSet); if (oldEffectiveIsRegex !== this.isRegex) { somethingChanged = true; @@ -243,6 +259,11 @@ export class FindReplaceState implements IDisposable { changeEvent.matchCase = true; } + if (oldEffectivePreserveCase !== this.preserveCase) { + somethingChanged = true; + changeEvent.preserveCase = true; + } + if (somethingChanged) { this._onFindReplaceStateChange.fire(changeEvent); } diff --git a/src/vs/editor/contrib/find/findWidget.css b/src/vs/editor/contrib/find/findWidget.css index b0311427209..7e1140442ea 100644 --- a/src/vs/editor/contrib/find/findWidget.css +++ b/src/vs/editor/contrib/find/findWidget.css @@ -39,6 +39,11 @@ transition: top 200ms linear; padding: 0 4px; } + +.monaco-editor .find-widget.hiddenEditor { + display: none; +} + /* Find widget when replace is toggled on */ .monaco-editor .find-widget.replaceToggled { top: -74px; /* find input height + replace input height + shadow (10px) */ @@ -79,6 +84,15 @@ height: 25px; } +.monaco-editor .find-widget > .find-part .monaco-inputbox > .wrapper > .input { + width: 100% !important; + padding-right: 66px; +} + +.monaco-editor .find-widget > .replace-part .monaco-inputbox > .wrapper > .input { + padding-right: 22px; +} + .monaco-editor .find-widget > .find-part .monaco-inputbox > .wrapper > .input, .monaco-editor .find-widget > .replace-part .monaco-inputbox > .wrapper > .input { padding-top: 2px; @@ -224,12 +238,19 @@ } .monaco-editor .find-widget > .replace-part > .replace-input { + position: relative; display: flex; display: -webkit-flex; vertical-align: middle; width: auto !important; } +.monaco-editor .find-widget > .replace-part > .replace-input > .controls { + position: absolute; + top: 3px; + right: 2px; +} + /* REDUCED */ .monaco-editor .find-widget.reduced-find-widget .matchesCount, .monaco-editor .find-widget.reduced-find-widget .monaco-checkbox { diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts index 738971c7879..9c9af950e6b 100644 --- a/src/vs/editor/contrib/find/findWidget.ts +++ b/src/vs/editor/contrib/find/findWidget.ts @@ -13,6 +13,7 @@ import { FindInput, IFindInputStyles } from 'vs/base/browser/ui/findinput/findIn import { HistoryInputBox, IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox'; import { IHorizontalSashLayoutProvider, ISashEvent, Orientation, Sash } from 'vs/base/browser/ui/sash/sash'; import { Widget } from 'vs/base/browser/ui/widget'; +import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; import { Delayer } from 'vs/base/common/async'; import { Color } from 'vs/base/common/color'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -47,6 +48,7 @@ const NLS_TOGGLE_SELECTION_FIND_TITLE = nls.localize('label.toggleSelectionFind' const NLS_CLOSE_BTN_LABEL = nls.localize('label.closeButton', "Close"); const NLS_REPLACE_INPUT_LABEL = nls.localize('label.replace', "Replace"); const NLS_REPLACE_INPUT_PLACEHOLDER = nls.localize('placeholder.replace', "Replace"); +const NLS_PRESERVE_CASE_LABEL = nls.localize('label.preserveCaseCheckbox', "Preserve Case"); const NLS_REPLACE_BTN_LABEL = nls.localize('label.replaceButton', "Replace"); const NLS_REPLACE_ALL_BTN_LABEL = nls.localize('label.replaceAllButton', "Replace All"); const NLS_TOGGLE_REPLACE_MODE_BTN_LABEL = nls.localize('label.toggleReplaceButton', "Toggle Replace mode"); @@ -101,6 +103,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas private _nextBtn: SimpleButton; private _toggleSelectionFind: SimpleCheckbox; private _closeBtn: SimpleButton; + private _preserveCase: Checkbox; private _replaceBtn: SimpleButton; private _replaceAllBtn: SimpleButton; @@ -590,14 +593,26 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas }; this._findInput.style(inputStyles); this._replaceInputBox.style(inputStyles); + this._preserveCase.style(inputStyles); } private _tryUpdateWidgetWidth() { if (!this._isVisible) { return; } - let editorWidth = this._codeEditor.getConfiguration().layoutInfo.width; - let minimapWidth = this._codeEditor.getConfiguration().layoutInfo.minimapWidth; + + const editorContentWidth = this._codeEditor.getConfiguration().layoutInfo.contentWidth; + + if (editorContentWidth <= 0) { + // for example, diff view original editor + dom.addClass(this._domNode, 'hiddenEditor'); + return; + } else if (dom.hasClass(this._domNode, 'hiddenEditor')) { + dom.removeClass(this._domNode, 'hiddenEditor'); + } + + const editorWidth = this._codeEditor.getConfiguration().layoutInfo.width; + const minimapWidth = this._codeEditor.getConfiguration().layoutInfo.minimapWidth; let collapsedFindWidget = false; let reducedFindWidget = false; let narrowFindWidget = false; @@ -913,6 +928,19 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas this._state.change({ replaceString: this._replaceInputBox.value }, false); })); + this._preserveCase = this._register(new Checkbox({ + actionClassName: 'monaco-case-sensitive', + title: NLS_PRESERVE_CASE_LABEL, + isChecked: false, + })); + this._preserveCase.checked = !!this._state.preserveCase; + this._register(this._preserveCase.onChange(viaKeyboard => { + if (!viaKeyboard) { + this._state.change({ preserveCase: !this._state.preserveCase }, false); + this._replaceInputBox.focus(); + } + })); + // Replace one button this._replaceBtn = this._register(new SimpleButton({ label: NLS_REPLACE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.ReplaceOneAction), @@ -937,6 +965,12 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas } })); + let controls = document.createElement('div'); + controls.className = 'controls'; + controls.style.display = 'block'; + controls.appendChild(this._preserveCase.domNode); + replaceInput.appendChild(controls); + let replacePart = document.createElement('div'); replacePart.className = 'replace-part'; replacePart.appendChild(replaceInput); diff --git a/src/vs/editor/contrib/find/replacePattern.ts b/src/vs/editor/contrib/find/replacePattern.ts index 3bd09a78e2d..50e90d7f3ea 100644 --- a/src/vs/editor/contrib/find/replacePattern.ts +++ b/src/vs/editor/contrib/find/replacePattern.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CharCode } from 'vs/base/common/charCode'; +import { containsUppercaseCharacter } from 'vs/base/common/strings'; const enum ReplacePatternKind { StaticValue = 0, @@ -48,9 +49,22 @@ export class ReplacePattern { } } - public buildReplaceString(matches: string[] | null): string { + public buildReplaceString(matches: string[] | null, preserveCase?: boolean): string { if (this._state.kind === ReplacePatternKind.StaticValue) { - return this._state.staticValue; + if (preserveCase && matches && (matches[0] !== '')) { + if (matches[0].toUpperCase() === matches[0]) { + return this._state.staticValue.toUpperCase(); + } else if (matches[0].toLowerCase() === matches[0]) { + return this._state.staticValue.toLowerCase(); + } else if (containsUppercaseCharacter(matches[0][0])) { + return this._state.staticValue[0].toUpperCase() + this._state.staticValue.substr(1); + } else { + // we don't understand its pattern yet. + return this._state.staticValue; + } + } else { + return this._state.staticValue; + } } let result = ''; diff --git a/src/vs/editor/contrib/find/simpleFindWidget.ts b/src/vs/editor/contrib/find/simpleFindWidget.ts index 54f256d48aa..e3723366157 100644 --- a/src/vs/editor/contrib/find/simpleFindWidget.ts +++ b/src/vs/editor/contrib/find/simpleFindWidget.ts @@ -41,7 +41,8 @@ export abstract class SimpleFindWidget extends Widget { @IContextViewService private readonly _contextViewService: IContextViewService, @IContextKeyService contextKeyService: IContextKeyService, private readonly _state: FindReplaceState = new FindReplaceState(), - showOptionButtons?: boolean + showOptionButtons?: boolean, + private readonly _invertDefaultDirection: boolean = false ) { super(); @@ -93,13 +94,13 @@ export abstract class SimpleFindWidget extends Widget { this._register(this._findInput.onKeyDown((e) => { if (e.equals(KeyCode.Enter)) { - this.find(false); + this.find(this._invertDefaultDirection); e.preventDefault(); return; } if (e.equals(KeyMod.Shift | KeyCode.Enter)) { - this.find(true); + this.find(!this._invertDefaultDirection); e.preventDefault(); return; } @@ -295,4 +296,4 @@ registerThemingParticipant((theme, collector) => { if (widgetShadowColor) { collector.addRule(`.monaco-workbench .simple-find-part { box-shadow: 0 2px 8px ${widgetShadowColor}; }`); } -}); \ No newline at end of file +}); diff --git a/src/vs/editor/contrib/find/test/replacePattern.test.ts b/src/vs/editor/contrib/find/test/replacePattern.test.ts index b36a509e34d..d252ac74c6a 100644 --- a/src/vs/editor/contrib/find/test/replacePattern.test.ts +++ b/src/vs/editor/contrib/find/test/replacePattern.test.ts @@ -153,4 +153,26 @@ suite('Replace Pattern test', () => { let actual = replacePattern.buildReplaceString(matches); assert.equal(actual, 'a{}'); }); + + test('preserve case', () => { + let replacePattern = parseReplaceString('Def'); + let actual = replacePattern.buildReplaceString(['abc'], true); + assert.equal(actual, 'def'); + actual = replacePattern.buildReplaceString(['Abc'], true); + assert.equal(actual, 'Def'); + actual = replacePattern.buildReplaceString(['ABC'], true); + assert.equal(actual, 'DEF'); + + actual = replacePattern.buildReplaceString(['abc', 'Abc'], true); + assert.equal(actual, 'def'); + actual = replacePattern.buildReplaceString(['Abc', 'abc'], true); + assert.equal(actual, 'Def'); + actual = replacePattern.buildReplaceString(['ABC', 'abc'], true); + assert.equal(actual, 'DEF'); + + actual = replacePattern.buildReplaceString(['AbC'], true); + assert.equal(actual, 'Def'); + actual = replacePattern.buildReplaceString(['aBC'], true); + assert.equal(actual, 'Def'); + }); }); diff --git a/src/vs/editor/contrib/suggest/suggestCommitCharacters.ts b/src/vs/editor/contrib/suggest/suggestCommitCharacters.ts index 8a6fca72612..20a7656532a 100644 --- a/src/vs/editor/contrib/suggest/suggestCommitCharacters.ts +++ b/src/vs/editor/contrib/suggest/suggestCommitCharacters.ts @@ -36,10 +36,17 @@ export class CommitCharacterController { private _onItem(selected: ISelectedSuggestion | undefined): void { if (!selected || !isNonEmptyArray(selected.item.completion.commitCharacters)) { + // no item or no commit characters this.reset(); return; } + if (this._active && this._active.item.item === selected.item) { + // still the same item + return; + } + + // keep item and its commit characters const acceptCharacters = new CharacterSet(); for (const ch of selected.item.completion.commitCharacters) { if (ch.length > 0) { diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index 9fa0cc0a87b..e6ac05dab25 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -50,7 +50,7 @@ interface ISuggestionTemplateData { iconLabel: IconLabel; typeLabel: HTMLElement; readMore: HTMLElement; - disposables: IDisposable[]; + disposables: DisposableStore; } /** @@ -106,8 +106,7 @@ class Renderer implements IListRenderer renderTemplate(container: HTMLElement): ISuggestionTemplateData { const data = Object.create(null); - const disposables = new DisposableStore(); - data.disposables = [disposables]; + data.disposables = new DisposableStore(); data.root = container; addClass(data.root, 'show-file-icons'); @@ -119,7 +118,7 @@ class Renderer implements IListRenderer const main = append(text, $('.main')); data.iconLabel = new IconLabel(main, { supportHighlights: true }); - disposables.add(data.iconLabel); + data.disposables.add(data.iconLabel); data.typeLabel = append(main, $('span.type-label')); @@ -147,7 +146,7 @@ class Renderer implements IListRenderer configureFont(); - disposables.add(Event.chain(this.editor.onDidChangeConfiguration.bind(this.editor)) + data.disposables.add(Event.chain(this.editor.onDidChangeConfiguration.bind(this.editor)) .filter(e => e.fontInfo || e.contribInfo) .on(configureFont, null)); @@ -218,7 +217,7 @@ class Renderer implements IListRenderer } disposeTemplate(templateData: ISuggestionTemplateData): void { - templateData.disposables = dispose(templateData.disposables); + templateData.disposables.dispose(); } } @@ -253,7 +252,7 @@ class SuggestionDetails { private type: HTMLElement; private docs: HTMLElement; private ariaLabel: string | null; - private disposables: IDisposable[]; + private readonly disposables: DisposableStore; private renderDisposeable: IDisposable; private borderWidth: number = 1; @@ -264,16 +263,16 @@ class SuggestionDetails { private readonly markdownRenderer: MarkdownRenderer, private readonly triggerKeybindingLabel: string, ) { - this.disposables = []; + this.disposables = new DisposableStore(); this.el = append(container, $('.details')); - this.disposables.push(toDisposable(() => container.removeChild(this.el))); + this.disposables.add(toDisposable(() => container.removeChild(this.el))); this.body = $('.body'); this.scrollbar = new DomScrollableElement(this.body, {}); append(this.el, this.scrollbar.getDomNode()); - this.disposables.push(this.scrollbar); + this.disposables.add(this.scrollbar); this.header = append(this.body, $('.header')); this.close = append(this.header, $('span.close')); @@ -414,7 +413,7 @@ class SuggestionDetails { } dispose(): void { - this.disposables = dispose(this.disposables); + this.disposables.dispose(); this.renderDisposeable = dispose(this.renderDisposeable); } } @@ -672,7 +671,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate { - if (this.list.length < index) { + if (index >= this.list.length || item !== this.list.element(index)) { return; } @@ -689,11 +688,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate { - if (this.focusedItem === item) { - this.currentSuggestionDetails = null; - } - }); + }).catch(onUnexpectedError); } // emit an event diff --git a/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts b/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts index 1cae3b38fe6..98c22c721ef 100644 --- a/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts +++ b/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts @@ -137,6 +137,22 @@ suite('WordOperations', () => { assert.deepEqual(actual, EXPECTED); }); + test('cursorWordLeftSelect - issue #74369: cursorWordLeft and cursorWordLeftSelect do not behave consistently', () => { + const EXPECTED = [ + '|this.|is.|a.|test', + ].join('\n'); + const [text,] = deserializePipePositions(EXPECTED); + const actualStops = testRepeatedActionAndExtractPositions( + text, + new Position(1, 15), + ed => cursorWordLeft(ed, true), + ed => ed.getPosition()!, + ed => ed.getPosition()!.equals(new Position(1, 1)) + ); + const actual = serializePipePositions(text, actualStops); + assert.deepEqual(actual, EXPECTED); + }); + test('cursorWordStartLeft', () => { // This is the behaviour observed in Visual Studio, please do not touch test const EXPECTED = ['| |/* |Just |some |more |text |a|+= |3 |+|5|-|3 |+ |7 |*/| '].join('\n'); diff --git a/src/vs/editor/contrib/wordOperations/wordOperations.ts b/src/vs/editor/contrib/wordOperations/wordOperations.ts index 282a4e05ac1..dc4e111f962 100644 --- a/src/vs/editor/contrib/wordOperations/wordOperations.ts +++ b/src/vs/editor/contrib/wordOperations/wordOperations.ts @@ -163,7 +163,7 @@ export class CursorWordLeftSelect extends WordLeftCommand { constructor() { super({ inSelectionMode: true, - wordNavigationType: WordNavigationType.WordStart, + wordNavigationType: WordNavigationType.WordStartFast, id: 'cursorWordLeftSelect', precondition: undefined }); diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 576092c9865..d333ce03746 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -356,7 +356,7 @@ export class StandaloneKeybindingService extends AbstractKeybindingService { private _toNormalizedKeybindingItems(items: IKeybindingItem[], isDefault: boolean): ResolvedKeybindingItem[] { let result: ResolvedKeybindingItem[] = [], resultLen = 0; for (const item of items) { - const when = (item.when ? item.when.normalize() : undefined); + const when = item.when || undefined; const keybinding = item.keybinding; if (!keybinding) { diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index cf0905a08ea..c2a4e4a4560 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -4344,12 +4344,12 @@ suite('autoClosingPairs', () => { let autoClosePositions = [ 'var a |=| [|]|;|', 'var b |=| |`asd`|;|', - 'var c |=| |\'asd!\'|;|', + 'var c |=| |\'asd\'|;|', 'var d |=| |"asd"|;|', 'var e |=| /*3*/| 3;|', 'var f |=| /**| 3 */3;|', 'var g |=| (3+5)|;|', - 'var h |=| {| a:| |\'value!\'| |}|;|', + 'var h |=| {| a:| |\'value\'| |}|;|', ]; for (let i = 0, len = autoClosePositions.length; i < len; i++) { const lineNumber = i + 1; @@ -4494,6 +4494,107 @@ suite('autoClosingPairs', () => { mode.dispose(); }); + test('issue #37315 - overtypes only those characters that it inserted', () => { + let mode = new AutoClosingMode(); + usingCursor({ + text: [ + '', + 'y=();' + ], + languageIdentifier: mode.getLanguageIdentifier() + }, (model, cursor) => { + assertCursor(cursor, new Position(1, 1)); + + cursorCommand(cursor, H.Type, { text: 'x=(' }, 'keyboard'); + assert.strictEqual(model.getLineContent(1), 'x=()'); + + cursorCommand(cursor, H.Type, { text: 'asd' }, 'keyboard'); + assert.strictEqual(model.getLineContent(1), 'x=(asd)'); + + // overtype! + cursorCommand(cursor, H.Type, { text: ')' }, 'keyboard'); + assert.strictEqual(model.getLineContent(1), 'x=(asd)'); + + // do not overtype! + cursor.setSelections('test', [new Selection(2, 4, 2, 4)]); + cursorCommand(cursor, H.Type, { text: ')' }, 'keyboard'); + assert.strictEqual(model.getLineContent(2), 'y=());'); + + }); + mode.dispose(); + }); + + test('issue #37315 - stops overtyping once cursor leaves area', () => { + let mode = new AutoClosingMode(); + usingCursor({ + text: [ + '', + 'y=();' + ], + languageIdentifier: mode.getLanguageIdentifier() + }, (model, cursor) => { + assertCursor(cursor, new Position(1, 1)); + + cursorCommand(cursor, H.Type, { text: 'x=(' }, 'keyboard'); + assert.strictEqual(model.getLineContent(1), 'x=()'); + + cursor.setSelections('test', [new Selection(1, 5, 1, 5)]); + cursorCommand(cursor, H.Type, { text: ')' }, 'keyboard'); + assert.strictEqual(model.getLineContent(1), 'x=())'); + }); + mode.dispose(); + }); + + test('issue #37315 - it overtypes only once', () => { + let mode = new AutoClosingMode(); + usingCursor({ + text: [ + '', + 'y=();' + ], + languageIdentifier: mode.getLanguageIdentifier() + }, (model, cursor) => { + assertCursor(cursor, new Position(1, 1)); + + cursorCommand(cursor, H.Type, { text: 'x=(' }, 'keyboard'); + assert.strictEqual(model.getLineContent(1), 'x=()'); + + cursorCommand(cursor, H.Type, { text: ')' }, 'keyboard'); + assert.strictEqual(model.getLineContent(1), 'x=()'); + + cursor.setSelections('test', [new Selection(1, 4, 1, 4)]); + cursorCommand(cursor, H.Type, { text: ')' }, 'keyboard'); + assert.strictEqual(model.getLineContent(1), 'x=())'); + }); + mode.dispose(); + }); + + test('issue #37315 - it can remember multiple auto-closed instances', () => { + let mode = new AutoClosingMode(); + usingCursor({ + text: [ + '', + 'y=();' + ], + languageIdentifier: mode.getLanguageIdentifier() + }, (model, cursor) => { + assertCursor(cursor, new Position(1, 1)); + + cursorCommand(cursor, H.Type, { text: 'x=(' }, 'keyboard'); + assert.strictEqual(model.getLineContent(1), 'x=()'); + + cursorCommand(cursor, H.Type, { text: '(' }, 'keyboard'); + assert.strictEqual(model.getLineContent(1), 'x=(())'); + + cursorCommand(cursor, H.Type, { text: ')' }, 'keyboard'); + assert.strictEqual(model.getLineContent(1), 'x=(())'); + + cursorCommand(cursor, H.Type, { text: ')' }, 'keyboard'); + assert.strictEqual(model.getLineContent(1), 'x=(())'); + }); + mode.dispose(); + }); + test('issue #15825: accents on mac US intl keyboard', () => { let mode = new AutoClosingMode(); usingCursor({ diff --git a/src/vs/editor/test/common/model/textModelSearch.test.ts b/src/vs/editor/test/common/model/textModelSearch.test.ts index 1dc5e82b52c..6476015cb5d 100644 --- a/src/vs/editor/test/common/model/textModelSearch.test.ts +++ b/src/vs/editor/test/common/model/textModelSearch.test.ts @@ -733,4 +733,23 @@ suite('TextModelSearch', () => { assert(isMultilineRegexSource('\\n')); assert(isMultilineRegexSource('foo\\W')); }); + + test('issue #74715. \\d* finds empty string and stops searching.', () => { + let model = TextModel.createFromString('10.243.30.10'); + + let searchParams = new SearchParams('\\d*', true, false, null); + + let actual = TextModelSearch.findMatches(model, searchParams, model.getFullModelRange(), true, 100); + assert.deepEqual(actual, [ + new FindMatch(new Range(1, 1, 1, 3), ['10']), + new FindMatch(new Range(1, 3, 1, 3), ['']), + new FindMatch(new Range(1, 4, 1, 7), ['243']), + new FindMatch(new Range(1, 7, 1, 7), ['']), + new FindMatch(new Range(1, 8, 1, 10), ['30']), + new FindMatch(new Range(1, 10, 1, 10), ['']), + new FindMatch(new Range(1, 11, 1, 13), ['10']) + ]); + + model.dispose(); + }); }); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 5f0f52de148..eb2000206ce 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -29,7 +29,8 @@ declare namespace monaco { export enum MarkerTag { - Unnecessary = 1 + Unnecessary = 1, + Deprecated = 2 } export enum MarkerSeverity { @@ -585,6 +586,14 @@ declare namespace monaco { * Test if `otherRange` is in `range`. If the ranges are equal, will return true. */ static containsRange(range: IRange, otherRange: IRange): boolean; + /** + * Test if `range` is strictly in this range. `range` must start after and end before this range for the result to be true. + */ + strictContainsRange(range: IRange): boolean; + /** + * Test if `otherRange` is strinctly in `range` (must start after, and end before). If the ranges are equal, will return false. + */ + static strictContainsRange(range: IRange, otherRange: IRange): boolean; /** * A reunion of the two ranges. * The smallest position will be used as the start point, and the largest one as the end point. diff --git a/src/vs/platform/browser/contextScopedHistoryWidget.ts b/src/vs/platform/browser/contextScopedHistoryWidget.ts index 94d4de658b5..5e5e8603683 100644 --- a/src/vs/platform/browser/contextScopedHistoryWidget.ts +++ b/src/vs/platform/browser/contextScopedHistoryWidget.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IContextKeyService, ContextKeyDefinedExpr, ContextKeyExpr, ContextKeyAndExpr, ContextKeyEqualsExpr, RawContextKey, IContextKey, IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey, IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey'; import { HistoryInputBox, IHistoryInputOptions } from 'vs/base/browser/ui/inputbox/inputBox'; import { FindInput, IFindInputOptions } from 'vs/base/browser/ui/findinput/findInput'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; @@ -66,7 +66,7 @@ export class ContextScopedFindInput extends FindInput { KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'history.showPrevious', weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(new ContextKeyDefinedExpr(HistoryNavigationWidgetContext), new ContextKeyEqualsExpr(HistoryNavigationEnablementContext, true)), + when: ContextKeyExpr.and(ContextKeyExpr.has(HistoryNavigationWidgetContext), ContextKeyExpr.equals(HistoryNavigationEnablementContext, true)), primary: KeyCode.UpArrow, secondary: [KeyMod.Alt | KeyCode.UpArrow], handler: (accessor, arg2) => { @@ -81,7 +81,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'history.showNext', weight: KeybindingWeight.WorkbenchContrib, - when: new ContextKeyAndExpr([new ContextKeyDefinedExpr(HistoryNavigationWidgetContext), new ContextKeyEqualsExpr(HistoryNavigationEnablementContext, true)]), + when: ContextKeyExpr.and(ContextKeyExpr.has(HistoryNavigationWidgetContext), ContextKeyExpr.equals(HistoryNavigationEnablementContext, true)), primary: KeyCode.DownArrow, secondary: [KeyMod.Alt | KeyCode.DownArrow], handler: (accessor, arg2) => { diff --git a/src/vs/platform/contextkey/common/contextkey.ts b/src/vs/platform/contextkey/common/contextkey.ts index 289ba8668ce..8c01c1452d8 100644 --- a/src/vs/platform/contextkey/common/contextkey.ts +++ b/src/vs/platform/contextkey/common/contextkey.ts @@ -13,7 +13,9 @@ export const enum ContextKeyExprType { Equals = 3, NotEquals = 4, And = 5, - Regex = 6 + Regex = 6, + NotRegex = 7, + Or = 8 } export interface IContextKeyExprMapper { @@ -27,27 +29,31 @@ export interface IContextKeyExprMapper { export abstract class ContextKeyExpr { public static has(key: string): ContextKeyExpr { - return new ContextKeyDefinedExpr(key); + return ContextKeyDefinedExpr.create(key); } public static equals(key: string, value: any): ContextKeyExpr { - return new ContextKeyEqualsExpr(key, value); + return ContextKeyEqualsExpr.create(key, value); } public static notEquals(key: string, value: any): ContextKeyExpr { - return new ContextKeyNotEqualsExpr(key, value); + return ContextKeyNotEqualsExpr.create(key, value); } public static regex(key: string, value: RegExp): ContextKeyExpr { - return new ContextKeyRegexExpr(key, value); + return ContextKeyRegexExpr.create(key, value); } public static not(key: string): ContextKeyExpr { - return new ContextKeyNotExpr(key); + return ContextKeyNotExpr.create(key); } - public static and(...expr: Array): ContextKeyExpr { - return new ContextKeyAndExpr(expr); + public static and(...expr: Array): ContextKeyExpr | undefined { + return ContextKeyAndExpr.create(expr); + } + + public static or(...expr: Array): ContextKeyExpr | undefined { + return ContextKeyOrExpr.create(expr); } public static deserialize(serialized: string | null | undefined, strict: boolean = false): ContextKeyExpr | undefined { @@ -55,9 +61,17 @@ export abstract class ContextKeyExpr { return undefined; } + return this._deserializeOrExpression(serialized, strict); + } + + private static _deserializeOrExpression(serialized: string, strict: boolean): ContextKeyExpr | undefined { + let pieces = serialized.split('||'); + return ContextKeyOrExpr.create(pieces.map(p => this._deserializeAndExpression(p, strict))); + } + + private static _deserializeAndExpression(serialized: string, strict: boolean): ContextKeyExpr | undefined { let pieces = serialized.split('&&'); - let result = new ContextKeyAndExpr(pieces.map(p => this._deserializeOne(p, strict))); - return result.normalize(); + return ContextKeyAndExpr.create(pieces.map(p => this._deserializeOne(p, strict))); } private static _deserializeOne(serializedOne: string, strict: boolean): ContextKeyExpr { @@ -65,24 +79,24 @@ export abstract class ContextKeyExpr { if (serializedOne.indexOf('!=') >= 0) { let pieces = serializedOne.split('!='); - return new ContextKeyNotEqualsExpr(pieces[0].trim(), this._deserializeValue(pieces[1], strict)); + return ContextKeyNotEqualsExpr.create(pieces[0].trim(), this._deserializeValue(pieces[1], strict)); } if (serializedOne.indexOf('==') >= 0) { let pieces = serializedOne.split('=='); - return new ContextKeyEqualsExpr(pieces[0].trim(), this._deserializeValue(pieces[1], strict)); + return ContextKeyEqualsExpr.create(pieces[0].trim(), this._deserializeValue(pieces[1], strict)); } if (serializedOne.indexOf('=~') >= 0) { let pieces = serializedOne.split('=~'); - return new ContextKeyRegexExpr(pieces[0].trim(), this._deserializeRegexValue(pieces[1], strict)); + return ContextKeyRegexExpr.create(pieces[0].trim(), this._deserializeRegexValue(pieces[1], strict)); } if (/^\!\s*/.test(serializedOne)) { - return new ContextKeyNotExpr(serializedOne.substr(1).trim()); + return ContextKeyNotExpr.create(serializedOne.substr(1).trim()); } - return new ContextKeyDefinedExpr(serializedOne); + return ContextKeyDefinedExpr.create(serializedOne); } private static _deserializeValue(serializedValue: string, strict: boolean): any { @@ -143,10 +157,10 @@ export abstract class ContextKeyExpr { public abstract getType(): ContextKeyExprType; public abstract equals(other: ContextKeyExpr): boolean; public abstract evaluate(context: IContext): boolean; - public abstract normalize(): ContextKeyExpr | undefined; public abstract serialize(): string; public abstract keys(): string[]; public abstract map(mapFnc: IContextKeyExprMapper): ContextKeyExpr; + public abstract negate(): ContextKeyExpr; } function cmp(a: ContextKeyExpr, b: ContextKeyExpr): number { @@ -166,13 +180,21 @@ function cmp(a: ContextKeyExpr, b: ContextKeyExpr): number { return (a).cmp(b); case ContextKeyExprType.Regex: return (a).cmp(b); + case ContextKeyExprType.NotRegex: + return (a).cmp(b); + case ContextKeyExprType.And: + return (a).cmp(b); default: throw new Error('Unknown ContextKeyExpr!'); } } export class ContextKeyDefinedExpr implements ContextKeyExpr { - constructor(protected key: string) { + public static create(key: string): ContextKeyExpr { + return new ContextKeyDefinedExpr(key); + } + + protected constructor(protected key: string) { } public getType(): ContextKeyExprType { @@ -200,10 +222,6 @@ export class ContextKeyDefinedExpr implements ContextKeyExpr { return (!!context.getValue(this.key)); } - public normalize(): ContextKeyExpr { - return this; - } - public serialize(): string { return this.key; } @@ -215,10 +233,25 @@ export class ContextKeyDefinedExpr implements ContextKeyExpr { public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { return mapFnc.mapDefined(this.key); } + + public negate(): ContextKeyExpr { + return ContextKeyNotExpr.create(this.key); + } } export class ContextKeyEqualsExpr implements ContextKeyExpr { - constructor(private readonly key: string, private readonly value: any) { + + public static create(key: string, value: any): ContextKeyExpr { + if (typeof value === 'boolean') { + if (value) { + return ContextKeyDefinedExpr.create(key); + } + return ContextKeyNotExpr.create(key); + } + return new ContextKeyEqualsExpr(key, value); + } + + private constructor(private readonly key: string, private readonly value: any) { } public getType(): ContextKeyExprType { @@ -255,21 +288,7 @@ export class ContextKeyEqualsExpr implements ContextKeyExpr { /* tslint:enable:triple-equals */ } - public normalize(): ContextKeyExpr { - if (typeof this.value === 'boolean') { - if (this.value) { - return new ContextKeyDefinedExpr(this.key); - } - return new ContextKeyNotExpr(this.key); - } - return this; - } - public serialize(): string { - if (typeof this.value === 'boolean') { - return this.normalize().serialize(); - } - return this.key + ' == \'' + this.value + '\''; } @@ -280,10 +299,25 @@ export class ContextKeyEqualsExpr implements ContextKeyExpr { public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { return mapFnc.mapEquals(this.key, this.value); } + + public negate(): ContextKeyExpr { + return ContextKeyNotEqualsExpr.create(this.key, this.value); + } } export class ContextKeyNotEqualsExpr implements ContextKeyExpr { - constructor(private key: string, private value: any) { + + public static create(key: string, value: any): ContextKeyExpr { + if (typeof value === 'boolean') { + if (value) { + return ContextKeyNotExpr.create(key); + } + return ContextKeyDefinedExpr.create(key); + } + return new ContextKeyNotEqualsExpr(key, value); + } + + private constructor(private key: string, private value: any) { } public getType(): ContextKeyExprType { @@ -320,21 +354,7 @@ export class ContextKeyNotEqualsExpr implements ContextKeyExpr { /* tslint:enable:triple-equals */ } - public normalize(): ContextKeyExpr { - if (typeof this.value === 'boolean') { - if (this.value) { - return new ContextKeyNotExpr(this.key); - } - return new ContextKeyDefinedExpr(this.key); - } - return this; - } - public serialize(): string { - if (typeof this.value === 'boolean') { - return this.normalize().serialize(); - } - return this.key + ' != \'' + this.value + '\''; } @@ -345,10 +365,19 @@ export class ContextKeyNotEqualsExpr implements ContextKeyExpr { public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { return mapFnc.mapNotEquals(this.key, this.value); } + + public negate(): ContextKeyExpr { + return ContextKeyEqualsExpr.create(this.key, this.value); + } } export class ContextKeyNotExpr implements ContextKeyExpr { - constructor(private key: string) { + + public static create(key: string): ContextKeyExpr { + return new ContextKeyNotExpr(key); + } + + private constructor(private key: string) { } public getType(): ContextKeyExprType { @@ -376,10 +405,6 @@ export class ContextKeyNotExpr implements ContextKeyExpr { return (!context.getValue(this.key)); } - public normalize(): ContextKeyExpr { - return this; - } - public serialize(): string { return '!' + this.key; } @@ -391,11 +416,19 @@ export class ContextKeyNotExpr implements ContextKeyExpr { public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { return mapFnc.mapNot(this.key); } + + public negate(): ContextKeyExpr { + return ContextKeyDefinedExpr.create(this.key); + } } export class ContextKeyRegexExpr implements ContextKeyExpr { - constructor(private key: string, private regexp: RegExp | null) { + public static create(key: string, regexp: RegExp | null): ContextKeyExpr { + return new ContextKeyRegexExpr(key, regexp); + } + + private constructor(private key: string, private regexp: RegExp | null) { // } @@ -435,10 +468,6 @@ export class ContextKeyRegexExpr implements ContextKeyExpr { return this.regexp ? this.regexp.test(value) : false; } - public normalize(): ContextKeyExpr { - return this; - } - public serialize(): string { const value = this.regexp ? `/${this.regexp.source}/${this.regexp.ignoreCase ? 'i' : ''}` @@ -450,22 +479,99 @@ export class ContextKeyRegexExpr implements ContextKeyExpr { return [this.key]; } - public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { + public map(mapFnc: IContextKeyExprMapper): ContextKeyRegexExpr { return mapFnc.mapRegex(this.key, this.regexp); } + + public negate(): ContextKeyExpr { + return ContextKeyNotRegexExpr.create(this); + } +} + +export class ContextKeyNotRegexExpr implements ContextKeyExpr { + + public static create(actual: ContextKeyRegexExpr): ContextKeyExpr { + return new ContextKeyNotRegexExpr(actual); + } + + private constructor(private readonly _actual: ContextKeyRegexExpr) { + // + } + + public getType(): ContextKeyExprType { + return ContextKeyExprType.NotRegex; + } + + public cmp(other: ContextKeyNotRegexExpr): number { + return this._actual.cmp(other._actual); + } + + public equals(other: ContextKeyExpr): boolean { + if (other instanceof ContextKeyNotRegexExpr) { + return this._actual.equals(other._actual); + } + return false; + } + + public evaluate(context: IContext): boolean { + return !this._actual.evaluate(context); + } + + public serialize(): string { + throw new Error('Method not implemented.'); + } + + public keys(): string[] { + return this._actual.keys(); + } + + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { + return new ContextKeyNotRegexExpr(this._actual.map(mapFnc)); + } + + public negate(): ContextKeyExpr { + return this._actual; + } } export class ContextKeyAndExpr implements ContextKeyExpr { - public readonly expr: ContextKeyExpr[]; - constructor(expr: Array) { - this.expr = ContextKeyAndExpr._normalizeArr(expr); + public static create(_expr: Array): ContextKeyExpr | undefined { + const expr = ContextKeyAndExpr._normalizeArr(_expr); + if (expr.length === 0) { + return undefined; + } + + if (expr.length === 1) { + return expr[0]; + } + + return new ContextKeyAndExpr(expr); + } + + private constructor(public readonly expr: ContextKeyExpr[]) { } public getType(): ContextKeyExprType { return ContextKeyExprType.And; } + public cmp(other: ContextKeyAndExpr): number { + if (this.expr.length < other.expr.length) { + return -1; + } + if (this.expr.length > other.expr.length) { + return 1; + } + for (let i = 0, len = this.expr.length; i < len; i++) { + const r = cmp(this.expr[i], other.expr[i]); + if (r !== 0) { + return r; + } + } + return 0; + } + public equals(other: ContextKeyExpr): boolean { if (other instanceof ContextKeyAndExpr) { if (this.expr.length !== other.expr.length) { @@ -500,16 +606,16 @@ export class ContextKeyAndExpr implements ContextKeyExpr { continue; } - e = e.normalize(); - if (!e) { - continue; - } - if (e instanceof ContextKeyAndExpr) { expr = expr.concat(e.expr); continue; } + if (e instanceof ContextKeyOrExpr) { + // Not allowed, because we don't have parens! + throw new Error(`It is not allowed to have an or expression here due to lack of parens!`); + } + expr.push(e); } @@ -519,29 +625,7 @@ export class ContextKeyAndExpr implements ContextKeyExpr { return expr; } - public normalize(): ContextKeyExpr | undefined { - if (this.expr.length === 0) { - return undefined; - } - - if (this.expr.length === 1) { - return this.expr[0]; - } - - return this; - } - public serialize(): string { - if (this.expr.length === 0) { - return ''; - } - if (this.expr.length === 1) { - const normalized = this.normalize(); - if (!normalized) { - return ''; - } - return normalized.serialize(); - } return this.expr.map(e => e.serialize()).join(' && '); } @@ -556,6 +640,132 @@ export class ContextKeyAndExpr implements ContextKeyExpr { public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { return new ContextKeyAndExpr(this.expr.map(expr => expr.map(mapFnc))); } + + public negate(): ContextKeyExpr { + let result: ContextKeyExpr[] = []; + for (let expr of this.expr) { + result.push(expr.negate()); + } + return ContextKeyOrExpr.create(result)!; + } +} + +export class ContextKeyOrExpr implements ContextKeyExpr { + + public static create(_expr: Array): ContextKeyExpr | undefined { + const expr = ContextKeyOrExpr._normalizeArr(_expr); + if (expr.length === 0) { + return undefined; + } + + if (expr.length === 1) { + return expr[0]; + } + + return new ContextKeyOrExpr(expr); + } + + private constructor(public readonly expr: ContextKeyExpr[]) { + } + + public getType(): ContextKeyExprType { + return ContextKeyExprType.Or; + } + + public equals(other: ContextKeyExpr): boolean { + if (other instanceof ContextKeyOrExpr) { + if (this.expr.length !== other.expr.length) { + return false; + } + for (let i = 0, len = this.expr.length; i < len; i++) { + if (!this.expr[i].equals(other.expr[i])) { + return false; + } + } + return true; + } + return false; + } + + public evaluate(context: IContext): boolean { + for (let i = 0, len = this.expr.length; i < len; i++) { + if (this.expr[i].evaluate(context)) { + return true; + } + } + return false; + } + + private static _normalizeArr(arr: Array): ContextKeyExpr[] { + let expr: ContextKeyExpr[] = []; + + if (arr) { + for (let i = 0, len = arr.length; i < len; i++) { + let e: ContextKeyExpr | null | undefined = arr[i]; + if (!e) { + continue; + } + + if (e instanceof ContextKeyOrExpr) { + expr = expr.concat(e.expr); + continue; + } + + expr.push(e); + } + + expr.sort(cmp); + } + + return expr; + } + + public serialize(): string { + return this.expr.map(e => e.serialize()).join(' || '); + } + + public keys(): string[] { + const result: string[] = []; + for (let expr of this.expr) { + result.push(...expr.keys()); + } + return result; + } + + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { + return new ContextKeyOrExpr(this.expr.map(expr => expr.map(mapFnc))); + } + + public negate(): ContextKeyExpr { + let result: ContextKeyExpr[] = []; + for (let expr of this.expr) { + result.push(expr.negate()); + } + + const terminals = (node: ContextKeyExpr) => { + if (node instanceof ContextKeyOrExpr) { + return node.expr; + } + return [node]; + }; + + // We don't support parens, so here we distribute the AND over the OR terminals + // We always take the first 2 AND pairs and distribute them + while (result.length > 1) { + const LEFT = result.shift()!; + const RIGHT = result.shift()!; + + const all: ContextKeyExpr[] = []; + for (const left of terminals(LEFT)) { + for (const right of terminals(RIGHT)) { + all.push(ContextKeyExpr.and(left, right)!); + } + } + result.unshift(ContextKeyExpr.or(...all)!); + } + + return result[0]; + } } export class RawContextKey extends ContextKeyDefinedExpr { diff --git a/src/vs/platform/contextkey/test/common/contextkey.test.ts b/src/vs/platform/contextkey/test/common/contextkey.test.ts index cb12470e3c2..9db3782da31 100644 --- a/src/vs/platform/contextkey/test/common/contextkey.test.ts +++ b/src/vs/platform/contextkey/test/common/contextkey.test.ts @@ -27,7 +27,7 @@ suite('ContextKeyExpr', () => { ContextKeyExpr.notEquals('c2', 'cc2'), ContextKeyExpr.not('d1'), ContextKeyExpr.not('d2') - ); + )!; let b = ContextKeyExpr.and( ContextKeyExpr.equals('b2', 'bb2'), ContextKeyExpr.notEquals('c1', 'cc1'), @@ -40,7 +40,7 @@ suite('ContextKeyExpr', () => { ContextKeyExpr.has('a1'), ContextKeyExpr.and(ContextKeyExpr.equals('and.a', true)), ContextKeyExpr.not('d2') - ); + )!; assert(a.equals(b), 'expressions should be equal'); }); @@ -50,10 +50,10 @@ suite('ContextKeyExpr', () => { let key1IsFalse = ContextKeyExpr.equals('key1', false); let key1IsNotTrue = ContextKeyExpr.notEquals('key1', true); - assert.ok(key1IsTrue.normalize()!.equals(ContextKeyExpr.has('key1'))); - assert.ok(key1IsNotFalse.normalize()!.equals(ContextKeyExpr.has('key1'))); - assert.ok(key1IsFalse.normalize()!.equals(ContextKeyExpr.not('key1'))); - assert.ok(key1IsNotTrue.normalize()!.equals(ContextKeyExpr.not('key1'))); + assert.ok(key1IsTrue.equals(ContextKeyExpr.has('key1'))); + assert.ok(key1IsNotFalse.equals(ContextKeyExpr.has('key1'))); + assert.ok(key1IsFalse.equals(ContextKeyExpr.not('key1'))); + assert.ok(key1IsNotTrue.equals(ContextKeyExpr.not('key1'))); }); test('evaluate', () => { @@ -93,5 +93,24 @@ suite('ContextKeyExpr', () => { testExpression('a && !b && c == 5', true && !false && '5' == '5'); testExpression('d =~ /e.*/', false); /* tslint:enable:triple-equals */ + + // precedence test: false && true || true === true because && is evaluated first + testExpression('b && a || a', true); + + testExpression('a || b', true); + testExpression('b || b', false); + testExpression('b && a || a && b', false); + }); + + test('negate', () => { + function testNegate(expr: string, expected: string): void { + const actual = ContextKeyExpr.deserialize(expr)!.negate().serialize(); + assert.strictEqual(actual, expected); + } + testNegate('a', '!a'); + testNegate('a && b || c', '!a && !c || !b && !c'); + testNegate('a && b || c || d', '!a && !c && !d || !b && !c && !d'); + testNegate('!a && !b || !c && !d', 'a && c || a && d || b && c || b && d'); + testNegate('!a && !b || !c && !d || !e && !f', 'a && c && e || a && c && f || a && d && e || a && d && f || b && c && e || b && c && f || b && d && e || b && d && f'); }); }); diff --git a/src/vs/platform/keybinding/common/keybindingResolver.ts b/src/vs/platform/keybinding/common/keybindingResolver.ts index 1436cfa6604..29d559eab4d 100644 --- a/src/vs/platform/keybinding/common/keybindingResolver.ts +++ b/src/vs/platform/keybinding/common/keybindingResolver.ts @@ -6,7 +6,7 @@ import { isNonEmptyArray } from 'vs/base/common/arrays'; import { MenuRegistry } from 'vs/platform/actions/common/actions'; import { CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; -import { ContextKeyAndExpr, ContextKeyExpr, IContext } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContext, ContextKeyOrExpr } from 'vs/platform/contextkey/common/contextkey'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { keys } from 'vs/base/common/map'; @@ -171,7 +171,6 @@ export class KeybindingResolver { /** * Returns true if it is provable `a` implies `b`. - * **Precondition**: Assumes `a` and `b` are normalized! */ public static whenIsEntirelyIncluded(a: ContextKeyExpr | null | undefined, b: ContextKeyExpr | null | undefined): boolean { if (!b) { @@ -181,26 +180,35 @@ export class KeybindingResolver { return false; } - const aExpressions: ContextKeyExpr[] = ((a instanceof ContextKeyAndExpr) ? a.expr : [a]); - const bExpressions: ContextKeyExpr[] = ((b instanceof ContextKeyAndExpr) ? b.expr : [b]); + return this._implies(a, b); + } - let aIndex = 0; - for (const bExpr of bExpressions) { - let bExprMatched = false; - while (!bExprMatched && aIndex < aExpressions.length) { - let aExpr = aExpressions[aIndex]; - if (aExpr.equals(bExpr)) { - bExprMatched = true; - } - aIndex++; + /** + * Returns true if it is provable `p` implies `q`. + */ + private static _implies(p: ContextKeyExpr, q: ContextKeyExpr): boolean { + const notP = p.negate(); + + const terminals = (node: ContextKeyExpr) => { + if (node instanceof ContextKeyOrExpr) { + return node.expr; } + return [node]; + }; - if (!bExprMatched) { - return false; + let expr = terminals(notP).concat(terminals(q)); + for (let i = 0; i < expr.length; i++) { + const a = expr[i]; + const notA = a.negate(); + for (let j = i + 1; j < expr.length; j++) { + const b = expr[j]; + if (notA.equals(b)) { + return true; + } } } - return true; + return false; } public getDefaultBoundCommands(): Map { diff --git a/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts b/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts index a4488cffb17..c85003be1ac 100644 --- a/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts +++ b/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { KeyChord, KeyCode, KeyMod, SimpleKeybinding, createKeybinding, createSimpleKeybinding } from 'vs/base/common/keyCodes'; import { OS } from 'vs/base/common/platform'; -import { ContextKeyAndExpr, ContextKeyExpr, IContext } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContext } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; @@ -20,13 +20,13 @@ function createContext(ctx: any) { suite('KeybindingResolver', () => { - function kbItem(keybinding: number, command: string, commandArgs: any, when: ContextKeyExpr, isDefault: boolean): ResolvedKeybindingItem { + function kbItem(keybinding: number, command: string, commandArgs: any, when: ContextKeyExpr | undefined, isDefault: boolean): ResolvedKeybindingItem { const resolvedKeybinding = (keybinding !== 0 ? new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS)!, OS) : undefined); return new ResolvedKeybindingItem( resolvedKeybinding, command, commandArgs, - when ? when.normalize() : undefined, + when, isDefault ); } @@ -191,64 +191,41 @@ suite('KeybindingResolver', () => { }); test('contextIsEntirelyIncluded', () => { - let assertIsIncluded = (a: ContextKeyExpr[], b: ContextKeyExpr[]) => { - let tmpA = new ContextKeyAndExpr(a).normalize(); - let tmpB = new ContextKeyAndExpr(b).normalize(); - assert.equal(KeybindingResolver.whenIsEntirelyIncluded(tmpA, tmpB), true); + const assertIsIncluded = (a: string | null, b: string | null) => { + assert.equal(KeybindingResolver.whenIsEntirelyIncluded(ContextKeyExpr.deserialize(a), ContextKeyExpr.deserialize(b)), true); }; - let assertIsNotIncluded = (a: ContextKeyExpr[], b: ContextKeyExpr[]) => { - let tmpA = new ContextKeyAndExpr(a).normalize(); - let tmpB = new ContextKeyAndExpr(b).normalize(); - assert.equal(KeybindingResolver.whenIsEntirelyIncluded(tmpA, tmpB), false); + const assertIsNotIncluded = (a: string | null, b: string | null) => { + assert.equal(KeybindingResolver.whenIsEntirelyIncluded(ContextKeyExpr.deserialize(a), ContextKeyExpr.deserialize(b)), false); }; - let key1IsTrue = ContextKeyExpr.equals('key1', true); - let key1IsNotFalse = ContextKeyExpr.notEquals('key1', false); - let key1IsFalse = ContextKeyExpr.equals('key1', false); - let key1IsNotTrue = ContextKeyExpr.notEquals('key1', true); - let key2IsTrue = ContextKeyExpr.equals('key2', true); - let key2IsNotFalse = ContextKeyExpr.notEquals('key2', false); - let key3IsTrue = ContextKeyExpr.equals('key3', true); - let key4IsTrue = ContextKeyExpr.equals('key4', true); - assertIsIncluded([key1IsTrue], null!); - assertIsIncluded([key1IsTrue], []); - assertIsIncluded([key1IsTrue], [key1IsTrue]); - assertIsIncluded([key1IsTrue], [key1IsNotFalse]); + assertIsIncluded('key1', null); + assertIsIncluded('key1', ''); + assertIsIncluded('key1', 'key1'); + assertIsIncluded('!key1', ''); + assertIsIncluded('!key1', '!key1'); + assertIsIncluded('key2', ''); + assertIsIncluded('key2', 'key2'); + assertIsIncluded('key1 && key1 && key2 && key2', 'key2'); + assertIsIncluded('key1 && key2', 'key2'); + assertIsIncluded('key1 && key2', 'key1'); + assertIsIncluded('key1 && key2', ''); + assertIsIncluded('key1', 'key1 || key2'); + assertIsIncluded('key1 || !key1', 'key2 || !key2'); + assertIsIncluded('key1', 'key1 || key2 && key3'); - assertIsIncluded([key1IsFalse], []); - assertIsIncluded([key1IsFalse], [key1IsFalse]); - assertIsIncluded([key1IsFalse], [key1IsNotTrue]); - - assertIsIncluded([key2IsNotFalse], []); - assertIsIncluded([key2IsNotFalse], [key2IsNotFalse]); - assertIsIncluded([key2IsNotFalse], [key2IsTrue]); - - assertIsIncluded([key1IsTrue, key2IsNotFalse], [key2IsTrue]); - assertIsIncluded([key1IsTrue, key2IsNotFalse], [key2IsNotFalse]); - assertIsIncluded([key1IsTrue, key2IsNotFalse], [key1IsTrue]); - assertIsIncluded([key1IsTrue, key2IsNotFalse], [key1IsNotFalse]); - assertIsIncluded([key1IsTrue, key2IsNotFalse], []); - - assertIsNotIncluded([key1IsTrue], [key1IsFalse]); - assertIsNotIncluded([key1IsTrue], [key1IsNotTrue]); - assertIsNotIncluded([key1IsNotFalse], [key1IsFalse]); - assertIsNotIncluded([key1IsNotFalse], [key1IsNotTrue]); - - assertIsNotIncluded([key1IsFalse], [key1IsTrue]); - assertIsNotIncluded([key1IsFalse], [key1IsNotFalse]); - assertIsNotIncluded([key1IsNotTrue], [key1IsTrue]); - assertIsNotIncluded([key1IsNotTrue], [key1IsNotFalse]); - - assertIsNotIncluded([key1IsTrue, key2IsNotFalse], [key3IsTrue]); - assertIsNotIncluded([key1IsTrue, key2IsNotFalse], [key4IsTrue]); - assertIsNotIncluded([key1IsTrue], [key2IsTrue]); - assertIsNotIncluded([], [key2IsTrue]); - assertIsNotIncluded(null!, [key2IsTrue]); + assertIsNotIncluded('key1', '!key1'); + assertIsNotIncluded('!key1', 'key1'); + assertIsNotIncluded('key1 && key2', 'key3'); + assertIsNotIncluded('key1 && key2', 'key4'); + assertIsNotIncluded('key1', 'key2'); + assertIsNotIncluded('key1 || key2', 'key2'); + assertIsNotIncluded('', 'key2'); + assertIsNotIncluded(null, 'key2'); }); test('resolve command', function () { - function _kbItem(keybinding: number, command: string, when: ContextKeyExpr): ResolvedKeybindingItem { + function _kbItem(keybinding: number, command: string, when: ContextKeyExpr | undefined): ResolvedKeybindingItem { return kbItem(keybinding, command, null, when, true); } diff --git a/src/vs/platform/markers/common/markers.ts b/src/vs/platform/markers/common/markers.ts index 9461b506c21..235c4b12f36 100644 --- a/src/vs/platform/markers/common/markers.ts +++ b/src/vs/platform/markers/common/markers.ts @@ -39,6 +39,7 @@ export interface IRelatedInformation { export const enum MarkerTag { Unnecessary = 1, + Deprecated = 2 } export enum MarkerSeverity { diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index 45c31ca0f3d..d39dd65c08f 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -72,8 +72,8 @@ export interface IProductConfiguration { readonly recommendationsUrl: string; }; extensionTips: { [id: string]: string; }; - extensionImportantTips: { [id: string]: { name: string; pattern: string; }; }; - readonly exeBasedExtensionTips: { [id: string]: { friendlyName: string, windowsPath?: string, recommendations: readonly string[] }; }; + extensionImportantTips: { [id: string]: { name: string; pattern: string; isExtensionPack?: boolean }; }; + readonly exeBasedExtensionTips: { [id: string]: IExeBasedExtensionTip; }; readonly extensionKeywords: { [extension: string]: readonly string[]; }; readonly extensionAllowedBadgeProviders: readonly string[]; readonly extensionAllowedProposedApi: readonly string[]; @@ -120,6 +120,14 @@ export interface IProductConfiguration { readonly uiExtensions?: readonly string[]; } +export interface IExeBasedExtensionTip { + friendlyName: string; + windowsPath?: string; + recommendations: readonly string[]; + important?: boolean; + exeFriendlyName?: string; +} + export interface ISurveyData { surveyId: string; surveyUrl: string; diff --git a/src/vs/platform/request/common/request.ts b/src/vs/platform/request/common/request.ts index ed6c2611166..31e3c314242 100644 --- a/src/vs/platform/request/common/request.ts +++ b/src/vs/platform/request/common/request.ts @@ -90,7 +90,7 @@ Registry.as(Extensions.Configuration) 'http.proxy': { type: 'string', pattern: '^https?://([^:]*(:[^@]*)?@)?([^:]+)(:\\d+)?/?$|^$', - description: localize('proxy', "The proxy setting to use. If not set will be taken from the http_proxy and https_proxy environment variables.") + markdownDescription: localize('proxy', "The proxy setting to use. If not set, will be inherited from the `http_proxy` and `https_proxy` environment variables.") }, 'http.proxyStrictSSL': { type: 'boolean', @@ -100,7 +100,7 @@ Registry.as(Extensions.Configuration) 'http.proxyAuthorization': { type: ['null', 'string'], default: null, - description: localize('proxyAuthorization', "The value to send as the 'Proxy-Authorization' header for every network request.") + markdownDescription: localize('proxyAuthorization', "The value to send as the `Proxy-Authorization` header for every network request.") }, 'http.proxySupport': { type: 'string', diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index d86d071f6f4..8eac40ea882 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -816,7 +816,7 @@ declare module 'vscode' { * [Terminal.sendText](#Terminal.sendText) is triggered that will fire the * [TerminalRenderer.onDidAcceptInput](#TerminalRenderer.onDidAcceptInput) event. * - * @deprecated Use [virtual processes](#TerminalVirtualProcess) instead. + * @deprecated Use [ExtensionTerminalOptions](#ExtensionTerminalOptions) instead. * * **Example:** Create a terminal renderer, show it and write hello world in red * ```typescript @@ -828,7 +828,7 @@ declare module 'vscode' { export interface TerminalRenderer { /** * The name of the terminal, this will appear in the terminal selector. - * @deprecated Use [virtual processes](#TerminalVirtualProcess) instead. + * @deprecated Use [ExtensionTerminalOptions](#ExtensionTerminalOptions) instead. */ name: string; @@ -837,7 +837,7 @@ declare module 'vscode' { * a value smaller than the maximum value, if this is undefined the terminal will auto fit * to the maximum value [maximumDimensions](TerminalRenderer.maximumDimensions). * - * @deprecated Use [virtual processes](#TerminalVirtualProcess) instead. + * @deprecated Use [ExtensionTerminalOptions](#ExtensionTerminalOptions) instead. * * **Example:** Override the dimensions of a TerminalRenderer to 20 columns and 10 rows * ```typescript @@ -855,14 +855,14 @@ declare module 'vscode' { * Listen to [onDidChangeMaximumDimensions](TerminalRenderer.onDidChangeMaximumDimensions) * to get notified when this value changes. * - * @deprecated Use [virtual processes](#TerminalVirtualProcess) instead. + * @deprecated Use [ExtensionTerminalOptions](#ExtensionTerminalOptions) instead. */ readonly maximumDimensions: TerminalDimensions | undefined; /** * The corresponding [Terminal](#Terminal) for this TerminalRenderer. * - * @deprecated Use [virtual processes](#TerminalVirtualProcess) instead. + * @deprecated Use [ExtensionTerminalOptions](#ExtensionTerminalOptions) instead. */ readonly terminal: Terminal; @@ -871,7 +871,7 @@ declare module 'vscode' { * text to the underlying _process_, this will write the text to the terminal itself. * * @param text The text to write. - * @deprecated Use [virtual processes](#TerminalVirtualProcess) instead. + * @deprecated Use [ExtensionTerminalOptions](#ExtensionTerminalOptions) instead. * * **Example:** Write red text to the terminal * ```typescript @@ -890,7 +890,7 @@ declare module 'vscode' { * [Terminal.sendText](#Terminal.sendText). Keystrokes are converted into their * corresponding VT sequence representation. * - * @deprecated Use [virtual processes](#TerminalVirtualProcess) instead. + * @deprecated Use [ExtensionTerminalOptions](#ExtensionTerminalOptions) instead. * * **Example:** Simulate interaction with the terminal from an outside extension or a * workbench command such as `workbench.action.terminal.runSelectedText` @@ -908,7 +908,7 @@ declare module 'vscode' { * An event which fires when the [maximum dimensions](#TerminalRenderer.maximumDimensions) of * the terminal renderer change. * - * @deprecated Use [virtual processes](#TerminalVirtualProcess) instead. + * @deprecated Use [ExtensionTerminalOptions](#ExtensionTerminalOptions) instead. */ readonly onDidChangeMaximumDimensions: Event; } @@ -918,60 +918,60 @@ declare module 'vscode' { * Create a [TerminalRenderer](#TerminalRenderer). * * @param name The name of the terminal renderer, this shows up in the terminal selector. - * @deprecated Use [virtual processes](#TerminalVirtualProcess) instead. + * @deprecated Use [ExtensionTerminalOptions](#ExtensionTerminalOptions) instead. */ export function createTerminalRenderer(name: string): TerminalRenderer; } //#endregion - //#region Terminal virtual process + //#region Extension terminals export namespace window { /** - * Creates a [Terminal](#Terminal) where an extension acts as the process. + * Creates a [Terminal](#Terminal) where an extension controls the teerminal. * - * @param options A [TerminalVirtualProcessOptions](#TerminalVirtualProcessOptions) object describing the - * characteristics of the new terminal. + * @param options An [ExtensionTerminalOptions](#ExtensionTerminalOptions) object describing + * the characteristics of the new terminal. * @return A new Terminal. */ - export function createTerminal(options: TerminalVirtualProcessOptions): Terminal; + export function createTerminal(options: ExtensionTerminalOptions): Terminal; } /** * Value-object describing what options a virtual process terminal should use. */ - export interface TerminalVirtualProcessOptions { + export interface ExtensionTerminalOptions { /** * A human-readable string which will be used to represent the terminal in the UI. */ name: string; /** - * An implementation of [TerminalVirtualProcess](#TerminalVirtualProcess) that allows an - * extension to act as a terminal's backing process. + * An implementation of [Pseudoterminal](#Pseudoterminal) that allows an extension to + * control a terminal. */ - virtualProcess: TerminalVirtualProcess; + pty: Pseudoterminal; } /** - * Defines the interface of a terminal virtual process, enabling extensions to act as a process - * in the terminal. + * Defines the interface of a terminal pty, enabling extensions to control a terminal. */ - interface TerminalVirtualProcess { + interface Pseudoterminal { /** * An event that when fired will write data to the terminal. Unlike - * [Terminal.sendText](#Terminal.sendText) which sends text to the underlying _process_, - * this will write the text to the terminal itself. + * [Terminal.sendText](#Terminal.sendText) which sends text to the underlying _process_ + * (the pty "slave"), this will write the text to the terminal itself (the pty "master"). * * **Example:** Write red text to the terminal * ```typescript * const writeEmitter = new vscode.EventEmitter(); - * const virtualProcess: TerminalVirtualProcess = { - * onDidWrite: writeEmitter.event + * const pty: vscode.Pseudoterminal = { + * onDidWrite: writeEmitter.event, + * open: () => writeEmitter.fire('\x1b[31mHello world\x1b[0m'), + * close: () => {} * }; - * vscode.window.createTerminal({ name: 'My terminal', virtualProcess }); - * writeEmitter.fire('\x1b[31mHello world\x1b[0m'); + * vscode.window.createTerminal({ name: 'My terminal', pty }); * ``` * * **Example:** Move the cursor to the 10th row and 20th column and write an asterisk @@ -985,58 +985,82 @@ declare module 'vscode' { * An event that when fired allows overriding the [dimensions](#Terminal.dimensions) of the * terminal. Note that when set the overridden dimensions will only take effect when they * are lower than the actual dimensions of the terminal (ie. there will never be a scroll - * bar). Set to `undefined` for the terminal to go back to the regular dimensions. + * bar). Set to `undefined` for the terminal to go back to the regular dimensions (fit to + * the size of the panel). * * **Example:** Override the dimensions of a terminal to 20 columns and 10 rows * ```typescript * const dimensionsEmitter = new vscode.EventEmitter(); - * const virtualProcess: TerminalVirtualProcess = { + * const pty: vscode.Pseudoterminal = { * onDidWrite: writeEmitter.event, - * onDidOverrideDimensions: dimensionsEmitter.event + * onDidOverrideDimensions: dimensionsEmitter.event, + * open: () => { + * dimensionsEmitter.fire({ + * columns: 20, + * rows: 10 + * }); + * }, + * close: () => {} * }; - * vscode.window.createTerminal({ name: 'My terminal', virtualProcess }); - * dimensionsEmitter.fire({ - * columns: 20, - * rows: 10 - * }); + * vscode.window.createTerminal({ name: 'My terminal', pty }); * ``` */ onDidOverrideDimensions?: Event; /** - * An event that when fired will exit the process with an exit code, this will behave the - * same for a virtual process as when a regular process exits with an exit code. Note that - * exit codes must be positive numbers, when negative the exit code will be forced to `1`. + * An event that when fired will signal that the pty is closed and dispose of the terminal. * - * **Example:** Exit with an exit code of `0` if the y key is pressed, otherwise `1`. + * **Example:** Exit the terminal when "y" is pressed, otherwise show a notification. * ```typescript * const writeEmitter = new vscode.EventEmitter(); - * const exitEmitter = new vscode.EventEmitter(); - * const virtualProcess: TerminalVirtualProcess = { + * const closeEmitter = new vscode.EventEmitter(); + * const pty: vscode.Pseudoterminal = { * onDidWrite: writeEmitter.event, - * input: data => exitEmitter.fire(data === 'y' ? 0 : 1) + * onDidClose: closeEmitter.event, + * open: () => writeEmitter.fire('Press y to exit successfully'), + * close: () => {} + * handleInput: { + * if (data !== 'y') { + * vscode.window.showInformationMessage('Something went wrong'); + * } + * data => closeEmitter.fire(); + * } * }; - * vscode.window.createTerminal({ name: 'Exit example', virtualProcess }); - * writeEmitter.fire('Press y to exit successfully'); + * vscode.window.createTerminal({ name: 'Exit example', pty }); */ - onDidExit?: Event; + onDidClose?: Event; /** - * Implement to handle keystrokes in the terminal or when an extension calls - * [Terminal.sendText](#Terminal.sendText). Keystrokes are converted into their - * corresponding VT sequence representation. + * Implement to handle when the pty is open and ready to start firing events. * - * @param data The sent data. + * @param initialDimensions The dimensions of the terminal, this will be undefined if the + * terminal panel has not been opened before this is called. + */ + open(initialDimensions: TerminalDimensions | undefined): void; + + /** + * Implement to handle when the terminal is closed by an act of the user. + */ + close(): void; + + /** + * Implement to handle incoming keystrokes in the terminal or when an extension calls + * [Terminal.sendText](#Terminal.sendText). `data` contains the keystrokes/text serialized into + * their corresponding VT sequence representation. + * + * @param data The incoming data. * * **Example:** Echo input in the terminal. The sequence for enter (`\r`) is translated to * CRLF to go to a new line and move the cursor to the start of the line. * ```typescript * const writeEmitter = new vscode.EventEmitter(); - * const virtualProcess: TerminalVirtualProcess = { + * const pty: vscode.Pseudoterminal = { * onDidWrite: writeEmitter.event, + * open: () => {}, + * close: () => {}, * handleInput: data => writeEmitter.fire(data === '\r' ? '\r\n' : data) * }; - * vscode.window.createTerminal({ name: 'Local echo', virtualProcess }); + * vscode.window.createTerminal({ name: 'Local echo', pty }); * ``` */ handleInput?(data: string): void; @@ -1050,19 +1074,6 @@ declare module 'vscode' { * @param dimensions The new dimensions. */ setDimensions?(dimensions: TerminalDimensions): void; - - /** - * Implement to handle when the terminal shuts down by an act of the user. - */ - shutdown?(): void; - - /** - * Implement to handle when the terminal is ready to start firing events. - * - * @param initialDimensions The dimensions of the terminal, this will be undefined if the - * terminal panel has not been opened before this is called. - */ - start?(initialDimensions: TerminalDimensions | undefined): void; } //#endregion @@ -1145,6 +1156,7 @@ declare module 'vscode' { } //#endregion + //#region CustomExecution /** * Class used to execute an extension callback as a task. */ @@ -1168,16 +1180,17 @@ declare module 'vscode' { */ export class CustomExecution2 { /** - * @param process The [TerminalVirtualProcess](#TerminalVirtualProcess) to be used by the task to display output. + * @param process The [Pseudotrminal](#Pseudoterminal) to be used by the task to display output. * @param callback The callback that will be called when the task is started by a user. */ - constructor(callback: (thisArg?: any) => Thenable); + constructor(callback: (thisArg?: any) => Thenable); /** - * The callback used to execute the task. Cancellation should be handled using the shutdown method of [TerminalVirtualProcess](#TerminalVirtualProcess). - * When the task is complete, onDidExit should be fired on the TerminalVirtualProcess with the exit code with '0' for success and a non-zero value for failure. + * The callback used to execute the task. Cancellation should be handled using + * [Pseudoterminal.close](#Pseudoterminal.close). When the task is complete fire + * [Pseudoterminal.onDidClose](#Pseudoterminal.onDidClose). */ - callback: (thisArg?: any) => Thenable; + callback: (thisArg?: any) => Thenable; } /** @@ -1203,6 +1216,7 @@ declare module 'vscode' { */ execution2?: ProcessExecution | ShellExecution | CustomExecution | CustomExecution2; } + //#endregion //#region Tasks export interface TaskPresentationOptions { @@ -1290,4 +1304,17 @@ declare module 'vscode' { } //#endregion + + //#region Deprecated support + + export enum DiagnosticTag { + /** + * Deprecated or obsolete code. + * + * Diagnostics with this tag are rendered with a strike through. + */ + Deprecated = 2, + } + + //#endregion } diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index 7386b69e611..85039369466 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -5,7 +5,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI as uri } from 'vs/base/common/uri'; -import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, ITerminalSettings, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugSession, IDebugAdapterFactory } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugSession, IDebugAdapterFactory } from 'vs/workbench/contrib/debug/common/debug'; import { ExtHostContext, ExtHostDebugServiceShape, MainThreadDebugServiceShape, DebugSessionUUID, MainContext, IExtHostContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, ISourceBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto @@ -71,8 +71,8 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb return Promise.resolve(this._proxy.$substituteVariables(folder ? folder.uri : undefined, config)); } - runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): Promise { - return Promise.resolve(this._proxy.$runInTerminal(args, config)); + runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): Promise { + return Promise.resolve(this._proxy.$runInTerminal(args)); } // RPC methods (MainThreadDebugServiceShape) diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index 13f4fad4663..f23b625d140 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest, ITerminalDimensions, EXT_HOST_CREATION_DELAY, IAvailableShellsRequest, IDefaultShellAndArgsRequest, ITerminalVirtualProcessRequest } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalProcessExtHostProxy, ISpawnExtHostProcessRequest, ITerminalDimensions, EXT_HOST_CREATION_DELAY, IAvailableShellsRequest, IDefaultShellAndArgsRequest, IStartExtensionTerminalRequest } from 'vs/workbench/contrib/terminal/common/terminal'; import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, ShellLaunchConfigDto, TerminalLaunchConfig, ITerminalDimensionsDto } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { URI } from 'vs/base/common/uri'; @@ -47,8 +47,8 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape this._toDispose.add(_terminalService.onInstanceProcessIdReady(instance => this._onTerminalProcessIdReady(instance))); this._toDispose.add(_terminalService.onInstanceDimensionsChanged(instance => this._onInstanceDimensionsChanged(instance))); this._toDispose.add(_terminalService.onInstanceMaximumDimensionsChanged(instance => this._onInstanceMaximumDimensionsChanged(instance))); - this._toDispose.add(_terminalService.onInstanceRequestExtHostProcess(request => this._onTerminalRequestExtHostProcess(request))); - this._toDispose.add(_terminalService.onInstanceRequestVirtualProcess(e => this._onTerminalRequestVirtualProcess(e))); + this._toDispose.add(_terminalService.onInstanceRequestSpawnExtHostProcess(request => this._onRequestSpawnExtHostProcess(request))); + this._toDispose.add(_terminalService.onInstanceRequestStartExtensionTerminal(e => this._onRequestStartExtensionTerminal(e))); this._toDispose.add(_terminalService.onActiveInstanceChanged(instance => this._onActiveTerminalChanged(instance ? instance.id : null))); this._toDispose.add(_terminalService.onInstanceTitleChanged(instance => this._onTitleChanged(instance.id, instance.title))); this._toDispose.add(_terminalService.configHelper.onWorkspacePermissionsChanged(isAllowed => this._onWorkspacePermissionsChanged(isAllowed))); @@ -90,7 +90,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape env: launchConfig.env, strictEnv: launchConfig.strictEnv, hideFromUser: launchConfig.hideFromUser, - isVirtualProcess: launchConfig.isVirtualProcess + isExtensionTerminal: launchConfig.isExtensionTerminal }; const terminal = this._terminalService.createTerminal(shellLaunchConfig); this._terminalProcesses.set(terminal.id, new Promise(r => this._terminalProcessesReady.set(terminal.id, r))); @@ -240,7 +240,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape this._proxy.$acceptTerminalMaximumDimensions(instance.id, instance.maxCols, instance.maxRows); } - private _onTerminalRequestExtHostProcess(request: ITerminalProcessExtHostRequest): void { + private _onRequestSpawnExtHostProcess(request: ISpawnExtHostProcessRequest): void { // Only allow processes on remote ext hosts if (!this._remoteAuthority) { return; @@ -261,7 +261,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape cwd: request.shellLaunchConfig.cwd, env: request.shellLaunchConfig.env }; - this._proxy.$createProcess(proxy.terminalId, shellLaunchConfigDto, request.activeWorkspaceRootUri, request.cols, request.rows, request.isWorkspaceShellAllowed); + this._proxy.$spawnExtHostProcess(proxy.terminalId, shellLaunchConfigDto, request.activeWorkspaceRootUri, request.cols, request.rows, request.isWorkspaceShellAllowed); proxy.onInput(data => this._proxy.$acceptProcessInput(proxy.terminalId, data)); proxy.onResize(dimensions => this._proxy.$acceptProcessResize(proxy.terminalId, dimensions.cols, dimensions.rows)); proxy.onShutdown(immediate => this._proxy.$acceptProcessShutdown(proxy.terminalId, immediate)); @@ -270,7 +270,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape proxy.onRequestLatency(() => this._onRequestLatency(proxy.terminalId)); } - private _onTerminalRequestVirtualProcess(request: ITerminalVirtualProcessRequest): void { + private _onRequestStartExtensionTerminal(request: IStartExtensionTerminalRequest): void { const proxy = request.proxy; const ready = this._terminalProcessesReady.get(proxy.terminalId); if (!ready) { @@ -286,7 +286,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape columns: request.cols, rows: request.rows } : undefined; - this._proxy.$startVirtualProcess(proxy.terminalId, initialDimensions); + this._proxy.$startExtensionTerminal(proxy.terminalId, initialDimensions); proxy.onInput(data => this._proxy.$acceptProcessInput(proxy.terminalId, data)); proxy.onShutdown(immediate => this._proxy.$acceptProcessShutdown(proxy.terminalId, immediate)); proxy.onRequestCwd(() => this._proxy.$acceptProcessRequestCwd(proxy.terminalId)); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index ad32f5f43f1..e224dcc9c5d 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -40,7 +40,7 @@ import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import * as tasks from 'vs/workbench/api/common/shared/tasks'; import { IRevealOptions, ITreeItem } from 'vs/workbench/common/views'; import * as callHierarchy from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; -import { IAdapterDescriptor, IConfig, ITerminalSettings } from 'vs/workbench/contrib/debug/common/debug'; +import { IAdapterDescriptor, IConfig } from 'vs/workbench/contrib/debug/common/debug'; import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder'; import { ITerminalDimensions, IShellLaunchConfig } from 'vs/workbench/contrib/terminal/common/terminal'; import { ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions'; @@ -390,7 +390,7 @@ export interface TerminalLaunchConfig { waitOnExit?: boolean; strictEnv?: boolean; hideFromUser?: boolean; - isVirtualProcess?: boolean; + isExtensionTerminal?: boolean; } export interface MainThreadTerminalServiceShape extends IDisposable { @@ -1161,8 +1161,8 @@ export interface ExtHostTerminalServiceShape { $acceptTerminalTitleChange(id: number, name: string): void; $acceptTerminalDimensions(id: number, cols: number, rows: number): void; $acceptTerminalMaximumDimensions(id: number, cols: number, rows: number): void; - $createProcess(id: number, shellLaunchConfig: ShellLaunchConfigDto, activeWorkspaceRootUri: UriComponents, cols: number, rows: number, isWorkspaceShellAllowed: boolean): void; - $startVirtualProcess(id: number, initialDimensions: ITerminalDimensionsDto | undefined): void; + $spawnExtHostProcess(id: number, shellLaunchConfig: ShellLaunchConfigDto, activeWorkspaceRootUri: UriComponents, cols: number, rows: number, isWorkspaceShellAllowed: boolean): void; + $startExtensionTerminal(id: number, initialDimensions: ITerminalDimensionsDto | undefined): void; $acceptProcessInput(id: number, data: string): void; $acceptProcessResize(id: number, cols: number, rows: number): void; $acceptProcessShutdown(id: number, immediate: boolean): void; @@ -1245,7 +1245,7 @@ export type IDebugSessionDto = IDebugSessionFullDto | DebugSessionUUID; export interface ExtHostDebugServiceShape { $substituteVariables(folder: UriComponents | undefined, config: IConfig): Promise; - $runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): Promise; + $runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): Promise; $startDASession(handle: number, session: IDebugSessionDto): Promise; $stopDASession(handle: number): Promise; $sendDAMessage(handle: number, message: DebugProtocol.ProtocolMessage): void; diff --git a/src/vs/workbench/api/common/extHostCommands.ts b/src/vs/workbench/api/common/extHostCommands.ts index f2456398c95..19e4d5655f1 100644 --- a/src/vs/workbench/api/common/extHostCommands.ts +++ b/src/vs/workbench/api/common/extHostCommands.ts @@ -50,7 +50,7 @@ export class ExtHostCommands implements ExtHostCommandsShape { onFirstListenerDidAdd: () => this._proxy.$registerCommandListener(), onLastListenerRemove: () => this._proxy.$unregisterCommandListener(), }); - this.onDidExecuteCommand = this._onDidExecuteCommand.event; + this.onDidExecuteCommand = Event.filter(this._onDidExecuteCommand.event, e => e.command[0] !== '_'); // filter 'private' commands this._logService = logService; this._converter = new CommandsConverter(this); this._argumentProcessors = [ @@ -222,7 +222,7 @@ export class CommandsConverter { // --- conversion between internal and api commands constructor(commands: ExtHostCommands) { - this._delegatingCommandId = `_internal_command_delegation_${Date.now()}`; + this._delegatingCommandId = `_vscode_delegate_cmd_${Date.now().toString(36)}`; this._commands = commands; this._commands.registerCommand(true, this._delegatingCommandId, this._executeConvertedCommand, this); } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index de31343f036..39b2cda9188 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -108,6 +108,8 @@ export namespace DiagnosticTag { switch (value) { case types.DiagnosticTag.Unnecessary: return MarkerTag.Unnecessary; + case types.DiagnosticTag.Deprecated: + return MarkerTag.Deprecated; } return undefined; } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index b8aebf92b45..0a0ce14f00e 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -773,6 +773,7 @@ export class SnippetString { export enum DiagnosticTag { Unnecessary = 1, + Deprecated = 2 } export enum DiagnosticSeverity { @@ -1773,19 +1774,19 @@ export class CustomExecution implements vscode.CustomExecution { } export class CustomExecution2 implements vscode.CustomExecution2 { - private _callback: () => Thenable; - constructor(callback: () => Thenable) { + private _callback: () => Thenable; + constructor(callback: () => Thenable) { this._callback = callback; } public computeId(): string { return 'customExecution' + generateUuid(); } - public set callback(value: () => Thenable) { + public set callback(value: () => Thenable) { this._callback = value; } - public get callback(): (() => Thenable) { + public get callback(): (() => Thenable) { return this._callback; } } diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 27fb09b2abd..f4df88c11ba 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -528,10 +528,10 @@ export function createApiFactory( checkProposedApiEnabled(extension); return extHostEditorInsets.createWebviewEditorInset(editor, line, height, options, extension); }, - createTerminal(nameOrOptions?: vscode.TerminalOptions | vscode.TerminalVirtualProcessOptions | string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal { + createTerminal(nameOrOptions?: vscode.TerminalOptions | vscode.ExtensionTerminalOptions | string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal { if (typeof nameOrOptions === 'object') { - if ('virtualProcess' in nameOrOptions) { - return extHostTerminalService.createVirtualProcessTerminal(nameOrOptions); + if ('pty' in nameOrOptions) { + return extHostTerminalService.createExtensionTerminal(nameOrOptions); } else { nameOrOptions.hideFromUser = nameOrOptions.hideFromUser || (nameOrOptions.runInBackground && extension.enableProposedApi); return extHostTerminalService.createTerminalFromOptions(nameOrOptions); diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index e5b2a445ec3..be353ca5bde 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -20,7 +20,7 @@ import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstract import { IExtHostWorkspaceProvider } from 'vs/workbench/api/common/extHostWorkspace'; import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; -import { ITerminalSettings, IDebuggerContribution, IConfig, IDebugAdapter, IDebugAdapterServer, IDebugAdapterExecutable, IAdapterDescriptor } from 'vs/workbench/contrib/debug/common/debug'; +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'; @@ -318,7 +318,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { // RPC methods (ExtHostDebugServiceShape) - public $runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): Promise { + public async $runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): Promise { if (args.kind === 'integrated') { @@ -341,10 +341,22 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { } else { resolve(true); } - }).then(needNewTerminal => { + }).then(async needNewTerminal => { + + const configProvider = await this._configurationService.getConfigProvider(); + const shell = this._terminalService.getDefaultShell(configProvider); if (needNewTerminal || !this._integratedTerminalInstance) { - this._integratedTerminalInstance = this._terminalService.createTerminal(args.title || nls.localize('debug.terminal.title', "debuggee")); + const options: vscode.TerminalOptions = { + shellPath: shell, + // shellArgs: this._terminalService._getDefaultShellArgs(configProvider), + cwd: args.cwd, + name: args.title || nls.localize('debug.terminal.title', "debuggee"), + env: args.env + }; + delete args.cwd; + delete args.env; + this._integratedTerminalInstance = this._terminalService.createTerminalFromOptions(options); } const terminal: vscode.Terminal = this._integratedTerminalInstance; @@ -352,7 +364,8 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { return this._integratedTerminalInstance.processId.then(shellProcessId => { - const command = prepareCommand(args, config); + const command = prepareCommand(args, shell, configProvider); + terminal.sendText(command, true); return shellProcessId; @@ -361,7 +374,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { } else if (args.kind === 'external') { - runInExternalTerminal(args, config); + runInExternalTerminal(args, await this._configurationService.getConfigProvider()); } return Promise.resolve(undefined); } diff --git a/src/vs/workbench/api/node/extHostTask.ts b/src/vs/workbench/api/node/extHostTask.ts index 0e7f5f9dd7a..9191b53aa47 100644 --- a/src/vs/workbench/api/node/extHostTask.ts +++ b/src/vs/workbench/api/node/extHostTask.ts @@ -588,7 +588,7 @@ export class ExtHostTask implements ExtHostTaskShape { // Clone the custom execution to keep the original untouched. This is important for multiple runs of the same task. this._activeCustomExecutions2.set(execution.id, execution2); - await this._terminalService.attachVirtualProcessToTerminal(terminalId, await execution2.callback()); + await this._terminalService.attachPtyToTerminal(terminalId, await execution2.callback()); } // Once a terminal is spun up for the custom execution task this event will be fired. diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index 4aa39f91127..58734d0827d 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -124,8 +124,8 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi this._runQueuedRequests(terminal.id); } - public async createVirtualProcess(): Promise { - const terminal = await this._proxy.$createTerminal({ name: this._name, isVirtualProcess: true }); + public async createExtensionTerminal(): Promise { + const terminal = await this._proxy.$createTerminal({ name: this._name, isExtensionTerminal: true }); this._name = terminal.name; this._runQueuedRequests(terminal.id); } @@ -310,9 +310,9 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { private _logService: ILogService ) { this._proxy = mainContext.getProxy(MainContext.MainThreadTerminalService); - this.updateLastActiveWorkspace(); - this.updateVariableResolver(); - this.registerListeners(); + this._updateLastActiveWorkspace(); + this._updateVariableResolver(); + this._registerListeners(); } public createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal { @@ -329,20 +329,20 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { return terminal; } - public createVirtualProcessTerminal(options: vscode.TerminalVirtualProcessOptions): vscode.Terminal { + public createExtensionTerminal(options: vscode.ExtensionTerminalOptions): vscode.Terminal { const terminal = new ExtHostTerminal(this._proxy, options.name); - const p = new ExtHostVirtualProcess(options.virtualProcess); - terminal.createVirtualProcess().then(() => this._setupExtHostProcessListeners(terminal._id, p)); + const p = new ExtHostPseudoterminal(options.pty); + terminal.createExtensionTerminal().then(() => this._setupExtHostProcessListeners(terminal._id, p)); this._terminals.push(terminal); return terminal; } - public async attachVirtualProcessToTerminal(id: number, virtualProcess: vscode.TerminalVirtualProcess): Promise { + public async attachPtyToTerminal(id: number, pty: vscode.Pseudoterminal): Promise { const terminal = this._getTerminalByIdEventually(id); if (!terminal) { throw new Error(`Cannot resolve terminal with id ${id} for virtual process`); } - const p = new ExtHostVirtualProcess(virtualProcess); + const p = new ExtHostPseudoterminal(pty); this._setupExtHostProcessListeners(id, p); } @@ -532,25 +532,25 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { return env; } - private registerListeners(): void { - this._extHostDocumentsAndEditors.onDidChangeActiveTextEditor(() => this.updateLastActiveWorkspace()); - this._extHostWorkspace.onDidChangeWorkspace(() => this.updateVariableResolver()); + private _registerListeners(): void { + this._extHostDocumentsAndEditors.onDidChangeActiveTextEditor(() => this._updateLastActiveWorkspace()); + this._extHostWorkspace.onDidChangeWorkspace(() => this._updateVariableResolver()); } - private updateLastActiveWorkspace(): void { + private _updateLastActiveWorkspace(): void { const activeEditor = this._extHostDocumentsAndEditors.activeEditor(); if (activeEditor) { this._lastActiveWorkspace = this._extHostWorkspace.getWorkspaceFolder(activeEditor.document.uri) as IWorkspaceFolder; } } - private async updateVariableResolver(): Promise { + private async _updateVariableResolver(): Promise { const configProvider = await this._extHostConfiguration.getConfigProvider(); const workspaceFolders = await this._extHostWorkspace.getWorkspaceFolders2(); this._variableResolver = new ExtHostVariableResolverService(workspaceFolders || [], this._extHostDocumentsAndEditors, configProvider); } - public async $createProcess(id: number, shellLaunchConfigDto: ShellLaunchConfigDto, activeWorkspaceRootUriComponents: UriComponents, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise { + public async $spawnExtHostProcess(id: number, shellLaunchConfigDto: ShellLaunchConfigDto, activeWorkspaceRootUriComponents: UriComponents, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise { const shellLaunchConfig: IShellLaunchConfig = { name: shellLaunchConfigDto.name, executable: shellLaunchConfigDto.executable, @@ -619,9 +619,9 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { this._setupExtHostProcessListeners(id, new TerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, enableConpty, this._logService)); } - public async $startVirtualProcess(id: number, initialDimensions: ITerminalDimensionsDto | undefined): Promise { + public async $startExtensionTerminal(id: number, initialDimensions: ITerminalDimensionsDto | undefined): Promise { // Make sure the ExtHostTerminal exists so onDidOpenTerminal has fired before we call - // TerminalVirtualProcess.start + // Pseudoterminal.start await this._getTerminalByIdEventually(id); // Processes should be initialized here for normal virtual process terminals, however for @@ -630,7 +630,7 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { let retries = 5; while (retries-- > 0) { if (this._terminalProcesses[id]) { - (this._terminalProcesses[id] as ExtHostVirtualProcess).startSendingEvents(initialDimensions); + (this._terminalProcesses[id] as ExtHostPseudoterminal).startSendingEvents(initialDimensions); return; } await timeout(50); @@ -773,7 +773,7 @@ class ApiRequest { } } -class ExtHostVirtualProcess implements ITerminalChildProcess { +class ExtHostPseudoterminal implements ITerminalChildProcess { private _queuedEvents: (IQueuedEvent | IQueuedEvent | IQueuedEvent<{ pid: number, cwd: string }> | IQueuedEvent)[] = []; private _queueDisposables: IDisposable[] | undefined; @@ -789,33 +789,33 @@ class ExtHostVirtualProcess implements ITerminalChildProcess { public get onProcessOverrideDimensions(): Event { return this._onProcessOverrideDimensions.event; } constructor( - private readonly _virtualProcess: vscode.TerminalVirtualProcess + private readonly _pty: vscode.Pseudoterminal ) { this._queueDisposables = []; - this._queueDisposables.push(this._virtualProcess.onDidWrite(e => this._queuedEvents.push({ emitter: this._onProcessData, data: e }))); - if (this._virtualProcess.onDidExit) { - this._queueDisposables.push(this._virtualProcess.onDidExit(e => this._queuedEvents.push({ emitter: this._onProcessExit, data: e }))); + this._queueDisposables.push(this._pty.onDidWrite(e => this._queuedEvents.push({ emitter: this._onProcessData, data: e }))); + if (this._pty.onDidClose) { + this._queueDisposables.push(this._pty.onDidClose(e => this._queuedEvents.push({ emitter: this._onProcessExit, data: 0 }))); } - if (this._virtualProcess.onDidOverrideDimensions) { - this._queueDisposables.push(this._virtualProcess.onDidOverrideDimensions(e => this._queuedEvents.push({ emitter: this._onProcessOverrideDimensions, data: e ? { cols: e.columns, rows: e.rows } : undefined }))); + if (this._pty.onDidOverrideDimensions) { + this._queueDisposables.push(this._pty.onDidOverrideDimensions(e => this._queuedEvents.push({ emitter: this._onProcessOverrideDimensions, data: e ? { cols: e.columns, rows: e.rows } : undefined }))); } } shutdown(): void { - if (this._virtualProcess.shutdown) { - this._virtualProcess.shutdown(); + if (this._pty.close) { + this._pty.close(); } } input(data: string): void { - if (this._virtualProcess.handleInput) { - this._virtualProcess.handleInput(data); + if (this._pty.handleInput) { + this._pty.handleInput(data); } } resize(cols: number, rows: number): void { - if (this._virtualProcess.setDimensions) { - this._virtualProcess.setDimensions({ columns: cols, rows }); + if (this._pty.setDimensions) { + this._pty.setDimensions({ columns: cols, rows }); } } @@ -838,19 +838,16 @@ class ExtHostVirtualProcess implements ITerminalChildProcess { this._queueDisposables = undefined; // Attach the real listeners - this._virtualProcess.onDidWrite(e => this._onProcessData.fire(e)); - if (this._virtualProcess.onDidExit) { - this._virtualProcess.onDidExit(e => { - // Ensure only positive exit codes are returned - this._onProcessExit.fire(e >= 0 ? e : 1); - }); + this._pty.onDidWrite(e => this._onProcessData.fire(e)); + if (this._pty.onDidClose) { + this._pty.onDidClose(e => this._onProcessExit.fire(0)); } - if (this._virtualProcess.onDidOverrideDimensions) { - this._virtualProcess.onDidOverrideDimensions(e => this._onProcessOverrideDimensions.fire(e ? { cols: e.columns, rows: e.rows } : e)); + if (this._pty.onDidOverrideDimensions) { + this._pty.onDidOverrideDimensions(e => this._onProcessOverrideDimensions.fire(e ? { cols: e.columns, rows: e.rows } : e)); } - if (this._virtualProcess.start) { - this._virtualProcess.start(initialDimensions); + if (this._pty.open) { + this._pty.open(initialDimensions); } } } diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index c2e011c5370..7950b273c23 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -5,7 +5,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; -import { EventType, addDisposableListener, addClass, removeClass, isAncestor, getClientArea, position, size, EventHelper } from 'vs/base/browser/dom'; +import { EventType, addDisposableListener, addClass, removeClass, isAncestor, getClientArea, position, size, EventHelper, Dimension } from 'vs/base/browser/dom'; import { onDidChangeFullscreen, isFullscreen, getZoomFactor } from 'vs/base/browser/browser'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -545,6 +545,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return true; // any other part cannot be hidden } + getDimension(part: Parts): Dimension { + return this.getPart(part).dimension; + } + getTitleBarOffset(): number { let offset = 0; if (this.isVisible(Parts.TITLEBAR_PART)) { diff --git a/src/vs/workbench/browser/legacyLayout.ts b/src/vs/workbench/browser/legacyLayout.ts index d936e13f1b2..8800afc09e8 100644 --- a/src/vs/workbench/browser/legacyLayout.ts +++ b/src/vs/workbench/browser/legacyLayout.ts @@ -626,11 +626,11 @@ export class WorkbenchLegacyLayout extends Disposable implements IVerticalSashLa } // Propagate to Part Layouts - this.parts.titlebar.layout(this.workbenchSize.width, this.titlebarHeight, -1); - this.parts.editor.layout(editorSize.width, editorSize.height, -1); - this.parts.sidebar.layout(sidebarSize.width, sidebarSize.height, -1); - this.parts.panel.layout(panelDimension.width, panelDimension.height, -1); - this.parts.activitybar.layout(activityBarSize.width, activityBarSize.height, -1); + this.parts.titlebar.layout(this.workbenchSize.width, this.titlebarHeight); + this.parts.editor.layout(editorSize.width, editorSize.height); + this.parts.sidebar.layout(sidebarSize.width, sidebarSize.height); + this.parts.panel.layout(panelDimension.width, panelDimension.height); + this.parts.activitybar.layout(activityBarSize.width, activityBarSize.height); // Propagate to Context View this.contextViewService.layout(); diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts index f80e4545957..c78ad168cb7 100644 --- a/src/vs/workbench/browser/part.ts +++ b/src/vs/workbench/browser/part.ts @@ -9,10 +9,9 @@ import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; import { Dimension, size } from 'vs/base/browser/dom'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IDimension } from 'vs/platform/layout/browser/layoutService'; -import { ISerializableView, Orientation } from 'vs/base/browser/ui/grid/grid'; +import { ISerializableView, IViewSize } from 'vs/base/browser/ui/grid/grid'; import { Event, Emitter } from 'vs/base/common/event'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { IViewSize } from 'vs/base/browser/ui/grid/gridview'; export interface IPartOptions { hasTitle?: boolean; @@ -29,6 +28,10 @@ export interface ILayoutContentResult { * arranges an optional title and mandatory content area to show content. */ export abstract class Part extends Component implements ISerializableView { + + private _dimension: Dimension; + get dimension(): Dimension { return this._dimension; } + private parent: HTMLElement; private titleArea: HTMLElement | null; private contentArea: HTMLElement | null; @@ -128,7 +131,10 @@ export abstract class Part extends Component implements ISerializableView { abstract minimumHeight: number; abstract maximumHeight: number; - abstract layout(width: number, height: number, orientation: Orientation): void; + layout(width: number, height: number): void { + this._dimension = new Dimension(width, height); + } + abstract toJSON(): object; //#endregion @@ -164,4 +170,4 @@ class PartLayout { return { titleSize, contentSize }; } -} \ No newline at end of file +} diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index d09cf93901b..e5d7cfbe7c8 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -466,6 +466,7 @@ export abstract class CompositePart extends Part { } layout(width: number, height: number): void { + super.layout(width, height); // Layout contents this.contentAreaSize = super.layoutContents(width, height).contentSize; diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 53629222ca3..354ef330025 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -448,7 +448,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands. interface IEditorToolItem { id: string; title: string; iconDark: string; iconLight: string; } -function appendEditorToolItem(primary: IEditorToolItem, when: ContextKeyExpr, order: number, alternative?: IEditorToolItem): void { +function appendEditorToolItem(primary: IEditorToolItem, when: ContextKeyExpr | undefined, order: number, alternative?: IEditorToolItem): void { const item: IMenuItem = { command: { id: primary.id, diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index a42cf0aed2f..14ea050a1b9 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -11,7 +11,7 @@ import { Event, Emitter, Relay } from 'vs/base/common/event'; import { contrastBorder, editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { GroupDirection, IAddGroupOptions, GroupsArrangement, GroupOrientation, IMergeGroupOptions, MergeGroupMode, ICopyEditorOptions, GroupsOrder, GroupChangeKind, GroupLocation, IFindGroupScope, EditorGroupLayout, GroupLayoutArgument, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; -import { Direction, SerializableGrid, Sizing, ISerializedGrid, Orientation, GridBranchNode, isGridBranchNode, GridNode, createSerializedGrid, Grid } from 'vs/base/browser/ui/grid/grid'; +import { IView, orthogonal, LayoutPriority, IViewSize, Direction, SerializableGrid, Sizing, ISerializedGrid, Orientation, GridBranchNode, isGridBranchNode, GridNode, createSerializedGrid, Grid } from 'vs/base/browser/ui/grid/grid'; import { GroupIdentifier, IWorkbenchEditorConfiguration, IEditorPartOptions } from 'vs/workbench/common/editor'; import { values } from 'vs/base/common/map'; import { EDITOR_GROUP_BORDER, EDITOR_PANE_BACKGROUND } from 'vs/workbench/common/theme'; @@ -27,7 +27,6 @@ import { EditorDropTarget } from 'vs/workbench/browser/parts/editor/editorDropTa import { localize } from 'vs/nls'; import { Color } from 'vs/base/common/color'; import { CenteredViewLayout } from 'vs/base/browser/ui/centered/centeredViewLayout'; -import { IView, orthogonal, LayoutPriority, IViewSize } from 'vs/base/browser/ui/grid/gridview'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -112,13 +111,8 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro private _onDidSizeConstraintsChange = this._register(new Relay<{ width: number; height: number; } | undefined>()); get onDidSizeConstraintsChange(): Event<{ width: number; height: number; } | undefined> { return Event.any(this.onDidSetGridWidget.event, this._onDidSizeConstraintsChange.event); } - private readonly _onDidPreferredSizeChange: Emitter = this._register(new Emitter()); - readonly onDidPreferredSizeChange: Event = this._onDidPreferredSizeChange.event; - //#endregion - private _preferredSize: Dimension | undefined; - private readonly workspaceMemento: MementoObject; private readonly globalMemento: MementoObject; @@ -205,8 +199,8 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro //#region IEditorGroupsService - private _dimension: Dimension; - get dimension(): Dimension { return this._dimension; } + private _contentDimension: Dimension; + get contentDimension(): Dimension { return this._contentDimension; } get activeGroup(): IEditorGroupView { return this._activeGroup; @@ -366,9 +360,6 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro const newOrientation = (orientation === GroupOrientation.HORIZONTAL) ? Orientation.HORIZONTAL : Orientation.VERTICAL; if (this.gridWidget.orientation !== newOrientation) { this.gridWidget.orientation = newOrientation; - - // Mark preferred size as changed - this.resetPreferredSize(); } } @@ -418,14 +409,11 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro this.doCreateGridControlWithState(gridDescriptor, activeGroup.id, currentGroupViews); // Layout - this.doLayout(this._dimension); + this.doLayout(this._contentDimension); // Update container this.updateContainer(); - // Mark preferred size as changed - this.resetPreferredSize(); - // Events for groups that got added this.getGroups(GroupsOrder.GRID_APPEARANCE).forEach(groupView => { if (currentGroupViews.indexOf(groupView) === -1) { @@ -490,9 +478,6 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro // Update container this.updateContainer(); - // Mark preferred size as changed - this.resetPreferredSize(); - // Event this._onDidAddGroup.fire(newGroupView); @@ -661,9 +646,6 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro // Update container this.updateContainer(); - // Mark preferred size as changed - this.resetPreferredSize(); - // Event this._onDidRemoveGroup.fire(groupView); } @@ -764,23 +746,6 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro get onDidChange(): Event { return this.centeredLayoutWidget.onDidChange; } readonly priority: LayoutPriority = LayoutPriority.High; - get preferredSize(): Dimension { - if (!this._preferredSize) { - this._preferredSize = new Dimension(this.gridWidget.minimumWidth, this.gridWidget.minimumHeight); - } - - return this._preferredSize; - } - - private resetPreferredSize(): void { - - // Reset (will be computed upon next access) - this._preferredSize = undefined; - - // Event - this._onDidPreferredSizeChange.fire(); - } - private get gridSeparatorBorder(): Color { return this.theme.getColor(EDITOR_GROUP_BORDER) || this.theme.getColor(contrastBorder) || Color.transparent; } @@ -968,10 +933,10 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro } private doLayout(dimension: Dimension): void { - this._dimension = dimension; + this._contentDimension = dimension; // Layout Grid - this.centeredLayoutWidget.layout(this._dimension.width, this._dimension.height); + this.centeredLayoutWidget.layout(this._contentDimension.width, this._contentDimension.height); // Event this._onDidLayout.fire(dimension); @@ -1028,4 +993,4 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro } } -registerSingleton(IEditorGroupsService, EditorPart); \ No newline at end of file +registerSingleton(IEditorGroupsService, EditorPart); diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 874d81baddb..1189df45c45 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -59,6 +59,16 @@ export class PanelPart extends CompositePart implements IPanelService { readonly snap = true; + get preferredHeight(): number | undefined { + const sidebarDimension = this.layoutService.getDimension(Parts.SIDEBAR_PART); + return sidebarDimension.height * 0.4; + } + + get preferredWidth(): number | undefined { + const statusbarPart = this.layoutService.getDimension(Parts.STATUSBAR_PART); + return statusbarPart.width * 0.4; + } + //#endregion get onDidPanelOpen(): Event<{ panel: IPanel, focus: boolean }> { return Event.map(this.onDidCompositeOpen.event, compositeOpen => ({ panel: compositeOpen.composite, focus: compositeOpen.focus })); } @@ -74,7 +84,7 @@ export class PanelPart extends CompositePart implements IPanelService { private compositeActions: Map = new Map(); private blockOpeningPanel: boolean; - private dimension: Dimension; + private _contentDimension: Dimension; constructor( @INotificationService notificationService: INotificationService, @@ -293,21 +303,21 @@ export class PanelPart extends CompositePart implements IPanelService { } if (this.layoutService.getPanelPosition() === Position.RIGHT) { - this.dimension = new Dimension(width - 1, height!); // Take into account the 1px border when layouting + this._contentDimension = new Dimension(width - 1, height!); // Take into account the 1px border when layouting } else { - this.dimension = new Dimension(width, height!); + this._contentDimension = new Dimension(width, height!); } // Layout contents - super.layout(this.dimension.width, this.dimension.height); + super.layout(this._contentDimension.width, this._contentDimension.height); // Layout composite bar this.layoutCompositeBar(); } private layoutCompositeBar(): void { - if (this.dimension) { - let availableWidth = this.dimension.width - 40; // take padding into account + if (this._contentDimension) { + let availableWidth = this._contentDimension.width - 40; // take padding into account if (this.toolBar) { availableWidth = Math.max(PanelPart.MIN_COMPOSITE_BAR_WIDTH, availableWidth - this.getToolbarWidth()); // adjust height for global actions showing } @@ -522,4 +532,4 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { } }); -registerSingleton(IPanelService, PanelPart); \ No newline at end of file +registerSingleton(IPanelService, PanelPart); diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index 88c40e827ae..8b50a07b662 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -47,6 +47,22 @@ export class SidebarPart extends CompositePart implements IViewletServi readonly snap = true; + get preferredWidth(): number | undefined { + const viewlet = this.getActiveViewlet(); + + if (!viewlet) { + return; + } + + const width = viewlet.getOptimalWidth(); + + if (typeof width !== 'number') { + return; + } + + return width; + } + //#endregion get onDidViewletRegister(): Event { return >this.viewletRegistry.onDidRegister; } @@ -303,4 +319,4 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(FocusSideBarAction, Fo primary: KeyMod.CtrlCmd | KeyCode.KEY_0 }), 'View: Focus into Side Bar', nls.localize('viewCategory', "View")); -registerSingleton(IViewletService, SidebarPart); \ No newline at end of file +registerSingleton(IViewletService, SidebarPart); diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 00fe6c45bc4..089cbd3003e 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -611,6 +611,7 @@ export class StatusbarPart extends Part implements IStatusbarService { } layout(width: number, height: number): void { + super.layout(width, height); super.layoutContents(width, height); } @@ -816,4 +817,4 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { } }); -registerSingleton(IStatusbarService, StatusbarPart); \ No newline at end of file +registerSingleton(IStatusbarService, StatusbarPart); diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index d3ac220871e..e8d7d850123 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -28,7 +28,7 @@ import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { ToggleReactionsAction, ReactionAction, ReactionActionViewItem } from './reactionsAction'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/commentThreadWidget'; -import { MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { MenuItemAction, SubmenuItemAction, IMenu } from 'vs/platform/actions/common/actions'; import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -54,7 +54,7 @@ export class CommentNode extends Disposable { private _commentContextValue: IContextKey; protected actionRunner?: IActionRunner; - protected toolbar: ToolBar; + protected toolbar: ToolBar | undefined; private _commentFormActions: CommentFormActions; private _onDidDelete = new Emitter(); @@ -134,14 +134,54 @@ export class CommentNode extends Disposable { this.createActionsToolbar(); } + private getToolbarActions(menu: IMenu): { primary: IAction[], secondary: IAction[] } { + const contributedActions = menu.getActions({ shouldForwardArgs: true }); + const primary: IAction[] = []; + const secondary: IAction[] = []; + const result = { primary, secondary }; + fillInActions(contributedActions, result, false, g => /^inline/.test(g)); + return result; + } + + private createToolbar() { + this.toolbar = new ToolBar(this._actionsToolbarContainer, this.contextMenuService, { + actionViewItemProvider: action => { + if (action.id === ToggleReactionsAction.ID) { + return new DropdownMenuActionViewItem( + action, + (action).menuActions, + this.contextMenuService, + action => { + return this.actionViewItemProvider(action as Action); + }, + this.actionRunner!, + undefined, + 'toolbar-toggle-pickReactions', + () => { return AnchorAlignment.RIGHT; } + ); + } + return this.actionViewItemProvider(action as Action); + }, + orientation: ActionsOrientation.HORIZONTAL + }); + + this.toolbar.context = { + thread: this.commentThread, + commentUniqueId: this.comment.uniqueIdInThread, + $mid: 9 + }; + + this.registerActionBarListeners(this._actionsToolbarContainer); + this._register(this.toolbar); + } + private createActionsToolbar() { const actions: IAction[] = []; - const secondaryActions: IAction[] = []; let hasReactionHandler = this.commentService.hasReactionHandler(this.owner); if (hasReactionHandler) { - let toggleReactionAction = this.createReactionPicker2(this.comment.commentReactions || []); + let toggleReactionAction = this.createReactionPicker(this.comment.commentReactions || []); actions.push(toggleReactionAction); } @@ -149,60 +189,26 @@ export class CommentNode extends Disposable { const menu = commentMenus.getCommentTitleActions(this.comment, this._contextKeyService); this._register(menu); this._register(menu.onDidChange(e => { - const primary: IAction[] = []; - const secondary: IAction[] = []; - const result = { primary, secondary }; - fillInActions(contributedActions, result, false, g => /^inline/.test(g)); - this.toolbar.setActions(primary, secondary); + const { primary, secondary } = this.getToolbarActions(menu); + if (!this.toolbar && (primary.length || secondary.length)) { + this.createToolbar(); + } + + this.toolbar!.setActions(primary, secondary)(); })); - const contributedActions = menu.getActions({ shouldForwardArgs: true }); - { - const primary: IAction[] = []; - const secondary: IAction[] = []; - const result = { primary, secondary }; - fillInActions(contributedActions, result, false, g => /^inline/.test(g)); - actions.push(...primary); - secondaryActions.push(...secondary); - } + const { primary, secondary } = this.getToolbarActions(menu); + actions.push(...primary); - if (actions.length || secondaryActions.length) { - this.toolbar = new ToolBar(this._actionsToolbarContainer, this.contextMenuService, { - actionViewItemProvider: action => { - if (action.id === ToggleReactionsAction.ID) { - return new DropdownMenuActionViewItem( - action, - (action).menuActions, - this.contextMenuService, - action => { - return this.actionViewItemProvider(action as Action); - }, - this.actionRunner!, - undefined, - 'toolbar-toggle-pickReactions', - () => { return AnchorAlignment.RIGHT; } - ); - } - return this.actionViewItemProvider(action as Action); - }, - orientation: ActionsOrientation.HORIZONTAL - }); - - this.toolbar.context = { - thread: this.commentThread, - commentUniqueId: this.comment.uniqueIdInThread, - $mid: 9 - }; - - this.registerActionBarListeners(this._actionsToolbarContainer); - this.toolbar.setActions(actions, secondaryActions)(); - this._register(this.toolbar); + if (actions.length || secondary.length) { + this.createToolbar(); + this.toolbar!.setActions(actions, secondary)(); } } actionViewItemProvider(action: Action) { let options = {}; - if (action.id === 'comment.delete' || action.id === 'comment.edit' || action.id === ToggleReactionsAction.ID) { + if (action.id === ToggleReactionsAction.ID) { options = { label: false, icon: true }; } else { options = { label: false, icon: true }; @@ -220,7 +226,7 @@ export class CommentNode extends Disposable { } } - private createReactionPicker2(reactionGroup: modes.CommentReaction[]): ToggleReactionsAction { + private createReactionPicker(reactionGroup: modes.CommentReaction[]): ToggleReactionsAction { let toggleReactionActionViewItem: DropdownMenuActionViewItem; let toggleReactionAction = this._register(new ToggleReactionsAction(() => { if (toggleReactionActionViewItem) { @@ -315,14 +321,8 @@ export class CommentNode extends Disposable { }); if (hasReactionHandler) { - let toggleReactionAction = this.createReactionPicker2(this.comment.commentReactions || []); + let toggleReactionAction = this.createReactionPicker(this.comment.commentReactions || []); this._reactionsActionBar.push(toggleReactionAction, { label: false, icon: true }); - } else { - // let reactionGroup = this.commentService.getReactionGroup(this.owner); - // if (reactionGroup && reactionGroup.length) { - // let toggleReactionAction = this.createReactionPicker2(reactionGroup || []); - // this._reactionsActionBar.push(toggleReactionAction, { label: false, icon: true }); - // } } } @@ -520,4 +520,4 @@ function fillInActions(groups: [string, Array local.dispose()); this._onDidClose.fire(undefined); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/comments/browser/media/review.css b/src/vs/workbench/contrib/comments/browser/media/review.css index 767e4c97057..47f8feb5fc9 100644 --- a/src/vs/workbench/contrib/comments/browser/media/review.css +++ b/src/vs/workbench/contrib/comments/browser/media/review.css @@ -400,7 +400,7 @@ .monaco-editor .review-widget .action-item { min-width: 18px; - min-height: 18px; + min-height: 20px; margin-left: 4px; } diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 84afaa1d2f1..9a1431da67e 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -534,7 +534,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { // Touch Bar if (isMacintosh) { - const registerTouchBarEntry = (id: string, title: string, order: number, when: ContextKeyExpr, icon: string) => { + const registerTouchBarEntry = (id: string, title: string, order: number, when: ContextKeyExpr | undefined, icon: string) => { MenuRegistry.appendMenuItem(MenuId.TouchBarContext, { command: { id, diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index 82be95aef9b..df8a68cbc67 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -21,7 +21,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterDescriptorFactory, IDebugAdapter, ITerminalSettings, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory, IDebugService } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterDescriptorFactory, IDebugAdapter, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory, IDebugService } from 'vs/workbench/contrib/debug/common/debug'; import { Debugger } from 'vs/workbench/contrib/debug/common/debugger'; import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -108,10 +108,10 @@ export class ConfigurationManager implements IConfigurationManager { return Promise.resolve(config); } - runInTerminal(debugType: string, args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): Promise { + runInTerminal(debugType: string, args: DebugProtocol.RunInTerminalRequestArguments): Promise { let tl = this.debugAdapterFactories.get(debugType); if (tl) { - return tl.runInTerminal(args, config); + return tl.runInTerminal(args); } return Promise.resolve(void 0); } diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index b67785c3d98..a0f2120b920 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -587,7 +587,13 @@ export class RawDebugSession { } } else { - args._.push(a2); + const match = /^--(.+)$/.exec(a2); + if (match && match.length === 2) { + const key = match[1]; + (args)[key] = true; + } else { + args._.push(a2); + } } } } diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 363f6eeb346..1ac7b96593d 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -24,9 +24,7 @@ import { IViewContainersRegistry, ViewContainer, Extensions as ViewContainerExte import { Registry } from 'vs/platform/registry/common/platform'; import { TaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks'; import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; -import { ITerminalConfiguration } from 'vs/workbench/contrib/terminal/common/terminal'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IExternalTerminalSettings } from 'vs/workbench/contrib/externalTerminal/common/externalTerminal'; export const VIEWLET_ID = 'workbench.view.debug'; export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID); @@ -573,12 +571,7 @@ export interface IDebugAdapterTrackerFactory { } export interface ITerminalLauncher { - runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): Promise; -} - -export interface ITerminalSettings { - external: IExternalTerminalSettings; - integrated: ITerminalConfiguration; + runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): Promise; } export interface IConfigurationManager { @@ -623,7 +616,7 @@ export interface IConfigurationManager { createDebugAdapter(session: IDebugSession): IDebugAdapter | undefined; substituteVariables(debugType: string, folder: IWorkspaceFolder | undefined, config: IConfig): Promise; - runInTerminal(debugType: string, args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): Promise; + runInTerminal(debugType: string, args: DebugProtocol.RunInTerminalRequestArguments): Promise; } export interface ILaunch { diff --git a/src/vs/workbench/contrib/debug/common/debugger.ts b/src/vs/workbench/contrib/debug/common/debugger.ts index 4034a6c9596..8f317ccc138 100644 --- a/src/vs/workbench/contrib/debug/common/debugger.ts +++ b/src/vs/workbench/contrib/debug/common/debugger.ts @@ -9,7 +9,7 @@ import * as objects from 'vs/base/common/objects'; import { isObject } from 'vs/base/common/types'; import { IJSONSchema, IJSONSchemaSnippet } from 'vs/base/common/jsonSchema'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { IConfig, IDebuggerContribution, INTERNAL_CONSOLE_OPTIONS_SCHEMA, IConfigurationManager, IDebugAdapter, ITerminalSettings, IDebugger, IDebugSession, IDebugHelperService } from 'vs/workbench/contrib/debug/common/debug'; +import { IConfig, IDebuggerContribution, INTERNAL_CONSOLE_OPTIONS_SCHEMA, IConfigurationManager, IDebugAdapter, IDebugger, IDebugSession, IDebugHelperService } from 'vs/workbench/contrib/debug/common/debug'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import * as ConfigurationResolverUtils from 'vs/workbench/services/configurationResolver/common/configurationResolverUtils'; @@ -108,8 +108,7 @@ export class Debugger implements IDebugger { } runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): Promise { - const config = this.configurationService.getValue('terminal'); - return this.configurationManager.runInTerminal(this.type, args, config); + return this.configurationManager.runInTerminal(this.type, args); } get label(): string { diff --git a/src/vs/workbench/contrib/debug/node/terminals.ts b/src/vs/workbench/contrib/debug/node/terminals.ts index 6f173a4864f..7813de75b26 100644 --- a/src/vs/workbench/contrib/debug/node/terminals.ts +++ b/src/vs/workbench/contrib/debug/node/terminals.ts @@ -5,15 +5,15 @@ import * as cp from 'child_process'; import * as env from 'vs/base/common/platform'; -import { ITerminalSettings } from 'vs/workbench/contrib/debug/common/debug'; import { getSystemShell } from 'vs/workbench/contrib/terminal/node/terminal'; import { WindowsExternalTerminalService, MacExternalTerminalService, LinuxExternalTerminalService } from 'vs/workbench/contrib/externalTerminal/node/externalTerminalService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IExternalTerminalService } from 'vs/workbench/contrib/externalTerminal/common/externalTerminal'; +import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; let externalTerminalService: IExternalTerminalService | undefined = undefined; -export function runInExternalTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): void { +export function runInExternalTerminal(args: DebugProtocol.RunInTerminalRequestArguments, configProvider: ExtHostConfigProvider): void { if (!externalTerminalService) { if (env.isWindows) { externalTerminalService = new WindowsExternalTerminalService(undefined); @@ -24,6 +24,7 @@ export function runInExternalTerminal(args: DebugProtocol.RunInTerminalRequestAr } } if (externalTerminalService) { + const config = configProvider.getConfiguration('terminal'); externalTerminalService.runInTerminal(args.title!, args.cwd, args.args, args.env || {}, config.external || {}); } } @@ -60,24 +61,25 @@ export function hasChildProcesses(processId: number): boolean { const enum ShellType { cmd, powershell, bash } -export function prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): string { +export function prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments, shell: string, configProvider: ExtHostConfigProvider): string { - let shellType: ShellType; + let shellType = env.isWindows ? ShellType.cmd : ShellType.bash; // pick a good default - // get the shell configuration for the current platform - let shell: string; - const shell_config = config.integrated.shell; - if (env.isWindows) { - shell = shell_config.windows || getSystemShell(env.Platform.Windows); - shellType = ShellType.cmd; - } else if (env.isLinux) { - shell = shell_config.linux || getSystemShell(env.Platform.Linux); - shellType = ShellType.bash; - } else if (env.isMacintosh) { - shell = shell_config.osx || getSystemShell(env.Platform.Mac); - shellType = ShellType.bash; - } else { - throw new Error('Unknown platform'); + if (shell) { + + const config = configProvider.getConfiguration('terminal'); + + // get the shell configuration for the current platform + const shell_config = config.integrated.shell; + if (env.isWindows) { + shell = shell_config.windows || getSystemShell(env.Platform.Windows); + } else if (env.isLinux) { + shell = shell_config.linux || getSystemShell(env.Platform.Linux); + } else if (env.isMacintosh) { + shell = shell_config.osx || getSystemShell(env.Platform.Mac); + } else { + throw new Error('Unknown platform'); + } } // try to determine the shell type diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 39b348072b9..32bd703fba0 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -188,6 +188,11 @@ Registry.as(ConfigurationExtensions.Configuration) type: 'boolean', description: localize('extensionsCloseExtensionDetailsOnViewChange', "When enabled, editors with extension details will be automatically closed upon navigating away from the Extensions View."), default: false + }, + 'extensions.confirmedUriHandlerExtensionIds': { + type: 'array', + description: localize('handleUriConfirmedExtensions', "When an extension is listed here, a confirmation prompt will not be shown when that extension handles a URI."), + default: [] } } }); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index df4be0957a3..fdb0056d228 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -1694,7 +1694,7 @@ export class InstallRecommendedExtensionAction extends Action { return this.viewletService.openViewlet(VIEWLET_ID, true) .then(viewlet => viewlet as IExtensionsViewlet) .then(viewlet => { - viewlet.search('@recommended '); + viewlet.search(`@id:${this.extensionId}`); viewlet.focus(); return this.extensionWorkbenchService.queryGallery({ names: [this.extensionId], source: 'install-recommendation', pageSize: 1 }, CancellationToken.None) .then(pager => { diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts index f4af7a1c9b6..b64e6d66977 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { join } from 'vs/base/common/path'; +import { join, basename } from 'vs/base/common/path'; import { forEach } from 'vs/base/common/collections'; import { Disposable } from 'vs/base/common/lifecycle'; import { match } from 'vs/base/common/glob'; @@ -43,6 +43,8 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { extname } from 'vs/base/common/resources'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IExeBasedExtensionTip } from 'vs/platform/product/common/product'; +import { timeout } from 'vs/base/common/async'; const milliSecondsInADay = 1000 * 60 * 60 * 24; const choiceNever = localize('neverShowAgain', "Don't Show Again"); @@ -71,7 +73,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe _serviceBrand: any; private _fileBasedRecommendations: { [id: string]: { recommendedTime: number, sources: ExtensionRecommendationSource[] }; } = Object.create(null); - private _exeBasedRecommendations: { [id: string]: string; } = Object.create(null); + private _exeBasedRecommendations: { [id: string]: IExeBasedExtensionTip; } = Object.create(null); + private _importantExeBasedRecommendations: { [id: string]: IExeBasedExtensionTip; } = Object.create(null); private _availableRecommendations: { [pattern: string]: string[] } = Object.create(null); private _allWorkspaceRecommendedExtensions: IExtensionRecommendation[] = []; private _dynamicWorkspaceRecommendations: string[] = []; @@ -187,7 +190,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe forEach(this._exeBasedRecommendations, entry => output[entry.key.toLowerCase()] = { reasonId: ExtensionRecommendationReason.Executable, - reasonText: localize('exeBasedRecommendation', "This extension is recommended because you have {0} installed.", entry.value) + reasonText: localize('exeBasedRecommendation', "This extension is recommended because you have {0} installed.", entry.value.friendlyName) }); forEach(this._fileBasedRecommendations, entry => output[entry.key.toLowerCase()] = { @@ -496,6 +499,100 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe //#endregion + //#region important exe based extension + + private async promptForImportantExeBasedExtension(): Promise { + + const storageKey = 'extensionsAssistant/workspaceRecommendationsIgnore'; + const config = this.configurationService.getValue(ConfigurationKey); + + if (config.ignoreRecommendations + || config.showRecommendationsOnlyOnDemand + || this.storageService.getBoolean(storageKey, StorageScope.WORKSPACE, false)) { + return false; + } + + const installed = await this.extensionManagementService.getInstalled(ExtensionType.User); + let recommendationsToSuggest = Object.keys(this._importantExeBasedRecommendations); + recommendationsToSuggest = this.filterAllIgnoredInstalledAndNotAllowed(recommendationsToSuggest, installed); + if (recommendationsToSuggest.length === 0) { + return false; + } + + const id = recommendationsToSuggest[0]; + const tip = this._importantExeBasedRecommendations[id]; + const message = localize('exeRecommended', "The '{0}' extension is recommended as you have {1} installed on your system.", tip.friendlyName!, tip.exeFriendlyName || basename(tip.windowsPath!)); + + this.notificationService.prompt(Severity.Info, message, + [{ + label: localize('install', 'Install'), + run: () => { + /* __GDPR__ + "exeExtensionRecommendations:popup" : { + "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('exeExtensionRecommendations:popup', { userReaction: 'install', extensionId: name }); + this.instantiationService.createInstance(InstallRecommendedExtensionAction, id).run(); + } + }, { + label: localize('showRecommendations', "Show Recommendations"), + run: () => { + /* __GDPR__ + "exeExtensionRecommendations:popup" : { + "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('exeExtensionRecommendations:popup', { userReaction: 'show', extensionId: name }); + + const recommendationsAction = this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, localize('showRecommendations', "Show Recommendations")); + recommendationsAction.run(); + recommendationsAction.dispose(); + } + }, { + label: choiceNever, + isSecondary: true, + run: () => { + this.addToImportantRecommendationsIgnore(id); + /* __GDPR__ + "exeExtensionRecommendations:popup" : { + "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('exeExtensionRecommendations:popup', { userReaction: 'neverShowAgain', extensionId: name }); + this.notificationService.prompt( + Severity.Info, + localize('ignoreExtensionRecommendations', "Do you want to ignore all extension recommendations?"), + [{ + label: localize('ignoreAll', "Yes, Ignore All"), + run: () => this.setIgnoreRecommendationsConfig(true) + }, { + label: localize('no', "No"), + run: () => this.setIgnoreRecommendationsConfig(false) + }] + ); + } + }], + { + sticky: true, + onCancel: () => { + /* __GDPR__ + "exeExtensionRecommendations:popup" : { + "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('exeExtensionRecommendations:popup', { userReaction: 'cancelled', extensionId: name }); + } + } + ); + + return true; + } + //#region fileBasedRecommendations getFileBasedRecommendations(): IExtensionRecommendation[] { @@ -597,7 +694,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe const now = Date.now(); forEach(this._availableRecommendations, entry => { let { key: pattern, value: ids } = entry; - if (match(pattern, model.uri.path)) { + if (match(pattern, model.uri.toString())) { for (let id of ids) { if (caseInsensitiveGet(product.extensionImportantTips, id)) { recommendationsToSuggest.push(id); @@ -646,21 +743,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } private async promptRecommendedExtensionForFileType(recommendationsToSuggest: string[], installed: ILocalExtension[]): Promise { - const importantRecommendationsIgnoreList = JSON.parse(this.storageService.get('extensionsAssistant/importantRecommendationsIgnore', StorageScope.GLOBAL, '[]')); - const installedExtensionsIds = installed.reduce((result, i) => { result.add(i.identifier.id.toLowerCase()); return result; }, new Set()); - recommendationsToSuggest = recommendationsToSuggest.filter(id => { - if (importantRecommendationsIgnoreList.indexOf(id) !== -1) { - return false; - } - if (!this.isExtensionAllowedToBeRecommended(id)) { - return false; - } - if (installedExtensionsIds.has(id.toLowerCase())) { - return false; - } - return true; - }); + recommendationsToSuggest = this.filterAllIgnoredInstalledAndNotAllowed(recommendationsToSuggest, installed); if (recommendationsToSuggest.length === 0) { return false; } @@ -670,22 +754,12 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe if (!entry) { return false; } - const name = entry['name']; - + const name = entry.name; let message = localize('reallyRecommended2', "The '{0}' extension is recommended for this file type.", name); - // Temporary fix for the only extension pack we recommend. See https://github.com/Microsoft/vscode/issues/35364 - if (id === 'vscjava.vscode-java-pack') { + if (entry.isExtensionPack) { message = localize('reallyRecommendedExtensionPack', "The '{0}' extension pack is recommended for this file type.", name); } - const setIgnoreRecommendationsConfig = (configVal: boolean) => { - this.configurationService.updateValue('extensions.ignoreRecommendations', configVal, ConfigurationTarget.USER); - if (configVal) { - const ignoreWorkspaceRecommendationsStorageKey = 'extensionsAssistant/workspaceRecommendationsIgnore'; - this.storageService.store(ignoreWorkspaceRecommendationsStorageKey, true, StorageScope.WORKSPACE); - } - }; - this.notificationService.prompt(Severity.Info, message, [{ label: localize('install', 'Install'), @@ -718,12 +792,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe label: choiceNever, isSecondary: true, run: () => { - importantRecommendationsIgnoreList.push(id); - this.storageService.store( - 'extensionsAssistant/importantRecommendationsIgnore', - JSON.stringify(importantRecommendationsIgnoreList), - StorageScope.GLOBAL - ); + this.addToImportantRecommendationsIgnore(id); /* __GDPR__ "extensionRecommendations:popup" : { "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, @@ -736,10 +805,10 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe localize('ignoreExtensionRecommendations', "Do you want to ignore all extension recommendations?"), [{ label: localize('ignoreAll', "Yes, Ignore All"), - run: () => setIgnoreRecommendationsConfig(true) + run: () => this.setIgnoreRecommendationsConfig(true) }, { label: localize('no', "No"), - run: () => setIgnoreRecommendationsConfig(false) + run: () => this.setIgnoreRecommendationsConfig(false) }] ); } @@ -831,6 +900,42 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe ); } + private filterAllIgnoredInstalledAndNotAllowed(recommendationsToSuggest: string[], installed: ILocalExtension[]): string[] { + + const importantRecommendationsIgnoreList = JSON.parse(this.storageService.get('extensionsAssistant/importantRecommendationsIgnore', StorageScope.GLOBAL, '[]')); + const installedExtensionsIds = installed.reduce((result, i) => { result.add(i.identifier.id.toLowerCase()); return result; }, new Set()); + return recommendationsToSuggest.filter(id => { + if (importantRecommendationsIgnoreList.indexOf(id) !== -1) { + return false; + } + if (!this.isExtensionAllowedToBeRecommended(id)) { + return false; + } + if (installedExtensionsIds.has(id.toLowerCase())) { + return false; + } + return true; + }); + } + + private addToImportantRecommendationsIgnore(id: string) { + const importantRecommendationsIgnoreList = JSON.parse(this.storageService.get('extensionsAssistant/importantRecommendationsIgnore', StorageScope.GLOBAL, '[]')); + importantRecommendationsIgnoreList.push(id); + this.storageService.store( + 'extensionsAssistant/importantRecommendationsIgnore', + JSON.stringify(importantRecommendationsIgnoreList), + StorageScope.GLOBAL + ); + } + + private setIgnoreRecommendationsConfig(configVal: boolean) { + this.configurationService.updateValue('extensions.ignoreRecommendations', configVal, ConfigurationTarget.USER); + if (configVal) { + const ignoreWorkspaceRecommendationsStorageKey = 'extensionsAssistant/workspaceRecommendationsIgnore'; + this.storageService.store(ignoreWorkspaceRecommendationsStorageKey, true, StorageScope.WORKSPACE); + } + } + //#endregion //#region otherRecommendations @@ -857,18 +962,18 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } private fetchProactiveRecommendations(calledDuringStartup?: boolean): Promise { - let fetchPromise = Promise.resolve(undefined); + let fetchPromise = Promise.resolve(undefined); if (!this.proactiveRecommendationsFetched) { this.proactiveRecommendationsFetched = true; - // Executable based recommendations carry out a lot of file stats, so run them after 10 secs - // So that the startup is not affected + // Executable based recommendations carry out a lot of file stats, delay the resolution so that the startup is not affected + // 10 sec for regular extensions + // 3 secs for important - fetchPromise = new Promise((c, e) => { - setTimeout(() => { - Promise.all([this.fetchExecutableRecommendations(), this.fetchDynamicWorkspaceRecommendations()]).then(() => c(undefined)); - }, calledDuringStartup ? 10000 : 0); - }); + const importantExeBasedRecommendations = timeout(calledDuringStartup ? 3000 : 0).then(_ => this.fetchExecutableRecommendations(true)); + importantExeBasedRecommendations.then(_ => this.promptForImportantExeBasedExtension()); + + fetchPromise = timeout(calledDuringStartup ? 10000 : 0).then(_ => Promise.all([this.fetchDynamicWorkspaceRecommendations(), this.fetchExecutableRecommendations(false), importantExeBasedRecommendations])); } return fetchPromise; @@ -877,20 +982,22 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe /** * If user has any of the tools listed in product.exeBasedExtensionTips, fetch corresponding recommendations */ - private fetchExecutableRecommendations(): Promise { + private fetchExecutableRecommendations(important: boolean): Promise { const homeDir = os.homedir(); let foundExecutables: Set = new Set(); - let findExecutable = (exeName: string, path: string) => { + let findExecutable = (exeName: string, tip: IExeBasedExtensionTip, path: string) => { return pfs.fileExists(path).then(exists => { if (exists && !foundExecutables.has(exeName)) { foundExecutables.add(exeName); - (product.exeBasedExtensionTips[exeName]['recommendations'] || []) - .forEach(extensionId => { - if (product.exeBasedExtensionTips[exeName]['friendlyName']) { - this._exeBasedRecommendations[extensionId.toLowerCase()] = product.exeBasedExtensionTips[exeName]['friendlyName']; + (tip['recommendations'] || []).forEach(extensionId => { + if (tip.friendlyName) { + if (important) { + this._importantExeBasedRecommendations[extensionId.toLowerCase()] = tip; } - }); + this._exeBasedRecommendations[extensionId.toLowerCase()] = tip; + } + }); } }); }; @@ -901,8 +1008,10 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe if (typeof entry.value !== 'object' || !Array.isArray(entry.value['recommendations'])) { return; } - - let exeName = entry.key; + if (important !== !!entry.value.important) { + return; + } + const exeName = entry.key; if (process.platform === 'win32') { let windowsPath = entry.value['windowsPath']; if (!windowsPath || typeof windowsPath !== 'string') { @@ -913,10 +1022,10 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe .replace('%ProgramFiles%', process.env['ProgramFiles']!) .replace('%APPDATA%', process.env['APPDATA']!) .replace('%WINDIR%', process.env['WINDIR']!); - promises.push(findExecutable(exeName, windowsPath)); + promises.push(findExecutable(exeName, entry.value, windowsPath)); } else { - promises.push(findExecutable(exeName, join('/usr/local/bin', exeName))); - promises.push(findExecutable(exeName, join(homeDir, exeName))); + promises.push(findExecutable(exeName, entry.value, join('/usr/local/bin', exeName))); + promises.push(findExecutable(exeName, entry.value, join(homeDir, exeName))); } }); diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index 044c0db7733..8b29304e715 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -203,7 +203,7 @@ appendEditorTitleContextMenuItem(REVEAL_IN_OS_COMMAND_ID, REVEAL_IN_OS_LABEL, Re appendEditorTitleContextMenuItem(REVEAL_IN_OS_COMMAND_ID, REVEAL_IN_OS_LABEL, ContextKeyExpr.and(IsWebContext.toNegated(), ResourceContextKey.Scheme.isEqualTo(Schemas.userData))); appendEditorTitleContextMenuItem(REVEAL_IN_EXPLORER_COMMAND_ID, nls.localize('revealInSideBar', "Reveal in Side Bar"), ResourceContextKey.IsFileSystemResource); -function appendEditorTitleContextMenuItem(id: string, title: string, when: ContextKeyExpr, group?: string): void { +function appendEditorTitleContextMenuItem(id: string, title: string, when: ContextKeyExpr | undefined, group?: string): void { // Menu MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index 7b6f13e67ef..04975876713 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -49,7 +49,7 @@ export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenc private windowCommandMenu: IMenu; private hasWindowActions: boolean = false; private remoteAuthority: string | undefined; - private disconnected: boolean = true; + private connectionState: 'initializing' | 'connected' | 'disconnected' | undefined = undefined; constructor( @IStatusbarService private readonly statusbarService: IStatusbarService, @@ -76,7 +76,8 @@ export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenc if (this.remoteAuthority) { // Pending entry until extensions are ready this.renderWindowIndicator(nls.localize('host.open', "$(sync~spin) Opening Remote..."), undefined, WINDOW_ACTIONS_COMMAND_ID); - RemoteConnectionState.bindTo(this.contextKeyService).set('initializing'); + this.connectionState = 'initializing'; + RemoteConnectionState.bindTo(this.contextKeyService).set(this.connectionState); MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { group: '6_close', @@ -86,6 +87,23 @@ export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenc }, order: 3.5 }); + + const connection = remoteAgentService.getConnection(); + if (connection) { + this._register(connection.onDidStateChange((e) => { + switch (e.type) { + case PersistentConnectionEventType.ConnectionLost: + case PersistentConnectionEventType.ReconnectionPermanentFailure: + case PersistentConnectionEventType.ReconnectionRunning: + case PersistentConnectionEventType.ReconnectionWait: + this.setDisconnected(true); + break; + case PersistentConnectionEventType.ConnectionGain: + this.setDisconnected(false); + break; + } + })); + } } extensionService.whenInstalledExtensionsRegistered().then(_ => { @@ -96,30 +114,14 @@ export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenc this._register(this.windowCommandMenu.onDidChange(e => this.updateWindowActions())); this.updateWindowIndicator(); }); - - const connection = remoteAgentService.getConnection(); - if (connection) { - this._register(connection.onDidStateChange((e) => { - switch (e.type) { - case PersistentConnectionEventType.ConnectionLost: - case PersistentConnectionEventType.ReconnectionPermanentFailure: - case PersistentConnectionEventType.ReconnectionRunning: - case PersistentConnectionEventType.ReconnectionWait: - this.setDisconnected(true); - break; - case PersistentConnectionEventType.ConnectionGain: - this.setDisconnected(false); - break; - } - })); - } } private setDisconnected(isDisconnected: boolean): void { - if (this.disconnected !== isDisconnected) { - this.disconnected = isDisconnected; - RemoteConnectionState.bindTo(this.contextKeyService).set(isDisconnected ? 'disconnected' : 'connected'); - Deprecated_RemoteAuthorityContext.bindTo(this.contextKeyService).set(isDisconnected ? '' : this.remoteAuthority || ''); + const newState = isDisconnected ? 'disconnected' : 'connected'; + if (this.connectionState !== newState) { + this.connectionState = newState; + RemoteConnectionState.bindTo(this.contextKeyService).set(this.connectionState); + Deprecated_RemoteAuthorityContext.bindTo(this.contextKeyService).set(isDisconnected ? 'disconnected/${this.remoteAuthority!}' : this.remoteAuthority!); this.updateWindowIndicator(); } } @@ -128,7 +130,7 @@ export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenc const windowActionCommand = (this.remoteAuthority || this.windowCommandMenu.getActions().length) ? WINDOW_ACTIONS_COMMAND_ID : undefined; if (this.remoteAuthority) { const hostLabel = this.labelService.getHostLabel(REMOTE_HOST_SCHEME, this.remoteAuthority) || this.remoteAuthority; - if (!this.disconnected) { + if (this.connectionState !== 'disconnected') { this.renderWindowIndicator(`$(remote) ${hostLabel}`, nls.localize('host.tooltip', "Editing on {0}", hostLabel), windowActionCommand); } else { this.renderWindowIndicator(`$(alert) ${nls.localize('disconnectedFrom', "Disconnected from")} ${hostLabel}`, nls.localize('host.tooltipDisconnected', "Disconnected from {0}", hostLabel), windowActionCommand); @@ -468,4 +470,4 @@ Registry.as(ConfigurationExtensions.Configuration) } } } - }); \ No newline at end of file + }); diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index db9568a6eb4..beef3cdc9fb 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -18,7 +18,7 @@ import { SCMViewlet } from 'vs/workbench/contrib/scm/browser/scmViewlet'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { ContextKeyDefinedExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; @@ -115,7 +115,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'scm.acceptInput', description: { description: localize('scm accept', "SCM: Accept Input"), args: [] }, weight: KeybindingWeight.WorkbenchContrib, - when: new ContextKeyDefinedExpr('scmRepository'), + when: ContextKeyExpr.has('scmRepository'), primary: KeyMod.CtrlCmd | KeyCode.Enter, handler: accessor => { const contextKeyService = accessor.get(IContextKeyService); @@ -134,4 +134,4 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); -registerSingleton(ISCMService, SCMService); \ No newline at end of file +registerSingleton(ISCMService, SCMService); diff --git a/src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts b/src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts index 5041cb4be1e..ef0dc08081c 100644 --- a/src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts +++ b/src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts @@ -47,7 +47,22 @@ const ModulesToLookFor = [ 'azure-storage', 'firebase', '@google-cloud/common', - 'heroku-cli' + 'heroku-cli', + //Office and Sharepoint packages + '@microsoft/office-js', + '@microsoft/office-js-helpers', + '@types/office-js', + '@types/office-runtime', + 'office-ui-fabric-react', + '@uifabric/icons', + '@uifabric/merge-styles', + '@uifabric/styling', + '@uifabric/experiments', + '@uifabric/utilities', + '@microsoft/rush', + 'lerna', + 'just-task', + 'beachball' ]; const PyModulesToLookFor = [ 'azure', @@ -143,6 +158,20 @@ export class WorkspaceStatsService implements IWorkspaceStatsService { "workspace.npm.@google-cloud/common" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.firebase" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.heroku-cli" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@microsoft/office-js" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@microsoft/office-js-helpers" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@types/office-js" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@types/office-runtime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.office-ui-fabric-react" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@uifabric/icons" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@uifabric/merge-styles" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@uifabric/styling" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@uifabric/experiments" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@uifabric/utilities" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@microsoft/rush" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.lerna" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.just-task" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.beachball" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.bower" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.yeoman.code.ext" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.cordova.high" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, @@ -188,7 +217,6 @@ export class WorkspaceStatsService implements IWorkspaceStatsService { "workspace.py.botbuilder-core" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.botbuilder-schema" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.botframework-connector" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - } */ private resolveWorkspaceTags(configuration: IWindowConfiguration, participant?: (rootFiles: string[]) => void): Promise { @@ -474,4 +502,4 @@ export class WorkspaceStatsService implements IWorkspaceStatsService { private searchArray(arr: string[], regEx: RegExp): boolean | undefined { return arr.some(v => v.search(regEx) > -1) || undefined; } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 21491081db7..efef7a28135 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -926,7 +926,7 @@ export class TerminalTaskSystem implements ITaskSystem { }; } else if (task.command.runtime === RuntimeType.CustomExecution2) { this.currentTask.shellLaunchConfig = launchConfigs = { - isVirtualProcess: true, + isExtensionTerminal: true, waitOnExit, name: this.createTerminalName(task, workspaceFolder), initialText: task.command.presentation && task.command.presentation.echo ? `\x1b[1m> Executing task: ${task._label} <\x1b[0m\n` : undefined diff --git a/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts b/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts index 75e51bcfa22..1ab97705568 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts @@ -19,7 +19,7 @@ export class TerminalFindWidget extends SimpleFindWidget { @IContextKeyService private readonly _contextKeyService: IContextKeyService, @ITerminalService private readonly _terminalService: ITerminalService ) { - super(_contextViewService, _contextKeyService, findState, true); + super(_contextViewService, _contextKeyService, findState, true, true); this._register(findState.onFindReplaceStateChange(() => { this.show(); })); @@ -50,7 +50,7 @@ export class TerminalFindWidget extends SimpleFindWidget { // Ignore input changes for now const instance = this._terminalService.getActiveInstance(); if (instance !== null) { - return instance.findNext(this.inputValue, { regex: this._getRegexValue(), wholeWord: this._getWholeWordValue(), caseSensitive: this._getCaseSensitiveValue(), incremental: true }); + return instance.findPrevious(this.inputValue, { regex: this._getRegexValue(), wholeWord: this._getWholeWordValue(), caseSensitive: this._getCaseSensitiveValue(), incremental: true }); } return false; } @@ -78,4 +78,4 @@ export class TerminalFindWidget extends SimpleFindWidget { protected onFindInputFocusTrackerBlur() { this._findInputFocused.reset(); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index a391164f1f2..348b93e3307 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -31,7 +31,7 @@ const LATENCY_MEASURING_INTERVAL = 1000; enum ProcessType { Process, - VirtualProcess + ExtensionTerminal } /** @@ -113,8 +113,8 @@ export class TerminalProcessManager implements ITerminalProcessManager { rows: number, isScreenReaderModeEnabled: boolean ): Promise { - if (shellLaunchConfig.isVirtualProcess) { - this._processType = ProcessType.VirtualProcess; + if (shellLaunchConfig.isExtensionTerminal) { + this._processType = ProcessType.ExtensionTerminal; this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, undefined, cols, rows, this._configHelper); } else { const forceExtHostProcess = (this._configHelper.config as any).extHostProcess; @@ -239,7 +239,7 @@ export class TerminalProcessManager implements ITerminalProcessManager { } public write(data: string): void { - if (this.shellProcessId || this._processType === ProcessType.VirtualProcess) { + if (this.shellProcessId || this._processType === ProcessType.ExtensionTerminal) { if (this._process) { // Send data if the pty is ready this._process.input(data); @@ -292,4 +292,4 @@ export class TerminalProcessManager implements ITerminalProcessManager { this._onProcessExit.fire(exitCode); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index f427fd3ef0e..f6413d9857e 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -188,14 +188,14 @@ export interface IShellLaunchConfig { initialText?: string; /** - * @deprecated use `isVirtualProcess` + * @deprecated use `isExtensionTerminal` */ isRendererOnly?: boolean; /** - * When true an extension is acting as the terminal's process. + * Whether an extension is controlling the terminal via a `vscode.Pseudoterminal`. */ - isVirtualProcess?: boolean; + isExtensionTerminal?: boolean; /** * Whether the terminal process environment should be exactly as provided in @@ -231,8 +231,8 @@ export interface ITerminalService { onInstanceProcessIdReady: Event; onInstanceDimensionsChanged: Event; onInstanceMaximumDimensionsChanged: Event; - onInstanceRequestExtHostProcess: Event; - onInstanceRequestVirtualProcess: Event; + onInstanceRequestSpawnExtHostProcess: Event; + onInstanceRequestStartExtensionTerminal: Event; onInstancesChanged: Event; onInstanceTitleChanged: Event; onActiveInstanceChanged: Event; @@ -299,8 +299,8 @@ export interface ITerminalService { preparePathForTerminalAsync(path: string, executable: string | undefined, title: string): Promise; extHostReady(remoteAuthority: string): void; - requestExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI, cols: number, rows: number, isWorkspaceShellAllowed: boolean): void; - requestVirtualProcess(proxy: ITerminalProcessExtHostProxy, cols: number, rows: number): void; + requestSpawnExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI, cols: number, rows: number, isWorkspaceShellAllowed: boolean): void; + requestStartExtensionTerminal(proxy: ITerminalProcessExtHostProxy, cols: number, rows: number): void; } /** @@ -760,7 +760,7 @@ export interface ITerminalProcessExtHostProxy extends IDisposable { onRequestLatency: Event; } -export interface ITerminalProcessExtHostRequest { +export interface ISpawnExtHostProcessRequest { proxy: ITerminalProcessExtHostProxy; shellLaunchConfig: IShellLaunchConfig; activeWorkspaceRootUri: URI; @@ -769,7 +769,7 @@ export interface ITerminalProcessExtHostRequest { isWorkspaceShellAllowed: boolean; } -export interface ITerminalVirtualProcessRequest { +export interface IStartExtensionTerminalRequest { proxy: ITerminalProcessExtHostProxy; cols: number; rows: number; diff --git a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts index 7442639d505..da109a496f4 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts @@ -106,6 +106,7 @@ function _getLangEnvVariable(locale?: string) { ko: 'KR', pl: 'PL', ru: 'RU', + sk: 'SK', zh: 'CN' }; if (parts[0] in languageVariants) { diff --git a/src/vs/workbench/contrib/terminal/common/terminalProcessExtHostProxy.ts b/src/vs/workbench/contrib/terminal/common/terminalProcessExtHostProxy.ts index e611bce74a6..80923d84486 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalProcessExtHostProxy.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalProcessExtHostProxy.ts @@ -58,14 +58,14 @@ export class TerminalProcessExtHostProxy extends Disposable implements ITerminal // Request a process if needed, if this is a virtual process this step can be skipped as // there is no real "process" and we know it's ready on the ext host already. - if (shellLaunchConfig.isVirtualProcess) { - this._terminalService.requestVirtualProcess(this, cols, rows); + if (shellLaunchConfig.isExtensionTerminal) { + this._terminalService.requestStartExtensionTerminal(this, cols, rows); } else { remoteAgentService.getEnvironment().then(env => { if (!env) { throw new Error('Could not fetch environment'); } - this._terminalService.requestExtHostProcess(this, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, configHelper.checkWorkspaceShellPermissions(env.os)); + this._terminalService.requestSpawnExtHostProcess(this, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, configHelper.checkWorkspaceShellPermissions(env.os)); }); if (!hasReceivedResponse) { setTimeout(() => this._onProcessTitleChanged.fire(nls.localize('terminal.integrated.starting', "Starting...")), 0); @@ -149,4 +149,4 @@ export class TerminalProcessExtHostProxy extends Disposable implements ITerminal this._pendingLatencyRequests.push(resolve); }); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/terminal/common/terminalService.ts b/src/vs/workbench/contrib/terminal/common/terminalService.ts index 558be1e44ea..8bf6a5bfa5b 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalService.ts @@ -8,7 +8,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalConfigHelper, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, TERMINAL_PANEL_ID, ITerminalTab, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, ITerminalNativeService, IShellDefinition, IAvailableShellsRequest, ITerminalVirtualProcessRequest } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalConfigHelper, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, TERMINAL_PANEL_ID, ITerminalTab, ITerminalProcessExtHostProxy, ISpawnExtHostProcessRequest, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, ITerminalNativeService, IShellDefinition, IAvailableShellsRequest, IStartExtensionTerminalRequest } from 'vs/workbench/contrib/terminal/common/terminal'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { URI } from 'vs/base/common/uri'; import { FindReplaceState } from 'vs/editor/contrib/find/findState'; @@ -20,11 +20,15 @@ import { escapeNonWindowsPath } from 'vs/workbench/contrib/terminal/common/termi import { isWindows, isMacintosh, OperatingSystem } from 'vs/base/common/platform'; import { basename } from 'vs/base/common/path'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { timeout } from 'vs/base/common/async'; import { IOpenFileRequest } from 'vs/platform/windows/common/windows'; import { IPickOptions, IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +interface IExtHostReadyEntry { + promise: Promise; + resolve: () => void; +} + export abstract class TerminalService implements ITerminalService { public _serviceBrand: any; @@ -38,7 +42,7 @@ export abstract class TerminalService implements ITerminalService { return this._terminalTabs.reduce((p, c) => p.concat(c.terminalInstances), []); } private _findState: FindReplaceState; - private _extHostsReady: { [authority: string]: boolean } = {}; + private _extHostsReady: { [authority: string]: IExtHostReadyEntry | undefined } = {}; private _activeTabIndex: number; public get activeTabIndex(): number { return this._activeTabIndex; } @@ -53,10 +57,10 @@ export abstract class TerminalService implements ITerminalService { public get onInstanceDisposed(): Event { return this._onInstanceDisposed.event; } protected readonly _onInstanceProcessIdReady = new Emitter(); public get onInstanceProcessIdReady(): Event { return this._onInstanceProcessIdReady.event; } - protected readonly _onInstanceRequestExtHostProcess = new Emitter(); - public get onInstanceRequestExtHostProcess(): Event { return this._onInstanceRequestExtHostProcess.event; } - protected readonly _onInstanceRequestVirtualProcess = new Emitter(); - public get onInstanceRequestVirtualProcess(): Event { return this._onInstanceRequestVirtualProcess.event; } + protected readonly _onInstanceRequestSpawnExtHostProcess = new Emitter(); + public get onInstanceRequestSpawnExtHostProcess(): Event { return this._onInstanceRequestSpawnExtHostProcess.event; } + protected readonly _onInstanceRequestStartExtensionTerminal = new Emitter(); + public get onInstanceRequestStartExtensionTerminal(): Event { return this._onInstanceRequestStartExtensionTerminal.event; } protected readonly _onInstanceDimensionsChanged = new Emitter(); public get onInstanceDimensionsChanged(): Event { return this._onInstanceDimensionsChanged.event; } protected readonly _onInstanceMaximumDimensionsChanged = new Emitter(); @@ -131,25 +135,39 @@ export abstract class TerminalService implements ITerminalService { return activeInstance ? activeInstance : this.createTerminal(undefined, wasNewTerminalAction); } - public requestExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI, cols: number, rows: number, isWorkspaceShellAllowed: boolean): void { + public requestSpawnExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI, cols: number, rows: number, isWorkspaceShellAllowed: boolean): void { this._extensionService.whenInstalledExtensionsRegistered().then(async () => { - // Wait for the remoteAuthority to be ready (and listening for events) before proceeding + // Wait for the remoteAuthority to be ready (and listening for events) before firing + // the event to spawn the ext host process const conn = this._remoteAgentService.getConnection(); const remoteAuthority = conn ? conn.remoteAuthority : 'null'; - let retries = 0; - while (!this._extHostsReady[remoteAuthority] && ++retries < 50) { - await timeout(100); - } - this._onInstanceRequestExtHostProcess.fire({ proxy, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, isWorkspaceShellAllowed }); + await this._whenExtHostReady(remoteAuthority); + this._onInstanceRequestSpawnExtHostProcess.fire({ proxy, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, isWorkspaceShellAllowed }); }); } - public requestVirtualProcess(proxy: ITerminalProcessExtHostProxy, cols: number, rows: number): void { - this._onInstanceRequestVirtualProcess.fire({ proxy, cols, rows }); + public requestStartExtensionTerminal(proxy: ITerminalProcessExtHostProxy, cols: number, rows: number): void { + this._onInstanceRequestStartExtensionTerminal.fire({ proxy, cols, rows }); } - public extHostReady(remoteAuthority: string): void { - this._extHostsReady[remoteAuthority] = true; + public async extHostReady(remoteAuthority: string): Promise { + this._createExtHostReadyEntry(remoteAuthority); + this._extHostsReady[remoteAuthority]!.resolve(); + } + + private async _whenExtHostReady(remoteAuthority: string): Promise { + this._createExtHostReadyEntry(remoteAuthority); + return this._extHostsReady[remoteAuthority]!.promise; + } + + private _createExtHostReadyEntry(remoteAuthority: string): void { + if (this._extHostsReady[remoteAuthority]) { + return; + } + + let resolve!: () => void; + const promise = new Promise(r => resolve = r); + this._extHostsReady[remoteAuthority] = { promise, resolve }; } private _onBeforeShutdown(): boolean | Promise { diff --git a/src/vs/workbench/contrib/watermark/browser/watermark.ts b/src/vs/workbench/contrib/watermark/browser/watermark.ts index 7788f6e36a0..54c9b8620f5 100644 --- a/src/vs/workbench/contrib/watermark/browser/watermark.ts +++ b/src/vs/workbench/contrib/watermark/browser/watermark.ts @@ -173,7 +173,7 @@ export class WatermarkContribution extends Disposable implements IWorkbenchContr dom.prepend(container.firstElementChild as HTMLElement, this.watermark); this._register(this.keybindingService.onDidUpdateKeybindings(update)); this._register(this.editorGroupsService.onDidLayout(dimension => this.handleEditorPartSize(container, dimension))); - this.handleEditorPartSize(container, this.editorGroupsService.dimension); + this.handleEditorPartSize(container, this.editorGroupsService.contentDimension); } private handleEditorPartSize(container: HTMLElement, dimension: IDimension): void { @@ -214,4 +214,4 @@ Registry.as(ConfigurationExtensions.Configuration) 'description': nls.localize('tips.enabled', "When enabled, will show the watermark tips when no editor is open.") }, } - }); \ No newline at end of file + }); diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts index 49d8bcc11cd..e81418df66a 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts @@ -90,7 +90,7 @@ export function areWebviewInputOptionsEqual(a: WebviewInputOptions, b: WebviewIn && a.retainContextWhenHidden === b.retainContextWhenHidden && a.tryRestoreScrollPosition === b.tryRestoreScrollPosition && (a.localResourceRoots === b.localResourceRoots || (Array.isArray(a.localResourceRoots) && Array.isArray(b.localResourceRoots) && equals(a.localResourceRoots, b.localResourceRoots, (a, b) => a.toString() === b.toString()))) - && (a.portMappings === b.portMappings || (Array.isArray(a.portMappings) && Array.isArray(b.portMappings) && equals(a.portMappings, b.portMappings, (a, b) => a.extensionHostPort === b.extensionHostPort && a.webviewPort === b.webviewPort))); + && (a.portMapping === b.portMapping || (Array.isArray(a.portMapping) && Array.isArray(b.portMapping) && equals(a.portMapping, b.portMapping, (a, b) => a.extensionHostPort === b.extensionHostPort && a.webviewPort === b.webviewPort))); } function canRevive(reviver: WebviewReviver, webview: WebviewEditorInput): boolean { diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index 5e5df2a02cf..aa4473313d4 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -53,7 +53,7 @@ export class IFrameWebview extends Disposable implements Webview { this._portMappingManager = this._register(new WebviewPortMappingManager( this._options.extension ? this._options.extension.location : undefined, - () => this.content.options.portMappings || [], + () => this.content.options.portMapping || [], tunnelService )); diff --git a/src/vs/workbench/contrib/webview/common/webview.ts b/src/vs/workbench/contrib/webview/common/webview.ts index 8d81b487a5a..d6a73ff5d61 100644 --- a/src/vs/workbench/contrib/webview/common/webview.ts +++ b/src/vs/workbench/contrib/webview/common/webview.ts @@ -55,7 +55,7 @@ export interface WebviewContentOptions { readonly allowScripts?: boolean; readonly svgWhiteList?: string[]; readonly localResourceRoots?: ReadonlyArray; - readonly portMappings?: ReadonlyArray; + readonly portMapping?: ReadonlyArray; readonly enableCommandUris?: boolean; } diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index e42e1a4b401..3c030feb235 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -327,7 +327,7 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview { this._register(new WebviewPortMappingProvider( session, _options.extension ? _options.extension.location : undefined, - () => (this.content.options.portMappings || []), + () => (this.content.options.portMapping || []), tunnelService, )); diff --git a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts index bcf4b31cef9..f406a3e0d63 100644 --- a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts @@ -227,6 +227,10 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR */ private showUserInput(variable: string, inputInfos: ConfiguredInput[]): Promise { + if (!inputInfos) { + return Promise.reject(new Error(nls.localize('inputVariable.noInputSection', "Variable '{0}' must be defined in an '{1}' section of the debug or task configuration.", variable, 'input'))); + } + // find info for the given input variable const info = inputInfos.filter(item => item.id === variable).pop(); if (info) { @@ -307,4 +311,4 @@ export class ConfigurationResolverService extends BaseConfigurationResolverServi } } -registerSingleton(IConfigurationResolverService, ConfigurationResolverService, true); \ No newline at end of file +registerSingleton(IConfigurationResolverService, ConfigurationResolverService, true); diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index c91173e5514..a9ab498cc98 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -179,7 +179,7 @@ export interface IEditorGroupsService { /** * The size of the editor groups area. */ - readonly dimension: IDimension; + readonly contentDimension: IDimension; /** * An active group is the default location for new editors to open. diff --git a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts index b577d40f181..477fdfc5bc7 100644 --- a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts @@ -118,11 +118,6 @@ suite('EditorGroupsService', () => { groupMovedCounter++; }); - let preferredSizeChangeCounter = 0; - const preferredSizeChangeListener = part.onDidPreferredSizeChange(() => { - preferredSizeChangeCounter++; - }); - // always a root group const rootGroup = part.groups[0]; assert.equal(part.groups.length, 1); @@ -141,7 +136,6 @@ suite('EditorGroupsService', () => { assert.equal(part.groups.length, 2); assert.equal(part.count, 2); assert.ok(part.activeGroup === rootGroup); - assert.equal(preferredSizeChangeCounter, 1); assert.equal(rootGroup.label, 'Group 1'); assert.equal(rightGroup.label, 'Group 2'); @@ -189,7 +183,6 @@ suite('EditorGroupsService', () => { assert.equal(part.groups.length, 3); assert.ok(part.activeGroup === rightGroup); assert.ok(!downGroup.activeControl); - assert.equal(preferredSizeChangeCounter, 2); assert.equal(rootGroup.label, 'Group 1'); assert.equal(rightGroup.label, 'Group 2'); assert.equal(downGroup.label, 'Group 3'); @@ -208,13 +201,11 @@ suite('EditorGroupsService', () => { part.moveGroup(downGroup, rightGroup, GroupDirection.DOWN); assert.equal(groupMovedCounter, 1); - assert.equal(preferredSizeChangeCounter, 2); part.removeGroup(downGroup); assert.ok(!part.getGroup(downGroup.id)); assert.equal(didDispose, true); assert.equal(groupRemovedCounter, 1); - assert.equal(preferredSizeChangeCounter, 3); assert.equal(part.groups.length, 2); assert.ok(part.activeGroup === rightGroup); assert.equal(rootGroup.label, 'Group 1'); @@ -254,13 +245,11 @@ suite('EditorGroupsService', () => { assert.ok(part.activeGroup === rootGroup); part.setGroupOrientation(part.orientation === GroupOrientation.HORIZONTAL ? GroupOrientation.VERTICAL : GroupOrientation.HORIZONTAL); - assert.equal(preferredSizeChangeCounter, 5); activeGroupChangeListener.dispose(); groupAddedListener.dispose(); groupRemovedListener.dispose(); groupMovedListener.dispose(); - preferredSizeChangeListener.dispose(); part.dispose(); }); diff --git a/src/vs/workbench/services/extensions/common/inactiveExtensionUrlHandler.ts b/src/vs/workbench/services/extensions/common/inactiveExtensionUrlHandler.ts index b850b474c62..7330ceedefb 100644 --- a/src/vs/workbench/services/extensions/common/inactiveExtensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/common/inactiveExtensionUrlHandler.ts @@ -7,6 +7,7 @@ import { localize } from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import { IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionEnablementService, EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; @@ -23,6 +24,8 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; const FIVE_MINUTES = 5 * 60 * 1000; const THIRTY_SECONDS = 30 * 1000; const URL_TO_HANDLE = 'extensionUrlHandler.urlToHandle'; +const CONFIRMED_EXTENSIONS_CONFIGURATION_KEY = 'extensions.confirmedUriHandlerExtensionIds'; +const CONFIRMED_EXTENSIONS_STORAGE_KEY = 'extensionUrlHandler.confirmedExtensions'; function isExtensionId(value: string): boolean { return /^[a-z0-9][a-z0-9\-]*\.[a-z0-9][a-z0-9\-]*$/i.test(value); @@ -62,7 +65,9 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { @IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService, @IWindowService private readonly windowService: IWindowService, @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, - @IStorageService private readonly storageService: IStorageService + @IStorageService private readonly storageService: IStorageService, + @IConfigurationService private readonly configurationService: IConfigurationService + ) { const interval = setInterval(() => this.garbageCollect(), THIRTY_SECONDS); const urlToHandleValue = this.storageService.get(URL_TO_HANDLE, StorageScope.WORKSPACE); @@ -91,6 +96,11 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { return true; } + if (!confirmed) { + const confirmedExtensionIds = this.getConfirmedExtensionIds(); + confirmed = confirmedExtensionIds.has(ExtensionIdentifier.toKey(extensionId)); + } + if (!confirmed) { let uriString = uri.toString(); @@ -100,6 +110,9 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { const result = await this.dialogService.confirm({ message: localize('confirmUrl', "Allow an extension to open this URL?", extensionId), + checkbox: { + label: localize('rememberConfirmUrl', "Don't ask again for this extension."), + }, detail: `${extension.displayName || extension.name} (${extensionId}) wants to open a URL:\n\n${uriString}`, primaryButton: localize('open', "&&Open"), type: 'question' @@ -108,6 +121,10 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { if (!result.confirmed) { return true; } + + if (result.checkboxChecked) { + this.addConfirmedExtensionIdToStorage(extensionId); + } } const handler = this.extensionHandlers.get(ExtensionIdentifier.toKey(extensionId)); @@ -267,6 +284,47 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { this.uriBuffer = uriBuffer; } + private getConfirmedExtensionIds(): Set { + const ids = [ + ...this.getConfirmedExtensionIdsFromStorage(), + ...this.getConfirmedExtensionIdsFromConfiguration(), + ].map(extensionId => ExtensionIdentifier.toKey(extensionId)); + + return new Set(ids); + } + + private getConfirmedExtensionIdsFromConfiguration(): Array { + const confirmedExtensionIds = this.configurationService.getValue>(CONFIRMED_EXTENSIONS_CONFIGURATION_KEY); + + if (!Array.isArray(confirmedExtensionIds)) { + return []; + } + + return confirmedExtensionIds; + } + + private getConfirmedExtensionIdsFromStorage(): Array { + const confirmedExtensionIdsJson = this.storageService.get(CONFIRMED_EXTENSIONS_STORAGE_KEY, StorageScope.GLOBAL, '[]'); + + try { + return JSON.parse(confirmedExtensionIdsJson); + } catch (err) { + return []; + } + } + + private addConfirmedExtensionIdToStorage(extensionId: string): void { + const existingConfirmedExtensionIds = this.getConfirmedExtensionIdsFromStorage(); + this.storageService.store( + CONFIRMED_EXTENSIONS_STORAGE_KEY, + JSON.stringify([ + ...existingConfirmedExtensionIds, + ExtensionIdentifier.toKey(extensionId), + ]), + StorageScope.GLOBAL, + ); + } + dispose(): void { this.disposable.dispose(); this.extensionHandlers.clear(); diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index 4e64ebdc0c6..dec59cfca2c 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -300,7 +300,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { private _resolveKeybindingItems(items: IKeybindingItem[], isDefault: boolean): ResolvedKeybindingItem[] { let result: ResolvedKeybindingItem[] = [], resultLen = 0; for (const item of items) { - const when = (item.when ? item.when.normalize() : undefined); + const when = item.when || undefined; const keybinding = item.keybinding; if (!keybinding) { // This might be a removal keybinding item in user settings => accept it @@ -323,7 +323,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { private _resolveUserKeybindingItems(items: IUserKeybindingItem[], isDefault: boolean): ResolvedKeybindingItem[] { let result: ResolvedKeybindingItem[] = [], resultLen = 0; for (const item of items) { - const when = (item.when ? item.when.normalize() : undefined); + const when = item.when || undefined; const parts = item.parts; if (parts.length === 0) { // This might be a removal keybinding item in user settings => accept it diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index d0bf836984f..344c7657de5 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -8,6 +8,7 @@ import { Event } from 'vs/base/common/event'; import { MenuBarVisibility } from 'vs/platform/windows/common/windows'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { Part } from 'vs/workbench/browser/part'; +import { Dimension } from 'vs/base/browser/dom'; export const IWorkbenchLayoutService = createDecorator('layoutService'); @@ -81,6 +82,11 @@ export interface IWorkbenchLayoutService extends ILayoutService { */ isVisible(part: Parts): boolean; + /** + * Returns if the part is visible. + */ + getDimension(part: Parts): Dimension; + /** * Set activity bar hidden or not */ diff --git a/src/vs/workbench/test/browser/part.test.ts b/src/vs/workbench/test/browser/part.test.ts index 73878b4e603..ed151b3aced 100644 --- a/src/vs/workbench/test/browser/part.test.ts +++ b/src/vs/workbench/test/browser/part.test.ts @@ -10,7 +10,6 @@ import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService import { append, $, hide } from 'vs/base/browser/dom'; import { TestStorageService, TestLayoutService } from 'vs/workbench/test/workbenchTestServices'; import { StorageScope } from 'vs/platform/storage/common/storage'; -import { Orientation } from 'vs/base/browser/ui/grid/grid'; class SimplePart extends Part { @@ -19,7 +18,7 @@ class SimplePart extends Part { minimumHeight: number; maximumHeight: number; - layout(width: number, height: number, orientation: Orientation): void { + layout(width: number, height: number): void { throw new Error('Method not implemented.'); } @@ -172,4 +171,4 @@ suite('Workbench parts', () => { assert(!document.getElementById('myPart.title')); assert(document.getElementById('myPart.content')); }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 9e9e0d739e8..153fc2bbae5 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -474,6 +474,10 @@ export class TestLayoutService implements IWorkbenchLayoutService { return true; } + getDimension(_part: Parts): Dimension { + return new Dimension(0, 0); + } + public getContainer(_part: Parts): HTMLElement { return null!; } @@ -667,7 +671,7 @@ export class TestEditorGroupsService implements IEditorGroupsService { whenRestored: Promise = Promise.resolve(undefined); willRestoreEditors = false; - dimension = { width: 800, height: 600 }; + contentDimension = { width: 800, height: 600 }; get activeGroup(): IEditorGroup { return this.groups[0]; @@ -1622,4 +1626,4 @@ export class RemoteFileSystemProvider implements IFileSystemProvider { write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { return this.diskFileSystemProvider.write!(fd, pos, data, offset, length); } private toFileResource(resource: URI): URI { return resource.with({ scheme: Schemas.file, authority: '' }); } -} \ No newline at end of file +} diff --git a/test/electron/renderer.html b/test/electron/renderer.html index 1eb67318bfa..72454389597 100644 --- a/test/electron/renderer.html +++ b/test/electron/renderer.html @@ -19,4 +19,4 @@ - \ No newline at end of file + diff --git a/tslint.json b/tslint.json index 3ff7147730b..155bd9a4115 100644 --- a/tslint.json +++ b/tslint.json @@ -515,6 +515,13 @@ "**/vs/**" ] }, + { + "target": "**/vs/workbench/contrib/extensions/browser/**", + "restrictions": [ + "semver-umd", + "**/vs/**" + ] + }, { "target": "**/vs/code/node/**", "restrictions": [ @@ -563,7 +570,11 @@ ] }, { - "target": "**/{node,electron-browser,electron-main,extensions}/**", + "target": "**/{node,electron-browser,electron-main}/**", + "restrictions": "**/*" + }, + { + "target": "**/extensions/**", "restrictions": "**/*" }, { diff --git a/yarn.lock b/yarn.lock index 39e22647784..8c907deb523 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3924,14 +3924,15 @@ gulp-plumber@^1.2.0: plugin-error "^0.1.2" through2 "^2.0.3" -gulp-remote-src@^0.4.4: - version "0.4.4" - resolved "https://registry.yarnpkg.com/gulp-remote-src/-/gulp-remote-src-0.4.4.tgz#4a4d18fac0ffedde94a7855953de90db00a1d1b1" - integrity sha512-mo7lGgZmNXyTbcUzfjSnUVkx1pnqqiwv/pPaIrYdTO77hq0WNTxXLAzQdoYOnyJ0mfVLNmNl9AGqWLiAzTPMMA== +gulp-remote-retry-src@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/gulp-remote-retry-src/-/gulp-remote-retry-src-0.6.0.tgz#fdcb5d5c9e67c31ae378a2a886ddad3d47913bb1" + integrity sha512-lFxpwwbM/GEIdYiNumxiUcPHZUROFJaF1zTBne1H8b3Pwx6Te6O9uEYp++JZPP62jdheOWcHUTBREiMkpdbm4Q== dependencies: event-stream "3.3.4" node.extend "~1.1.2" request "^2.88.0" + requestretry "^4.0.0" through2 "~2.0.3" vinyl "~2.0.1" @@ -4178,6 +4179,13 @@ has@^1.0.1: dependencies: function-bind "^1.0.2" +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + hash-base@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" @@ -6110,11 +6118,12 @@ node-pty@0.9.0-beta19: nan "^2.13.2" node.extend@~1.1.2: - version "1.1.6" - resolved "https://registry.yarnpkg.com/node.extend/-/node.extend-1.1.6.tgz#a7b882c82d6c93a4863a5504bd5de8ec86258b96" - integrity sha1-p7iCyC1sk6SGOlUEvV3o7IYli5Y= + version "1.1.8" + resolved "https://registry.yarnpkg.com/node.extend/-/node.extend-1.1.8.tgz#0aab3e63789f4e6d68b42bc00073ad1881243cf0" + integrity sha512-L/dvEBwyg3UowwqOUTyDsGBU6kjBQOpOhshio9V3i3BMPv5YUb9+mWNN8MK0IbWqT0AqaTSONZf0aTuMMahWgA== dependencies: - is "^3.1.0" + has "^1.0.3" + is "^3.2.1" noop-logger@^0.1.1: version "0.1.1" @@ -7778,6 +7787,15 @@ request@^2.86.0, request@^2.88.0: tunnel-agent "^0.6.0" uuid "^3.3.2" +requestretry@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/requestretry/-/requestretry-4.0.0.tgz#4e9e7280a7d8561bf33e9925264cf026e2be3e89" + integrity sha512-ST8m0+5FQH2FA+gbzUQyOQjUwHf22kbPQnd6TexveR0p+2UV1YYBg+Roe7BnKQ1Bb/+LtJwwm0QzxK2NA20Cug== + dependencies: + extend "^3.0.2" + lodash "^4.17.10" + when "^3.7.7" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -9725,6 +9743,11 @@ webpack@^4.16.5, webpack@^4.7.0: watchpack "^1.5.0" webpack-sources "^1.0.1" +when@^3.7.7: + version "3.7.8" + resolved "https://registry.yarnpkg.com/when/-/when-3.7.8.tgz#c7130b6a7ea04693e842cdc9e7a1f2aa39a39f82" + integrity sha1-xxMLan6gRpPoQs3J56Hyqjmjn4I= + whet.extend@~0.9.9: version "0.9.9" resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1" @@ -9910,20 +9933,20 @@ xtend@~2.1.1: dependencies: object-keys "~0.4.0" -xterm-addon-search@0.2.0-beta2: - version "0.2.0-beta2" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.2.0-beta2.tgz#c3173f0a6f207ee9f1848849174ee5d6b6ce8262" - integrity sha512-XEcwi2TeFGk2MuIFjiI/OpVXSNO5dGQBvHH3o+9KzqG3ooVqhhDqzwxs092QGNcNCGh8hGn/PWZiczaBBnKm/g== +xterm-addon-search@0.2.0-beta3: + version "0.2.0-beta3" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.2.0-beta3.tgz#710ce14658e269c5a4791f5a9e2f520883a2e62b" + integrity sha512-KzVdkEtGbKJe9ER2TmrI7XjF/wUq1lir9U63vPJi0t2ymQvIECl1V63f9QtOp1vvpdhbZiXBxO+vGTj+y0tRow== xterm-addon-web-links@0.1.0-beta10: version "0.1.0-beta10" resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.1.0-beta10.tgz#610fa9773a2a5ccd41c1c83ba0e2dd2c9eb66a23" integrity sha512-xfpjy0V6bB4BR44qIgZQPoCMVakxb65gMscPkHpO//QxvUxKzabV3dxOsIbeZRFkUGsWTFlvz2OoaBLoNtv5gg== -xterm@3.15.0-beta87: - version "3.15.0-beta87" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.15.0-beta87.tgz#d04af8d89f1f2c6c1d1580960653c32b2cc344e9" - integrity sha512-9HdgqnWCoEErvhk2Q0flDSlpAOnd4o+qe4+GeN6EwzKWPVdm1aNYLwdmeaOKAjZZfE+wchGu+HaslSRwfyu5yg== +xterm@3.15.0-beta89: + version "3.15.0-beta89" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.15.0-beta89.tgz#255962e2595deefb42b8c0043001256526163a3f" + integrity sha512-rNaoUamacPRg+ejbKDGRDNqR3SZ3Uf/pUW0mO+FF25/lIgdLq8x7RgZVBgFweCZ/dijPjxoyMcgfNDTH9h8LOg== y18n@^3.2.1: version "3.2.1"