don't leak proxies in editor land, also remove indentSize property which isn't API

This commit is contained in:
Johannes Rieken
2021-02-01 11:24:30 +01:00
parent 82c629eb3a
commit a60beb9d7a
8 changed files with 258 additions and 446 deletions

View File

@@ -10,11 +10,11 @@ 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 {
@@ -134,36 +134,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 +210,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 +229,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 +238,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 +253,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 +266,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 +348,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 +519,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 +600,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 +614,3 @@ export class ExtHostTextEditor implements vscode.TextEditor {
});
}
}