src/vs/workbench/api/common/extHostTextEditor.ts

690 LOC · 153 covered · 537 uncovered · 32 ranges · 30 concepts · 1 introducers · 26 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the filesrc/vs/workbench/api/common/extHostDocumentData.ts · 312 LOCcommon/extHostDocumentDa…src/vs/workbench/api/common/extHostDocumentsAndEditors.ts · 197 LOCcommon/extHostDocumentsA…src/vs/workbench/api/common/extHostEditorTabs.ts · 444 LOCcommon/extHostEditorTabs…src/vs/workbench/api/common/extHostMcp.ts · 1202 LOCcommon/extHostMcp.tssrc/vs/workbench/api/common/extHostVariableResolverService.ts · 176 LOCcommon/extHostVariableRe…src/vs/workbench/services/configurationResolver/common/variableResolver.ts · 345 LOCcommon/variableResolver.…extHostMcp.test|title=ExtHostMcp IAuthMetadata properties should expose readonly properties|occurrence=1, extHostMcp.test|title=ExtHostMcp createAuthMetadata should create IAuthMetadata with fetched server metadata|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.ts ×1 · 3 introduced LOCextHostMcp.ts ×1extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should handle multiple Bearer challenges and use first scope|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when scopes are the same|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true and update scopes when WWW-Authenticate header contains new scopes|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when scopes are same but in different order|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true when updating from defined scopes to undefined (no scope in header)|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle non-401 status codes in update()|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should ignore non-Bearer schemes|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true when updating from undefined scopes to defined scopes|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.ts ×1 · 1 introduced LOCextHostMcp.ts ×1extHostMcp.ts ×1 · 2 introduced LOCextHostMcp.ts ×1extHostMcp.ts ×1 · 6 introduced LOCextHostMcp.ts ×1extHostMcp.ts ×1 · 4 introduced LOCextHostMcp.ts ×1extHostMcp.ts ×2 · 4 introduced LOCextHostMcp.ts ×2extHostMcp.ts ×1 · 1 introduced LOCextHostMcp.ts ×1extHostMcp.test|title=ExtHostMcp createAuthMetadata should fall back to default metadata when server metadata fetch fails|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.ts ×4 · 6 introduced LOCextHostMcp.ts ×4extHostMcp.test|title=ExtHostMcp createAuthMetadata should use scopes from WWW-Authenticate header when resource metadata has none|occurrence=1, extHostMcp.test|title=ExtHostMcp createAuthMetadata should use scopes from WWW-Authenticate header even when resource metadata has scopes_supported|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.ts ×1 · 2 introduced LOCextHostMcp.ts ×1extHostMcp.ts ×1 · 6 introduced LOCextHostMcp.ts ×1extHostMcp.ts ×4 · 13 introduced LOCextHostMcp.ts ×4extHostMcpNode.ts ×8 · 45 introduced LOCextHostMcpNode.ts ×8extHostMcp.test|title=ExtHostMcp createAuthMetadata should pass launch headers when fetching metadata from same origin|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.ts ×5 · 25 introduced LOCextHostMcp.ts ×5extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle invalid JSON in resource metadata response|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.ts ×3 · 23 introduced LOCextHostMcp.ts ×3extHostMcp.ts ×1 · 2 introduced LOCextHostMcp.ts ×1extHostMcp.ts ×8 · 60 introduced LOCextHostMcp.ts ×8extHostMcp.ts ×43 · 874 introduced LOCextHostMcp.ts ×43extHostMcp.test|title=ExtHostMcp IAuthMetadata properties should allow undefined scopes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata properties should allow undefined scopes|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata properties should expose readonly properties|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata properties should expose readonly properties|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should handle multiple Bearer challenges and use first scope|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should handle multiple Bearer challenges and use first scope|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should ignore non-Bearer schemes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should ignore non-Bearer schemes|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when no WWW-Authenticate header and scopes are already undefined|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when no WWW-Authenticate header and scopes are already undefined|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when scopes are same but in different order|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when scopes are same but in different order|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when scopes are the same|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when scopes are the same|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true and update scopes when WWW-Authenticate header contains new scopes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true and update scopes when WWW-Authenticate header contains new scopes|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true when updating from defined scopes to undefined (no scope in header)|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true when updating from defined scopes to undefined (no scope in header)|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true when updating from undefined scopes to defined scopes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true when updating from undefined scopes to defined scopes|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should create IAuthMetadata with fetched server metadata|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should create IAuthMetadata with fetched server metadata|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should fall back to default metadata when server metadata fetch fails|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should fall back to default metadata when server metadata fetch fails|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle empty scope string in WWW-Authenticate header|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle empty scope string in WWW-Authenticate header|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle invalid JSON in resource metadata response|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle invalid JSON in resource metadata response|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle malformed WWW-Authenticate header gracefully|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle malformed WWW-Authenticate header gracefully|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle non-401 status codes in update()|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle non-401 status codes in update()|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should pass launch headers when fetching metadata from same origin|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should pass launch headers when fetching metadata from same origin|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should use resource_metadata challenge URL from WWW-Authenticate header|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should use resource_metadata challenge URL from WWW-Authenticate header|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should use scopes from WWW-Authenticate header even when resource metadata has scopes_supported|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should use scopes from WWW-Authenticate header even when resource metadata has scopes_supported|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should use scopes from WWW-Authenticate header when resource metadata has none|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should use scopes from WWW-Authenticate header when resource metadata has none|occurrence=1extHostMcp.test|title=Ex…extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg does not add stray ^ to argument values|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg does not add stray ^ to argument values|occurrence=1extHostMcpNode.test|titl…extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg doubles embedded double quotes (cmd.exe convention)|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg doubles embedded double quotes (cmd.exe convention)|occurrence=1extHostMcpNode.test|titl…extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg neutralizes cmd.exe metacharacters inside quotes (CVE-2024-27980)|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg neutralizes cmd.exe metacharacters inside quotes (CVE-2024-27980)|occurrence=1extHostMcpNode.test|titl…extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg preserves paths with parentheses without injecting ^|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg preserves paths with parentheses without injecting ^|occurrence=1extHostMcpNode.test|titl…extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg preserves paths with spaces|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg preserves paths with spaces|occurrence=1extHostMcpNode.test|titl…extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg wraps simple values in double quotes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg wraps simple values in double quotes|occurrence=1extHostMcpNode.test|titl…Focused file · src/vs/workbench/api/common/extHostTextEditor.ts · 690 LOCcommon/extHostTextEditor…

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- extHostMcp.ts ×43
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { ok } from '../../../base/common/assert.js';
7 > import { ReadonlyError, illegalArgument } from '../../../base/common/errors.js';
8 > import { IdGenerator } from '../../../base/common/idGenerator.js';
9 > import { TextEditorCursorStyle } from '../../../editor/common/config/editorOptions.js';
10 > import { IRange } from '../../../editor/common/core/range.js';
11 > import { ISingleEditOperation } from '../../../editor/common/core/editOperation.js';
12 > import { IResolvedTextEditorConfiguration, ITextEditorConfigurationUpdate, MainThreadTextEditorsShape } from './extHost.protocol.js';
13 > import * as TypeConverters from './extHostTypeConverters.js';
14 > import { EndOfLine, Position, Range, Selection, SnippetString, TextEditorLineNumbersStyle, TextEditorRevealType } from './extHostTypes.js';
15 > import type * as vscode from 'vscode';
16 > import { ILogService } from '../../../platform/log/common/log.js';
17 > import { Lazy } from '../../../base/common/lazy.js';
18 > import { IExtensionDescription } from '../../../platform/extensions/common/extensions.js';
19 >
20 > export class TextEditorDecorationType {
21 >
22 > private static readonly _Keys = new IdGenerator('TextEditorDecorationType');
23 >
24 > readonly value: vscode.TextEditorDecorationType;
25 >
26 > constructor(proxy: MainThreadTextEditorsShape, extension: IExtensionDescription, options: vscode.DecorationRenderOptions) {
27 const key = TextEditorDecorationType._Keys.nextId();
28 proxy.$registerTextEditorDecorationType(extension.identifier, key, TypeConverters.DecorationRenderOptions.from(options));
29 this.value = Object.freeze({
30 key,
31 dispose() {
32 proxy.$removeTextEditorDecorationType(key);
33 }
34 });
35 }
37 > }
38 >
39 > export interface ITextEditOperation {
40 > range: vscode.Range;
41 > text: string | null;
42 > forceMoveMarkers: boolean;
43 > }
44 >
45 > export interface IEditData {
46 > documentVersionId: number;
47 > edits: ITextEditOperation[];
48 > setEndOfLine: EndOfLine | undefined;
49 > undoStopBefore: boolean;
50 > undoStopAfter: boolean;
51 > }
52 >
53 > class TextEditorEdit {
54 >
55 > private readonly _document: vscode.TextDocument;
56 > private readonly _documentVersionId: number;
57 > private readonly _undoStopBefore: boolean;
58 > private readonly _undoStopAfter: boolean;
59 > private _collectedEdits: ITextEditOperation[] = [];
60 > private _setEndOfLine: EndOfLine | undefined = undefined;
61 > private _finalized: boolean = false;
62 >
63 > constructor(document: vscode.TextDocument, options: { undoStopBefore: boolean; undoStopAfter: boolean }) {
64 this._document = document;
65 this._documentVersionId = document.version;
66 this._undoStopBefore = options.undoStopBefore;
67 this._undoStopAfter = options.undoStopAfter;
68 }
70 > finalize(): IEditData {
71 this._finalized = true;
72 return {
73 documentVersionId: this._documentVersionId,
74 edits: this._collectedEdits,
75 setEndOfLine: this._setEndOfLine,
76 undoStopBefore: this._undoStopBefore,
77 undoStopAfter: this._undoStopAfter
78 };
79 }
81 > private _throwIfFinalized() {
82 if (this._finalized) {
83 throw new Error('Edit is only valid while callback runs');
84 }
85 }
87 > replace(location: Position | Range | Selection, value: string): void {
88 this._throwIfFinalized();
89 let range: Range | null = null;
90
91 if (location instanceof Position) {
92 range = new Range(location, location);
93 } else if (location instanceof Range) {
94 range = location;
95 } else {
96 throw new Error('Unrecognized location');
97 }
98
99 this._pushEdit(range, value, false);
100 }
102 > insert(location: Position, value: string): void {
103 this._throwIfFinalized();
104 this._pushEdit(new Range(location, location), value, true);
105 }
107 > delete(location: Range | Selection): void {
108 this._throwIfFinalized();
109 let range: Range | null = null;
110
111 if (location instanceof Range) {
112 range = location;
113 } else {
114 throw new Error('Unrecognized location');
115 }
116
117 this._pushEdit(range, null, true);
118 }
120 > private _pushEdit(range: Range, text: string | null, forceMoveMarkers: boolean): void {
121 const validRange = this._document.validateRange(range);
122 this._collectedEdits.push({
123 range: validRange,
124 text: text,
125 forceMoveMarkers: forceMoveMarkers
126 });
127 }
129 > setEndOfLine(endOfLine: EndOfLine): void {
130 this._throwIfFinalized();
131 if (endOfLine !== EndOfLine.LF && endOfLine !== EndOfLine.CRLF) {
132 throw illegalArgument('endOfLine');
133 }
134
135 this._setEndOfLine = endOfLine;
136 }
138 >
139 > export class ExtHostTextEditorOptions {
140 >
141 > private _proxy: MainThreadTextEditorsShape;
142 > private _id: string;
143 > private _logService: ILogService;
144 >
145 > private _tabSize!: number;
146 > private _indentSize!: number;
147 > private _originalIndentSize!: number | 'tabSize';
148 > private _insertSpaces!: boolean;
149 > private _cursorStyle!: TextEditorCursorStyle;
150 > private _lineNumbers!: TextEditorLineNumbersStyle;
151 >
152 > readonly value: vscode.TextEditorOptions;
153 >
154 > constructor(proxy: MainThreadTextEditorsShape, id: string, source: IResolvedTextEditorConfiguration, logService: ILogService) {
155 this._proxy = proxy;
156 this._id = id;
157 this._accept(source);
158 this._logService = logService;
159
160 const that = this;
161
162 this.value = {
163 get tabSize(): number | string {
164 return that._tabSize;
165 },
166 set tabSize(value: number | string) {
167 that._setTabSize(value);
168 },
169 get indentSize(): number | string {
170 return that._indentSize;
171 },
172 set indentSize(value: number | string) {
173 that._setIndentSize(value);
174 },
175 get insertSpaces(): boolean | string {
176 return that._insertSpaces;
177 },
178 set insertSpaces(value: boolean | string) {
179 that._setInsertSpaces(value);
180 },
181 get cursorStyle(): TextEditorCursorStyle {
182 return that._cursorStyle;
183 },
184 set cursorStyle(value: TextEditorCursorStyle) {
185 that._setCursorStyle(value);
186 },
187 get lineNumbers(): TextEditorLineNumbersStyle {
188 return that._lineNumbers;
189 },
190 set lineNumbers(value: TextEditorLineNumbersStyle) {
191 that._setLineNumbers(value);
192 }
193 };
194 }
196 > public _accept(source: IResolvedTextEditorConfiguration): void {
197 this._tabSize = source.tabSize;
198 this._indentSize = source.indentSize;
199 this._originalIndentSize = source.originalIndentSize;
200 this._insertSpaces = source.insertSpaces;
201 this._cursorStyle = source.cursorStyle;
202 this._lineNumbers = TypeConverters.TextEditorLineNumbersStyle.to(source.lineNumbers);
203 }
205 > // --- internal: tabSize
206 >
207 > private _validateTabSize(value: number | string): number | 'auto' | null {
208 if (value === 'auto') {
209 return 'auto';
210 }
211 if (typeof value === 'number') {
212 const r = Math.floor(value);
213 return (r > 0 ? r : null);
214 }
215 if (typeof value === 'string') {
216 const r = parseInt(value, 10);
217 if (isNaN(r)) {
218 return null;
219 }
220 return (r > 0 ? r : null);
221 }
222 return null;
223 }
225 > private _setTabSize(value: number | string) {
226 const tabSize = this._validateTabSize(value);
227 if (tabSize === null) {
228 // ignore invalid call
229 return;
230 }
231 if (typeof tabSize === 'number') {
232 if (this._tabSize === tabSize) {
233 // nothing to do
234 return;
235 }
236 // reflect the new tabSize value immediately
237 this._tabSize = tabSize;
238 }
239 this._warnOnError('setTabSize', this._proxy.$trySetOptions(this._id, {
240 tabSize: tabSize
241 }));
242 }
244 > // --- internal: indentSize
245 >
246 > private _validateIndentSize(value: number | string): number | 'tabSize' | null {
247 if (value === 'tabSize') {
248 return 'tabSize';
249 }
250 if (typeof value === 'number') {
251 const r = Math.floor(value);
252 return (r > 0 ? r : null);
253 }
254 if (typeof value === 'string') {
255 const r = parseInt(value, 10);
256 if (isNaN(r)) {
257 return null;
258 }
259 return (r > 0 ? r : null);
260 }
261 return null;
262 }
264 > private _setIndentSize(value: number | string) {
265 const indentSize = this._validateIndentSize(value);
266 if (indentSize === null) {
267 // ignore invalid call
268 return;
269 }
270 if (typeof indentSize === 'number') {
271 if (this._originalIndentSize === indentSize) {
272 // nothing to do
273 return;
274 }
275 // reflect the new indentSize value immediately
276 this._indentSize = indentSize;
277 this._originalIndentSize = indentSize;
278 }
279 this._warnOnError('setIndentSize', this._proxy.$trySetOptions(this._id, {
280 indentSize: indentSize
281 }));
282 }
284 > // --- internal: insert spaces
285 >
286 > private _validateInsertSpaces(value: boolean | string): boolean | 'auto' {
287 if (value === 'auto') {
288 return 'auto';
289 }
290 return (value === 'false' ? false : Boolean(value));
291 }
293 > private _setInsertSpaces(value: boolean | string) {
294 const insertSpaces = this._validateInsertSpaces(value);
295 if (typeof insertSpaces === 'boolean') {
296 if (this._insertSpaces === insertSpaces) {
297 // nothing to do
298 return;
299 }
300 // reflect the new insertSpaces value immediately
301 this._insertSpaces = insertSpaces;
302 }
303 this._warnOnError('setInsertSpaces', this._proxy.$trySetOptions(this._id, {
304 insertSpaces: insertSpaces
305 }));
306 }
308 > // --- internal: cursor style
309 >
310 > private _setCursorStyle(value: TextEditorCursorStyle) {
311 if (this._cursorStyle === value) {
312 // nothing to do
313 return;
314 }
315 this._cursorStyle = value;
316 this._warnOnError('setCursorStyle', this._proxy.$trySetOptions(this._id, {
317 cursorStyle: value
318 }));
319 }
321 > // --- internal: line number
322 >
323 > private _setLineNumbers(value: TextEditorLineNumbersStyle) {
324 if (this._lineNumbers === value) {
325 // nothing to do
326 return;
327 }
328 this._lineNumbers = value;
329 this._warnOnError('setLineNumbers', this._proxy.$trySetOptions(this._id, {
330 lineNumbers: TypeConverters.TextEditorLineNumbersStyle.from(value)
331 }));
332 }
334 > public assign(newOptions: vscode.TextEditorOptions) {
335 const bulkConfigurationUpdate: ITextEditorConfigurationUpdate = {};
336 let hasUpdate = false;
337
338 if (typeof newOptions.tabSize !== 'undefined') {
339 const tabSize = this._validateTabSize(newOptions.tabSize);
340 if (tabSize === 'auto') {
341 hasUpdate = true;
342 bulkConfigurationUpdate.tabSize = tabSize;
343 } else if (typeof tabSize === 'number' && this._tabSize !== tabSize) {
344 // reflect the new tabSize value immediately
345 this._tabSize = tabSize;
346 hasUpdate = true;
347 bulkConfigurationUpdate.tabSize = tabSize;
348 }
349 }
350
351 if (typeof newOptions.indentSize !== 'undefined') {
352 const indentSize = this._validateIndentSize(newOptions.indentSize);
353 if (indentSize === 'tabSize') {
354 hasUpdate = true;
355 bulkConfigurationUpdate.indentSize = indentSize;
356 } else if (typeof indentSize === 'number' && this._originalIndentSize !== indentSize) {
357 // reflect the new indentSize value immediately
358 this._indentSize = indentSize;
359 this._originalIndentSize = indentSize;
360 hasUpdate = true;
361 bulkConfigurationUpdate.indentSize = indentSize;
362 }
363 }
364
365 if (typeof newOptions.insertSpaces !== 'undefined') {
366 const insertSpaces = this._validateInsertSpaces(newOptions.insertSpaces);
367 if (insertSpaces === 'auto') {
368 hasUpdate = true;
369 bulkConfigurationUpdate.insertSpaces = insertSpaces;
370 } else if (this._insertSpaces !== insertSpaces) {
371 // reflect the new insertSpaces value immediately
372 this._insertSpaces = insertSpaces;
373 hasUpdate = true;
374 bulkConfigurationUpdate.insertSpaces = insertSpaces;
375 }
376 }
377
378 if (typeof newOptions.cursorStyle !== 'undefined') {
379 if (this._cursorStyle !== newOptions.cursorStyle) {
380 this._cursorStyle = newOptions.cursorStyle;
381 hasUpdate = true;
382 bulkConfigurationUpdate.cursorStyle = newOptions.cursorStyle;
383 }
384 }
385
386 if (typeof newOptions.lineNumbers !== 'undefined') {
387 if (this._lineNumbers !== newOptions.lineNumbers) {
388 this._lineNumbers = newOptions.lineNumbers;
389 hasUpdate = true;
390 bulkConfigurationUpdate.lineNumbers = TypeConverters.TextEditorLineNumbersStyle.from(newOptions.lineNumbers);
391 }
392 }
393
394 if (hasUpdate) {
395 this._warnOnError('setOptions', this._proxy.$trySetOptions(this._id, bulkConfigurationUpdate));
396 }
397 }
399 > private _warnOnError(action: string, promise: Promise<any>): void {
400 promise.catch(err => {
401 this._logService.warn(`ExtHostTextEditorOptions '${action}' failed:'`);
402 this._logService.warn(err);
403 });
404 }
406 >
407 > export class ExtHostTextEditor {
408 >
409 > private _selections: Selection[];
410 > private _options: ExtHostTextEditorOptions;
411 > private _visibleRanges: Range[];
412 > private _viewColumn: vscode.ViewColumn | undefined;
413 > private _disposed: boolean = false;
414 > private _hasDecorationsForKey = new Set<string>();
415 > private _diffInformation: vscode.TextEditorDiffInformation[] | undefined;
416 >
417 > readonly value: vscode.TextEditor;
418 >
419 > constructor(
420 readonly id: string,
421 private readonly _proxy: MainThreadTextEditorsShape,
422 private readonly _logService: ILogService,
423 document: Lazy<vscode.TextDocument>,
424 selections: Selection[], options: IResolvedTextEditorConfiguration,
425 visibleRanges: Range[], viewColumn: vscode.ViewColumn | undefined
426 ) {
427 this._selections = selections;
428 this._options = new ExtHostTextEditorOptions(this._proxy, this.id, options, _logService);
429 this._visibleRanges = visibleRanges;
430 this._viewColumn = viewColumn;
431
432 const that = this;
433
434 this.value = Object.freeze({
435 get document(): vscode.TextDocument {
436 return document.value;
437 },
438 set document(_value) {
439 throw new ReadonlyError('document');
440 },
441 // --- selection
442 get selection(): Selection {
443 return that._selections && that._selections[0];
444 },
445 set selection(value: Selection) {
446 if (!(value instanceof Selection)) {
447 throw illegalArgument('selection');
448 }
449 that._selections = [value];
450 that._trySetSelection();
451 },
452 get selections(): Selection[] {
453 return that._selections;
454 },
455 set selections(value: Selection[]) {
456 if (!Array.isArray(value) || value.some(a => !(a instanceof Selection))) {
457 throw illegalArgument('selections');
458 }
459 if (value.length === 0) {
460 value = [new Selection(0, 0, 0, 0)];
461 }
462 that._selections = value;
463 that._trySetSelection();
464 },
465 // --- visible ranges
466 get visibleRanges(): Range[] {
467 return that._visibleRanges;
468 },
469 set visibleRanges(_value: Range[]) {
470 throw new ReadonlyError('visibleRanges');
471 },
472 get diffInformation() {
473 return that._diffInformation;
474 },
475 // --- options
476 get options(): vscode.TextEditorOptions {
477 return that._options.value;
478 },
479 set options(value: vscode.TextEditorOptions) {
480 if (!that._disposed) {
481 that._options.assign(value);
482 }
483 },
484 // --- view column
485 get viewColumn(): vscode.ViewColumn | undefined {
486 return that._viewColumn;
487 },
488 set viewColumn(_value) {
489 throw new ReadonlyError('viewColumn');
490 },
491 // --- edit
492 edit(callback: (edit: TextEditorEdit) => void, options: { undoStopBefore: boolean; undoStopAfter: boolean } = { undoStopBefore: true, undoStopAfter: true }): Promise<boolean> {
493 if (that._disposed) {
494 return Promise.reject(new Error('TextEditor#edit not possible on closed editors'));
495 }
496 const edit = new TextEditorEdit(document.value, options);
497 callback(edit);
498 return that._applyEdit(edit);
499 },
500 // --- snippet edit
501 insertSnippet(snippet: SnippetString, where?: Position | readonly Position[] | Range | readonly Range[], options: { undoStopBefore: boolean; undoStopAfter: boolean; keepWhitespace?: boolean } = { undoStopBefore: true, undoStopAfter: true }): Promise<boolean> {
502 if (that._disposed) {
503 return Promise.reject(new Error('TextEditor#insertSnippet not possible on closed editors'));
504 }
505 let ranges: IRange[];
506
507 if (!where || (Array.isArray(where) && where.length === 0)) {
508 ranges = that._selections.map(range => TypeConverters.Range.from(range));
509
510 } else if (where instanceof Position) {
511 const { lineNumber, column } = TypeConverters.Position.from(where);
512 ranges = [{ startLineNumber: lineNumber, startColumn: column, endLineNumber: lineNumber, endColumn: column }];
513
514 } else if (where instanceof Range) {
515 ranges = [TypeConverters.Range.from(where)];
516 } else {
517 ranges = [];
518 for (const posOrRange of where) {
519 if (posOrRange instanceof Range) {
520 ranges.push(TypeConverters.Range.from(posOrRange));
521 } else {
522 const { lineNumber, column } = TypeConverters.Position.from(posOrRange);
523 ranges.push({ startLineNumber: lineNumber, startColumn: column, endLineNumber: lineNumber, endColumn: column });
524 }
525 }
526 }
527 if (options.keepWhitespace === undefined) {
528 options.keepWhitespace = false;
529 }
530 return _proxy.$tryInsertSnippet(id, document.value.version, snippet.value, ranges, options);
531 },
532 setDecorations(decorationType: vscode.TextEditorDecorationType, ranges: Range[] | vscode.DecorationOptions[]): void {
533 const willBeEmpty = (ranges.length === 0);
534 if (willBeEmpty && !that._hasDecorationsForKey.has(decorationType.key)) {
535 // avoid no-op call to the renderer
536 return;
537 }
538 if (willBeEmpty) {
539 that._hasDecorationsForKey.delete(decorationType.key);
540 } else {
541 that._hasDecorationsForKey.add(decorationType.key);
542 }
543 that._runOnProxy(() => {
544 if (TypeConverters.isDecorationOptionsArr(ranges)) {
545 return _proxy.$trySetDecorations(
546 id,
547 decorationType.key,
548 TypeConverters.fromRangeOrRangeWithMessage(ranges)
549 );
550 } else {
551 const _ranges: number[] = new Array<number>(4 * ranges.length);
552 for (let i = 0, len = ranges.length; i < len; i++) {
553 const range = ranges[i];
554 _ranges[4 * i] = range.start.line + 1;
555 _ranges[4 * i + 1] = range.start.character + 1;
556 _ranges[4 * i + 2] = range.end.line + 1;
557 _ranges[4 * i + 3] = range.end.character + 1;
558 }
559 return _proxy.$trySetDecorationsFast(
560 id,
561 decorationType.key,
562 _ranges
563 );
564 }
565 });
566 },
567 revealRange(range: Range, revealType: vscode.TextEditorRevealType): void {
568 that._runOnProxy(() => _proxy.$tryRevealRange(
569 id,
570 TypeConverters.Range.from(range),
571 (revealType || TextEditorRevealType.Default)
572 ));
573 },
574 show(column: vscode.ViewColumn) {
575 _proxy.$tryShowEditor(id, TypeConverters.ViewColumn.from(column));
576 },
577 hide() {
578 _proxy.$tryHideEditor(id);
579 },
580 [Symbol.for('debug.description')]() {
581 return `TextEditor(${this.document.uri.toString()})`;
582 }
583 });
584 }
586 > dispose() {
587 ok(!this._disposed);
588 this._disposed = true;
589 }
591 > // --- incoming: extension host MUST accept what the renderer says
592 >
593 > _acceptOptions(options: IResolvedTextEditorConfiguration): void {
594 ok(!this._disposed);
595 this._options._accept(options);
596 }
598 > _acceptVisibleRanges(value: Range[]): void {
599 ok(!this._disposed);
600 this._visibleRanges = value;
601 }
603 > _acceptViewColumn(value: vscode.ViewColumn) {
604 ok(!this._disposed);
605 this._viewColumn = value;
606 }
608 > _acceptSelections(selections: Selection[]): void {
609 ok(!this._disposed);
610 this._selections = selections;
611 }
613 > _acceptDiffInformation(diffInformation: vscode.TextEditorDiffInformation[] | undefined): void {
614 ok(!this._disposed);
615 this._diffInformation = diffInformation;
616 }
618 > private async _trySetSelection(): Promise<vscode.TextEditor | null | undefined> {
619 const selection = this._selections.map(TypeConverters.Selection.from);
620 await this._runOnProxy(() => this._proxy.$trySetSelections(this.id, selection));
621 return this.value;
622 }
624 > private _applyEdit(editBuilder: TextEditorEdit): Promise<boolean> {
625 const editData = editBuilder.finalize();
626
627 // return when there is nothing to do
628 if (editData.edits.length === 0 && !editData.setEndOfLine) {
629 return Promise.resolve(true);
630 }
631
632 // check that the edits are not overlapping (i.e. illegal)
633 const editRanges = editData.edits.map(edit => edit.range);
634
635 // sort ascending (by end and then by start)
636 editRanges.sort((a, b) => {
637 if (a.end.line === b.end.line) {
638 if (a.end.character === b.end.character) {
639 if (a.start.line === b.start.line) {
640 return a.start.character - b.start.character;
641 }
642 return a.start.line - b.start.line;
643 }
644 return a.end.character - b.end.character;
645 }
646 return a.end.line - b.end.line;
647 });
648
649 // check that no edits are overlapping
650 for (let i = 0, count = editRanges.length - 1; i < count; i++) {
651 const rangeEnd = editRanges[i].end;
652 const nextRangeStart = editRanges[i + 1].start;
653
654 if (nextRangeStart.isBefore(rangeEnd)) {
655 // overlapping ranges
656 return Promise.reject(
657 new Error('Overlapping ranges are not allowed!')
658 );
659 }
660 }
661
662 // prepare data for serialization
663 const edits = editData.edits.map((edit): ISingleEditOperation => {
664 return {
665 range: TypeConverters.Range.from(edit.range),
666 text: edit.text,
667 forceMoveMarkers: edit.forceMoveMarkers
668 };
669 });
670
671 return this._proxy.$tryApplyEdits(this.id, editData.documentVersionId, edits, {
672 setEndOfLine: typeof editData.setEndOfLine === 'number' ? TypeConverters.EndOfLine.from(editData.setEndOfLine) : undefined,
673 undoStopBefore: editData.undoStopBefore,
674 undoStopAfter: editData.undoStopAfter
675 });
676 }
677 > private _runOnProxy(callback: () => Promise<any>): Promise<ExtHostTextEditor | undefined | null> { extHostMcp.ts ×43
678 if (this._disposed) {
679 this._logService.warn('TextEditor is closed/disposed');
680 return Promise.resolve(undefined);
681 }
682
683 return callback().then(() => this, err => {
684 if (!(err instanceof Error && err.name === 'DISPOSED')) {
685 this._logService.warn(err);
686 }
687 return null;
688 });
689 }