Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/common/viewModel/viewModelLines.ts
3294 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 * as arrays from '../../../base/common/arrays.js';
7
import { IDisposable } from '../../../base/common/lifecycle.js';
8
import { WrappingIndent } from '../config/editorOptions.js';
9
import { FontInfo } from '../config/fontInfo.js';
10
import { IPosition, Position } from '../core/position.js';
11
import { Range } from '../core/range.js';
12
import { IModelDecoration, IModelDeltaDecoration, ITextModel, PositionAffinity } from '../model.js';
13
import { IActiveIndentGuideInfo, BracketGuideOptions, IndentGuide, IndentGuideHorizontalLine } from '../textModelGuides.js';
14
import { ModelDecorationOptions } from '../model/textModel.js';
15
import { LineInjectedText } from '../textModelEvents.js';
16
import * as viewEvents from '../viewEvents.js';
17
import { createModelLineProjection, IModelLineProjection } from './modelLineProjection.js';
18
import { ILineBreaksComputer, ModelLineProjectionData, InjectedText, ILineBreaksComputerFactory } from '../modelLineProjectionData.js';
19
import { ConstantTimePrefixSumComputer } from '../model/prefixSumComputer.js';
20
import { ViewLineData } from '../viewModel.js';
21
import { ICoordinatesConverter, IdentityCoordinatesConverter } from '../coordinatesConverter.js';
22
23
export interface IViewModelLines extends IDisposable {
24
createCoordinatesConverter(): ICoordinatesConverter;
25
26
setWrappingSettings(fontInfo: FontInfo, wrappingStrategy: 'simple' | 'advanced', wrappingColumn: number, wrappingIndent: WrappingIndent, wordBreak: 'normal' | 'keepAll'): boolean;
27
setTabSize(newTabSize: number): boolean;
28
getHiddenAreas(): Range[];
29
setHiddenAreas(_ranges: readonly Range[]): boolean;
30
31
createLineBreaksComputer(): ILineBreaksComputer;
32
onModelFlushed(): void;
33
onModelLinesDeleted(versionId: number | null, fromLineNumber: number, toLineNumber: number): viewEvents.ViewLinesDeletedEvent | null;
34
onModelLinesInserted(versionId: number | null, fromLineNumber: number, toLineNumber: number, lineBreaks: (ModelLineProjectionData | null)[]): viewEvents.ViewLinesInsertedEvent | null;
35
onModelLineChanged(versionId: number | null, lineNumber: number, lineBreakData: ModelLineProjectionData | null): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null];
36
acceptVersionId(versionId: number): void;
37
38
getViewLineCount(): number;
39
getActiveIndentGuide(viewLineNumber: number, minLineNumber: number, maxLineNumber: number): IActiveIndentGuideInfo;
40
getViewLinesIndentGuides(viewStartLineNumber: number, viewEndLineNumber: number): number[];
41
getViewLinesBracketGuides(startLineNumber: number, endLineNumber: number, activePosition: IPosition | null, options: BracketGuideOptions): IndentGuide[][];
42
getViewLineContent(viewLineNumber: number): string;
43
getViewLineLength(viewLineNumber: number): number;
44
getViewLineMinColumn(viewLineNumber: number): number;
45
getViewLineMaxColumn(viewLineNumber: number): number;
46
getViewLineData(viewLineNumber: number): ViewLineData;
47
getViewLinesData(viewStartLineNumber: number, viewEndLineNumber: number, needed: boolean[]): Array<ViewLineData | null>;
48
49
getDecorationsInRange(range: Range, ownerId: number, filterOutValidation: boolean, filterFontDecorations: boolean, onlyMinimapDecorations: boolean, onlyMarginDecorations: boolean): IModelDecoration[];
50
51
getInjectedTextAt(viewPosition: Position): InjectedText | null;
52
53
normalizePosition(position: Position, affinity: PositionAffinity): Position;
54
/**
55
* Gets the column at which indentation stops at a given line.
56
* @internal
57
*/
58
getLineIndentColumn(lineNumber: number): number;
59
}
60
61
export class ViewModelLinesFromProjectedModel implements IViewModelLines {
62
private readonly _editorId: number;
63
private readonly model: ITextModel;
64
private _validModelVersionId: number;
65
66
private readonly _domLineBreaksComputerFactory: ILineBreaksComputerFactory;
67
private readonly _monospaceLineBreaksComputerFactory: ILineBreaksComputerFactory;
68
69
private fontInfo: FontInfo;
70
private tabSize: number;
71
private wrappingColumn: number;
72
private wrappingIndent: WrappingIndent;
73
private wordBreak: 'normal' | 'keepAll';
74
private wrappingStrategy: 'simple' | 'advanced';
75
private wrapOnEscapedLineFeeds: boolean;
76
77
private modelLineProjections!: IModelLineProjection[];
78
79
/**
80
* Reflects the sum of the line counts of all projected model lines.
81
*/
82
private projectedModelLineLineCounts!: ConstantTimePrefixSumComputer;
83
84
private hiddenAreasDecorationIds!: string[];
85
86
constructor(
87
editorId: number,
88
model: ITextModel,
89
domLineBreaksComputerFactory: ILineBreaksComputerFactory,
90
monospaceLineBreaksComputerFactory: ILineBreaksComputerFactory,
91
fontInfo: FontInfo,
92
tabSize: number,
93
wrappingStrategy: 'simple' | 'advanced',
94
wrappingColumn: number,
95
wrappingIndent: WrappingIndent,
96
wordBreak: 'normal' | 'keepAll',
97
wrapOnEscapedLineFeeds: boolean
98
) {
99
this._editorId = editorId;
100
this.model = model;
101
this._validModelVersionId = -1;
102
this._domLineBreaksComputerFactory = domLineBreaksComputerFactory;
103
this._monospaceLineBreaksComputerFactory = monospaceLineBreaksComputerFactory;
104
this.fontInfo = fontInfo;
105
this.tabSize = tabSize;
106
this.wrappingStrategy = wrappingStrategy;
107
this.wrappingColumn = wrappingColumn;
108
this.wrappingIndent = wrappingIndent;
109
this.wordBreak = wordBreak;
110
this.wrapOnEscapedLineFeeds = wrapOnEscapedLineFeeds;
111
112
this._constructLines(/*resetHiddenAreas*/true, null);
113
}
114
115
public dispose(): void {
116
this.hiddenAreasDecorationIds = this.model.deltaDecorations(this.hiddenAreasDecorationIds, []);
117
}
118
119
public createCoordinatesConverter(): ICoordinatesConverter {
120
return new CoordinatesConverter(this);
121
}
122
123
private _constructLines(resetHiddenAreas: boolean, previousLineBreaks: ((ModelLineProjectionData | null)[]) | null): void {
124
this.modelLineProjections = [];
125
126
if (resetHiddenAreas) {
127
this.hiddenAreasDecorationIds = this.model.deltaDecorations(this.hiddenAreasDecorationIds, []);
128
}
129
130
const linesContent = this.model.getLinesContent();
131
const injectedTextDecorations = this.model.getInjectedTextDecorations(this._editorId);
132
const lineCount = linesContent.length;
133
const lineBreaksComputer = this.createLineBreaksComputer();
134
135
const injectedTextQueue = new arrays.ArrayQueue(LineInjectedText.fromDecorations(injectedTextDecorations));
136
for (let i = 0; i < lineCount; i++) {
137
const lineInjectedText = injectedTextQueue.takeWhile(t => t.lineNumber === i + 1);
138
lineBreaksComputer.addRequest(linesContent[i], lineInjectedText, previousLineBreaks ? previousLineBreaks[i] : null);
139
}
140
const linesBreaks = lineBreaksComputer.finalize();
141
142
const values: number[] = [];
143
144
const hiddenAreas = this.hiddenAreasDecorationIds.map((areaId) => this.model.getDecorationRange(areaId)!).sort(Range.compareRangesUsingStarts);
145
let hiddenAreaStart = 1, hiddenAreaEnd = 0;
146
let hiddenAreaIdx = -1;
147
let nextLineNumberToUpdateHiddenArea = (hiddenAreaIdx + 1 < hiddenAreas.length) ? hiddenAreaEnd + 1 : lineCount + 2;
148
149
for (let i = 0; i < lineCount; i++) {
150
const lineNumber = i + 1;
151
152
if (lineNumber === nextLineNumberToUpdateHiddenArea) {
153
hiddenAreaIdx++;
154
hiddenAreaStart = hiddenAreas[hiddenAreaIdx]!.startLineNumber;
155
hiddenAreaEnd = hiddenAreas[hiddenAreaIdx]!.endLineNumber;
156
nextLineNumberToUpdateHiddenArea = (hiddenAreaIdx + 1 < hiddenAreas.length) ? hiddenAreaEnd + 1 : lineCount + 2;
157
}
158
159
const isInHiddenArea = (lineNumber >= hiddenAreaStart && lineNumber <= hiddenAreaEnd);
160
const line = createModelLineProjection(linesBreaks[i], !isInHiddenArea);
161
values[i] = line.getViewLineCount();
162
this.modelLineProjections[i] = line;
163
}
164
165
this._validModelVersionId = this.model.getVersionId();
166
167
this.projectedModelLineLineCounts = new ConstantTimePrefixSumComputer(values);
168
}
169
170
public getHiddenAreas(): Range[] {
171
return this.hiddenAreasDecorationIds.map(
172
(decId) => this.model.getDecorationRange(decId)!
173
);
174
}
175
176
public setHiddenAreas(_ranges: Range[]): boolean {
177
const validatedRanges = _ranges.map(r => this.model.validateRange(r));
178
const newRanges = normalizeLineRanges(validatedRanges);
179
180
// TODO@Martin: Please stop calling this method on each model change!
181
182
// This checks if there really was a change
183
const oldRanges = this.hiddenAreasDecorationIds.map((areaId) => this.model.getDecorationRange(areaId)!).sort(Range.compareRangesUsingStarts);
184
if (newRanges.length === oldRanges.length) {
185
let hasDifference = false;
186
for (let i = 0; i < newRanges.length; i++) {
187
if (!newRanges[i].equalsRange(oldRanges[i])) {
188
hasDifference = true;
189
break;
190
}
191
}
192
if (!hasDifference) {
193
return false;
194
}
195
}
196
197
const newDecorations = newRanges.map<IModelDeltaDecoration>(
198
(r) =>
199
({
200
range: r,
201
options: ModelDecorationOptions.EMPTY,
202
})
203
);
204
205
this.hiddenAreasDecorationIds = this.model.deltaDecorations(this.hiddenAreasDecorationIds, newDecorations);
206
207
const hiddenAreas = newRanges;
208
let hiddenAreaStart = 1, hiddenAreaEnd = 0;
209
let hiddenAreaIdx = -1;
210
let nextLineNumberToUpdateHiddenArea = (hiddenAreaIdx + 1 < hiddenAreas.length) ? hiddenAreaEnd + 1 : this.modelLineProjections.length + 2;
211
212
let hasVisibleLine = false;
213
for (let i = 0; i < this.modelLineProjections.length; i++) {
214
const lineNumber = i + 1;
215
216
if (lineNumber === nextLineNumberToUpdateHiddenArea) {
217
hiddenAreaIdx++;
218
hiddenAreaStart = hiddenAreas[hiddenAreaIdx].startLineNumber;
219
hiddenAreaEnd = hiddenAreas[hiddenAreaIdx].endLineNumber;
220
nextLineNumberToUpdateHiddenArea = (hiddenAreaIdx + 1 < hiddenAreas.length) ? hiddenAreaEnd + 1 : this.modelLineProjections.length + 2;
221
}
222
223
let lineChanged = false;
224
if (lineNumber >= hiddenAreaStart && lineNumber <= hiddenAreaEnd) {
225
// Line should be hidden
226
if (this.modelLineProjections[i].isVisible()) {
227
this.modelLineProjections[i] = this.modelLineProjections[i].setVisible(false);
228
lineChanged = true;
229
}
230
} else {
231
hasVisibleLine = true;
232
// Line should be visible
233
if (!this.modelLineProjections[i].isVisible()) {
234
this.modelLineProjections[i] = this.modelLineProjections[i].setVisible(true);
235
lineChanged = true;
236
}
237
}
238
if (lineChanged) {
239
const newOutputLineCount = this.modelLineProjections[i].getViewLineCount();
240
this.projectedModelLineLineCounts.setValue(i, newOutputLineCount);
241
}
242
}
243
244
if (!hasVisibleLine) {
245
// Cannot have everything be hidden => reveal everything!
246
this.setHiddenAreas([]);
247
}
248
249
return true;
250
}
251
252
public modelPositionIsVisible(modelLineNumber: number, _modelColumn: number): boolean {
253
if (modelLineNumber < 1 || modelLineNumber > this.modelLineProjections.length) {
254
// invalid arguments
255
return false;
256
}
257
return this.modelLineProjections[modelLineNumber - 1].isVisible();
258
}
259
260
public getModelLineViewLineCount(modelLineNumber: number): number {
261
if (modelLineNumber < 1 || modelLineNumber > this.modelLineProjections.length) {
262
// invalid arguments
263
return 1;
264
}
265
return this.modelLineProjections[modelLineNumber - 1].getViewLineCount();
266
}
267
268
public setTabSize(newTabSize: number): boolean {
269
if (this.tabSize === newTabSize) {
270
return false;
271
}
272
this.tabSize = newTabSize;
273
274
this._constructLines(/*resetHiddenAreas*/false, null);
275
276
return true;
277
}
278
279
public setWrappingSettings(fontInfo: FontInfo, wrappingStrategy: 'simple' | 'advanced', wrappingColumn: number, wrappingIndent: WrappingIndent, wordBreak: 'normal' | 'keepAll'): boolean {
280
const equalFontInfo = this.fontInfo.equals(fontInfo);
281
const equalWrappingStrategy = (this.wrappingStrategy === wrappingStrategy);
282
const equalWrappingColumn = (this.wrappingColumn === wrappingColumn);
283
const equalWrappingIndent = (this.wrappingIndent === wrappingIndent);
284
const equalWordBreak = (this.wordBreak === wordBreak);
285
if (equalFontInfo && equalWrappingStrategy && equalWrappingColumn && equalWrappingIndent && equalWordBreak) {
286
return false;
287
}
288
289
const onlyWrappingColumnChanged = (equalFontInfo && equalWrappingStrategy && !equalWrappingColumn && equalWrappingIndent && equalWordBreak);
290
291
this.fontInfo = fontInfo;
292
this.wrappingStrategy = wrappingStrategy;
293
this.wrappingColumn = wrappingColumn;
294
this.wrappingIndent = wrappingIndent;
295
this.wordBreak = wordBreak;
296
297
let previousLineBreaks: ((ModelLineProjectionData | null)[]) | null = null;
298
if (onlyWrappingColumnChanged) {
299
previousLineBreaks = [];
300
for (let i = 0, len = this.modelLineProjections.length; i < len; i++) {
301
previousLineBreaks[i] = this.modelLineProjections[i].getProjectionData();
302
}
303
}
304
305
this._constructLines(/*resetHiddenAreas*/false, previousLineBreaks);
306
307
return true;
308
}
309
310
public createLineBreaksComputer(): ILineBreaksComputer {
311
const lineBreaksComputerFactory = (
312
this.wrappingStrategy === 'advanced'
313
? this._domLineBreaksComputerFactory
314
: this._monospaceLineBreaksComputerFactory
315
);
316
return lineBreaksComputerFactory.createLineBreaksComputer(this.fontInfo, this.tabSize, this.wrappingColumn, this.wrappingIndent, this.wordBreak, this.wrapOnEscapedLineFeeds);
317
}
318
319
public onModelFlushed(): void {
320
this._constructLines(/*resetHiddenAreas*/true, null);
321
}
322
323
public onModelLinesDeleted(versionId: number | null, fromLineNumber: number, toLineNumber: number): viewEvents.ViewLinesDeletedEvent | null {
324
if (!versionId || versionId <= this._validModelVersionId) {
325
// Here we check for versionId in case the lines were reconstructed in the meantime.
326
// We don't want to apply stale change events on top of a newer read model state.
327
return null;
328
}
329
330
const outputFromLineNumber = (fromLineNumber === 1 ? 1 : this.projectedModelLineLineCounts.getPrefixSum(fromLineNumber - 1) + 1);
331
const outputToLineNumber = this.projectedModelLineLineCounts.getPrefixSum(toLineNumber);
332
333
this.modelLineProjections.splice(fromLineNumber - 1, toLineNumber - fromLineNumber + 1);
334
this.projectedModelLineLineCounts.removeValues(fromLineNumber - 1, toLineNumber - fromLineNumber + 1);
335
336
return new viewEvents.ViewLinesDeletedEvent(outputFromLineNumber, outputToLineNumber);
337
}
338
339
public onModelLinesInserted(versionId: number | null, fromLineNumber: number, _toLineNumber: number, lineBreaks: (ModelLineProjectionData | null)[]): viewEvents.ViewLinesInsertedEvent | null {
340
if (!versionId || versionId <= this._validModelVersionId) {
341
// Here we check for versionId in case the lines were reconstructed in the meantime.
342
// We don't want to apply stale change events on top of a newer read model state.
343
return null;
344
}
345
346
// cannot use this.getHiddenAreas() because those decorations have already seen the effect of this model change
347
const isInHiddenArea = (fromLineNumber > 2 && !this.modelLineProjections[fromLineNumber - 2].isVisible());
348
349
const outputFromLineNumber = (fromLineNumber === 1 ? 1 : this.projectedModelLineLineCounts.getPrefixSum(fromLineNumber - 1) + 1);
350
351
let totalOutputLineCount = 0;
352
const insertLines: IModelLineProjection[] = [];
353
const insertPrefixSumValues: number[] = [];
354
355
for (let i = 0, len = lineBreaks.length; i < len; i++) {
356
const line = createModelLineProjection(lineBreaks[i], !isInHiddenArea);
357
insertLines.push(line);
358
359
const outputLineCount = line.getViewLineCount();
360
totalOutputLineCount += outputLineCount;
361
insertPrefixSumValues[i] = outputLineCount;
362
}
363
364
// TODO@Alex: use arrays.arrayInsert
365
this.modelLineProjections =
366
this.modelLineProjections.slice(0, fromLineNumber - 1)
367
.concat(insertLines)
368
.concat(this.modelLineProjections.slice(fromLineNumber - 1));
369
370
this.projectedModelLineLineCounts.insertValues(fromLineNumber - 1, insertPrefixSumValues);
371
372
return new viewEvents.ViewLinesInsertedEvent(outputFromLineNumber, outputFromLineNumber + totalOutputLineCount - 1);
373
}
374
375
public onModelLineChanged(versionId: number | null, lineNumber: number, lineBreakData: ModelLineProjectionData | null): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null] {
376
if (versionId !== null && versionId <= this._validModelVersionId) {
377
// Here we check for versionId in case the lines were reconstructed in the meantime.
378
// We don't want to apply stale change events on top of a newer read model state.
379
return [false, null, null, null];
380
}
381
382
const lineIndex = lineNumber - 1;
383
384
const oldOutputLineCount = this.modelLineProjections[lineIndex].getViewLineCount();
385
const isVisible = this.modelLineProjections[lineIndex].isVisible();
386
const line = createModelLineProjection(lineBreakData, isVisible);
387
this.modelLineProjections[lineIndex] = line;
388
const newOutputLineCount = this.modelLineProjections[lineIndex].getViewLineCount();
389
390
let lineMappingChanged = false;
391
let changeFrom = 0;
392
let changeTo = -1;
393
let insertFrom = 0;
394
let insertTo = -1;
395
let deleteFrom = 0;
396
let deleteTo = -1;
397
398
if (oldOutputLineCount > newOutputLineCount) {
399
changeFrom = this.projectedModelLineLineCounts.getPrefixSum(lineNumber - 1) + 1;
400
changeTo = changeFrom + newOutputLineCount - 1;
401
deleteFrom = changeTo + 1;
402
deleteTo = deleteFrom + (oldOutputLineCount - newOutputLineCount) - 1;
403
lineMappingChanged = true;
404
} else if (oldOutputLineCount < newOutputLineCount) {
405
changeFrom = this.projectedModelLineLineCounts.getPrefixSum(lineNumber - 1) + 1;
406
changeTo = changeFrom + oldOutputLineCount - 1;
407
insertFrom = changeTo + 1;
408
insertTo = insertFrom + (newOutputLineCount - oldOutputLineCount) - 1;
409
lineMappingChanged = true;
410
} else {
411
changeFrom = this.projectedModelLineLineCounts.getPrefixSum(lineNumber - 1) + 1;
412
changeTo = changeFrom + newOutputLineCount - 1;
413
}
414
415
this.projectedModelLineLineCounts.setValue(lineIndex, newOutputLineCount);
416
417
const viewLinesChangedEvent = (changeFrom <= changeTo ? new viewEvents.ViewLinesChangedEvent(changeFrom, changeTo - changeFrom + 1) : null);
418
const viewLinesInsertedEvent = (insertFrom <= insertTo ? new viewEvents.ViewLinesInsertedEvent(insertFrom, insertTo) : null);
419
const viewLinesDeletedEvent = (deleteFrom <= deleteTo ? new viewEvents.ViewLinesDeletedEvent(deleteFrom, deleteTo) : null);
420
421
return [lineMappingChanged, viewLinesChangedEvent, viewLinesInsertedEvent, viewLinesDeletedEvent];
422
}
423
424
public acceptVersionId(versionId: number): void {
425
this._validModelVersionId = versionId;
426
if (this.modelLineProjections.length === 1 && !this.modelLineProjections[0].isVisible()) {
427
// At least one line must be visible => reset hidden areas
428
this.setHiddenAreas([]);
429
}
430
}
431
432
public getViewLineCount(): number {
433
return this.projectedModelLineLineCounts.getTotalSum();
434
}
435
436
private _toValidViewLineNumber(viewLineNumber: number): number {
437
if (viewLineNumber < 1) {
438
return 1;
439
}
440
const viewLineCount = this.getViewLineCount();
441
if (viewLineNumber > viewLineCount) {
442
return viewLineCount;
443
}
444
return viewLineNumber | 0;
445
}
446
447
public getActiveIndentGuide(viewLineNumber: number, minLineNumber: number, maxLineNumber: number): IActiveIndentGuideInfo {
448
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
449
minLineNumber = this._toValidViewLineNumber(minLineNumber);
450
maxLineNumber = this._toValidViewLineNumber(maxLineNumber);
451
452
const modelPosition = this.convertViewPositionToModelPosition(viewLineNumber, this.getViewLineMinColumn(viewLineNumber));
453
const modelMinPosition = this.convertViewPositionToModelPosition(minLineNumber, this.getViewLineMinColumn(minLineNumber));
454
const modelMaxPosition = this.convertViewPositionToModelPosition(maxLineNumber, this.getViewLineMinColumn(maxLineNumber));
455
const result = this.model.guides.getActiveIndentGuide(modelPosition.lineNumber, modelMinPosition.lineNumber, modelMaxPosition.lineNumber);
456
457
const viewStartPosition = this.convertModelPositionToViewPosition(result.startLineNumber, 1);
458
const viewEndPosition = this.convertModelPositionToViewPosition(result.endLineNumber, this.model.getLineMaxColumn(result.endLineNumber));
459
return {
460
startLineNumber: viewStartPosition.lineNumber,
461
endLineNumber: viewEndPosition.lineNumber,
462
indent: result.indent
463
};
464
}
465
466
// #region ViewLineInfo
467
468
private getViewLineInfo(viewLineNumber: number): ViewLineInfo {
469
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
470
const r = this.projectedModelLineLineCounts.getIndexOf(viewLineNumber - 1);
471
const lineIndex = r.index;
472
const remainder = r.remainder;
473
return new ViewLineInfo(lineIndex + 1, remainder);
474
}
475
476
private getMinColumnOfViewLine(viewLineInfo: ViewLineInfo): number {
477
return this.modelLineProjections[viewLineInfo.modelLineNumber - 1].getViewLineMinColumn(
478
this.model,
479
viewLineInfo.modelLineNumber,
480
viewLineInfo.modelLineWrappedLineIdx
481
);
482
}
483
484
private getMaxColumnOfViewLine(viewLineInfo: ViewLineInfo): number {
485
return this.modelLineProjections[viewLineInfo.modelLineNumber - 1].getViewLineMaxColumn(
486
this.model,
487
viewLineInfo.modelLineNumber,
488
viewLineInfo.modelLineWrappedLineIdx
489
);
490
}
491
492
private getModelStartPositionOfViewLine(viewLineInfo: ViewLineInfo): Position {
493
const line = this.modelLineProjections[viewLineInfo.modelLineNumber - 1];
494
const minViewColumn = line.getViewLineMinColumn(
495
this.model,
496
viewLineInfo.modelLineNumber,
497
viewLineInfo.modelLineWrappedLineIdx
498
);
499
const column = line.getModelColumnOfViewPosition(
500
viewLineInfo.modelLineWrappedLineIdx,
501
minViewColumn
502
);
503
return new Position(viewLineInfo.modelLineNumber, column);
504
}
505
506
private getModelEndPositionOfViewLine(viewLineInfo: ViewLineInfo): Position {
507
const line = this.modelLineProjections[viewLineInfo.modelLineNumber - 1];
508
const maxViewColumn = line.getViewLineMaxColumn(
509
this.model,
510
viewLineInfo.modelLineNumber,
511
viewLineInfo.modelLineWrappedLineIdx
512
);
513
const column = line.getModelColumnOfViewPosition(
514
viewLineInfo.modelLineWrappedLineIdx,
515
maxViewColumn
516
);
517
return new Position(viewLineInfo.modelLineNumber, column);
518
}
519
520
private getViewLineInfosGroupedByModelRanges(viewStartLineNumber: number, viewEndLineNumber: number): ViewLineInfoGroupedByModelRange[] {
521
const startViewLine = this.getViewLineInfo(viewStartLineNumber);
522
const endViewLine = this.getViewLineInfo(viewEndLineNumber);
523
524
const result = new Array<ViewLineInfoGroupedByModelRange>();
525
let lastVisibleModelPos: Position | null = this.getModelStartPositionOfViewLine(startViewLine);
526
let viewLines = new Array<ViewLineInfo>();
527
528
for (let curModelLine = startViewLine.modelLineNumber; curModelLine <= endViewLine.modelLineNumber; curModelLine++) {
529
const line = this.modelLineProjections[curModelLine - 1];
530
531
if (line.isVisible()) {
532
const startOffset =
533
curModelLine === startViewLine.modelLineNumber
534
? startViewLine.modelLineWrappedLineIdx
535
: 0;
536
537
const endOffset =
538
curModelLine === endViewLine.modelLineNumber
539
? endViewLine.modelLineWrappedLineIdx + 1
540
: line.getViewLineCount();
541
542
for (let i = startOffset; i < endOffset; i++) {
543
viewLines.push(new ViewLineInfo(curModelLine, i));
544
}
545
}
546
547
if (!line.isVisible() && lastVisibleModelPos) {
548
const lastVisibleModelPos2 = new Position(curModelLine - 1, this.model.getLineMaxColumn(curModelLine - 1) + 1);
549
550
const modelRange = Range.fromPositions(lastVisibleModelPos, lastVisibleModelPos2);
551
result.push(new ViewLineInfoGroupedByModelRange(modelRange, viewLines));
552
viewLines = [];
553
554
lastVisibleModelPos = null;
555
} else if (line.isVisible() && !lastVisibleModelPos) {
556
lastVisibleModelPos = new Position(curModelLine, 1);
557
}
558
}
559
560
if (lastVisibleModelPos) {
561
const modelRange = Range.fromPositions(lastVisibleModelPos, this.getModelEndPositionOfViewLine(endViewLine));
562
result.push(new ViewLineInfoGroupedByModelRange(modelRange, viewLines));
563
}
564
565
return result;
566
}
567
568
// #endregion
569
570
public getViewLinesBracketGuides(viewStartLineNumber: number, viewEndLineNumber: number, activeViewPosition: IPosition | null, options: BracketGuideOptions): IndentGuide[][] {
571
const modelActivePosition = activeViewPosition ? this.convertViewPositionToModelPosition(activeViewPosition.lineNumber, activeViewPosition.column) : null;
572
const resultPerViewLine: IndentGuide[][] = [];
573
574
for (const group of this.getViewLineInfosGroupedByModelRanges(viewStartLineNumber, viewEndLineNumber)) {
575
const modelRangeStartLineNumber = group.modelRange.startLineNumber;
576
577
const bracketGuidesPerModelLine = this.model.guides.getLinesBracketGuides(
578
modelRangeStartLineNumber,
579
group.modelRange.endLineNumber,
580
modelActivePosition,
581
options
582
);
583
584
for (const viewLineInfo of group.viewLines) {
585
586
const bracketGuides = bracketGuidesPerModelLine[viewLineInfo.modelLineNumber - modelRangeStartLineNumber];
587
588
// visibleColumns stay as they are (this is a bug and needs to be fixed, but it is not a regression)
589
// model-columns must be converted to view-model columns.
590
const result = bracketGuides.map(g => {
591
if (g.forWrappedLinesAfterColumn !== -1) {
592
const p = this.modelLineProjections[viewLineInfo.modelLineNumber - 1].getViewPositionOfModelPosition(0, g.forWrappedLinesAfterColumn);
593
if (p.lineNumber >= viewLineInfo.modelLineWrappedLineIdx) {
594
return undefined;
595
}
596
}
597
598
if (g.forWrappedLinesBeforeOrAtColumn !== -1) {
599
const p = this.modelLineProjections[viewLineInfo.modelLineNumber - 1].getViewPositionOfModelPosition(0, g.forWrappedLinesBeforeOrAtColumn);
600
if (p.lineNumber < viewLineInfo.modelLineWrappedLineIdx) {
601
return undefined;
602
}
603
}
604
605
if (!g.horizontalLine) {
606
return g;
607
}
608
609
let column = -1;
610
if (g.column !== -1) {
611
const p = this.modelLineProjections[viewLineInfo.modelLineNumber - 1].getViewPositionOfModelPosition(0, g.column);
612
if (p.lineNumber === viewLineInfo.modelLineWrappedLineIdx) {
613
column = p.column;
614
} else if (p.lineNumber < viewLineInfo.modelLineWrappedLineIdx) {
615
column = this.getMinColumnOfViewLine(viewLineInfo);
616
} else if (p.lineNumber > viewLineInfo.modelLineWrappedLineIdx) {
617
return undefined;
618
}
619
}
620
621
const viewPosition = this.convertModelPositionToViewPosition(viewLineInfo.modelLineNumber, g.horizontalLine.endColumn);
622
const p = this.modelLineProjections[viewLineInfo.modelLineNumber - 1].getViewPositionOfModelPosition(0, g.horizontalLine.endColumn);
623
if (p.lineNumber === viewLineInfo.modelLineWrappedLineIdx) {
624
return new IndentGuide(g.visibleColumn, column, g.className,
625
new IndentGuideHorizontalLine(g.horizontalLine.top,
626
viewPosition.column),
627
- 1,
628
-1,
629
);
630
} else if (p.lineNumber < viewLineInfo.modelLineWrappedLineIdx) {
631
return undefined;
632
} else {
633
if (g.visibleColumn !== -1) {
634
// Don't repeat horizontal lines that use visibleColumn for unrelated lines.
635
return undefined;
636
}
637
return new IndentGuide(g.visibleColumn, column, g.className,
638
new IndentGuideHorizontalLine(g.horizontalLine.top,
639
this.getMaxColumnOfViewLine(viewLineInfo)
640
),
641
-1,
642
-1,
643
);
644
}
645
});
646
resultPerViewLine.push(result.filter((r): r is IndentGuide => !!r));
647
648
}
649
}
650
651
return resultPerViewLine;
652
}
653
654
public getViewLinesIndentGuides(viewStartLineNumber: number, viewEndLineNumber: number): number[] {
655
// TODO: Use the same code as in `getViewLinesBracketGuides`.
656
// Future TODO: Merge with `getViewLinesBracketGuides`.
657
// However, this requires more refactoring of indent guides.
658
viewStartLineNumber = this._toValidViewLineNumber(viewStartLineNumber);
659
viewEndLineNumber = this._toValidViewLineNumber(viewEndLineNumber);
660
661
const modelStart = this.convertViewPositionToModelPosition(viewStartLineNumber, this.getViewLineMinColumn(viewStartLineNumber));
662
const modelEnd = this.convertViewPositionToModelPosition(viewEndLineNumber, this.getViewLineMaxColumn(viewEndLineNumber));
663
664
let result: number[] = [];
665
const resultRepeatCount: number[] = [];
666
const resultRepeatOption: IndentGuideRepeatOption[] = [];
667
const modelStartLineIndex = modelStart.lineNumber - 1;
668
const modelEndLineIndex = modelEnd.lineNumber - 1;
669
670
let reqStart: Position | null = null;
671
for (let modelLineIndex = modelStartLineIndex; modelLineIndex <= modelEndLineIndex; modelLineIndex++) {
672
const line = this.modelLineProjections[modelLineIndex];
673
if (line.isVisible()) {
674
const viewLineStartIndex = line.getViewLineNumberOfModelPosition(0, modelLineIndex === modelStartLineIndex ? modelStart.column : 1);
675
const viewLineEndIndex = line.getViewLineNumberOfModelPosition(0, this.model.getLineMaxColumn(modelLineIndex + 1));
676
const count = viewLineEndIndex - viewLineStartIndex + 1;
677
let option = IndentGuideRepeatOption.BlockNone;
678
if (count > 1 && line.getViewLineMinColumn(this.model, modelLineIndex + 1, viewLineEndIndex) === 1) {
679
// wrapped lines should block indent guides
680
option = (viewLineStartIndex === 0 ? IndentGuideRepeatOption.BlockSubsequent : IndentGuideRepeatOption.BlockAll);
681
}
682
resultRepeatCount.push(count);
683
resultRepeatOption.push(option);
684
// merge into previous request
685
if (reqStart === null) {
686
reqStart = new Position(modelLineIndex + 1, 0);
687
}
688
} else {
689
// hit invisible line => flush request
690
if (reqStart !== null) {
691
result = result.concat(this.model.guides.getLinesIndentGuides(reqStart.lineNumber, modelLineIndex));
692
reqStart = null;
693
}
694
}
695
}
696
697
if (reqStart !== null) {
698
result = result.concat(this.model.guides.getLinesIndentGuides(reqStart.lineNumber, modelEnd.lineNumber));
699
reqStart = null;
700
}
701
702
const viewLineCount = viewEndLineNumber - viewStartLineNumber + 1;
703
const viewIndents = new Array<number>(viewLineCount);
704
let currIndex = 0;
705
for (let i = 0, len = result.length; i < len; i++) {
706
let value = result[i];
707
const count = Math.min(viewLineCount - currIndex, resultRepeatCount[i]);
708
const option = resultRepeatOption[i];
709
let blockAtIndex: number;
710
if (option === IndentGuideRepeatOption.BlockAll) {
711
blockAtIndex = 0;
712
} else if (option === IndentGuideRepeatOption.BlockSubsequent) {
713
blockAtIndex = 1;
714
} else {
715
blockAtIndex = count;
716
}
717
for (let j = 0; j < count; j++) {
718
if (j === blockAtIndex) {
719
value = 0;
720
}
721
viewIndents[currIndex++] = value;
722
}
723
}
724
return viewIndents;
725
}
726
727
public getViewLineContent(viewLineNumber: number): string {
728
const info = this.getViewLineInfo(viewLineNumber);
729
return this.modelLineProjections[info.modelLineNumber - 1].getViewLineContent(this.model, info.modelLineNumber, info.modelLineWrappedLineIdx);
730
}
731
732
public getViewLineLength(viewLineNumber: number): number {
733
const info = this.getViewLineInfo(viewLineNumber);
734
return this.modelLineProjections[info.modelLineNumber - 1].getViewLineLength(this.model, info.modelLineNumber, info.modelLineWrappedLineIdx);
735
}
736
737
public getViewLineMinColumn(viewLineNumber: number): number {
738
const info = this.getViewLineInfo(viewLineNumber);
739
return this.modelLineProjections[info.modelLineNumber - 1].getViewLineMinColumn(this.model, info.modelLineNumber, info.modelLineWrappedLineIdx);
740
}
741
742
public getViewLineMaxColumn(viewLineNumber: number): number {
743
const info = this.getViewLineInfo(viewLineNumber);
744
return this.modelLineProjections[info.modelLineNumber - 1].getViewLineMaxColumn(this.model, info.modelLineNumber, info.modelLineWrappedLineIdx);
745
}
746
747
public getViewLineData(viewLineNumber: number): ViewLineData {
748
const info = this.getViewLineInfo(viewLineNumber);
749
return this.modelLineProjections[info.modelLineNumber - 1].getViewLineData(this.model, info.modelLineNumber, info.modelLineWrappedLineIdx);
750
}
751
752
public getViewLinesData(viewStartLineNumber: number, viewEndLineNumber: number, needed: boolean[]): ViewLineData[] {
753
754
viewStartLineNumber = this._toValidViewLineNumber(viewStartLineNumber);
755
viewEndLineNumber = this._toValidViewLineNumber(viewEndLineNumber);
756
757
const start = this.projectedModelLineLineCounts.getIndexOf(viewStartLineNumber - 1);
758
let viewLineNumber = viewStartLineNumber;
759
const startModelLineIndex = start.index;
760
const startRemainder = start.remainder;
761
762
const result: ViewLineData[] = [];
763
for (let modelLineIndex = startModelLineIndex, len = this.model.getLineCount(); modelLineIndex < len; modelLineIndex++) {
764
const line = this.modelLineProjections[modelLineIndex];
765
if (!line.isVisible()) {
766
continue;
767
}
768
const fromViewLineIndex = (modelLineIndex === startModelLineIndex ? startRemainder : 0);
769
let remainingViewLineCount = line.getViewLineCount() - fromViewLineIndex;
770
771
let lastLine = false;
772
if (viewLineNumber + remainingViewLineCount > viewEndLineNumber) {
773
lastLine = true;
774
remainingViewLineCount = viewEndLineNumber - viewLineNumber + 1;
775
}
776
777
line.getViewLinesData(this.model, modelLineIndex + 1, fromViewLineIndex, remainingViewLineCount, viewLineNumber - viewStartLineNumber, needed, result);
778
779
viewLineNumber += remainingViewLineCount;
780
781
if (lastLine) {
782
break;
783
}
784
}
785
786
return result;
787
}
788
789
public validateViewPosition(viewLineNumber: number, viewColumn: number, expectedModelPosition: Position): Position {
790
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
791
792
const r = this.projectedModelLineLineCounts.getIndexOf(viewLineNumber - 1);
793
const lineIndex = r.index;
794
const remainder = r.remainder;
795
796
const line = this.modelLineProjections[lineIndex];
797
798
const minColumn = line.getViewLineMinColumn(this.model, lineIndex + 1, remainder);
799
const maxColumn = line.getViewLineMaxColumn(this.model, lineIndex + 1, remainder);
800
if (viewColumn < minColumn) {
801
viewColumn = minColumn;
802
}
803
if (viewColumn > maxColumn) {
804
viewColumn = maxColumn;
805
}
806
807
const computedModelColumn = line.getModelColumnOfViewPosition(remainder, viewColumn);
808
const computedModelPosition = this.model.validatePosition(new Position(lineIndex + 1, computedModelColumn));
809
810
if (computedModelPosition.equals(expectedModelPosition)) {
811
return new Position(viewLineNumber, viewColumn);
812
}
813
814
return this.convertModelPositionToViewPosition(expectedModelPosition.lineNumber, expectedModelPosition.column);
815
}
816
817
public validateViewRange(viewRange: Range, expectedModelRange: Range): Range {
818
const validViewStart = this.validateViewPosition(viewRange.startLineNumber, viewRange.startColumn, expectedModelRange.getStartPosition());
819
const validViewEnd = this.validateViewPosition(viewRange.endLineNumber, viewRange.endColumn, expectedModelRange.getEndPosition());
820
return new Range(validViewStart.lineNumber, validViewStart.column, validViewEnd.lineNumber, validViewEnd.column);
821
}
822
823
public convertViewPositionToModelPosition(viewLineNumber: number, viewColumn: number): Position {
824
const info = this.getViewLineInfo(viewLineNumber);
825
826
const inputColumn = this.modelLineProjections[info.modelLineNumber - 1].getModelColumnOfViewPosition(info.modelLineWrappedLineIdx, viewColumn);
827
// console.log('out -> in ' + viewLineNumber + ',' + viewColumn + ' ===> ' + (lineIndex+1) + ',' + inputColumn);
828
return this.model.validatePosition(new Position(info.modelLineNumber, inputColumn));
829
}
830
831
public convertViewRangeToModelRange(viewRange: Range): Range {
832
const start = this.convertViewPositionToModelPosition(viewRange.startLineNumber, viewRange.startColumn);
833
const end = this.convertViewPositionToModelPosition(viewRange.endLineNumber, viewRange.endColumn);
834
return new Range(start.lineNumber, start.column, end.lineNumber, end.column);
835
}
836
837
public convertModelPositionToViewPosition(_modelLineNumber: number, _modelColumn: number, affinity: PositionAffinity = PositionAffinity.None, allowZeroLineNumber: boolean = false, belowHiddenRanges: boolean = false): Position {
838
839
const validPosition = this.model.validatePosition(new Position(_modelLineNumber, _modelColumn));
840
const inputLineNumber = validPosition.lineNumber;
841
const inputColumn = validPosition.column;
842
843
let lineIndex = inputLineNumber - 1, lineIndexChanged = false;
844
if (belowHiddenRanges) {
845
while (lineIndex < this.modelLineProjections.length && !this.modelLineProjections[lineIndex].isVisible()) {
846
lineIndex++;
847
lineIndexChanged = true;
848
}
849
} else {
850
while (lineIndex > 0 && !this.modelLineProjections[lineIndex].isVisible()) {
851
lineIndex--;
852
lineIndexChanged = true;
853
}
854
}
855
if (lineIndex === 0 && !this.modelLineProjections[lineIndex].isVisible()) {
856
// Could not reach a real line
857
// console.log('in -> out ' + inputLineNumber + ',' + inputColumn + ' ===> ' + 1 + ',' + 1);
858
// TODO@alexdima@hediet this isn't soo pretty
859
return new Position(allowZeroLineNumber ? 0 : 1, 1);
860
}
861
const deltaLineNumber = 1 + this.projectedModelLineLineCounts.getPrefixSum(lineIndex);
862
863
let r: Position;
864
if (lineIndexChanged) {
865
if (belowHiddenRanges) {
866
r = this.modelLineProjections[lineIndex].getViewPositionOfModelPosition(deltaLineNumber, 1, affinity);
867
} else {
868
r = this.modelLineProjections[lineIndex].getViewPositionOfModelPosition(deltaLineNumber, this.model.getLineMaxColumn(lineIndex + 1), affinity);
869
}
870
} else {
871
r = this.modelLineProjections[inputLineNumber - 1].getViewPositionOfModelPosition(deltaLineNumber, inputColumn, affinity);
872
}
873
874
// console.log('in -> out ' + inputLineNumber + ',' + inputColumn + ' ===> ' + r.lineNumber + ',' + r);
875
return r;
876
}
877
878
/**
879
* @param affinity The affinity in case of an empty range. Has no effect for non-empty ranges.
880
*/
881
public convertModelRangeToViewRange(modelRange: Range, affinity: PositionAffinity = PositionAffinity.Left): Range {
882
if (modelRange.isEmpty()) {
883
const start = this.convertModelPositionToViewPosition(modelRange.startLineNumber, modelRange.startColumn, affinity);
884
return Range.fromPositions(start);
885
} else {
886
const start = this.convertModelPositionToViewPosition(modelRange.startLineNumber, modelRange.startColumn, PositionAffinity.Right);
887
const end = this.convertModelPositionToViewPosition(modelRange.endLineNumber, modelRange.endColumn, PositionAffinity.Left);
888
return new Range(start.lineNumber, start.column, end.lineNumber, end.column);
889
}
890
}
891
892
public getViewLineNumberOfModelPosition(modelLineNumber: number, modelColumn: number): number {
893
let lineIndex = modelLineNumber - 1;
894
if (this.modelLineProjections[lineIndex].isVisible()) {
895
// this model line is visible
896
const deltaLineNumber = 1 + this.projectedModelLineLineCounts.getPrefixSum(lineIndex);
897
return this.modelLineProjections[lineIndex].getViewLineNumberOfModelPosition(deltaLineNumber, modelColumn);
898
}
899
900
// this model line is not visible
901
while (lineIndex > 0 && !this.modelLineProjections[lineIndex].isVisible()) {
902
lineIndex--;
903
}
904
if (lineIndex === 0 && !this.modelLineProjections[lineIndex].isVisible()) {
905
// Could not reach a real line
906
return 1;
907
}
908
const deltaLineNumber = 1 + this.projectedModelLineLineCounts.getPrefixSum(lineIndex);
909
return this.modelLineProjections[lineIndex].getViewLineNumberOfModelPosition(deltaLineNumber, this.model.getLineMaxColumn(lineIndex + 1));
910
}
911
912
public getDecorationsInRange(range: Range, ownerId: number, filterOutValidation: boolean, filterFontDecorations: boolean, onlyMinimapDecorations: boolean, onlyMarginDecorations: boolean): IModelDecoration[] {
913
const modelStart = this.convertViewPositionToModelPosition(range.startLineNumber, range.startColumn);
914
const modelEnd = this.convertViewPositionToModelPosition(range.endLineNumber, range.endColumn);
915
916
if (modelEnd.lineNumber - modelStart.lineNumber <= range.endLineNumber - range.startLineNumber) {
917
// most likely there are no hidden lines => fast path
918
// fetch decorations from column 1 to cover the case of wrapped lines that have whole line decorations at column 1
919
return this.model.getDecorationsInRange(new Range(modelStart.lineNumber, 1, modelEnd.lineNumber, modelEnd.column), ownerId, filterOutValidation, filterFontDecorations, onlyMinimapDecorations, onlyMarginDecorations);
920
}
921
922
let result: IModelDecoration[] = [];
923
const modelStartLineIndex = modelStart.lineNumber - 1;
924
const modelEndLineIndex = modelEnd.lineNumber - 1;
925
926
let reqStart: Position | null = null;
927
for (let modelLineIndex = modelStartLineIndex; modelLineIndex <= modelEndLineIndex; modelLineIndex++) {
928
const line = this.modelLineProjections[modelLineIndex];
929
if (line.isVisible()) {
930
// merge into previous request
931
if (reqStart === null) {
932
reqStart = new Position(modelLineIndex + 1, modelLineIndex === modelStartLineIndex ? modelStart.column : 1);
933
}
934
} else {
935
// hit invisible line => flush request
936
if (reqStart !== null) {
937
const maxLineColumn = this.model.getLineMaxColumn(modelLineIndex);
938
result = result.concat(this.model.getDecorationsInRange(new Range(reqStart.lineNumber, reqStart.column, modelLineIndex, maxLineColumn), ownerId, filterOutValidation, filterFontDecorations, onlyMinimapDecorations));
939
reqStart = null;
940
}
941
}
942
}
943
944
if (reqStart !== null) {
945
result = result.concat(this.model.getDecorationsInRange(new Range(reqStart.lineNumber, reqStart.column, modelEnd.lineNumber, modelEnd.column), ownerId, filterOutValidation, filterFontDecorations, onlyMinimapDecorations));
946
reqStart = null;
947
}
948
949
result.sort((a, b) => {
950
const res = Range.compareRangesUsingStarts(a.range, b.range);
951
if (res === 0) {
952
if (a.id < b.id) {
953
return -1;
954
}
955
if (a.id > b.id) {
956
return 1;
957
}
958
return 0;
959
}
960
return res;
961
});
962
963
// Eliminate duplicate decorations that might have intersected our visible ranges multiple times
964
const finalResult: IModelDecoration[] = [];
965
let finalResultLen = 0;
966
let prevDecId: string | null = null;
967
for (const dec of result) {
968
const decId = dec.id;
969
if (prevDecId === decId) {
970
// skip
971
continue;
972
}
973
prevDecId = decId;
974
finalResult[finalResultLen++] = dec;
975
}
976
977
return finalResult;
978
}
979
980
public getInjectedTextAt(position: Position): InjectedText | null {
981
const info = this.getViewLineInfo(position.lineNumber);
982
return this.modelLineProjections[info.modelLineNumber - 1].getInjectedTextAt(info.modelLineWrappedLineIdx, position.column);
983
}
984
985
normalizePosition(position: Position, affinity: PositionAffinity): Position {
986
const info = this.getViewLineInfo(position.lineNumber);
987
return this.modelLineProjections[info.modelLineNumber - 1].normalizePosition(info.modelLineWrappedLineIdx, position, affinity);
988
}
989
990
public getLineIndentColumn(lineNumber: number): number {
991
const info = this.getViewLineInfo(lineNumber);
992
if (info.modelLineWrappedLineIdx === 0) {
993
return this.model.getLineIndentColumn(info.modelLineNumber);
994
}
995
996
// wrapped lines have no indentation.
997
// We deliberately don't handle the case that indentation is wrapped
998
// to avoid two view lines reporting indentation for the very same model line.
999
return 0;
1000
}
1001
}
1002
1003
/**
1004
* Overlapping unsorted ranges:
1005
* [ ) [ ) [ )
1006
* [ ) [ )
1007
* ->
1008
* Non overlapping sorted ranges:
1009
* [ ) [ ) [ )
1010
*
1011
* Note: This function only considers line information! Columns are ignored.
1012
*/
1013
function normalizeLineRanges(ranges: Range[]): Range[] {
1014
if (ranges.length === 0) {
1015
return [];
1016
}
1017
1018
const sortedRanges = ranges.slice();
1019
sortedRanges.sort(Range.compareRangesUsingStarts);
1020
1021
const result: Range[] = [];
1022
let currentRangeStart = sortedRanges[0].startLineNumber;
1023
let currentRangeEnd = sortedRanges[0].endLineNumber;
1024
1025
for (let i = 1, len = sortedRanges.length; i < len; i++) {
1026
const range = sortedRanges[i];
1027
1028
if (range.startLineNumber > currentRangeEnd + 1) {
1029
result.push(new Range(currentRangeStart, 1, currentRangeEnd, 1));
1030
currentRangeStart = range.startLineNumber;
1031
currentRangeEnd = range.endLineNumber;
1032
} else if (range.endLineNumber > currentRangeEnd) {
1033
currentRangeEnd = range.endLineNumber;
1034
}
1035
}
1036
result.push(new Range(currentRangeStart, 1, currentRangeEnd, 1));
1037
return result;
1038
}
1039
1040
/**
1041
* Represents a view line. Can be used to efficiently query more information about it.
1042
*/
1043
class ViewLineInfo {
1044
public get isWrappedLineContinuation(): boolean {
1045
return this.modelLineWrappedLineIdx > 0;
1046
}
1047
1048
constructor(
1049
public readonly modelLineNumber: number,
1050
public readonly modelLineWrappedLineIdx: number,
1051
) { }
1052
}
1053
1054
/**
1055
* A list of view lines that have a contiguous span in the model.
1056
*/
1057
class ViewLineInfoGroupedByModelRange {
1058
constructor(public readonly modelRange: Range, public readonly viewLines: ViewLineInfo[]) {
1059
}
1060
}
1061
1062
class CoordinatesConverter implements ICoordinatesConverter {
1063
private readonly _lines: ViewModelLinesFromProjectedModel;
1064
1065
constructor(lines: ViewModelLinesFromProjectedModel) {
1066
this._lines = lines;
1067
}
1068
1069
// View -> Model conversion and related methods
1070
1071
public convertViewPositionToModelPosition(viewPosition: Position): Position {
1072
return this._lines.convertViewPositionToModelPosition(viewPosition.lineNumber, viewPosition.column);
1073
}
1074
1075
public convertViewRangeToModelRange(viewRange: Range): Range {
1076
return this._lines.convertViewRangeToModelRange(viewRange);
1077
}
1078
1079
public validateViewPosition(viewPosition: Position, expectedModelPosition: Position): Position {
1080
return this._lines.validateViewPosition(viewPosition.lineNumber, viewPosition.column, expectedModelPosition);
1081
}
1082
1083
public validateViewRange(viewRange: Range, expectedModelRange: Range): Range {
1084
return this._lines.validateViewRange(viewRange, expectedModelRange);
1085
}
1086
1087
// Model -> View conversion and related methods
1088
1089
public convertModelPositionToViewPosition(modelPosition: Position, affinity?: PositionAffinity, allowZero?: boolean, belowHiddenRanges?: boolean): Position {
1090
return this._lines.convertModelPositionToViewPosition(modelPosition.lineNumber, modelPosition.column, affinity, allowZero, belowHiddenRanges);
1091
}
1092
1093
public convertModelRangeToViewRange(modelRange: Range, affinity?: PositionAffinity): Range {
1094
return this._lines.convertModelRangeToViewRange(modelRange, affinity);
1095
}
1096
1097
public modelPositionIsVisible(modelPosition: Position): boolean {
1098
return this._lines.modelPositionIsVisible(modelPosition.lineNumber, modelPosition.column);
1099
}
1100
1101
public getModelLineViewLineCount(modelLineNumber: number): number {
1102
return this._lines.getModelLineViewLineCount(modelLineNumber);
1103
}
1104
1105
public getViewLineNumberOfModelPosition(modelLineNumber: number, modelColumn: number): number {
1106
return this._lines.getViewLineNumberOfModelPosition(modelLineNumber, modelColumn);
1107
}
1108
}
1109
1110
const enum IndentGuideRepeatOption {
1111
BlockNone = 0,
1112
BlockSubsequent = 1,
1113
BlockAll = 2
1114
}
1115
1116
export class ViewModelLinesFromModelAsIs implements IViewModelLines {
1117
public readonly model: ITextModel;
1118
1119
constructor(model: ITextModel) {
1120
this.model = model;
1121
}
1122
1123
public dispose(): void {
1124
}
1125
1126
public createCoordinatesConverter(): ICoordinatesConverter {
1127
return new IdentityCoordinatesConverter(this.model);
1128
}
1129
1130
public getHiddenAreas(): Range[] {
1131
return [];
1132
}
1133
1134
public setHiddenAreas(_ranges: Range[]): boolean {
1135
return false;
1136
}
1137
1138
public setTabSize(_newTabSize: number): boolean {
1139
return false;
1140
}
1141
1142
public setWrappingSettings(_fontInfo: FontInfo, _wrappingStrategy: 'simple' | 'advanced', _wrappingColumn: number, _wrappingIndent: WrappingIndent): boolean {
1143
return false;
1144
}
1145
1146
public createLineBreaksComputer(): ILineBreaksComputer {
1147
const result: null[] = [];
1148
return {
1149
addRequest: (lineText: string, injectedText: LineInjectedText[] | null, previousLineBreakData: ModelLineProjectionData | null) => {
1150
result.push(null);
1151
},
1152
finalize: () => {
1153
return result;
1154
}
1155
};
1156
}
1157
1158
public onModelFlushed(): void {
1159
}
1160
1161
public onModelLinesDeleted(_versionId: number | null, fromLineNumber: number, toLineNumber: number): viewEvents.ViewLinesDeletedEvent | null {
1162
return new viewEvents.ViewLinesDeletedEvent(fromLineNumber, toLineNumber);
1163
}
1164
1165
public onModelLinesInserted(_versionId: number | null, fromLineNumber: number, toLineNumber: number, lineBreaks: (ModelLineProjectionData | null)[]): viewEvents.ViewLinesInsertedEvent | null {
1166
return new viewEvents.ViewLinesInsertedEvent(fromLineNumber, toLineNumber);
1167
}
1168
1169
public onModelLineChanged(_versionId: number | null, lineNumber: number, lineBreakData: ModelLineProjectionData | null): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null] {
1170
return [false, new viewEvents.ViewLinesChangedEvent(lineNumber, 1), null, null];
1171
}
1172
1173
public acceptVersionId(_versionId: number): void {
1174
}
1175
1176
public getViewLineCount(): number {
1177
return this.model.getLineCount();
1178
}
1179
1180
public getActiveIndentGuide(viewLineNumber: number, _minLineNumber: number, _maxLineNumber: number): IActiveIndentGuideInfo {
1181
return {
1182
startLineNumber: viewLineNumber,
1183
endLineNumber: viewLineNumber,
1184
indent: 0
1185
};
1186
}
1187
1188
public getViewLinesBracketGuides(startLineNumber: number, endLineNumber: number, activePosition: IPosition | null): IndentGuide[][] {
1189
return new Array(endLineNumber - startLineNumber + 1).fill([]);
1190
}
1191
1192
public getViewLinesIndentGuides(viewStartLineNumber: number, viewEndLineNumber: number): number[] {
1193
const viewLineCount = viewEndLineNumber - viewStartLineNumber + 1;
1194
const result = new Array<number>(viewLineCount);
1195
for (let i = 0; i < viewLineCount; i++) {
1196
result[i] = 0;
1197
}
1198
return result;
1199
}
1200
1201
public getViewLineContent(viewLineNumber: number): string {
1202
return this.model.getLineContent(viewLineNumber);
1203
}
1204
1205
public getViewLineLength(viewLineNumber: number): number {
1206
return this.model.getLineLength(viewLineNumber);
1207
}
1208
1209
public getViewLineMinColumn(viewLineNumber: number): number {
1210
return this.model.getLineMinColumn(viewLineNumber);
1211
}
1212
1213
public getViewLineMaxColumn(viewLineNumber: number): number {
1214
return this.model.getLineMaxColumn(viewLineNumber);
1215
}
1216
1217
public getViewLineData(viewLineNumber: number): ViewLineData {
1218
const lineTokens = this.model.tokenization.getLineTokens(viewLineNumber);
1219
const lineContent = lineTokens.getLineContent();
1220
return new ViewLineData(
1221
lineContent,
1222
false,
1223
1,
1224
lineContent.length + 1,
1225
0,
1226
lineTokens.inflate(),
1227
null
1228
);
1229
}
1230
1231
public getViewLinesData(viewStartLineNumber: number, viewEndLineNumber: number, needed: boolean[]): Array<ViewLineData | null> {
1232
const lineCount = this.model.getLineCount();
1233
viewStartLineNumber = Math.min(Math.max(1, viewStartLineNumber), lineCount);
1234
viewEndLineNumber = Math.min(Math.max(1, viewEndLineNumber), lineCount);
1235
1236
const result: Array<ViewLineData | null> = [];
1237
for (let lineNumber = viewStartLineNumber; lineNumber <= viewEndLineNumber; lineNumber++) {
1238
const idx = lineNumber - viewStartLineNumber;
1239
result[idx] = needed[idx] ? this.getViewLineData(lineNumber) : null;
1240
}
1241
1242
return result;
1243
}
1244
1245
public getDecorationsInRange(range: Range, ownerId: number, filterOutValidation: boolean, filterFontDecorations: boolean, onlyMinimapDecorations: boolean, onlyMarginDecorations: boolean): IModelDecoration[] {
1246
return this.model.getDecorationsInRange(range, ownerId, filterOutValidation, filterFontDecorations, onlyMinimapDecorations, onlyMarginDecorations);
1247
}
1248
1249
normalizePosition(position: Position, affinity: PositionAffinity): Position {
1250
return this.model.normalizePosition(position, affinity);
1251
}
1252
1253
public getLineIndentColumn(lineNumber: number): number {
1254
return this.model.getLineIndentColumn(lineNumber);
1255
}
1256
1257
public getInjectedTextAt(position: Position): InjectedText | null {
1258
// Identity lines collection does not support injected text.
1259
return null;
1260
}
1261
}
1262
1263