Merge pull request #115530 from microsoft/joh/norpc

Hide API implementation proxies, add tests to check for "proxy leakage"
This commit is contained in:
Johannes Rieken 2021-02-03 09:17:56 +01:00 committed by GitHub
commit 74937e0a89
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 623 additions and 629 deletions

View file

@ -7,9 +7,12 @@ import 'mocha';
import * as assert from 'assert';
import { join } from 'path';
import { commands, workspace, window, Uri, Range, Position, ViewColumn } from 'vscode';
import { assertNoRpc } from '../utils';
suite('vscode API - commands', () => {
teardown(assertNoRpc);
test('getCommands', function (done) {
let p1 = commands.getCommands().then(commands => {

View file

@ -6,9 +6,12 @@
import 'mocha';
import * as assert from 'assert';
import * as vscode from 'vscode';
import { assertNoRpc } from '../utils';
suite('vscode API - configuration', () => {
teardown(assertNoRpc);
test('configurations, language defaults', function () {
const defaultLanguageSettings = vscode.workspace.getConfiguration().get('[abcLang]');

View file

@ -5,11 +5,13 @@
import * as assert from 'assert';
import { debug, workspace, Disposable, commands, window } from 'vscode';
import { disposeAll } from '../utils';
import { assertNoRpc, disposeAll } from '../utils';
import { basename } from 'path';
suite('vscode API - debug', function () {
teardown(assertNoRpc);
test('breakpoints', async function () {
assert.equal(debug.breakpoints.length, 0);
let onDidChangeBreakpointsCounter = 0;

View file

@ -5,11 +5,14 @@
import * as assert from 'assert';
import { workspace, window, Position, Range, commands, TextEditor, TextDocument, TextEditorCursorStyle, TextEditorLineNumbersStyle, SnippetString, Selection, Uri, env } from 'vscode';
import { createRandomFile, deleteFile, closeAllEditors } from '../utils';
import { createRandomFile, deleteFile, closeAllEditors, assertNoRpc } from '../utils';
suite('vscode API - editors', () => {
teardown(closeAllEditors);
teardown(async function () {
assertNoRpc();
await closeAllEditors();
});
function withRandomFileEditor(initialContents: string, run: (editor: TextEditor, doc: TextDocument) => Thenable<void>): Thenable<boolean> {
return createRandomFile(initialContents).then(file => {

View file

@ -5,9 +5,12 @@
import * as assert from 'assert';
import { env, extensions, ExtensionKind, UIKind, Uri } from 'vscode';
import { assertNoRpc } from '../utils';
suite('vscode API - env', () => {
teardown(assertNoRpc);
test('env is set', function () {
assert.equal(typeof env.language, 'string');
assert.equal(typeof env.appRoot, 'string');

View file

@ -6,10 +6,12 @@
import * as assert from 'assert';
import { join } from 'path';
import * as vscode from 'vscode';
import { createRandomFile, testFs } from '../utils';
import { assertNoRpc, createRandomFile, testFs } from '../utils';
suite('vscode API - languages', () => {
teardown(assertNoRpc);
const isWindows = process.platform === 'win32';
function positionToString(p: vscode.Position) {

View file

@ -5,7 +5,7 @@
import * as assert from 'assert';
import { window, commands } from 'vscode';
import { closeAllEditors } from '../utils';
import { assertNoRpc, closeAllEditors } from '../utils';
interface QuickPickExpected {
events: string[];
@ -20,7 +20,10 @@ interface QuickPickExpected {
suite('vscode API - quick input', function () {
teardown(closeAllEditors);
teardown(async function () {
assertNoRpc();
await closeAllEditors();
});
test('createQuickPick, select second', function (_done) {
let done = (err?: any) => {

View file

@ -0,0 +1,94 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { assertNoRpcFromEntry, assertNoRpc, disposeAll } from '../utils';
import * as vscode from 'vscode';
suite('vscode', function () {
const dispo: vscode.Disposable[] = [];
teardown(() => {
assertNoRpc();
disposeAll(dispo);
});
test('no rpc', function () {
assertNoRpc();
});
test('no rpc, createTextEditorDecorationType(...)', function () {
const item = vscode.window.createTextEditorDecorationType({});
dispo.push(item);
assertNoRpcFromEntry([item, 'TextEditorDecorationType']);
});
test('no rpc, createOutputChannel(...)', function () {
const item = vscode.window.createOutputChannel('hello');
dispo.push(item);
assertNoRpcFromEntry([item, 'OutputChannel']);
});
test('no rpc, createDiagnosticCollection(...)', function () {
const item = vscode.languages.createDiagnosticCollection();
dispo.push(item);
assertNoRpcFromEntry([item, 'DiagnosticCollection']);
});
test('no rpc, createQuickPick(...)', function () {
this.skip();
const item = vscode.window.createQuickPick();
dispo.push(item);
assertNoRpcFromEntry([item, 'QuickPick']);
});
test('no rpc, createInputBox(...)', function () {
this.skip();
const item = vscode.window.createInputBox();
dispo.push(item);
assertNoRpcFromEntry([item, 'InputBox']);
});
test('no rpc, createStatusBarItem(...)', function () {
this.skip();
const item = vscode.window.createStatusBarItem();
dispo.push(item);
assertNoRpcFromEntry([item, 'StatusBarItem']);
});
test('no rpc, createSourceControl(...)', function () {
this.skip();
const item = vscode.scm.createSourceControl('foo', 'Hello');
dispo.push(item);
assertNoRpcFromEntry([item, 'SourceControl']);
});
test('no rpc, createCommentController(...)', function () {
this.skip();
const item = vscode.comments.createCommentController('foo', 'Hello');
dispo.push(item);
assertNoRpcFromEntry([item, 'CommentController']);
});
test('no rpc, createWebviewPanel(...)', function () {
const item = vscode.window.createWebviewPanel('webview', 'Hello', vscode.ViewColumn.Active);
dispo.push(item);
assertNoRpcFromEntry([item, 'WebviewPanel']);
});
test('no rpc, createTreeView(...)', function () {
const treeDataProvider = new class implements vscode.TreeDataProvider<string> {
getTreeItem(element: string): vscode.TreeItem | Thenable<vscode.TreeItem> {
return new vscode.TreeItem(element);
}
getChildren(_element?: string): vscode.ProviderResult<string[]> {
return ['foo', 'bar'];
}
};
const item = vscode.window.createTreeView('test.treeId', { treeDataProvider });
dispo.push(item);
assertNoRpcFromEntry([item, 'TreeView']);
});
});

View file

@ -5,6 +5,7 @@
import { window, Pseudoterminal, EventEmitter, TerminalDimensions, workspace, ConfigurationTarget, Disposable, UIKind, env, EnvironmentVariableMutatorType, EnvironmentVariableMutator, extensions, ExtensionContext, TerminalOptions, ExtensionTerminalOptions } from 'vscode';
import { doesNotThrow, equal, ok, deepEqual, throws } from 'assert';
import { assertNoRpc } from '../utils';
// Disable terminal tests:
// - Web https://github.com/microsoft/vscode/issues/92826
@ -30,6 +31,7 @@ import { doesNotThrow, equal, ok, deepEqual, throws } from 'assert';
let disposables: Disposable[] = [];
teardown(() => {
assertNoRpc();
disposables.forEach(d => d.dispose());
disposables.length = 0;
});

View file

@ -6,9 +6,12 @@
import 'mocha';
import * as assert from 'assert';
import * as vscode from 'vscode';
import { assertNoRpc } from '../utils';
suite('vscode API - types', () => {
teardown(assertNoRpc);
test('static properties, es5 compat class', function () {
assert.ok(vscode.ThemeIcon.File instanceof vscode.ThemeIcon);
assert.ok(vscode.ThemeIcon.Folder instanceof vscode.ThemeIcon);

View file

@ -7,7 +7,7 @@ import * as assert from 'assert';
import 'mocha';
import * as os from 'os';
import * as vscode from 'vscode';
import { closeAllEditors, delay, disposeAll } from '../utils';
import { assertNoRpc, closeAllEditors, delay, disposeAll } from '../utils';
const webviewId = 'myWebview';
@ -26,8 +26,8 @@ suite.skip('vscode API - webview', () => {
}
teardown(async () => {
assertNoRpc();
await closeAllEditors();
disposeAll(disposables);
});

View file

@ -6,12 +6,15 @@
import * as assert from 'assert';
import { workspace, window, commands, ViewColumn, TextEditorViewColumnChangeEvent, Uri, Selection, Position, CancellationTokenSource, TextEditorSelectionChangeKind, QuickPickItem, TextEditor } from 'vscode';
import { join } from 'path';
import { closeAllEditors, pathEquals, createRandomFile } from '../utils';
import { closeAllEditors, pathEquals, createRandomFile, assertNoRpc } from '../utils';
suite('vscode API - window', () => {
teardown(closeAllEditors);
teardown(async function () {
assertNoRpc();
await closeAllEditors();
});
test('editor, active text editor', async () => {
const doc = await workspace.openTextDocument(join(workspace.rootPath || '', './far.js'));
@ -429,8 +432,8 @@ suite('vscode API - window', () => {
});
test('showQuickPick, select first two', async function () {
const label = 'showQuickPick, select first two';
let i = 0;
// const label = 'showQuickPick, select first two';
// let i = 0;
const resolves: ((value: string) => void)[] = [];
let done: () => void;
const unexpected = new Promise<void>((resolve, reject) => {
@ -442,26 +445,26 @@ suite('vscode API - window', () => {
canPickMany: true
});
const first = new Promise(resolve => resolves.push(resolve));
console.log(`${label}: ${++i}`);
// console.log(`${label}: ${++i}`);
await new Promise(resolve => setTimeout(resolve, 100)); // Allow UI to update.
console.log(`${label}: ${++i}`);
// console.log(`${label}: ${++i}`);
await commands.executeCommand('workbench.action.quickOpenSelectNext');
console.log(`${label}: ${++i}`);
// console.log(`${label}: ${++i}`);
assert.equal(await first, 'eins');
console.log(`${label}: ${++i}`);
// console.log(`${label}: ${++i}`);
await commands.executeCommand('workbench.action.quickPickManyToggle');
console.log(`${label}: ${++i}`);
// console.log(`${label}: ${++i}`);
const second = new Promise(resolve => resolves.push(resolve));
await commands.executeCommand('workbench.action.quickOpenSelectNext');
console.log(`${label}: ${++i}`);
// console.log(`${label}: ${++i}`);
assert.equal(await second, 'zwei');
console.log(`${label}: ${++i}`);
// console.log(`${label}: ${++i}`);
await commands.executeCommand('workbench.action.quickPickManyToggle');
console.log(`${label}: ${++i}`);
// console.log(`${label}: ${++i}`);
await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem');
console.log(`${label}: ${++i}`);
// console.log(`${label}: ${++i}`);
assert.deepStrictEqual(await picks, ['eins', 'zwei']);
console.log(`${label}: ${++i}`);
// console.log(`${label}: ${++i}`);
done!();
return unexpected;
});

View file

@ -5,16 +5,15 @@
import * as assert from 'assert';
import * as vscode from 'vscode';
import { createRandomFile, withLogDisabled } from '../utils';
import { assertNoRpc, createRandomFile, disposeAll, withLogDisabled } from '../utils';
suite('vscode API - workspace events', () => {
const disposables: vscode.Disposable[] = [];
teardown(() => {
for (const dispo of disposables) {
dispo.dispose();
}
assertNoRpc();
disposeAll(disposables);
disposables.length = 0;
});

View file

@ -6,6 +6,7 @@
import * as assert from 'assert';
import * as vscode from 'vscode';
import { posix } from 'path';
import { assertNoRpc } from '../utils';
suite('vscode API - workspace-fs', () => {
@ -15,6 +16,8 @@ suite('vscode API - workspace-fs', () => {
root = vscode.workspace.workspaceFolders![0]!.uri;
});
teardown(assertNoRpc);
test('fs.stat', async function () {
const stat = await vscode.workspace.fs.stat(root);
assert.equal(stat.type, vscode.FileType.Directory);

View file

@ -5,6 +5,7 @@
import * as assert from 'assert';
import { window, tasks, Disposable, TaskDefinition, Task, EventEmitter, CustomExecution, Pseudoterminal, TaskScope, commands, env, UIKind, ShellExecution, TaskExecution, Terminal, Event } from 'vscode';
import { assertNoRpc } from '../utils';
// Disable tasks tests:
// - Web https://github.com/microsoft/vscode/issues/90528
@ -14,6 +15,7 @@ import { window, tasks, Disposable, TaskDefinition, Task, EventEmitter, CustomEx
let disposables: Disposable[] = [];
teardown(() => {
assertNoRpc();
disposables.forEach(d => d.dispose());
disposables.length = 0;
});

View file

@ -5,14 +5,17 @@
import * as assert from 'assert';
import * as vscode from 'vscode';
import { createRandomFile, deleteFile, closeAllEditors, pathEquals, rndName, disposeAll, testFs, delay, withLogDisabled, revertAllDirty } from '../utils';
import { createRandomFile, deleteFile, closeAllEditors, pathEquals, rndName, disposeAll, testFs, delay, withLogDisabled, revertAllDirty, assertNoRpc } from '../utils';
import { join, posix, basename } from 'path';
import * as fs from 'fs';
import { TestFS } from '../memfs';
suite('vscode API - workspace', () => {
teardown(closeAllEditors);
teardown(async function () {
assertNoRpc();
await closeAllEditors();
});
test('MarkdownString', function () {
let md = new vscode.MarkdownString();

View file

@ -72,3 +72,48 @@ export function withLogDisabled(runnable: () => Promise<any>): () => Promise<voi
}
};
}
export function assertNoRpc() {
assertNoRpcFromEntry([vscode, 'vscode']);
}
export function assertNoRpcFromEntry(entry: [obj: any, name: string]) {
const symProxy = Symbol.for('rpcProxy');
const symProtocol = Symbol.for('rpcProtocol');
const proxyPaths: string[] = [];
const rpcPaths: string[] = [];
function walk(obj: any, path: string, seen: Set<any>) {
if (!obj) {
return;
}
if (typeof obj !== 'object' && typeof obj !== 'function') {
return;
}
if (seen.has(obj)) {
return;
}
seen.add(obj);
if (obj[symProtocol]) {
rpcPaths.push(`PROTOCOL via ${path}`);
}
if (obj[symProxy]) {
proxyPaths.push(`PROXY '${obj[symProxy]}' via ${path}`);
}
for (const key in obj) {
walk(obj[key], `${path}.${String(key)}`, seen);
}
}
try {
walk(entry[0], entry[1], new Set());
} catch (err) {
assert.fail(err);
}
assert.strictEqual(rpcPaths.length, 0, rpcPaths.join('\n'));
assert.strictEqual(proxyPaths.length, 0, proxyPaths.join('\n')); // happens...
}

View file

@ -79,7 +79,6 @@ export class MainThreadTextEditorProperties {
return {
insertSpaces: modelOptions.insertSpaces,
tabSize: modelOptions.tabSize,
indentSize: modelOptions.indentSize,
cursorStyle: cursorStyle,
lineNumbers: lineNumbers
};
@ -146,7 +145,6 @@ export class MainThreadTextEditorProperties {
}
return (
a.tabSize === b.tabSize
&& a.indentSize === b.indentSize
&& a.insertSpaces === b.insertSpaces
&& a.cursorStyle === b.cursorStyle
&& a.lineNumbers === b.lineNumbers
@ -377,13 +375,6 @@ export class MainThreadTextEditor {
if (typeof newConfiguration.tabSize !== 'undefined') {
newOpts.tabSize = newConfiguration.tabSize;
}
if (typeof newConfiguration.indentSize !== 'undefined') {
if (newConfiguration.indentSize === 'tabSize') {
newOpts.indentSize = newOpts.tabSize || creationOpts.tabSize;
} else {
newOpts.indentSize = newConfiguration.indentSize;
}
}
this._model.updateOptions(newOpts);
}

View file

@ -262,7 +262,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
registerDiffInformationCommand: (id: string, callback: (diff: vscode.LineChange[], ...args: any[]) => any, thisArg?: any): vscode.Disposable => {
checkProposedApiEnabled(extension);
return extHostCommands.registerCommand(true, id, async (...args: any[]): Promise<any> => {
const activeTextEditor = extHostEditors.getActiveTextEditor();
const activeTextEditor = extHostDocumentsAndEditors.activeEditor(true);
if (!activeTextEditor) {
extHostLogService.warn('Cannot execute ' + id + ' because there is no active text editor.');
return undefined;
@ -288,9 +288,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
get appName() { return initData.environment.appName; },
get appRoot() { return initData.environment.appRoot?.fsPath ?? ''; },
get uriScheme() { return initData.environment.appUriScheme; },
get clipboard(): vscode.Clipboard {
return extHostClipboard;
},
get clipboard(): vscode.Clipboard { return extHostClipboard.value; },
get shell() {
return extHostTerminalService.getDefaultShell(false, configProvider);
},
@ -831,7 +829,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
return extHostFileSystem.registerFileSystemProvider(extension.identifier, scheme, provider, options);
},
get fs() {
return extHostConsumerFileSystem;
return extHostConsumerFileSystem.value;
},
registerFileSearchProvider: (scheme: string, provider: vscode.FileSearchProvider) => {
checkProposedApiEnabled(extension);
@ -1292,9 +1290,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
class Extension<T> implements vscode.Extension<T> {
private _extensionService: IExtHostExtensionService;
private _originExtensionId: ExtensionIdentifier;
private _identifier: ExtensionIdentifier;
#extensionService: IExtHostExtensionService;
#originExtensionId: ExtensionIdentifier;
#identifier: ExtensionIdentifier;
readonly id: string;
readonly extensionUri: URI;
@ -1303,9 +1301,9 @@ class Extension<T> implements vscode.Extension<T> {
readonly extensionKind: vscode.ExtensionKind;
constructor(extensionService: IExtHostExtensionService, originExtensionId: ExtensionIdentifier, description: IExtensionDescription, kind: extHostTypes.ExtensionKind) {
this._extensionService = extensionService;
this._originExtensionId = originExtensionId;
this._identifier = description.identifier;
this.#extensionService = extensionService;
this.#originExtensionId = originExtensionId;
this.#identifier = description.identifier;
this.id = description.identifier.value;
this.extensionUri = description.extensionLocation;
this.extensionPath = path.normalize(originalFSPath(description.extensionLocation));
@ -1314,17 +1312,17 @@ class Extension<T> implements vscode.Extension<T> {
}
get isActive(): boolean {
return this._extensionService.isActivated(this._identifier);
return this.#extensionService.isActivated(this.#identifier);
}
get exports(): T {
if (this.packageJSON.api === 'none') {
return undefined!; // Strict nulloverride - Public api
}
return <T>this._extensionService.getExtensionExports(this._identifier);
return <T>this.#extensionService.getExtensionExports(this.#identifier);
}
activate(): Thenable<T> {
return this._extensionService.activateByIdWithErrors(this._identifier, { startup: false, extensionId: this._originExtensionId, activationEvent: 'api' }).then(() => this.exports);
return this.#extensionService.activateByIdWithErrors(this.#identifier, { startup: false, extensionId: this.#originExtensionId, activationEvent: 'api' }).then(() => this.exports);
}
}

View file

@ -237,7 +237,6 @@ export interface MainThreadDocumentsShape extends IDisposable {
export interface ITextEditorConfigurationUpdate {
tabSize?: number | 'auto';
indentSize?: number | 'tabSize';
insertSpaces?: boolean | 'auto';
cursorStyle?: TextEditorCursorStyle;
lineNumbers?: RenderLineNumbersType;
@ -245,7 +244,6 @@ export interface ITextEditorConfigurationUpdate {
export interface IResolvedTextEditorConfiguration {
tabSize: number;
indentSize: number;
insertSpaces: boolean;
cursorStyle: TextEditorCursorStyle;
lineNumbers: RenderLineNumbersType;

View file

@ -3,22 +3,22 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IMainContext, MainContext, MainThreadClipboardShape } from 'vs/workbench/api/common/extHost.protocol';
import { IMainContext, MainContext } from 'vs/workbench/api/common/extHost.protocol';
import type * as vscode from 'vscode';
export class ExtHostClipboard implements vscode.Clipboard {
export class ExtHostClipboard {
private readonly _proxy: MainThreadClipboardShape;
readonly value: vscode.Clipboard;
constructor(mainContext: IMainContext) {
this._proxy = mainContext.getProxy(MainContext.MainThreadClipboard);
}
readText(): Promise<string> {
return this._proxy.$readText();
}
writeText(value: string): Promise<void> {
return this._proxy.$writeText(value);
const proxy = mainContext.getProxy(MainContext.MainThreadClipboard);
this.value = Object.freeze({
readText() {
return proxy.$readText();
},
writeText(value: string) {
return proxy.$writeText(value);
}
});
}
}

View file

@ -44,8 +44,8 @@ export class ExtHostEditorInsets implements ExtHostEditorInsetsShape {
createWebviewEditorInset(editor: vscode.TextEditor, line: number, height: number, options: vscode.WebviewOptions | undefined, extension: IExtensionDescription): vscode.WebviewEditorInset {
let apiEditor: ExtHostTextEditor | undefined;
for (const candidate of this._editors.getVisibleTextEditors()) {
if (candidate === editor) {
for (const candidate of this._editors.getVisibleTextEditors(true)) {
if (candidate.value === editor) {
apiEditor = <ExtHostTextEditor>candidate;
break;
}
@ -121,7 +121,7 @@ export class ExtHostEditorInsets implements ExtHostEditorInsetsShape {
}
};
this._proxy.$createEditorInset(handle, apiEditor.id, apiEditor.document.uri, line + 1, height, options || {}, extension.identifier, extension.extensionLocation);
this._proxy.$createEditorInset(handle, apiEditor.id, apiEditor.value.document.uri, line + 1, height, options || {}, extension.identifier, extension.extensionLocation);
this._insets.set(handle, { editor, inset, onDidReceiveMessage });
return inset;

View file

@ -89,7 +89,7 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E
get onDidReceiveDebugSessionCustomEvent(): Event<vscode.DebugSessionCustomEvent> { return this._onDidReceiveDebugSessionCustomEvent.event; }
private _activeDebugConsole: ExtHostDebugConsole;
get activeDebugConsole(): ExtHostDebugConsole { return this._activeDebugConsole; }
get activeDebugConsole(): vscode.DebugConsole { return this._activeDebugConsole.value; }
private _breakpoints: Map<string, vscode.Breakpoint>;
private _breakpointEventsActive: boolean;
@ -911,20 +911,20 @@ export class ExtHostDebugSession implements vscode.DebugSession {
}
}
export class ExtHostDebugConsole implements vscode.DebugConsole {
export class ExtHostDebugConsole {
private _debugServiceProxy: MainThreadDebugServiceShape;
readonly value: vscode.DebugConsole;
constructor(proxy: MainThreadDebugServiceShape) {
this._debugServiceProxy = proxy;
}
append(value: string): void {
this._debugServiceProxy.$appendDebugConsole(value);
}
appendLine(value: string): void {
this.append(value + '\n');
this.value = Object.freeze({
append(value: string): void {
proxy.$appendDebugConsole(value);
},
appendLine(value: string): void {
this.append(value + '\n');
}
});
}
}

View file

@ -18,6 +18,7 @@ import { ILogService } from 'vs/platform/log/common/log';
import { ResourceMap } from 'vs/base/common/map';
import { Schemas } from 'vs/base/common/network';
import { Iterable } from 'vs/base/common/iterator';
import { Lazy } from 'vs/base/common/lazy';
class Reference<T> {
private _count = 0;
@ -49,13 +50,13 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
private readonly _onDidAddDocuments = new Emitter<ExtHostDocumentData[]>();
private readonly _onDidRemoveDocuments = new Emitter<ExtHostDocumentData[]>();
private readonly _onDidChangeVisibleTextEditors = new Emitter<ExtHostTextEditor[]>();
private readonly _onDidChangeActiveTextEditor = new Emitter<ExtHostTextEditor | undefined>();
private readonly _onDidChangeVisibleTextEditors = new Emitter<vscode.TextEditor[]>();
private readonly _onDidChangeActiveTextEditor = new Emitter<vscode.TextEditor | undefined>();
readonly onDidAddDocuments: Event<ExtHostDocumentData[]> = this._onDidAddDocuments.event;
readonly onDidRemoveDocuments: Event<ExtHostDocumentData[]> = this._onDidRemoveDocuments.event;
readonly onDidChangeVisibleTextEditors: Event<ExtHostTextEditor[]> = this._onDidChangeVisibleTextEditors.event;
readonly onDidChangeActiveTextEditor: Event<ExtHostTextEditor | undefined> = this._onDidChangeActiveTextEditor.event;
readonly onDidChangeVisibleTextEditors: Event<vscode.TextEditor[]> = this._onDidChangeVisibleTextEditors.event;
readonly onDidChangeActiveTextEditor: Event<vscode.TextEditor | undefined> = this._onDidChangeActiveTextEditor.event;
constructor(
@IExtHostRpcService private readonly _extHostRpc: IExtHostRpcService,
@ -135,7 +136,7 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
data.id,
this._extHostRpc.getProxy(MainContext.MainThreadTextEditors),
this._logService,
documentData,
new Lazy(() => documentData.document),
data.selections.map(typeConverters.Selection.to),
data.options,
data.visibleRanges.map(range => typeConverters.Range.to(range)),
@ -162,7 +163,7 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
}
if (delta.removedEditors || delta.addedEditors) {
this._onDidChangeVisibleTextEditors.fire(this.allEditors());
this._onDidChangeVisibleTextEditors.fire(this.allEditors().map(editor => editor.value));
}
if (delta.newActiveEditor !== undefined) {
this._onDidChangeActiveTextEditor.fire(this.activeEditor());
@ -181,11 +182,17 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
return this._editors.get(id);
}
activeEditor(): ExtHostTextEditor | undefined {
activeEditor(): vscode.TextEditor | undefined;
activeEditor(internal: true): ExtHostTextEditor | undefined;
activeEditor(internal?: true): vscode.TextEditor | ExtHostTextEditor | undefined {
if (!this._activeEditorId) {
return undefined;
}
const editor = this._editors.get(this._activeEditorId);
if (internal) {
return editor;
} else {
return this._editors.get(this._activeEditorId);
return editor?.value;
}
}

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { MainThreadFileSystemShape, MainContext } from './extHost.protocol';
import { MainContext } from './extHost.protocol';
import * as vscode from 'vscode';
import * as files from 'vs/platform/files/common/files';
import { FileSystemError } from 'vs/workbench/api/common/extHostTypes';
@ -12,49 +12,51 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo';
export class ExtHostConsumerFileSystem implements vscode.FileSystem {
export class ExtHostConsumerFileSystem {
readonly _serviceBrand: undefined;
private readonly _proxy: MainThreadFileSystemShape;
readonly value: vscode.FileSystem;
constructor(
@IExtHostRpcService extHostRpc: IExtHostRpcService,
@IExtHostFileSystemInfo private readonly _fileSystemInfo: IExtHostFileSystemInfo,
@IExtHostFileSystemInfo fileSystemInfo: IExtHostFileSystemInfo,
) {
this._proxy = extHostRpc.getProxy(MainContext.MainThreadFileSystem);
}
const proxy = extHostRpc.getProxy(MainContext.MainThreadFileSystem);
stat(uri: vscode.Uri): Promise<vscode.FileStat> {
return this._proxy.$stat(uri).catch(ExtHostConsumerFileSystem._handleError);
}
readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> {
return this._proxy.$readdir(uri).catch(ExtHostConsumerFileSystem._handleError);
}
createDirectory(uri: vscode.Uri): Promise<void> {
return this._proxy.$mkdir(uri).catch(ExtHostConsumerFileSystem._handleError);
}
async readFile(uri: vscode.Uri): Promise<Uint8Array> {
return this._proxy.$readFile(uri).then(buff => buff.buffer).catch(ExtHostConsumerFileSystem._handleError);
}
writeFile(uri: vscode.Uri, content: Uint8Array): Promise<void> {
return this._proxy.$writeFile(uri, VSBuffer.wrap(content)).catch(ExtHostConsumerFileSystem._handleError);
}
delete(uri: vscode.Uri, options?: { recursive?: boolean; useTrash?: boolean; }): Promise<void> {
return this._proxy.$delete(uri, { ...{ recursive: false, useTrash: false }, ...options }).catch(ExtHostConsumerFileSystem._handleError);
}
rename(oldUri: vscode.Uri, newUri: vscode.Uri, options?: { overwrite?: boolean; }): Promise<void> {
return this._proxy.$rename(oldUri, newUri, { ...{ overwrite: false }, ...options }).catch(ExtHostConsumerFileSystem._handleError);
}
copy(source: vscode.Uri, destination: vscode.Uri, options?: { overwrite?: boolean; }): Promise<void> {
return this._proxy.$copy(source, destination, { ...{ overwrite: false }, ...options }).catch(ExtHostConsumerFileSystem._handleError);
}
isWritableFileSystem(scheme: string): boolean | undefined {
const capabilities = this._fileSystemInfo.getCapabilities(scheme);
if (typeof capabilities === 'number') {
return !(capabilities & files.FileSystemProviderCapabilities.Readonly);
}
return undefined;
this.value = Object.freeze({
stat(uri: vscode.Uri): Promise<vscode.FileStat> {
return proxy.$stat(uri).catch(ExtHostConsumerFileSystem._handleError);
},
readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> {
return proxy.$readdir(uri).catch(ExtHostConsumerFileSystem._handleError);
},
createDirectory(uri: vscode.Uri): Promise<void> {
return proxy.$mkdir(uri).catch(ExtHostConsumerFileSystem._handleError);
},
async readFile(uri: vscode.Uri): Promise<Uint8Array> {
return proxy.$readFile(uri).then(buff => buff.buffer).catch(ExtHostConsumerFileSystem._handleError);
},
writeFile(uri: vscode.Uri, content: Uint8Array): Promise<void> {
return proxy.$writeFile(uri, VSBuffer.wrap(content)).catch(ExtHostConsumerFileSystem._handleError);
},
delete(uri: vscode.Uri, options?: { recursive?: boolean; useTrash?: boolean; }): Promise<void> {
return proxy.$delete(uri, { ...{ recursive: false, useTrash: false }, ...options }).catch(ExtHostConsumerFileSystem._handleError);
},
rename(oldUri: vscode.Uri, newUri: vscode.Uri, options?: { overwrite?: boolean; }): Promise<void> {
return proxy.$rename(oldUri, newUri, { ...{ overwrite: false }, ...options }).catch(ExtHostConsumerFileSystem._handleError);
},
copy(source: vscode.Uri, destination: vscode.Uri, options?: { overwrite?: boolean; }): Promise<void> {
return proxy.$copy(source, destination, { ...{ overwrite: false }, ...options }).catch(ExtHostConsumerFileSystem._handleError);
},
isWritableFileSystem(scheme: string): boolean | undefined {
const capabilities = fileSystemInfo.getCapabilities(scheme);
if (typeof capabilities === 'number') {
return !(capabilities & files.FileSystemProviderCapabilities.Readonly);
}
return undefined;
}
});
}
private static _handleError(err: any): never {

View file

@ -48,7 +48,7 @@ export class ExtensionStoragePaths implements IExtensionStoragePaths {
const storageUri = URI.joinPath(this._environment.workspaceStorageHome, storageName);
try {
await this._extHostFileSystem.stat(storageUri);
await this._extHostFileSystem.value.stat(storageUri);
this._logService.trace('[ExtHostStorage] storage dir already exists', storageUri);
return storageUri;
} catch {
@ -57,8 +57,8 @@ export class ExtensionStoragePaths implements IExtensionStoragePaths {
try {
this._logService.trace('[ExtHostStorage] creating dir and metadata-file', storageUri);
await this._extHostFileSystem.createDirectory(storageUri);
await this._extHostFileSystem.writeFile(
await this._extHostFileSystem.value.createDirectory(storageUri);
await this._extHostFileSystem.value.writeFile(
URI.joinPath(storageUri, 'meta.json'),
new TextEncoder().encode(JSON.stringify({
id: this._workspace.id,

View file

@ -341,7 +341,10 @@ export namespace TaskFilterDTO {
class TaskExecutionImpl implements vscode.TaskExecution {
constructor(private readonly _tasks: ExtHostTaskBase, readonly _id: string, private readonly _task: vscode.Task) {
readonly #tasks: ExtHostTaskBase;
constructor(tasks: ExtHostTaskBase, readonly _id: string, private readonly _task: vscode.Task) {
this.#tasks = tasks;
}
public get task(): vscode.Task {
@ -349,7 +352,7 @@ class TaskExecutionImpl implements vscode.TaskExecution {
}
public terminate(): void {
this._tasks.terminateTask(this);
this.#tasks.terminateTask(this);
}
public fireDidStartProcess(value: tasks.TaskProcessStartedDTO): void {

View file

@ -47,7 +47,7 @@ export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, ID
export const IExtHostTerminalService = createDecorator<IExtHostTerminalService>('IExtHostTerminalService');
export class ExtHostTerminal implements vscode.Terminal {
export class ExtHostTerminal {
private _disposed: boolean = false;
private _pidPromise: Promise<number | undefined>;
private _cols: number | undefined;
@ -57,6 +57,8 @@ export class ExtHostTerminal implements vscode.Terminal {
public isOpen: boolean = false;
readonly value: vscode.Terminal;
constructor(
private _proxy: MainThreadTerminalServiceShape,
public _id: TerminalIdentifier,
@ -65,6 +67,49 @@ export class ExtHostTerminal implements vscode.Terminal {
) {
this._creationOptions = Object.freeze(this._creationOptions);
this._pidPromise = new Promise<number | undefined>(c => this._pidPromiseComplete = c);
const that = this;
this.value = {
get name(): string {
return that._name || '';
},
get processId(): Promise<number | undefined> {
return that._pidPromise;
},
get creationOptions(): Readonly<vscode.TerminalOptions | vscode.ExtensionTerminalOptions> {
return that._creationOptions;
},
get exitStatus(): vscode.TerminalExitStatus | undefined {
return that._exitStatus;
},
sendText(text: string, addNewLine: boolean = true): void {
that._checkDisposed();
that._proxy.$sendText(that._id, text, addNewLine);
},
show(preserveFocus: boolean): void {
that._checkDisposed();
that._proxy.$show(that._id, preserveFocus);
},
hide(): void {
that._checkDisposed();
that._proxy.$hide(that._id);
},
dispose(): void {
if (!that._disposed) {
that._disposed = true;
that._proxy.$dispose(that._id);
}
},
get dimensions(): vscode.TerminalDimensions | undefined {
if (that._cols === undefined || that._rows === undefined) {
return undefined;
}
return {
columns: that._cols,
rows: that._rows
};
}
};
}
public async create(
@ -95,41 +140,16 @@ export class ExtHostTerminal implements vscode.Terminal {
return this._id;
}
public dispose(): void {
if (!this._disposed) {
this._disposed = true;
this._proxy.$dispose(this._id);
}
}
private _checkDisposed() {
if (this._disposed) {
throw new Error('Terminal has already been disposed');
}
}
public get name(): string {
return this._name || '';
}
public set name(name: string) {
this._name = name;
}
public get exitStatus(): vscode.TerminalExitStatus | undefined {
return this._exitStatus;
}
public get dimensions(): vscode.TerminalDimensions | undefined {
if (this._cols === undefined || this._rows === undefined) {
return undefined;
}
return {
columns: this._cols,
rows: this._rows
};
}
public setExitCode(code: number | undefined) {
this._exitStatus = Object.freeze({ code });
}
@ -147,29 +167,6 @@ export class ExtHostTerminal implements vscode.Terminal {
return true;
}
public get processId(): Promise<number | undefined> {
return this._pidPromise;
}
public get creationOptions(): Readonly<vscode.TerminalOptions | vscode.ExtensionTerminalOptions> {
return this._creationOptions;
}
public sendText(text: string, addNewLine: boolean = true): void {
this._checkDisposed();
this._proxy.$sendText(this._id, text, addNewLine);
}
public show(preserveFocus: boolean): void {
this._checkDisposed();
this._proxy.$show(this._id, preserveFocus);
}
public hide(): void {
this._checkDisposed();
this._proxy.$hide(this._id);
}
public _setProcessId(processId: number | undefined): void {
// The event may fire 2 times when the panel is restored
if (this._pidPromiseComplete) {
@ -284,8 +281,8 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
private readonly _terminalLinkCache: Map<number, Map<number, ICachedLinkEntry>> = new Map();
private readonly _terminalLinkCancellationSource: Map<number, CancellationTokenSource> = new Map();
public get activeTerminal(): ExtHostTerminal | undefined { return this._activeTerminal; }
public get terminals(): ExtHostTerminal[] { return this._terminals; }
public get activeTerminal(): vscode.Terminal | undefined { return this._activeTerminal?.value; }
public get terminals(): vscode.Terminal[] { return this._terminals.map(term => term.value); }
protected readonly _onDidCloseTerminal: Emitter<vscode.Terminal> = new Emitter<vscode.Terminal>();
public get onDidCloseTerminal(): Event<vscode.Terminal> { return this._onDidCloseTerminal && this._onDidCloseTerminal.event; }
@ -336,7 +333,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
this._terminalProcessDisposables[id] = disposable;
});
this._terminals.push(terminal);
return terminal;
return terminal.value;
}
public attachPtyToTerminal(id: number, pty: vscode.Pseudoterminal): void {
@ -362,7 +359,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
if (terminal) {
this._activeTerminal = terminal;
if (original !== this._activeTerminal) {
this._onDidChangeActiveTerminal.fire(this._activeTerminal);
this._onDidChangeActiveTerminal.fire(this._activeTerminal.value);
}
}
}
@ -370,7 +367,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
public async $acceptTerminalProcessData(id: number, data: string): Promise<void> {
const terminal = this._getTerminalById(id);
if (terminal) {
this._onDidWriteTerminalData.fire({ terminal, data });
this._onDidWriteTerminalData.fire({ terminal: terminal.value, data });
}
}
@ -379,8 +376,8 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
if (terminal) {
if (terminal.setDimensions(cols, rows)) {
this._onDidChangeTerminalDimensions.fire({
terminal: terminal,
dimensions: terminal.dimensions as vscode.TerminalDimensions
terminal: terminal.value,
dimensions: terminal.value.dimensions as vscode.TerminalDimensions
});
}
}
@ -400,11 +397,11 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
}
public async $acceptTerminalClosed(id: number, exitCode: number | undefined): Promise<void> {
const index = this._getTerminalObjectIndexById(this.terminals, id);
const index = this._getTerminalObjectIndexById(this._terminals, id);
if (index !== null) {
const terminal = this._terminals.splice(index, 1)[0];
terminal.setExitCode(exitCode);
this._onDidCloseTerminal.fire(terminal);
this._onDidCloseTerminal.fire(terminal.value);
}
}
@ -414,9 +411,9 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
const index = this._getTerminalObjectIndexById(this._terminals, extHostTerminalId);
if (index !== null) {
// The terminal has already been created (via createTerminal*), only fire the event
this.terminals[index]._id = id;
this._terminals[index]._id = id;
this._onDidOpenTerminal.fire(this.terminals[index]);
this.terminals[index].isOpen = true;
this._terminals[index].isOpen = true;
return;
}
}
@ -431,7 +428,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
};
const terminal = new ExtHostTerminal(this._proxy, id, creationOptions, name);
this._terminals.push(terminal);
this._onDidOpenTerminal.fire(terminal);
this._onDidOpenTerminal.fire(terminal.value);
terminal.isOpen = true;
}
@ -455,7 +452,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
await new Promise<void>(r => {
// Ensure open is called after onDidOpenTerminal
const listener = this.onDidOpenTerminal(async e => {
if (e === terminal) {
if (e === terminal.value) {
listener.dispose();
r();
}
@ -564,7 +561,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
this._terminalLinkCancellationSource.set(terminalId, cancellationSource);
const result: ITerminalLinkDto[] = [];
const context: vscode.TerminalLinkContext = { terminal, line };
const context: vscode.TerminalLinkContext = { terminal: terminal.value, line };
const promises: vscode.ProviderResult<{ provider: vscode.TerminalLinkProvider, links: vscode.TerminalLink[] }>[] = [];
for (const provider of this._linkProviders) {

View file

@ -10,28 +10,29 @@ import { TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions';
import { IRange } from 'vs/editor/common/core/range';
import { ISingleEditOperation } from 'vs/editor/common/model';
import { IResolvedTextEditorConfiguration, ITextEditorConfigurationUpdate, MainThreadTextEditorsShape } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData';
import * as TypeConverters from 'vs/workbench/api/common/extHostTypeConverters';
import { EndOfLine, Position, Range, Selection, SnippetString, TextEditorLineNumbersStyle, TextEditorRevealType } from 'vs/workbench/api/common/extHostTypes';
import type * as vscode from 'vscode';
import { ILogService } from 'vs/platform/log/common/log';
import { Lazy } from 'vs/base/common/lazy';
export class TextEditorDecorationType implements vscode.TextEditorDecorationType {
export class TextEditorDecorationType {
private static readonly _Keys = new IdGenerator('TextEditorDecorationType');
private _proxy: MainThreadTextEditorsShape;
public key: string;
readonly value: vscode.TextEditorDecorationType;
constructor(proxy: MainThreadTextEditorsShape, options: vscode.DecorationRenderOptions) {
this.key = TextEditorDecorationType._Keys.nextId();
this._proxy = proxy;
this._proxy.$registerTextEditorDecorationType(this.key, TypeConverters.DecorationRenderOptions.from(options));
const key = TextEditorDecorationType._Keys.nextId();
proxy.$registerTextEditorDecorationType(key, TypeConverters.DecorationRenderOptions.from(options));
this.value = Object.freeze({
key,
dispose() {
proxy.$removeTextEditorDecorationType(key);
}
});
}
public dispose(): void {
this._proxy.$removeTextEditorDecorationType(this.key);
}
}
export interface ITextEditOperation {
@ -134,36 +135,63 @@ export class TextEditorEdit {
}
}
export class ExtHostTextEditorOptions implements vscode.TextEditorOptions {
export class ExtHostTextEditorOptions {
private _proxy: MainThreadTextEditorsShape;
private _id: string;
private _logService: ILogService;
private _tabSize!: number;
private _indentSize!: number;
private _insertSpaces!: boolean;
private _cursorStyle!: TextEditorCursorStyle;
private _lineNumbers!: TextEditorLineNumbersStyle;
readonly value: vscode.TextEditorOptions;
constructor(proxy: MainThreadTextEditorsShape, id: string, source: IResolvedTextEditorConfiguration, logService: ILogService) {
this._proxy = proxy;
this._id = id;
this._accept(source);
this._logService = logService;
const that = this;
this.value = {
get tabSize(): number | string {
return that._tabSize;
},
set tabSize(value: number | string) {
that._setTabSize(value);
},
get insertSpaces(): boolean | string {
return that._insertSpaces;
},
set insertSpaces(value: boolean | string) {
that._setInsertSpaces(value);
},
get cursorStyle(): TextEditorCursorStyle {
return that._cursorStyle;
},
set cursorStyle(value: TextEditorCursorStyle) {
that._setCursorStyle(value);
},
get lineNumbers(): TextEditorLineNumbersStyle {
return that._lineNumbers;
},
set lineNumbers(value: TextEditorLineNumbersStyle) {
that._setLineNumbers(value);
}
};
}
public _accept(source: IResolvedTextEditorConfiguration): void {
this._tabSize = source.tabSize;
this._indentSize = source.indentSize;
this._insertSpaces = source.insertSpaces;
this._cursorStyle = source.cursorStyle;
this._lineNumbers = TypeConverters.TextEditorLineNumbersStyle.to(source.lineNumbers);
}
public get tabSize(): number | string {
return this._tabSize;
}
// --- internal: tabSize
private _validateTabSize(value: number | string): number | 'auto' | null {
if (value === 'auto') {
@ -183,7 +211,7 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions {
return null;
}
public set tabSize(value: number | string) {
private _setTabSize(value: number | string) {
const tabSize = this._validateTabSize(value);
if (tabSize === null) {
// ignore invalid call
@ -202,50 +230,7 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions {
}));
}
public get indentSize(): number | string {
return this._indentSize;
}
private _validateIndentSize(value: number | string): number | 'tabSize' | null {
if (value === 'tabSize') {
return 'tabSize';
}
if (typeof value === 'number') {
const r = Math.floor(value);
return (r > 0 ? r : null);
}
if (typeof value === 'string') {
const r = parseInt(value, 10);
if (isNaN(r)) {
return null;
}
return (r > 0 ? r : null);
}
return null;
}
public set indentSize(value: number | string) {
const indentSize = this._validateIndentSize(value);
if (indentSize === null) {
// ignore invalid call
return;
}
if (typeof indentSize === 'number') {
if (this._indentSize === indentSize) {
// nothing to do
return;
}
// reflect the new indentSize value immediately
this._indentSize = indentSize;
}
this._warnOnError(this._proxy.$trySetOptions(this._id, {
indentSize: indentSize
}));
}
public get insertSpaces(): boolean | string {
return this._insertSpaces;
}
// --- internal: insert spaces
private _validateInsertSpaces(value: boolean | string): boolean | 'auto' {
if (value === 'auto') {
@ -254,7 +239,7 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions {
return (value === 'false' ? false : Boolean(value));
}
public set insertSpaces(value: boolean | string) {
private _setInsertSpaces(value: boolean | string) {
const insertSpaces = this._validateInsertSpaces(value);
if (typeof insertSpaces === 'boolean') {
if (this._insertSpaces === insertSpaces) {
@ -269,11 +254,9 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions {
}));
}
public get cursorStyle(): TextEditorCursorStyle {
return this._cursorStyle;
}
// --- internal: cursor style
public set cursorStyle(value: TextEditorCursorStyle) {
private _setCursorStyle(value: TextEditorCursorStyle) {
if (this._cursorStyle === value) {
// nothing to do
return;
@ -284,11 +267,9 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions {
}));
}
public get lineNumbers(): TextEditorLineNumbersStyle {
return this._lineNumbers;
}
// --- internal: line number
public set lineNumbers(value: TextEditorLineNumbersStyle) {
private _setLineNumbers(value: TextEditorLineNumbersStyle) {
if (this._lineNumbers === value) {
// nothing to do
return;
@ -368,31 +349,170 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions {
}
}
export class ExtHostTextEditor implements vscode.TextEditor {
private readonly _documentData: ExtHostDocumentData;
export class ExtHostTextEditor {
private _selections: Selection[];
private _options: ExtHostTextEditorOptions;
private _visibleRanges: Range[];
private _viewColumn: vscode.ViewColumn | undefined;
private _disposed: boolean = false;
private _hasDecorationsForKey: { [key: string]: boolean; };
private _hasDecorationsForKey = new Set<string>();
readonly value: vscode.TextEditor;
constructor(
readonly id: string,
private readonly _proxy: MainThreadTextEditorsShape,
private readonly _logService: ILogService,
document: ExtHostDocumentData,
document: Lazy<vscode.TextDocument>,
selections: Selection[], options: IResolvedTextEditorConfiguration,
visibleRanges: Range[], viewColumn: vscode.ViewColumn | undefined
) {
this._documentData = document;
this._selections = selections;
this._options = new ExtHostTextEditorOptions(this._proxy, this.id, options, _logService);
this._visibleRanges = visibleRanges;
this._viewColumn = viewColumn;
this._hasDecorationsForKey = Object.create(null);
const that = this;
this.value = Object.freeze({
get document(): vscode.TextDocument {
return document.getValue();
},
set document(_value) {
throw readonly('document');
},
// --- selection
get selection(): Selection {
return that._selections && that._selections[0];
},
set selection(value: Selection) {
if (!(value instanceof Selection)) {
throw illegalArgument('selection');
}
that._selections = [value];
that._trySetSelection();
},
get selections(): Selection[] {
return that._selections;
},
set selections(value: Selection[]) {
if (!Array.isArray(value) || value.some(a => !(a instanceof Selection))) {
throw illegalArgument('selections');
}
that._selections = value;
that._trySetSelection();
},
// --- visible ranges
get visibleRanges(): Range[] {
return that._visibleRanges;
},
set visibleRanges(_value: Range[]) {
throw readonly('visibleRanges');
},
// --- options
get options(): vscode.TextEditorOptions {
return that._options.value;
},
set options(value: vscode.TextEditorOptions) {
if (!that._disposed) {
that._options.assign(value);
}
},
// --- view column
get viewColumn(): vscode.ViewColumn | undefined {
return that._viewColumn;
},
set viewColumn(_value) {
throw readonly('viewColumn');
},
// --- edit
edit(callback: (edit: TextEditorEdit) => void, options: { undoStopBefore: boolean; undoStopAfter: boolean; } = { undoStopBefore: true, undoStopAfter: true }): Promise<boolean> {
if (that._disposed) {
return Promise.reject(new Error('TextEditor#edit not possible on closed editors'));
}
const edit = new TextEditorEdit(document.getValue(), options);
callback(edit);
return that._applyEdit(edit);
},
// --- snippet edit
insertSnippet(snippet: SnippetString, where?: Position | readonly Position[] | Range | readonly Range[], options: { undoStopBefore: boolean; undoStopAfter: boolean; } = { undoStopBefore: true, undoStopAfter: true }): Promise<boolean> {
if (that._disposed) {
return Promise.reject(new Error('TextEditor#insertSnippet not possible on closed editors'));
}
let ranges: IRange[];
if (!where || (Array.isArray(where) && where.length === 0)) {
ranges = that._selections.map(range => TypeConverters.Range.from(range));
} else if (where instanceof Position) {
const { lineNumber, column } = TypeConverters.Position.from(where);
ranges = [{ startLineNumber: lineNumber, startColumn: column, endLineNumber: lineNumber, endColumn: column }];
} else if (where instanceof Range) {
ranges = [TypeConverters.Range.from(where)];
} else {
ranges = [];
for (const posOrRange of where) {
if (posOrRange instanceof Range) {
ranges.push(TypeConverters.Range.from(posOrRange));
} else {
const { lineNumber, column } = TypeConverters.Position.from(posOrRange);
ranges.push({ startLineNumber: lineNumber, startColumn: column, endLineNumber: lineNumber, endColumn: column });
}
}
}
return _proxy.$tryInsertSnippet(id, snippet.value, ranges, options);
},
setDecorations(decorationType: vscode.TextEditorDecorationType, ranges: Range[] | vscode.DecorationOptions[]): void {
const willBeEmpty = (ranges.length === 0);
if (willBeEmpty && !that._hasDecorationsForKey.has(decorationType.key)) {
// avoid no-op call to the renderer
return;
}
if (willBeEmpty) {
that._hasDecorationsForKey.delete(decorationType.key);
} else {
that._hasDecorationsForKey.add(decorationType.key);
}
that._runOnProxy(() => {
if (TypeConverters.isDecorationOptionsArr(ranges)) {
return _proxy.$trySetDecorations(
id,
decorationType.key,
TypeConverters.fromRangeOrRangeWithMessage(ranges)
);
} else {
const _ranges: number[] = new Array<number>(4 * ranges.length);
for (let i = 0, len = ranges.length; i < len; i++) {
const range = ranges[i];
_ranges[4 * i] = range.start.line + 1;
_ranges[4 * i + 1] = range.start.character + 1;
_ranges[4 * i + 2] = range.end.line + 1;
_ranges[4 * i + 3] = range.end.character + 1;
}
return _proxy.$trySetDecorationsFast(
id,
decorationType.key,
_ranges
);
}
});
},
revealRange(range: Range, revealType: vscode.TextEditorRevealType): void {
that._runOnProxy(() => _proxy.$tryRevealRange(
id,
TypeConverters.Range.from(range),
(revealType || TextEditorRevealType.Default)
));
},
show(column: vscode.ViewColumn) {
_proxy.$tryShowEditor(id, TypeConverters.ViewColumn.from(column));
},
hide() {
_proxy.$tryHideEditor(id);
}
});
}
dispose() {
@ -400,164 +520,32 @@ export class ExtHostTextEditor implements vscode.TextEditor {
this._disposed = true;
}
show(column: vscode.ViewColumn) {
this._proxy.$tryShowEditor(this.id, TypeConverters.ViewColumn.from(column));
}
hide() {
this._proxy.$tryHideEditor(this.id);
}
// ---- the document
get document(): vscode.TextDocument {
return this._documentData.document;
}
set document(value) {
throw readonly('document');
}
// ---- options
get options(): vscode.TextEditorOptions {
return this._options;
}
set options(value: vscode.TextEditorOptions) {
if (!this._disposed) {
this._options.assign(value);
}
}
// --- incoming: extension host MUST accept what the renderer says
_acceptOptions(options: IResolvedTextEditorConfiguration): void {
ok(!this._disposed);
this._options._accept(options);
}
// ---- visible ranges
get visibleRanges(): Range[] {
return this._visibleRanges;
}
set visibleRanges(value: Range[]) {
throw readonly('visibleRanges');
}
_acceptVisibleRanges(value: Range[]): void {
ok(!this._disposed);
this._visibleRanges = value;
}
// ---- view column
get viewColumn(): vscode.ViewColumn | undefined {
return this._viewColumn;
}
set viewColumn(value) {
throw readonly('viewColumn');
}
_acceptViewColumn(value: vscode.ViewColumn) {
ok(!this._disposed);
this._viewColumn = value;
}
// ---- selections
get selection(): Selection {
return this._selections && this._selections[0];
}
set selection(value: Selection) {
if (!(value instanceof Selection)) {
throw illegalArgument('selection');
}
this._selections = [value];
this._trySetSelection();
}
get selections(): Selection[] {
return this._selections;
}
set selections(value: Selection[]) {
if (!Array.isArray(value) || value.some(a => !(a instanceof Selection))) {
throw illegalArgument('selections');
}
this._selections = value;
this._trySetSelection();
}
setDecorations(decorationType: vscode.TextEditorDecorationType, ranges: Range[] | vscode.DecorationOptions[]): void {
const willBeEmpty = (ranges.length === 0);
if (willBeEmpty && !this._hasDecorationsForKey[decorationType.key]) {
// avoid no-op call to the renderer
return;
}
if (willBeEmpty) {
delete this._hasDecorationsForKey[decorationType.key];
} else {
this._hasDecorationsForKey[decorationType.key] = true;
}
this._runOnProxy(
() => {
if (TypeConverters.isDecorationOptionsArr(ranges)) {
return this._proxy.$trySetDecorations(
this.id,
decorationType.key,
TypeConverters.fromRangeOrRangeWithMessage(ranges)
);
} else {
const _ranges: number[] = new Array<number>(4 * ranges.length);
for (let i = 0, len = ranges.length; i < len; i++) {
const range = ranges[i];
_ranges[4 * i] = range.start.line + 1;
_ranges[4 * i + 1] = range.start.character + 1;
_ranges[4 * i + 2] = range.end.line + 1;
_ranges[4 * i + 3] = range.end.character + 1;
}
return this._proxy.$trySetDecorationsFast(
this.id,
decorationType.key,
_ranges
);
}
}
);
}
revealRange(range: Range, revealType: vscode.TextEditorRevealType): void {
this._runOnProxy(
() => this._proxy.$tryRevealRange(
this.id,
TypeConverters.Range.from(range),
(revealType || TextEditorRevealType.Default)
)
);
}
private _trySetSelection(): Promise<vscode.TextEditor | null | undefined> {
const selection = this._selections.map(TypeConverters.Selection.from);
return this._runOnProxy(() => this._proxy.$trySetSelections(this.id, selection));
}
_acceptSelections(selections: Selection[]): void {
ok(!this._disposed);
this._selections = selections;
}
// ---- editing
edit(callback: (edit: TextEditorEdit) => void, options: { undoStopBefore: boolean; undoStopAfter: boolean; } = { undoStopBefore: true, undoStopAfter: true }): Promise<boolean> {
if (this._disposed) {
return Promise.reject(new Error('TextEditor#edit not possible on closed editors'));
}
const edit = new TextEditorEdit(this._documentData.document, options);
callback(edit);
return this._applyEdit(edit);
private async _trySetSelection(): Promise<vscode.TextEditor | null | undefined> {
const selection = this._selections.map(TypeConverters.Selection.from);
await this._runOnProxy(() => this._proxy.$trySetSelections(this.id, selection));
return this.value;
}
private _applyEdit(editBuilder: TextEditorEdit): Promise<boolean> {
@ -613,44 +601,12 @@ export class ExtHostTextEditor implements vscode.TextEditor {
undoStopAfter: editData.undoStopAfter
});
}
insertSnippet(snippet: SnippetString, where?: Position | readonly Position[] | Range | readonly Range[], options: { undoStopBefore: boolean; undoStopAfter: boolean; } = { undoStopBefore: true, undoStopAfter: true }): Promise<boolean> {
if (this._disposed) {
return Promise.reject(new Error('TextEditor#insertSnippet not possible on closed editors'));
}
let ranges: IRange[];
if (!where || (Array.isArray(where) && where.length === 0)) {
ranges = this._selections.map(range => TypeConverters.Range.from(range));
} else if (where instanceof Position) {
const { lineNumber, column } = TypeConverters.Position.from(where);
ranges = [{ startLineNumber: lineNumber, startColumn: column, endLineNumber: lineNumber, endColumn: column }];
} else if (where instanceof Range) {
ranges = [TypeConverters.Range.from(where)];
} else {
ranges = [];
for (const posOrRange of where) {
if (posOrRange instanceof Range) {
ranges.push(TypeConverters.Range.from(posOrRange));
} else {
const { lineNumber, column } = TypeConverters.Position.from(posOrRange);
ranges.push({ startLineNumber: lineNumber, startColumn: column, endLineNumber: lineNumber, endColumn: column });
}
}
}
return this._proxy.$tryInsertSnippet(this.id, snippet.value, ranges, options);
}
// ---- util
private _runOnProxy(callback: () => Promise<any>): Promise<ExtHostTextEditor | undefined | null> {
if (this._disposed) {
this._logService.warn('TextEditor is closed/disposed');
return Promise.resolve(undefined);
}
return callback().then(() => this, err => {
if (!(err instanceof Error && err.name === 'DISPOSED')) {
this._logService.warn(err);
@ -659,4 +615,3 @@ export class ExtHostTextEditor implements vscode.TextEditor {
});
}
}

View file

@ -41,12 +41,17 @@ export class ExtHostEditors implements ExtHostEditorsShape {
this._extHostDocumentsAndEditors.onDidChangeActiveTextEditor(e => this._onDidChangeActiveTextEditor.fire(e));
}
getActiveTextEditor(): ExtHostTextEditor | undefined {
getActiveTextEditor(): vscode.TextEditor | undefined {
return this._extHostDocumentsAndEditors.activeEditor();
}
getVisibleTextEditors(): vscode.TextEditor[] {
return this._extHostDocumentsAndEditors.allEditors();
getVisibleTextEditors(): vscode.TextEditor[];
getVisibleTextEditors(internal: true): ExtHostTextEditor[];
getVisibleTextEditors(internal?: true): ExtHostTextEditor[] | vscode.TextEditor[] {
const editors = this._extHostDocumentsAndEditors.allEditors();
return internal
? editors
: editors.map(editor => editor.value);
}
showTextDocument(document: vscode.TextDocument, column: vscode.ViewColumn, preserveFocus: boolean): Promise<vscode.TextEditor>;
@ -75,7 +80,7 @@ export class ExtHostEditors implements ExtHostEditorsShape {
const editorId = await this._proxy.$tryShowTextDocument(document.uri, options);
const editor = editorId && this._extHostDocumentsAndEditors.getEditor(editorId);
if (editor) {
return editor;
return editor.value;
}
// we have no editor... having an id means that we had an editor
// on the main side and that it isn't the current editor anymore...
@ -87,7 +92,7 @@ export class ExtHostEditors implements ExtHostEditorsShape {
}
createTextEditorDecorationType(options: vscode.DecorationRenderOptions): vscode.TextEditorDecorationType {
return new TextEditorDecorationType(this._proxy, options);
return new TextEditorDecorationType(this._proxy, options).value;
}
// --- called from main thread
@ -114,7 +119,7 @@ export class ExtHostEditors implements ExtHostEditorsShape {
// (2) fire change events
if (data.options) {
this._onDidChangeTextEditorOptions.fire({
textEditor: textEditor,
textEditor: textEditor.value,
options: { ...data.options, lineNumbers: TypeConverters.TextEditorLineNumbersStyle.to(data.options.lineNumbers) }
});
}
@ -122,7 +127,7 @@ export class ExtHostEditors implements ExtHostEditorsShape {
const kind = TextEditorSelectionChangeKind.fromValue(data.selections.source);
const selections = data.selections.selections.map(TypeConverters.Selection.to);
this._onDidChangeTextEditorSelection.fire({
textEditor,
textEditor: textEditor.value,
selections,
kind
});
@ -130,7 +135,7 @@ export class ExtHostEditors implements ExtHostEditorsShape {
if (data.visibleRanges) {
const visibleRanges = arrays.coalesce(data.visibleRanges.map(TypeConverters.Range.to));
this._onDidChangeTextEditorVisibleRanges.fire({
textEditor,
textEditor: textEditor.value,
visibleRanges
});
}
@ -143,9 +148,9 @@ export class ExtHostEditors implements ExtHostEditorsShape {
throw new Error('Unknown text editor');
}
const viewColumn = TypeConverters.ViewColumn.to(data[id]);
if (textEditor.viewColumn !== viewColumn) {
if (textEditor.value.viewColumn !== viewColumn) {
textEditor._acceptViewColumn(viewColumn);
this._onDidChangeTextEditorViewColumn.fire({ textEditor, viewColumn });
this._onDidChangeTextEditorViewColumn.fire({ textEditor: textEditor.value, viewColumn });
}
}
}

View file

@ -60,7 +60,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
const terminal = new ExtHostTerminal(this._proxy, generateUuid(), { name, shellPath, shellArgs }, name);
this._terminals.push(terminal);
terminal.create(shellPath, shellArgs);
return terminal;
return terminal.value;
}
public createTerminalFromOptions(options: vscode.TerminalOptions, isFeatureTerminal?: boolean): vscode.Terminal {
@ -75,7 +75,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
withNullAsUndefined(options.strictEnv),
withNullAsUndefined(options.hideFromUser),
withNullAsUndefined(isFeatureTerminal));
return terminal;
return terminal.value;
}
public getDefaultShell(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string {

View file

@ -60,8 +60,13 @@ export interface IRPCProtocolLogger {
const noop = () => { };
const _RPCProtocolSymbol = Symbol.for('rpcProtocol');
const _RPCProxySymbol = Symbol.for('rpcProxy');
export class RPCProtocol extends Disposable implements IRPCProtocol {
[_RPCProtocolSymbol] = true;
private static readonly UNRESPONSIVE_TIME = 3 * 1000; // 3s
private readonly _onDidChangeResponsiveState: Emitter<ResponsiveState> = this._register(new Emitter<ResponsiveState>());
@ -182,14 +187,14 @@ export class RPCProtocol extends Disposable implements IRPCProtocol {
}
public getProxy<T>(identifier: ProxyIdentifier<T>): T {
const rpcId = identifier.nid;
const { nid: rpcId, sid } = identifier;
if (!this._proxies[rpcId]) {
this._proxies[rpcId] = this._createProxy(rpcId);
this._proxies[rpcId] = this._createProxy(rpcId, sid);
}
return this._proxies[rpcId];
}
private _createProxy<T>(rpcId: number): T {
private _createProxy<T>(rpcId: number, debugName: string): T {
let handler = {
get: (target: any, name: PropertyKey) => {
if (typeof name === 'string' && !target[name] && name.charCodeAt(0) === CharCode.DollarSign) {
@ -197,6 +202,9 @@ export class RPCProtocol extends Disposable implements IRPCProtocol {
return this._remoteCall(rpcId, name, myArgs);
};
}
if (name === _RPCProxySymbol) {
return debugName;
}
return target[name];
}
};

View file

@ -11,6 +11,7 @@ import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData
import { URI } from 'vs/base/common/uri';
import { mock } from 'vs/base/test/common/mock';
import { NullLogService } from 'vs/platform/log/common/log';
import { Lazy } from 'vs/base/common/lazy';
suite('ExtHostTextEditor', () => {
@ -20,21 +21,21 @@ suite('ExtHostTextEditor', () => {
], '\n', 1, 'text', false);
setup(() => {
editor = new ExtHostTextEditor('fake', null!, new NullLogService(), doc, [], { cursorStyle: 0, insertSpaces: true, lineNumbers: 1, tabSize: 4, indentSize: 4 }, [], 1);
editor = new ExtHostTextEditor('fake', null!, new NullLogService(), new Lazy(() => doc.document), [], { cursorStyle: 0, insertSpaces: true, lineNumbers: 1, tabSize: 4 }, [], 1);
});
test('disposed editor', () => {
assert.ok(editor.document);
assert.ok(editor.value.document);
editor._acceptViewColumn(3);
assert.strictEqual(3, editor.viewColumn);
assert.strictEqual(3, editor.value.viewColumn);
editor.dispose();
assert.throws(() => editor._acceptViewColumn(2));
assert.strictEqual(3, editor.viewColumn);
assert.strictEqual(3, editor.value.viewColumn);
assert.ok(editor.document);
assert.ok(editor.value.document);
assert.throws(() => editor._acceptOptions(null!));
assert.throws(() => editor._acceptSelections([]));
});
@ -47,15 +48,15 @@ suite('ExtHostTextEditor', () => {
applyCount += 1;
return Promise.resolve(true);
}
}, new NullLogService(), doc, [], { cursorStyle: 0, insertSpaces: true, lineNumbers: 1, tabSize: 4, indentSize: 4 }, [], 1);
}, new NullLogService(), new Lazy(() => doc.document), [], { cursorStyle: 0, insertSpaces: true, lineNumbers: 1, tabSize: 4 }, [], 1);
await editor.edit(edit => { });
await editor.value.edit(edit => { });
assert.strictEqual(applyCount, 0);
await editor.edit(edit => { edit.setEndOfLine(1); });
await editor.value.edit(edit => { edit.setEndOfLine(1); });
assert.strictEqual(applyCount, 1);
await editor.edit(edit => { edit.delete(new Range(0, 0, 1, 1)); });
await editor.value.edit(edit => { edit.delete(new Range(0, 0, 1, 1)); });
assert.strictEqual(applyCount, 2);
});
});
@ -89,7 +90,6 @@ suite('ExtHostTextEditorOptions', () => {
};
opts = new ExtHostTextEditorOptions(mockProxy, '1', {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
@ -103,20 +103,18 @@ suite('ExtHostTextEditorOptions', () => {
function assertState(opts: ExtHostTextEditorOptions, expected: IResolvedTextEditorConfiguration): void {
let actual = {
tabSize: opts.tabSize,
indentSize: opts.indentSize,
insertSpaces: opts.insertSpaces,
cursorStyle: opts.cursorStyle,
lineNumbers: opts.lineNumbers
tabSize: opts.value.tabSize,
insertSpaces: opts.value.insertSpaces,
cursorStyle: opts.value.cursorStyle,
lineNumbers: opts.value.lineNumbers
};
assert.deepStrictEqual(actual, expected);
}
test('can set tabSize to the same value', () => {
opts.tabSize = 4;
opts.value.tabSize = 4;
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
@ -125,10 +123,9 @@ suite('ExtHostTextEditorOptions', () => {
});
test('can change tabSize to positive integer', () => {
opts.tabSize = 1;
opts.value.tabSize = 1;
assertState(opts, {
tabSize: 1,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
@ -137,10 +134,9 @@ suite('ExtHostTextEditorOptions', () => {
});
test('can change tabSize to positive float', () => {
opts.tabSize = 2.3;
opts.value.tabSize = 2.3;
assertState(opts, {
tabSize: 2,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
@ -149,10 +145,9 @@ suite('ExtHostTextEditorOptions', () => {
});
test('can change tabSize to a string number', () => {
opts.tabSize = '2';
opts.value.tabSize = '2';
assertState(opts, {
tabSize: 2,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
@ -161,10 +156,9 @@ suite('ExtHostTextEditorOptions', () => {
});
test('tabSize can request indentation detection', () => {
opts.tabSize = 'auto';
opts.value.tabSize = 'auto';
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
@ -173,10 +167,9 @@ suite('ExtHostTextEditorOptions', () => {
});
test('ignores invalid tabSize 1', () => {
opts.tabSize = null!;
opts.value.tabSize = null!;
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
@ -185,10 +178,9 @@ suite('ExtHostTextEditorOptions', () => {
});
test('ignores invalid tabSize 2', () => {
opts.tabSize = -5;
opts.value.tabSize = -5;
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
@ -197,10 +189,9 @@ suite('ExtHostTextEditorOptions', () => {
});
test('ignores invalid tabSize 3', () => {
opts.tabSize = 'hello';
opts.value.tabSize = 'hello';
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
@ -209,130 +200,9 @@ suite('ExtHostTextEditorOptions', () => {
});
test('ignores invalid tabSize 4', () => {
opts.tabSize = '-17';
opts.value.tabSize = '-17';
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepStrictEqual(calls, []);
});
test('can set indentSize to the same value', () => {
opts.indentSize = 4;
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepStrictEqual(calls, []);
});
test('can change indentSize to positive integer', () => {
opts.indentSize = 1;
assertState(opts, {
tabSize: 4,
indentSize: 1,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepStrictEqual(calls, [{ indentSize: 1 }]);
});
test('can change indentSize to positive float', () => {
opts.indentSize = 2.3;
assertState(opts, {
tabSize: 4,
indentSize: 2,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepStrictEqual(calls, [{ indentSize: 2 }]);
});
test('can change indentSize to a string number', () => {
opts.indentSize = '2';
assertState(opts, {
tabSize: 4,
indentSize: 2,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepStrictEqual(calls, [{ indentSize: 2 }]);
});
test('indentSize can request to use tabSize', () => {
opts.indentSize = 'tabSize';
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepStrictEqual(calls, [{ indentSize: 'tabSize' }]);
});
test('indentSize cannot request indentation detection', () => {
opts.indentSize = 'auto';
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepStrictEqual(calls, []);
});
test('ignores invalid indentSize 1', () => {
opts.indentSize = null!;
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepStrictEqual(calls, []);
});
test('ignores invalid indentSize 2', () => {
opts.indentSize = -5;
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepStrictEqual(calls, []);
});
test('ignores invalid indentSize 3', () => {
opts.indentSize = 'hello';
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepStrictEqual(calls, []);
});
test('ignores invalid indentSize 4', () => {
opts.indentSize = '-17';
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
@ -341,10 +211,9 @@ suite('ExtHostTextEditorOptions', () => {
});
test('can set insertSpaces to the same value', () => {
opts.insertSpaces = false;
opts.value.insertSpaces = false;
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
@ -353,10 +222,9 @@ suite('ExtHostTextEditorOptions', () => {
});
test('can set insertSpaces to boolean', () => {
opts.insertSpaces = true;
opts.value.insertSpaces = true;
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: true,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
@ -365,10 +233,9 @@ suite('ExtHostTextEditorOptions', () => {
});
test('can set insertSpaces to false string', () => {
opts.insertSpaces = 'false';
opts.value.insertSpaces = 'false';
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
@ -377,10 +244,9 @@ suite('ExtHostTextEditorOptions', () => {
});
test('can set insertSpaces to truey', () => {
opts.insertSpaces = 'hello';
opts.value.insertSpaces = 'hello';
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: true,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
@ -389,10 +255,9 @@ suite('ExtHostTextEditorOptions', () => {
});
test('insertSpaces can request indentation detection', () => {
opts.insertSpaces = 'auto';
opts.value.insertSpaces = 'auto';
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
@ -401,10 +266,9 @@ suite('ExtHostTextEditorOptions', () => {
});
test('can set cursorStyle to same value', () => {
opts.cursorStyle = TextEditorCursorStyle.Line;
opts.value.cursorStyle = TextEditorCursorStyle.Line;
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
@ -413,10 +277,9 @@ suite('ExtHostTextEditorOptions', () => {
});
test('can change cursorStyle', () => {
opts.cursorStyle = TextEditorCursorStyle.Block;
opts.value.cursorStyle = TextEditorCursorStyle.Block;
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Block,
lineNumbers: RenderLineNumbersType.On
@ -425,10 +288,9 @@ suite('ExtHostTextEditorOptions', () => {
});
test('can set lineNumbers to same value', () => {
opts.lineNumbers = TextEditorLineNumbersStyle.On;
opts.value.lineNumbers = TextEditorLineNumbersStyle.On;
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
@ -437,10 +299,9 @@ suite('ExtHostTextEditorOptions', () => {
});
test('can change lineNumbers', () => {
opts.lineNumbers = TextEditorLineNumbersStyle.Off;
opts.value.lineNumbers = TextEditorLineNumbersStyle.Off;
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.Off
@ -457,7 +318,6 @@ suite('ExtHostTextEditorOptions', () => {
});
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
@ -472,7 +332,6 @@ suite('ExtHostTextEditorOptions', () => {
});
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: true,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
@ -487,7 +346,6 @@ suite('ExtHostTextEditorOptions', () => {
});
assertState(opts, {
tabSize: 3,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
@ -502,7 +360,6 @@ suite('ExtHostTextEditorOptions', () => {
});
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Block,
lineNumbers: RenderLineNumbersType.Relative