Merge remote-tracking branch 'origin/master' into tyriar/puppeteer

This commit is contained in:
Daniel Imms
2019-07-27 13:05:36 -07:00
144 changed files with 2469 additions and 927 deletions
+126 -33
View File
@@ -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: [],
+3 -2
View File
@@ -59,5 +59,6 @@
"git.ignoreLimitWarning": true,
"remote.extensionKind": {
"msjsdiag.debugger-for-chrome": "workspace"
}
}
},
"files.insertFinalNewline": true
}
+3 -3
View File
@@ -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
displayName: Sync & Merge Distro
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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');
+1 -1
View File
@@ -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");
+1 -1
View File
@@ -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');
+2 -2
View File
@@ -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;
}
}
+1 -1
View File
@@ -30,7 +30,7 @@ import * as editorAPI from './vs/editor/editor.api';
a = (<ServiceIdentifier<any>>b).type;
a = (<IHighlight>b).start;
a = (<IHighlight>b).end;
a = (<SimpleWorkerClient<any>>b).getProxyObject; // IWorkerClient
a = (<SimpleWorkerClient<any, any>>b).getProxyObject; // IWorkerClient
a = create1;
a = create2;
a = (<DocumentRangeFormattingEditProvider>b).extensionId;
+2 -2
View File
@@ -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"
}
}
}
+14 -9
View File
@@ -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"
@@ -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": {
@@ -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 };
});
@@ -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"
+9 -1
View File
@@ -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<APIState> {
return this._model.onDidChangeState;
}
get onDidOpenRepository(): Event<Repository> {
return mapEvent(this._model.onDidOpenRepository, r => new ApiRepository(r));
}
+4
View File
@@ -176,7 +176,11 @@ export interface Repository {
log(options?: LogOptions): Promise<Commit[]>;
}
export type APIState = 'uninitialized' | 'initialized';
export interface API {
readonly state: APIState;
readonly onDidChangeState: Event<APIState>;
readonly git: Git;
readonly repositories: Repository[];
readonly onDidOpenRepository: Event<Repository>;
+28 -12
View File
@@ -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<string>();
private _onDidChangeState = new EventEmitter<State>();
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<void> {
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<void> {
const config = workspace.getConfiguration('git');
const autoRepositoryDetection = config.get<boolean | 'subFolders' | 'openEditors'>('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);
}
}
}
+18 -8
View File
@@ -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<Uri>;
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 {
@@ -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",
@@ -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 };
});
@@ -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<HTMLDocumentRegions>, workspace: Workspace): LanguageMode {
let cssLanguageService = getCSSLanguageService();
export function getCSSMode(cssLanguageService: CSSLanguageService, documentRegions: LanguageModelCache<HTMLDocumentRegions>, workspace: Workspace): LanguageMode {
let embeddedCSSDocuments = getLanguageModelCache<TextDocument>(10, 60, document => documentRegions.get(document).getEmbeddedDocument('css'));
let cssStylesheets = getLanguageModelCache<Stylesheet>(10, 60, document => cssLanguageService.parseStylesheet(document));
@@ -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<HTMLDocumentRegions>(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);
@@ -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);
@@ -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 = [];
@@ -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;
@@ -32,7 +32,11 @@ export function getDocumentContext(documentUri: string, workspaceFolders: Worksp
}
}
}
return url.resolve(base, ref);
try {
return url.resolve(base, ref);
} catch {
return '';
}
},
};
}
@@ -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"
@@ -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",
@@ -154,7 +154,8 @@ connection.onInitialize((params: InitializeParams): InitializeResult => {
documentSymbolProvider: true,
documentRangeFormattingProvider: false,
colorProvider: {},
foldingRangeProvider: true
foldingRangeProvider: true,
selectionRangeProvider: true
};
return { capabilities };
@@ -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==
@@ -314,6 +314,7 @@ const preferredFixes = new Set([
'forgottenThisPropertyAccess',
'spelling',
'unusedIdentifier',
'addMissingAwait',
]);
function isPreferredFix(tsAction: Proto.CodeFixAction): boolean {
return preferredFixes.has(tsAction.fixName);
@@ -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<string>().event
const pty: Pseudoterminal = {
onDidWrite: new EventEmitter<string>().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<void> = new Promise<void>(r => startResolve = r);
const writeEmitter = new EventEmitter<string>();
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<string>().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<string>();
const overrideDimensionsEmitter = new EventEmitter<TerminalDimensions>();
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 });
});
});
});
@@ -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<string>();
const execution = new vscode.CustomExecution2((): Thenable<vscode.TerminalVirtualProcess> => {
return Promise.resolve(<vscode.TerminalVirtualProcess>{
const execution = new vscode.CustomExecution2((): Thenable<vscode.Pseudoterminal> => {
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);
+4 -4
View File
@@ -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",
+2 -2
View File
@@ -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"
+8 -8
View File
@@ -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"
+1 -1
View File
@@ -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) {
+5 -1
View File
@@ -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 {
}
}
}
}
}
@@ -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';
+85 -24
View File
@@ -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<T extends IView> {
readonly view: T;
readonly box: Box;
readonly cachedVisibleSize: number | undefined;
}
export interface GridBranchNode<T extends IView> {
@@ -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<T extends IView = IView> extends Disposable {
@@ -208,9 +222,13 @@ export class Grid<T extends IView = IView> 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<T extends IView = IView> 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<T extends IView = IView> 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<T extends IView = IView> extends Disposable {
}
getViews(): GridBranchNode<T> {
return this.gridview.getViews() as GridBranchNode<T>;
return this.gridview.getView() as GridBranchNode<T>;
}
getNeighborViews(view: T, direction: Direction, wrap: boolean = false): T[] {
@@ -355,8 +375,36 @@ export class Grid<T extends IView = IView> 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<T>;
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<T extends ISerializableView> extends Grid<T> {
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<T extends ISerializableView> extends Grid<T> {
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<T extends IView>(node: GridNode<T>): GridLeafNode<T> | undefined {
private static getFirstLeaf<T extends IView>(node: GridNode<T>): GridLeafNode<T> {
if (!isGridBranchNode(node)) {
return node;
}
@@ -473,6 +527,10 @@ export class SerializableGrid<T extends ISerializableView> extends Grid<T> {
throw new Error('Invalid serialized state, first leaf not found');
}
if (typeof firstLeaf.cachedVisibleSize === 'number') {
options = { ...options, firstViewVisibleCachedSize: firstLeaf.cachedVisibleSize };
}
const result = new SerializableGrid<T>(firstLeaf.view, options);
result.orientation = orientation;
result.restoreViews(firstLeaf.view, orientation, root);
@@ -522,13 +580,16 @@ export class SerializableGrid<T extends ISerializableView> extends Grid<T> {
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]);
}
}
+33 -4
View File
@@ -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[] = [];
@@ -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));
}
}
+68 -12
View File
@@ -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);
}
+1 -1
View File
@@ -414,7 +414,7 @@ export function createMatches(score: undefined | FuzzyScore): IMatch[] {
return res;
}
const _maxLen = 53;
const _maxLen = 128;
function initTable() {
const table: number[][] = [];
@@ -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);
});
});
@@ -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();
});
+3 -2
View File
@@ -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;
}
}
}
+15
View File
@@ -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
);
});
});
@@ -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);
}
@@ -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 {
@@ -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; }`);
});
@@ -39,4 +39,10 @@
.monaco-editor .view-overlays {
position: absolute;
top: 0;
}
}
/*
.monaco-editor .auto-closed-character {
opacity: 0.3;
}
*/
@@ -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: {
+120 -4
View File
@@ -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 {
@@ -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);
}
}
+26
View File
@@ -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.
+2 -1
View File
@@ -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 {
@@ -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;
}
@@ -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 {
@@ -7,7 +7,8 @@
export enum MarkerTag {
Unnecessary = 1
Unnecessary = 1,
Deprecated = 2
}
export enum MarkerSeverity {
@@ -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);
}
+12 -2
View File
@@ -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);
+8 -5
View File
@@ -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(<string[]><any>arguments);
return replacePattern.buildReplaceString(<string[]><any>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);
+21
View File
@@ -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);
}
+21
View File
@@ -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 {
+36 -2
View File
@@ -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);
+16 -2
View File
@@ -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 = '';
@@ -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}; }`);
}
});
});
@@ -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');
});
});
@@ -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) {
+12 -17
View File
@@ -50,7 +50,7 @@ interface ISuggestionTemplateData {
iconLabel: IconLabel;
typeLabel: HTMLElement;
readMore: HTMLElement;
disposables: IDisposable[];
disposables: DisposableStore;
}
/**
@@ -106,8 +106,7 @@ class Renderer implements IListRenderer<CompletionItem, ISuggestionTemplateData>
renderTemplate(container: HTMLElement): ISuggestionTemplateData {
const data = <ISuggestionTemplateData>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<CompletionItem, ISuggestionTemplateData>
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<CompletionItem, ISuggestionTemplateData>
configureFont();
disposables.add(Event.chain<IConfigurationChangedEvent>(this.editor.onDidChangeConfiguration.bind(this.editor))
data.disposables.add(Event.chain<IConfigurationChangedEvent>(this.editor.onDidChangeConfiguration.bind(this.editor))
.filter(e => e.fontInfo || e.contribInfo)
.on(configureFont, null));
@@ -218,7 +217,7 @@ class Renderer implements IListRenderer<CompletionItem, ISuggestionTemplateData>
}
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<Compl
});
this.currentSuggestionDetails.then(() => {
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<Compl
}
this._ariaAlert(this._getSuggestionAriaAlertLabel(item));
}).catch(onUnexpectedError).then(() => {
if (this.focusedItem === item) {
this.currentSuggestionDetails = null;
}
});
}).catch(onUnexpectedError);
}
// emit an event
@@ -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');
@@ -163,7 +163,7 @@ export class CursorWordLeftSelect extends WordLeftCommand {
constructor() {
super({
inSelectionMode: true,
wordNavigationType: WordNavigationType.WordStart,
wordNavigationType: WordNavigationType.WordStartFast,
id: 'cursorWordLeftSelect',
precondition: undefined
});
@@ -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) {
@@ -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({
@@ -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();
});
});
+10 -1
View File
@@ -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.
@@ -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) => {
+302 -92
View File
@@ -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 | undefined | null>): ContextKeyExpr {
return new ContextKeyAndExpr(expr);
public static and(...expr: Array<ContextKeyExpr | undefined | null>): ContextKeyExpr | undefined {
return ContextKeyAndExpr.create(expr);
}
public static or(...expr: Array<ContextKeyExpr | undefined | null>): 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 (<ContextKeyNotEqualsExpr>a).cmp(<ContextKeyNotEqualsExpr>b);
case ContextKeyExprType.Regex:
return (<ContextKeyRegexExpr>a).cmp(<ContextKeyRegexExpr>b);
case ContextKeyExprType.NotRegex:
return (<ContextKeyNotRegexExpr>a).cmp(<ContextKeyNotRegexExpr>b);
case ContextKeyExprType.And:
return (<ContextKeyAndExpr>a).cmp(<ContextKeyAndExpr>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<ContextKeyExpr | null | undefined>) {
this.expr = ContextKeyAndExpr._normalizeArr(expr);
public static create(_expr: Array<ContextKeyExpr | null | undefined>): 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 | null | undefined>): 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 | null | undefined>): 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<T> extends ContextKeyDefinedExpr {
@@ -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');
});
});
@@ -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<string, boolean> {
@@ -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);
}
@@ -39,6 +39,7 @@ export interface IRelatedInformation {
export const enum MarkerTag {
Unnecessary = 1,
Deprecated = 2
}
export enum MarkerSeverity {
+10 -2
View File
@@ -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;
+2 -2
View File
@@ -90,7 +90,7 @@ Registry.as<IConfigurationRegistry>(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<IConfigurationRegistry>(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',
+96 -69
View File
@@ -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<TerminalDimensions>;
}
@@ -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<string>();
* 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<string>();
* 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<TerminalDimensions | undefined>;
/**
* 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<string>();
* const exitEmitter = new vscode.EventEmitter<number>();
* const virtualProcess: TerminalVirtualProcess = {
* const closeEmitter = new vscode.EventEmitter<number>();
* 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<number>;
onDidClose?: Event<void>;
/**
* 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<string>();
* 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<TerminalVirtualProcess>);
constructor(callback: (thisArg?: any) => Thenable<Pseudoterminal>);
/**
* 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<TerminalVirtualProcess>;
callback: (thisArg?: any) => Thenable<Pseudoterminal>;
}
/**
@@ -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
}
@@ -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<number | undefined> {
return Promise.resolve(this._proxy.$runInTerminal(args, config));
runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): Promise<number | undefined> {
return Promise.resolve(this._proxy.$runInTerminal(args));
}
// RPC methods (MainThreadDebugServiceShape)
@@ -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<ITerminalProcessExtHostProxy>(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));
@@ -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<IConfig>;
$runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): Promise<number | undefined>;
$runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): Promise<number | undefined>;
$startDASession(handle: number, session: IDebugSessionDto): Promise<void>;
$stopDASession(handle: number): Promise<void>;
$sendDAMessage(handle: number, message: DebugProtocol.ProtocolMessage): void;
@@ -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);
}
@@ -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;
}
+5 -4
View File
@@ -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<vscode.TerminalVirtualProcess>;
constructor(callback: () => Thenable<vscode.TerminalVirtualProcess>) {
private _callback: () => Thenable<vscode.Pseudoterminal>;
constructor(callback: () => Thenable<vscode.Pseudoterminal>) {
this._callback = callback;
}
public computeId(): string {
return 'customExecution' + generateUuid();
}
public set callback(value: () => Thenable<vscode.TerminalVirtualProcess>) {
public set callback(value: () => Thenable<vscode.Pseudoterminal>) {
this._callback = value;
}
public get callback(): (() => Thenable<vscode.TerminalVirtualProcess>) {
public get callback(): (() => Thenable<vscode.Pseudoterminal>) {
return this._callback;
}
}
@@ -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);
@@ -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<number | undefined> {
public async $runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): Promise<number | undefined> {
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);
}
+1 -1
View File
@@ -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.
@@ -124,8 +124,8 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi
this._runQueuedRequests(terminal.id);
}
public async createVirtualProcess(): Promise<void> {
const terminal = await this._proxy.$createTerminal({ name: this._name, isVirtualProcess: true });
public async createExtensionTerminal(): Promise<void> {
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<void> {
public async attachPtyToTerminal(id: number, pty: vscode.Pseudoterminal): Promise<void> {
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<void> {
private async _updateVariableResolver(): Promise<void> {
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<void> {
public async $spawnExtHostProcess(id: number, shellLaunchConfigDto: ShellLaunchConfigDto, activeWorkspaceRootUriComponents: UriComponents, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise<void> {
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<void> {
public async $startExtensionTerminal(id: number, initialDimensions: ITerminalDimensionsDto | undefined): Promise<void> {
// 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<string> | IQueuedEvent<number> | IQueuedEvent<{ pid: number, cwd: string }> | IQueuedEvent<ITerminalDimensions | undefined>)[] = [];
private _queueDisposables: IDisposable[] | undefined;
@@ -789,33 +789,33 @@ class ExtHostVirtualProcess implements ITerminalChildProcess {
public get onProcessOverrideDimensions(): Event<ITerminalDimensions | undefined> { 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);
}
}
}
+5 -1
View File
@@ -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)) {
+5 -5
View File
@@ -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();
+10 -4
View File
@@ -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 };
}
}
}
@@ -466,6 +466,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
}
layout(width: number, height: number): void {
super.layout(width, height);
// Layout contents
this.contentAreaSize = super.layoutContents(width, height).contentSize;

Some files were not shown because too many files have changed in this diff Show More