Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/api/common/extHostDocumentData.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
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 { Schemas } from '../../../base/common/network.js';
8
import { regExpLeadsToEndlessLoop } from '../../../base/common/strings.js';
9
import { URI, UriComponents } from '../../../base/common/uri.js';
10
import { MirrorTextModel } from '../../../editor/common/model/mirrorTextModel.js';
11
import { ensureValidWordDefinition, getWordAtText } from '../../../editor/common/core/wordHelper.js';
12
import type * as vscode from 'vscode';
13
import { equals } from '../../../base/common/arrays.js';
14
import { EndOfLine } from './extHostTypes/textEdit.js';
15
import { Position } from './extHostTypes/position.js';
16
import { Range } from './extHostTypes/range.js';
17
18
const _languageId2WordDefinition = new Map<string, RegExp>();
19
export function setWordDefinitionFor(languageId: string, wordDefinition: RegExp | undefined): void {
20
if (!wordDefinition) {
21
_languageId2WordDefinition.delete(languageId);
22
} else {
23
_languageId2WordDefinition.set(languageId, wordDefinition);
24
}
25
}
26
27
function getWordDefinitionFor(languageId: string): RegExp | undefined {
28
return _languageId2WordDefinition.get(languageId);
29
}
30
31
export interface IExtHostDocumentSaveDelegate {
32
$trySaveDocument(uri: UriComponents): Promise<boolean>;
33
}
34
35
export class ExtHostDocumentData extends MirrorTextModel {
36
37
private _document?: vscode.TextDocument;
38
private _isDisposed: boolean = false;
39
40
constructor(
41
private readonly _proxy: IExtHostDocumentSaveDelegate,
42
uri: URI, lines: string[], eol: string, versionId: number,
43
private _languageId: string,
44
private _isDirty: boolean,
45
private _encoding: string,
46
private readonly _strictInstanceofChecks = true // used for code reuse
47
) {
48
super(uri, lines, eol, versionId);
49
}
50
51
// eslint-disable-next-line local/code-must-use-super-dispose
52
override dispose(): void {
53
// we don't really dispose documents but let
54
// extensions still read from them. some
55
// operations, live saving, will now error tho
56
ok(!this._isDisposed);
57
this._isDisposed = true;
58
this._isDirty = false;
59
}
60
61
equalLines(lines: readonly string[]): boolean {
62
return equals(this._lines, lines);
63
}
64
65
get document(): vscode.TextDocument {
66
if (!this._document) {
67
const that = this;
68
this._document = {
69
get uri() { return that._uri; },
70
get fileName() { return that._uri.fsPath; },
71
get isUntitled() { return that._uri.scheme === Schemas.untitled; },
72
get languageId() { return that._languageId; },
73
get version() { return that._versionId; },
74
get isClosed() { return that._isDisposed; },
75
get isDirty() { return that._isDirty; },
76
get encoding() { return that._encoding; },
77
save() { return that._save(); },
78
getText(range?) { return range ? that._getTextInRange(range) : that.getText(); },
79
get eol() { return that._eol === '\n' ? EndOfLine.LF : EndOfLine.CRLF; },
80
get lineCount() { return that._lines.length; },
81
lineAt(lineOrPos: number | vscode.Position) { return that._lineAt(lineOrPos); },
82
offsetAt(pos) { return that._offsetAt(pos); },
83
positionAt(offset) { return that._positionAt(offset); },
84
validateRange(ran) { return that._validateRange(ran); },
85
validatePosition(pos) { return that._validatePosition(pos); },
86
getWordRangeAtPosition(pos, regexp?) { return that._getWordRangeAtPosition(pos, regexp); },
87
[Symbol.for('debug.description')]() {
88
return `TextDocument(${that._uri.toString()})`;
89
}
90
};
91
}
92
return Object.freeze(this._document);
93
}
94
95
_acceptLanguageId(newLanguageId: string): void {
96
ok(!this._isDisposed);
97
this._languageId = newLanguageId;
98
}
99
100
_acceptIsDirty(isDirty: boolean): void {
101
ok(!this._isDisposed);
102
this._isDirty = isDirty;
103
}
104
105
_acceptEncoding(encoding: string): void {
106
ok(!this._isDisposed);
107
this._encoding = encoding;
108
}
109
110
private _save(): Promise<boolean> {
111
if (this._isDisposed) {
112
return Promise.reject(new Error('Document has been closed'));
113
}
114
return this._proxy.$trySaveDocument(this._uri);
115
}
116
117
private _getTextInRange(_range: vscode.Range): string {
118
const range = this._validateRange(_range);
119
120
if (range.isEmpty) {
121
return '';
122
}
123
124
if (range.isSingleLine) {
125
return this._lines[range.start.line].substring(range.start.character, range.end.character);
126
}
127
128
const lineEnding = this._eol,
129
startLineIndex = range.start.line,
130
endLineIndex = range.end.line,
131
resultLines: string[] = [];
132
133
resultLines.push(this._lines[startLineIndex].substring(range.start.character));
134
for (let i = startLineIndex + 1; i < endLineIndex; i++) {
135
resultLines.push(this._lines[i]);
136
}
137
resultLines.push(this._lines[endLineIndex].substring(0, range.end.character));
138
139
return resultLines.join(lineEnding);
140
}
141
142
private _lineAt(lineOrPosition: number | vscode.Position): vscode.TextLine {
143
144
let line: number | undefined;
145
if (lineOrPosition instanceof Position) {
146
line = lineOrPosition.line;
147
} else if (typeof lineOrPosition === 'number') {
148
line = lineOrPosition;
149
} else if (!this._strictInstanceofChecks && Position.isPosition(lineOrPosition)) {
150
line = lineOrPosition.line;
151
}
152
153
if (typeof line !== 'number' || line < 0 || line >= this._lines.length || Math.floor(line) !== line) {
154
throw new Error('Illegal value for `line`');
155
}
156
157
return new ExtHostDocumentLine(line, this._lines[line], line === this._lines.length - 1);
158
}
159
160
private _offsetAt(position: vscode.Position): number {
161
position = this._validatePosition(position);
162
this._ensureLineStarts();
163
return this._lineStarts!.getPrefixSum(position.line - 1) + position.character;
164
}
165
166
private _positionAt(offset: number): vscode.Position {
167
offset = Math.floor(offset);
168
offset = Math.max(0, offset);
169
170
this._ensureLineStarts();
171
const out = this._lineStarts!.getIndexOf(offset);
172
173
const lineLength = this._lines[out.index].length;
174
175
// Ensure we return a valid position
176
return new Position(out.index, Math.min(out.remainder, lineLength));
177
}
178
179
// ---- range math
180
181
private _validateRange(range: vscode.Range): vscode.Range {
182
if (this._strictInstanceofChecks) {
183
if (!(range instanceof Range)) {
184
throw new Error('Invalid argument');
185
}
186
} else {
187
if (!Range.isRange(range)) {
188
throw new Error('Invalid argument');
189
}
190
}
191
192
const start = this._validatePosition(range.start);
193
const end = this._validatePosition(range.end);
194
195
if (start === range.start && end === range.end) {
196
return range;
197
}
198
return new Range(start.line, start.character, end.line, end.character);
199
}
200
201
private _validatePosition(position: vscode.Position): vscode.Position {
202
if (this._strictInstanceofChecks) {
203
if (!(position instanceof Position)) {
204
throw new Error('Invalid argument');
205
}
206
} else {
207
if (!Position.isPosition(position)) {
208
throw new Error('Invalid argument');
209
}
210
}
211
212
if (this._lines.length === 0) {
213
return position.with(0, 0);
214
}
215
216
let { line, character } = position;
217
let hasChanged = false;
218
219
if (line < 0) {
220
line = 0;
221
character = 0;
222
hasChanged = true;
223
}
224
else if (line >= this._lines.length) {
225
line = this._lines.length - 1;
226
character = this._lines[line].length;
227
hasChanged = true;
228
}
229
else {
230
const maxCharacter = this._lines[line].length;
231
if (character < 0) {
232
character = 0;
233
hasChanged = true;
234
}
235
else if (character > maxCharacter) {
236
character = maxCharacter;
237
hasChanged = true;
238
}
239
}
240
241
if (!hasChanged) {
242
return position;
243
}
244
return new Position(line, character);
245
}
246
247
private _getWordRangeAtPosition(_position: vscode.Position, regexp?: RegExp): vscode.Range | undefined {
248
const position = this._validatePosition(_position);
249
250
if (!regexp) {
251
// use default when custom-regexp isn't provided
252
regexp = getWordDefinitionFor(this._languageId);
253
254
} else if (regExpLeadsToEndlessLoop(regexp)) {
255
// use default when custom-regexp is bad
256
throw new Error(`[getWordRangeAtPosition]: ignoring custom regexp '${regexp.source}' because it matches the empty string.`);
257
}
258
259
const wordAtText = getWordAtText(
260
position.character + 1,
261
ensureValidWordDefinition(regexp),
262
this._lines[position.line],
263
0
264
);
265
266
if (wordAtText) {
267
return new Range(position.line, wordAtText.startColumn - 1, position.line, wordAtText.endColumn - 1);
268
}
269
return undefined;
270
}
271
}
272
273
export class ExtHostDocumentLine implements vscode.TextLine {
274
275
private readonly _line: number;
276
private readonly _text: string;
277
private readonly _isLastLine: boolean;
278
279
constructor(line: number, text: string, isLastLine: boolean) {
280
this._line = line;
281
this._text = text;
282
this._isLastLine = isLastLine;
283
}
284
285
public get lineNumber(): number {
286
return this._line;
287
}
288
289
public get text(): string {
290
return this._text;
291
}
292
293
public get range(): Range {
294
return new Range(this._line, 0, this._line, this._text.length);
295
}
296
297
public get rangeIncludingLineBreak(): Range {
298
if (this._isLastLine) {
299
return this.range;
300
}
301
return new Range(this._line, 0, this._line + 1, 0);
302
}
303
304
public get firstNonWhitespaceCharacterIndex(): number {
305
//TODO@api, rename to 'leadingWhitespaceLength'
306
return /^(\s*)/.exec(this._text)![1].length;
307
}
308
309
public get isEmptyOrWhitespace(): boolean {
310
return this.firstNonWhitespaceCharacterIndex === this._text.length;
311
}
312
}
313
314