Skip to content

Commit b8b0b55

Browse files
committed
wip
1 parent 25e65e6 commit b8b0b55

3 files changed

Lines changed: 372 additions & 18 deletions

File tree

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

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,222 @@ function createLineMapping(classifier: WrappingCharacterClassifier, previousBrea
105105
let breakingOffsets: number[] = [];
106106
let breakingOffsetsVisibleColumn: number[] = [];
107107
let breakingOffsetsCount: number = 0;
108+
109+
if (previousBreakingData/* && firstLineBreakingColumn >= 10 && Math.abs(previousBreakingData.breakingColumn - firstLineBreakingColumn) <= 3 */) {
110+
const prevBreakingOffsets = previousBreakingData.breakOffsets;
111+
const prevBreakingOffsetsVisibleColumn = previousBreakingData.breakingOffsetsVisibleColumn;
112+
113+
let breakingColumn = firstLineBreakingColumn;
114+
const prevLen = prevBreakingOffsets.length;
115+
let prevIndex = 0;
116+
while (prevIndex < prevLen) {
117+
118+
// Allow for prevIndex to be -1 (for the case where we hit a tab when walking backwards from the first break)
119+
let breakOffset = prevIndex < 0 ? 0 : prevBreakingOffsets[prevIndex];
120+
let breakOffsetVisibleColumn = prevIndex < 0 ? 0 : prevBreakingOffsetsVisibleColumn[prevIndex];
121+
122+
if (breakOffsetVisibleColumn === breakingColumn) {
123+
// perfect fit, nothing to do
124+
breakingOffsets[breakingOffsetsCount] = breakOffset;
125+
breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn;
126+
breakingOffsetsCount++;
127+
breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakingColumn;
128+
prevIndex++;
129+
} else if (breakOffsetVisibleColumn < breakingColumn) {
130+
// try to add more characters
131+
const initialBreakOffset = breakOffset;
132+
let visibleColumn = breakOffsetVisibleColumn;
133+
breakOffset = 0;
134+
135+
let prevCharCode = lineText.charCodeAt(initialBreakOffset - 1);
136+
let prevCharCodeClass = classifier.get(prevCharCode);
137+
let mustBreak = false;
138+
for (let i = initialBreakOffset; i < len; i++) {
139+
const charCode = lineText.charCodeAt(i);
140+
const charCodeClass = classifier.get(charCode);
141+
142+
if (strings.isHighSurrogate(prevCharCode)) {
143+
// A surrogate pair must always be considered as a single unit, so it is never to be broken
144+
visibleColumn += 1;
145+
prevCharCode = charCode;
146+
prevCharCodeClass = charCodeClass;
147+
continue;
148+
}
149+
150+
if (canBreak(prevCharCodeClass, charCodeClass)) {
151+
breakOffset = i;
152+
breakOffsetVisibleColumn = visibleColumn;
153+
}
154+
155+
const charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar);
156+
visibleColumn += charWidth;
157+
158+
if (visibleColumn > breakingColumn) {
159+
// We need to break at least before character at `i`:
160+
161+
if (breakOffset === 0 || visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakingColumn) {
162+
// Cannot break at `breakOffset`, must break at `i`
163+
breakOffset = i;
164+
breakOffsetVisibleColumn = visibleColumn - charWidth;
165+
}
166+
167+
mustBreak = true;
168+
break;
169+
}
170+
171+
prevCharCode = charCode;
172+
prevCharCodeClass = charCodeClass;
173+
}
174+
175+
if (!mustBreak) {
176+
// there is no more need to break => stop the outer loop!
177+
// Add last segment
178+
breakingOffsets[breakingOffsetsCount] = prevBreakingOffsets[prevBreakingOffsets.length - 1];
179+
breakingOffsetsVisibleColumn[breakingOffsetsCount] = prevBreakingOffsetsVisibleColumn[prevBreakingOffsets.length - 1];
180+
break;
181+
}
182+
183+
breakingOffsets[breakingOffsetsCount] = breakOffset;
184+
breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn;
185+
breakingOffsetsCount++;
186+
breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakingColumn;
187+
prevIndex++;
188+
} else if (breakOffsetVisibleColumn > breakingColumn) {
189+
const initialBreakOffset = breakOffset;
190+
let visibleColumn = breakOffsetVisibleColumn;
191+
breakOffset = 0;
192+
193+
let charCode = lineText.charCodeAt(initialBreakOffset);
194+
let charCodeClass = classifier.get(charCode);
195+
let hitTab = false;
196+
197+
let firstValidBreakOffset = 0;
198+
let firstValidBreakOffsetVisibleColumn = 0;
199+
for (let i = initialBreakOffset - 1; i >= 0; i--) {
200+
let prevCharCode = lineText.charCodeAt(i);
201+
let prevCharCodeClass = classifier.get(prevCharCode);
202+
203+
if (strings.isHighSurrogate(prevCharCode)) {
204+
// A surrogate pair must always be considered as a single unit, so it is never to be broken
205+
visibleColumn -= 1;
206+
charCode = prevCharCode;
207+
charCodeClass = prevCharCodeClass;
208+
continue;
209+
}
210+
211+
if (prevCharCode === CharCode.Tab) {
212+
// cannot determine the width of a tab when going backwards, so we must go forwards
213+
hitTab = true;
214+
break;
215+
}
216+
217+
const charWidth = (strings.isFullWidthCharacter(prevCharCode) ? columnsForFullWidthChar : 1);
218+
219+
if (visibleColumn <= breakingColumn) {
220+
if (firstValidBreakOffset === 0) {
221+
firstValidBreakOffset = i + 1;
222+
firstValidBreakOffsetVisibleColumn = visibleColumn;
223+
}
224+
225+
if (visibleColumn <= breakingColumn - wrappedLineBreakingColumn) {
226+
// went too far!
227+
break;
228+
}
229+
230+
if (canBreak(prevCharCodeClass, charCodeClass)) {
231+
breakOffset = i + 1;
232+
breakOffsetVisibleColumn = visibleColumn;
233+
break;
234+
}
235+
}
236+
237+
visibleColumn -= charWidth;
238+
charCode = prevCharCode;
239+
charCodeClass = prevCharCodeClass;
240+
}
241+
242+
if (hitTab) {
243+
// cannot determine the width of a tab when going backwards, so we must go forwards
244+
prevIndex--;
245+
continue;
246+
}
247+
248+
if (breakOffset === 0) {
249+
// Could not find a good breaking point
250+
breakOffset = firstValidBreakOffset;
251+
breakOffsetVisibleColumn = firstValidBreakOffsetVisibleColumn;
252+
}
253+
254+
breakingOffsets[breakingOffsetsCount] = breakOffset;
255+
breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn;
256+
breakingOffsetsCount++;
257+
breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakingColumn;
258+
}
259+
260+
if (prevIndex < 0) {
261+
prevIndex = 0;
262+
} else {
263+
let currentDiff = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex] - breakingColumn);
264+
while (prevIndex + 1 < prevLen) {
265+
const potentialDiff = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex + 1] - breakingColumn);
266+
if (potentialDiff >= currentDiff) {
267+
break;
268+
}
269+
currentDiff = potentialDiff;
270+
prevIndex++;
271+
}
272+
}
273+
}
274+
275+
if (breakingOffsetsCount === 0) {
276+
return null;
277+
}
278+
279+
return new LineBreakingData(firstLineBreakingColumn, breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength);
280+
const expected = createLineMapping(classifier, null, lineText, tabSize, firstLineBreakingColumn, columnsForFullWidthChar, hardWrappingIndent);
281+
const actual = new LineBreakingData(firstLineBreakingColumn, breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength);
282+
try {
283+
actual.assertEqual(expected);
284+
} catch (err) {
285+
console.log(`BREAKING!!`);
286+
console.log(err);
287+
console.log(`
288+
assertIncrementalLineMapping(
289+
factory, ${str(lineText)}, 4,
290+
${previousBreakingData.breakingColumn}, ${str(toAnnotatedText(lineText, previousBreakingData))},
291+
${expected!.breakingColumn}, ${str(toAnnotatedText(lineText, expected))}
292+
);
293+
`);
294+
function str(strr: string) {
295+
return `'${strr.replace(/'/g, '\\\'')}'`;
296+
}
297+
function toAnnotatedText(text: string, lineBreakingData: LineBreakingData | null): string {
298+
// Insert line break markers again, according to algorithm
299+
let actualAnnotatedText = '';
300+
if (lineBreakingData) {
301+
let previousLineIndex = 0;
302+
for (let i = 0, len = text.length; i < len; i++) {
303+
let r = LineBreakingData.getOutputPositionOfInputOffset(lineBreakingData.breakOffsets, i);
304+
if (previousLineIndex !== r.outputLineIndex) {
305+
previousLineIndex = r.outputLineIndex;
306+
actualAnnotatedText += '|';
307+
}
308+
actualAnnotatedText += text.charAt(i);
309+
}
310+
} else {
311+
// No wrapping
312+
actualAnnotatedText = text;
313+
}
314+
return actualAnnotatedText;
315+
}
316+
}
317+
return actual;
318+
319+
breakingOffsets = [];
320+
breakingOffsetsVisibleColumn = [];
321+
breakingOffsetsCount = 0;
322+
}
323+
108324
let breakOffset = 0;
109325
let breakOffsetVisibleColumn = 0;
110326

@@ -165,6 +381,62 @@ function createLineMapping(classifier: WrappingCharacterClassifier, previousBrea
165381
return new LineBreakingData(firstLineBreakingColumn, breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength);
166382
}
167383

384+
// class BreakSearchResult {
385+
386+
// public static INSTANCE = new BreakSearchResult();
387+
388+
// prevCharCode: number = CharCode.Null;
389+
// prevCharCodeClass: CharacterClass = CharacterClass.NONE;
390+
// breakOffset: number = 0;
391+
// breakOffsetVisibleColumn: number = 0;
392+
// visibleColumn: number = 0;
393+
// }
394+
395+
// function searchForBreak(classifier: WrappingCharacterClassifier, lineText: string, len: number, prevCharCode: number, prevCharCodeClass: number): boolean {
396+
// let breakOffset = 0;
397+
// let breakOffsetVisibleColumn = 0;
398+
// for (let i = 1; i < len; i++) {
399+
// const charCode = lineText.charCodeAt(i);
400+
// const charCodeClass = classifier.get(charCode);
401+
402+
// if (strings.isHighSurrogate(prevCharCode)) {
403+
// // A surrogate pair must always be considered as a single unit, so it is never to be broken
404+
// visibleColumn += 1;
405+
// prevCharCode = charCode;
406+
// prevCharCodeClass = charCodeClass;
407+
// continue;
408+
// }
409+
410+
// if (canBreak(prevCharCodeClass, charCodeClass)) {
411+
// breakOffset = i;
412+
// breakOffsetVisibleColumn = visibleColumn;
413+
// }
414+
415+
// const charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar);
416+
// visibleColumn += charWidth;
417+
418+
// // check if adding character at `i` will go over the breaking column
419+
// if (visibleColumn > breakingColumn) {
420+
// // We need to break at least before character at `i`:
421+
422+
// if (breakOffset === 0 || visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakingColumn) {
423+
// // Cannot break at `breakOffset`, must break at `i`
424+
// breakOffset = i;
425+
// breakOffsetVisibleColumn = visibleColumn - charWidth;
426+
// }
427+
428+
// breakingOffsets[breakingOffsetsCount] = breakOffset;
429+
// breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn;
430+
// breakingOffsetsCount++;
431+
// breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakingColumn;
432+
// breakOffset = 0;
433+
// }
434+
435+
// prevCharCode = charCode;
436+
// prevCharCodeClass = charCodeClass;
437+
// }
438+
// }
439+
168440
function computeCharWidth(charCode: number, visibleColumn: number, tabSize: number, columnsForFullWidthChar: number): number {
169441
if (charCode === CharCode.Tab) {
170442
return (tabSize - (visibleColumn % tabSize));

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,29 @@ export class LineBreakingData {
3434
public readonly wrappedTextIndentLength: number
3535
) { }
3636

37+
assertEqual(other: LineBreakingData | null): void {
38+
if (other === null) {
39+
throw new Error(`x--unexpected--1`);
40+
}
41+
if (other.breakingColumn !== this.breakingColumn) {
42+
throw new Error(`x--unexpected--2`);
43+
}
44+
if (other.wrappedTextIndentLength !== this.wrappedTextIndentLength) {
45+
throw new Error(`x--unexpected--3`);
46+
}
47+
if (other.breakOffsets.length !== this.breakOffsets.length) {
48+
throw new Error(`x--unexpected--4`);
49+
}
50+
for (let i = 0; i < this.breakOffsets.length; i++) {
51+
if (this.breakOffsets[i] !== other.breakOffsets[i]) {
52+
throw new Error(`x--unexpected--5`);
53+
}
54+
if (this.breakingOffsetsVisibleColumn[i] !== other.breakingOffsetsVisibleColumn[i]) {
55+
throw new Error(`x--unexpected--6`);
56+
}
57+
}
58+
}
59+
3760
public static getInputOffsetOfOutputPosition(breakOffsets: number[], outputLineIndex: number, outputOffset: number): number {
3861
if (outputLineIndex === 0) {
3962
return outputOffset;

0 commit comments

Comments
 (0)