mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-16 13:21:04 +01:00
Merge remote-tracking branch 'origin/master' into tyriar/puppeteer
This commit is contained in:
+126
-33
@@ -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: [],
|
||||
|
||||
Vendored
+3
-2
@@ -59,5 +59,6 @@
|
||||
"git.ignoreLimitWarning": true,
|
||||
"remote.extensionKind": {
|
||||
"msjsdiag.debugger-for-chrome": "workspace"
|
||||
}
|
||||
}
|
||||
},
|
||||
"files.insertFinalNewline": true
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
Vendored
+4
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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"
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
Vendored
+10
-1
@@ -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) => {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
|
||||
Vendored
+96
-69
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,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)) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user