Skip to content

Commit 3d2581a

Browse files
committed
Pass in previousBreakingData to line mapping computation
1 parent f3a313a commit 3d2581a

4 files changed

Lines changed: 91 additions & 63 deletions

File tree

src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -72,50 +72,28 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor
7272
columnsForFullWidthChar = +columnsForFullWidthChar; //@perf
7373

7474
let requests: string[] = [];
75+
let previousBreakingData: (LineBreakingData | null)[] = [];
7576
return {
76-
addRequest: (lineText: string) => {
77+
addRequest: (lineText: string, previousLineBreakingData: LineBreakingData | null) => {
7778
requests.push(lineText);
79+
previousBreakingData.push(previousLineBreakingData);
7880
},
7981
finalize: () => {
8082
let result: (LineBreakingData | null)[] = [];
8183
for (let i = 0, len = requests.length; i < len; i++) {
82-
result[i] = this._createLineMapping(requests[i], tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent);
84+
result[i] = this._createLineMapping(requests[i], previousBreakingData[i], tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent);
8385
}
8486
return result;
8587
}
8688
};
8789
}
8890

89-
private _createLineMapping(lineText: string, tabSize: number, firstLineBreakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): LineBreakingData | null {
91+
private _createLineMapping(lineText: string, previousBreakingData: LineBreakingData | null, tabSize: number, firstLineBreakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): LineBreakingData | null {
9092
if (firstLineBreakingColumn === -1) {
9193
return null;
9294
}
9395

94-
let firstNonWhitespaceIndex = -1;
95-
let wrappedTextIndentLength = 0;
96-
if (hardWrappingIndent !== WrappingIndent.None) {
97-
firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineText);
98-
if (firstNonWhitespaceIndex !== -1) {
99-
// Track existing indent
100-
101-
for (let i = 0; i < firstNonWhitespaceIndex; i++) {
102-
const charWidth = (lineText.charCodeAt(i) === CharCode.Tab ? tabCharacterWidth(wrappedTextIndentLength, tabSize) : 1);
103-
wrappedTextIndentLength += charWidth;
104-
}
105-
106-
// Increase indent of continuation lines, if desired
107-
const numberOfAdditionalTabs = (hardWrappingIndent === WrappingIndent.DeepIndent ? 2 : hardWrappingIndent === WrappingIndent.Indent ? 1 : 0);
108-
for (let i = 0; i < numberOfAdditionalTabs; i++) {
109-
const charWidth = tabCharacterWidth(wrappedTextIndentLength, tabSize);
110-
wrappedTextIndentLength += charWidth;
111-
}
112-
113-
// Force sticking to beginning of line if no character would fit except for the indentation
114-
if (wrappedTextIndentLength + columnsForFullWidthChar > firstLineBreakingColumn) {
115-
wrappedTextIndentLength = 0;
116-
}
117-
}
118-
}
96+
const wrappedTextIndentLength = computeWrappedTextIndentLength(lineText, tabSize, firstLineBreakingColumn, columnsForFullWidthChar, hardWrappingIndent);
11997
const wrappedLineBreakingColumn = firstLineBreakingColumn - wrappedTextIndentLength;
12098

12199
const classifier = this.classifier;
@@ -223,3 +201,31 @@ function canBreakAfter(charCodeClass: CharacterClass, nextCharCodeClass: Charact
223201
|| (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && nextCharCodeClass !== CharacterClass.BREAK_AFTER)
224202
);
225203
}
204+
205+
function computeWrappedTextIndentLength(lineText: string, tabSize: number, firstLineBreakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): number {
206+
let wrappedTextIndentLength = 0;
207+
if (hardWrappingIndent !== WrappingIndent.None) {
208+
const firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineText);
209+
if (firstNonWhitespaceIndex !== -1) {
210+
// Track existing indent
211+
212+
for (let i = 0; i < firstNonWhitespaceIndex; i++) {
213+
const charWidth = (lineText.charCodeAt(i) === CharCode.Tab ? tabCharacterWidth(wrappedTextIndentLength, tabSize) : 1);
214+
wrappedTextIndentLength += charWidth;
215+
}
216+
217+
// Increase indent of continuation lines, if desired
218+
const numberOfAdditionalTabs = (hardWrappingIndent === WrappingIndent.DeepIndent ? 2 : hardWrappingIndent === WrappingIndent.Indent ? 1 : 0);
219+
for (let i = 0; i < numberOfAdditionalTabs; i++) {
220+
const charWidth = tabCharacterWidth(wrappedTextIndentLength, tabSize);
221+
wrappedTextIndentLength += charWidth;
222+
}
223+
224+
// Force sticking to beginning of line if no character would fit except for the indentation
225+
if (wrappedTextIndentLength + columnsForFullWidthChar > firstLineBreakingColumn) {
226+
wrappedTextIndentLength = 0;
227+
}
228+
}
229+
}
230+
return wrappedTextIndentLength;
231+
}

src/vs/editor/common/viewModel/splitLinesCollection.ts

Lines changed: 54 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,10 @@ export class LineBreakingData {
6767
}
6868

6969
export interface ILineMappingComputer {
70-
addRequest(lineText: string): void;
70+
/**
71+
* Pass in previousLineBreakingData if the only difference is in breaking columns!!!
72+
*/
73+
addRequest(lineText: string, previousLineBreakingData: LineBreakingData | null): void;
7174
finalize(): (LineBreakingData | null)[];
7275
}
7376

@@ -88,6 +91,7 @@ export interface ISplitLine {
8891
isVisible(): boolean;
8992
setVisible(isVisible: boolean): ISplitLine;
9093

94+
getLineBreakingData(): LineBreakingData | null;
9195
getViewLineCount(): number;
9296
getViewLineContent(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): string;
9397
getViewLineLength(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number;
@@ -286,7 +290,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
286290
this.wrappingIndent = wrappingIndent;
287291
this.linePositionMapperFactory = linePositionMapperFactory;
288292

289-
this._constructLines(true);
293+
this._constructLines(/*resetHiddenAreas*/true, null);
290294
}
291295

292296
public dispose(): void {
@@ -297,7 +301,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
297301
return new CoordinatesConverter(this);
298302
}
299303

300-
private _constructLines(resetHiddenAreas: boolean): void {
304+
private _constructLines(resetHiddenAreas: boolean, previousLineMapping: ((LineBreakingData | null)[]) | null): void {
301305
this.lines = [];
302306

303307
if (resetHiddenAreas) {
@@ -308,7 +312,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
308312
const lineCount = linesContent.length;
309313
const lineMappingComputer = this.linePositionMapperFactory.createLineMappingComputer(this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent);
310314
for (let i = 0; i < lineCount; i++) {
311-
lineMappingComputer.addRequest(linesContent[i]);
315+
lineMappingComputer.addRequest(linesContent[i], previousLineMapping ? previousLineMapping[i] : null);
312316
}
313317
const lineMappings = lineMappingComputer.finalize();
314318

@@ -461,7 +465,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
461465
}
462466
this.tabSize = newTabSize;
463467

464-
this._constructLines(false);
468+
this._constructLines(/*resetHiddenAreas*/false, null);
465469

466470
return true;
467471
}
@@ -471,11 +475,21 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
471475
return false;
472476
}
473477

478+
const onlyWrappingColumnChanged = (this.wrappingIndent === wrappingIndent && this.wrappingColumn !== wrappingColumn && this.columnsForFullWidthChar === columnsForFullWidthChar);
479+
474480
this.wrappingIndent = wrappingIndent;
475481
this.wrappingColumn = wrappingColumn;
476482
this.columnsForFullWidthChar = columnsForFullWidthChar;
477483

478-
this._constructLines(false);
484+
let previousLineMapping: ((LineBreakingData | null)[]) | null = null;
485+
if (onlyWrappingColumnChanged) {
486+
previousLineMapping = [];
487+
for (let i = 0, len = this.lines.length; i < len; i++) {
488+
previousLineMapping[i] = this.lines[i].getLineBreakingData();
489+
}
490+
}
491+
492+
this._constructLines(/*resetHiddenAreas*/false, previousLineMapping);
479493

480494
return true;
481495
}
@@ -485,7 +499,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
485499
}
486500

487501
public onModelFlushed(): void {
488-
this._constructLines(true);
502+
this._constructLines(/*resetHiddenAreas*/true, null);
489503
}
490504

491505
public onModelLinesDeleted(versionId: number, fromLineNumber: number, toLineNumber: number): viewEvents.ViewLinesDeletedEvent | null {
@@ -1005,6 +1019,10 @@ class VisibleIdentitySplitLine implements ISplitLine {
10051019
return InvisibleIdentitySplitLine.INSTANCE;
10061020
}
10071021

1022+
public getLineBreakingData(): LineBreakingData | null {
1023+
return null;
1024+
}
1025+
10081026
public getViewLineCount(): number {
10091027
return 1;
10101028
}
@@ -1076,6 +1094,10 @@ class InvisibleIdentitySplitLine implements ISplitLine {
10761094
return VisibleIdentitySplitLine.INSTANCE;
10771095
}
10781096

1097+
public getLineBreakingData(): LineBreakingData | null {
1098+
return null;
1099+
}
1100+
10791101
public getViewLineCount(): number {
10801102
return 0;
10811103
}
@@ -1119,15 +1141,11 @@ class InvisibleIdentitySplitLine implements ISplitLine {
11191141

11201142
export class SplitLine implements ISplitLine {
11211143

1122-
private readonly _breakOffsets: number[];
1123-
private readonly _breakingOffsetsVisibleColumn: number[];
1124-
private readonly _wrappedTextIndentLength: number;
1144+
private readonly _lineBreakingData: LineBreakingData;
11251145
private _isVisible: boolean;
11261146

1127-
constructor(lineBreaking: LineBreakingData, isVisible: boolean) {
1128-
this._breakOffsets = lineBreaking.breakOffsets;
1129-
this._breakingOffsetsVisibleColumn = lineBreaking.breakingOffsetsVisibleColumn;
1130-
this._wrappedTextIndentLength = lineBreaking.wrappedTextIndentLength;
1147+
constructor(lineBreakingData: LineBreakingData, isVisible: boolean) {
1148+
this._lineBreakingData = lineBreakingData;
11311149
this._isVisible = isVisible;
11321150
}
11331151

@@ -1140,22 +1158,26 @@ export class SplitLine implements ISplitLine {
11401158
return this;
11411159
}
11421160

1161+
public getLineBreakingData(): LineBreakingData | null {
1162+
return this._lineBreakingData;
1163+
}
1164+
11431165
public getViewLineCount(): number {
11441166
if (!this._isVisible) {
11451167
return 0;
11461168
}
1147-
return this._breakOffsets.length;
1169+
return this._lineBreakingData.breakOffsets.length;
11481170
}
11491171

11501172
private getInputStartOffsetOfOutputLineIndex(outputLineIndex: number): number {
1151-
return LineBreakingData.getInputOffsetOfOutputPosition(this._breakOffsets, outputLineIndex, 0);
1173+
return LineBreakingData.getInputOffsetOfOutputPosition(this._lineBreakingData.breakOffsets, outputLineIndex, 0);
11521174
}
11531175

11541176
private getInputEndOffsetOfOutputLineIndex(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number {
1155-
if (outputLineIndex + 1 === this._breakOffsets.length) {
1177+
if (outputLineIndex + 1 === this._lineBreakingData.breakOffsets.length) {
11561178
return model.getLineMaxColumn(modelLineNumber) - 1;
11571179
}
1158-
return LineBreakingData.getInputOffsetOfOutputPosition(this._breakOffsets, outputLineIndex + 1, 0);
1180+
return LineBreakingData.getInputOffsetOfOutputPosition(this._lineBreakingData.breakOffsets, outputLineIndex + 1, 0);
11591181
}
11601182

11611183
public getViewLineContent(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): string {
@@ -1172,7 +1194,7 @@ export class SplitLine implements ISplitLine {
11721194
});
11731195

11741196
if (outputLineIndex > 0) {
1175-
r = spaces(this._wrappedTextIndentLength) + r;
1197+
r = spaces(this._lineBreakingData.wrappedTextIndentLength) + r;
11761198
}
11771199

11781200
return r;
@@ -1187,7 +1209,7 @@ export class SplitLine implements ISplitLine {
11871209
let r = endOffset - startOffset;
11881210

11891211
if (outputLineIndex > 0) {
1190-
r = this._wrappedTextIndentLength + r;
1212+
r = this._lineBreakingData.wrappedTextIndentLength + r;
11911213
}
11921214

11931215
return r;
@@ -1198,7 +1220,7 @@ export class SplitLine implements ISplitLine {
11981220
throw new Error('Not supported');
11991221
}
12001222
if (outputLineIndex > 0) {
1201-
return this._wrappedTextIndentLength + 1;
1223+
return this._lineBreakingData.wrappedTextIndentLength + 1;
12021224
}
12031225
return 1;
12041226
}
@@ -1226,21 +1248,21 @@ export class SplitLine implements ISplitLine {
12261248
});
12271249

12281250
if (outputLineIndex > 0) {
1229-
lineContent = spaces(this._wrappedTextIndentLength) + lineContent;
1251+
lineContent = spaces(this._lineBreakingData.wrappedTextIndentLength) + lineContent;
12301252
}
12311253

1232-
let minColumn = (outputLineIndex > 0 ? this._wrappedTextIndentLength + 1 : 1);
1254+
let minColumn = (outputLineIndex > 0 ? this._lineBreakingData.wrappedTextIndentLength + 1 : 1);
12331255
let maxColumn = lineContent.length + 1;
12341256

12351257
let continuesWithWrappedLine = (outputLineIndex + 1 < this.getViewLineCount());
12361258

12371259
let deltaStartIndex = 0;
12381260
if (outputLineIndex > 0) {
1239-
deltaStartIndex = this._wrappedTextIndentLength;
1261+
deltaStartIndex = this._lineBreakingData.wrappedTextIndentLength;
12401262
}
12411263
let lineTokens = model.getLineTokens(modelLineNumber);
12421264

1243-
const startVisibleColumn = (outputLineIndex === 0 ? 0 : this._breakingOffsetsVisibleColumn[outputLineIndex - 1]);
1265+
const startVisibleColumn = (outputLineIndex === 0 ? 0 : this._lineBreakingData.breakingOffsetsVisibleColumn[outputLineIndex - 1]);
12441266

12451267
return new ViewLineData(
12461268
lineContent,
@@ -1273,25 +1295,25 @@ export class SplitLine implements ISplitLine {
12731295
}
12741296
let adjustedColumn = outputColumn - 1;
12751297
if (outputLineIndex > 0) {
1276-
if (adjustedColumn < this._wrappedTextIndentLength) {
1298+
if (adjustedColumn < this._lineBreakingData.wrappedTextIndentLength) {
12771299
adjustedColumn = 0;
12781300
} else {
1279-
adjustedColumn -= this._wrappedTextIndentLength;
1301+
adjustedColumn -= this._lineBreakingData.wrappedTextIndentLength;
12801302
}
12811303
}
1282-
return LineBreakingData.getInputOffsetOfOutputPosition(this._breakOffsets, outputLineIndex, adjustedColumn) + 1;
1304+
return LineBreakingData.getInputOffsetOfOutputPosition(this._lineBreakingData.breakOffsets, outputLineIndex, adjustedColumn) + 1;
12831305
}
12841306

12851307
public getViewPositionOfModelPosition(deltaLineNumber: number, inputColumn: number): Position {
12861308
if (!this._isVisible) {
12871309
throw new Error('Not supported');
12881310
}
1289-
let r = LineBreakingData.getOutputPositionOfInputOffset(this._breakOffsets, inputColumn - 1);
1311+
let r = LineBreakingData.getOutputPositionOfInputOffset(this._lineBreakingData.breakOffsets, inputColumn - 1);
12901312
let outputLineIndex = r.outputLineIndex;
12911313
let outputColumn = r.outputOffset + 1;
12921314

12931315
if (outputLineIndex > 0) {
1294-
outputColumn += this._wrappedTextIndentLength;
1316+
outputColumn += this._lineBreakingData.wrappedTextIndentLength;
12951317
}
12961318

12971319
// console.log('in -> out ' + deltaLineNumber + ',' + inputColumn + ' ===> ' + (deltaLineNumber+outputLineIndex) + ',' + outputColumn);
@@ -1302,7 +1324,7 @@ export class SplitLine implements ISplitLine {
13021324
if (!this._isVisible) {
13031325
throw new Error('Not supported');
13041326
}
1305-
const r = LineBreakingData.getOutputPositionOfInputOffset(this._breakOffsets, inputColumn - 1);
1327+
const r = LineBreakingData.getOutputPositionOfInputOffset(this._lineBreakingData.breakOffsets, inputColumn - 1);
13061328
return (deltaLineNumber + r.outputLineIndex);
13071329
}
13081330
}
@@ -1421,7 +1443,7 @@ export class IdentityLinesCollection implements IViewModelLinesCollection {
14211443
public createLineMappingComputer(): ILineMappingComputer {
14221444
let result: null[] = [];
14231445
return {
1424-
addRequest: (lineText: string) => {
1446+
addRequest: (lineText: string, previousLineBreakingData: LineBreakingData | null) => {
14251447
result.push(null);
14261448
},
14271449
finalize: () => {

src/vs/editor/common/viewModel/viewModelImpl.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,12 +202,12 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
202202
switch (change.changeType) {
203203
case textModelEvents.RawContentChangedType.LinesInserted: {
204204
for (const line of change.detail) {
205-
lineMappingComputer.addRequest(line);
205+
lineMappingComputer.addRequest(line, null);
206206
}
207207
break;
208208
}
209209
case textModelEvents.RawContentChangedType.LineChanged: {
210-
lineMappingComputer.addRequest(change.detail);
210+
lineMappingComputer.addRequest(change.detail, null);
211211
break;
212212
}
213213
}

src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ function assertLineMapping(factory: ILineMapperFactory, tabSize: number, breakAf
2222
}
2323

2424
const lineMappingComputer = factory.createLineMappingComputer(tabSize, breakAfter, 2, wrappingIndent);
25-
lineMappingComputer.addRequest(rawText);
25+
lineMappingComputer.addRequest(rawText, null);
2626
const lineMappings = lineMappingComputer.finalize();
2727
const mapper = lineMappings[0];
2828

0 commit comments

Comments
 (0)