Skip to content

Commit b145d33

Browse files
committed
First cut for DOMLineBreaksComputer
1 parent caa505d commit b145d33

3 files changed

Lines changed: 230 additions & 3 deletions

File tree

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
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 { ILineBreaksComputerFactory, LineBreakData, ILineBreaksComputer } from 'vs/editor/common/viewModel/splitLinesCollection';
7+
import { IComputedEditorOptions, EditorOption, WrappingIndent } from 'vs/editor/common/config/editorOptions';
8+
import { FontInfo } from 'vs/editor/common/config/fontInfo';
9+
import { createStringBuilder, IStringBuilder } from 'vs/editor/common/core/stringBuilder';
10+
import { CharCode } from 'vs/base/common/charCode';
11+
import * as strings from 'vs/base/common/strings';
12+
import { Configuration } from 'vs/editor/browser/config/configuration';
13+
14+
export class DOMLineBreaksComputerFactory implements ILineBreaksComputerFactory {
15+
16+
public static create(options: IComputedEditorOptions): DOMLineBreaksComputerFactory {
17+
return new DOMLineBreaksComputerFactory(
18+
options.get(EditorOption.fontInfo)
19+
);
20+
}
21+
22+
private _fontInfo: FontInfo;
23+
24+
constructor(fontInfo: FontInfo) {
25+
this._fontInfo = fontInfo;
26+
}
27+
28+
public createLineBreaksComputer(tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): ILineBreaksComputer {
29+
tabSize = tabSize | 0; //@perf
30+
wrappingColumn = +wrappingColumn; //@perf
31+
columnsForFullWidthChar = +columnsForFullWidthChar; //@perf
32+
33+
let requests: string[] = [];
34+
return {
35+
addRequest: (lineText: string, previousLineBreakData: LineBreakData | null) => {
36+
requests.push(lineText);
37+
},
38+
finalize: () => {
39+
return createLineBreaks(this._fontInfo, tabSize, wrappingColumn, wrappingIndent, requests);
40+
}
41+
};
42+
}
43+
}
44+
45+
function createLineBreaks(fontInfo: FontInfo, tabSize: number, firstLineBreakColumn: number, wrappingIndent: WrappingIndent, requests: string[]): (LineBreakData | null)[] {
46+
const width = Math.round(firstLineBreakColumn * fontInfo.typicalHalfwidthCharacterWidth);
47+
48+
const containerDomNode = document.createElement('div');
49+
Configuration.applyFontInfoSlow(containerDomNode, fontInfo);
50+
containerDomNode.style.width = `${width}px`;
51+
52+
const sb = createStringBuilder(10000);
53+
const charOffsets: number[][] = [];
54+
const visibleColumns: number[][] = [];
55+
for (let i = 0; i < requests.length; i++) {
56+
const r = renderLine(i, requests[i], tabSize, sb);
57+
charOffsets[i] = r[0];
58+
visibleColumns[i] = r[1];
59+
}
60+
containerDomNode.innerHTML = sb.build();
61+
62+
containerDomNode.style.position = 'absolute';
63+
containerDomNode.style.right = '0';
64+
containerDomNode.style.bottom = '0';
65+
containerDomNode.style.zIndex = '10000';
66+
document.body.appendChild(containerDomNode);
67+
68+
let range = document.createRange();
69+
const lineDomNodes = Array.prototype.slice.call(containerDomNode.children, 0);
70+
71+
let result: (LineBreakData | null)[] = [];
72+
for (let i = 0; i < requests.length; i++) {
73+
const lineDomNode = lineDomNodes[i];
74+
result[i] = readLineBreaks(range, lineDomNode, requests[i], charOffsets[i], visibleColumns[i]);
75+
}
76+
77+
document.body.removeChild(containerDomNode);
78+
return result;
79+
}
80+
81+
function renderLine(lineIndex: number, lineContent: string, tabSize: number, sb: IStringBuilder): [number[], number[]] {
82+
sb.appendASCIIString('<div>');
83+
// if (containsRTL) {
84+
// sb.appendASCIIString('" dir="ltr');
85+
// }
86+
87+
let visibleColumn = 0;
88+
let charOffset = 0;
89+
let charOffsets: number[] = [];
90+
let visibleColumns: number[] = [];
91+
92+
for (let charIndex = 0, len = lineContent.length; charIndex < len; charIndex++) {
93+
charOffsets[charIndex] = charOffset;
94+
visibleColumns[charIndex] = visibleColumn;
95+
const charCode = lineContent.charCodeAt(charIndex);
96+
let producedCharacters = 1;
97+
let charWidth = 1;
98+
switch (charCode) {
99+
case CharCode.Tab:
100+
producedCharacters = (tabSize - (visibleColumn % tabSize));
101+
charWidth = producedCharacters;
102+
for (let space = 1; space <= producedCharacters; space++) {
103+
sb.appendASCII(CharCode.Space);
104+
// sb.write1(0xA0); // &nbsp;
105+
}
106+
break;
107+
108+
case CharCode.Space:
109+
sb.appendASCII(CharCode.Space);
110+
// sb.write1(0xA0); // &nbsp;
111+
break;
112+
113+
case CharCode.LessThan:
114+
sb.appendASCIIString('&lt;');
115+
break;
116+
117+
case CharCode.GreaterThan:
118+
sb.appendASCIIString('&gt;');
119+
break;
120+
121+
case CharCode.Ampersand:
122+
sb.appendASCIIString('&amp;');
123+
break;
124+
125+
case CharCode.Null:
126+
sb.appendASCIIString('&#00;');
127+
break;
128+
129+
case CharCode.UTF8_BOM:
130+
case CharCode.LINE_SEPARATOR_2028:
131+
sb.write1(0xFFFD);
132+
break;
133+
134+
default:
135+
if (strings.isFullWidthCharacter(charCode)) {
136+
charWidth++;
137+
}
138+
// if (renderControlCharacters && charCode < 32) {
139+
// sb.write1(9216 + charCode);
140+
// } else {
141+
sb.write1(charCode);
142+
// }
143+
}
144+
145+
charOffset += producedCharacters;
146+
visibleColumn += charWidth;
147+
}
148+
149+
charOffsets[lineContent.length] = charOffset;
150+
visibleColumns[lineContent.length] = visibleColumn;
151+
152+
sb.appendASCIIString('</div>');
153+
154+
return [charOffsets, visibleColumns];
155+
}
156+
157+
function readLineBreaks(range: Range, lineDomNode: HTMLDivElement, lineContent: string, charOffsets: number[], visibleColumns: number[]): LineBreakData | null {
158+
if (lineContent.length <= 1) {
159+
return null;
160+
}
161+
const textContentNode = lineDomNode.firstChild!;
162+
163+
const breakOffsets: number[] = [];
164+
discoverBreaks(range, textContentNode, charOffsets, 0, null, lineContent.length - 1, null, breakOffsets);
165+
166+
if (breakOffsets.length === 0) {
167+
return null;
168+
}
169+
170+
breakOffsets.push(lineContent.length);
171+
172+
const breakOffsetsVisibleColumn = [];
173+
for (let i = 0, len = breakOffsets.length; i < len; i++) {
174+
breakOffsetsVisibleColumn[i] = visibleColumns[breakOffsets[i]];
175+
}
176+
177+
return new LineBreakData(breakOffsets, breakOffsetsVisibleColumn, 0);
178+
}
179+
180+
type MaybeRects = ClientRectList | DOMRectList | null;
181+
182+
function discoverBreaks(range: Range, textContentNode: Node, charOffsets: number[], low: number, lowRects: MaybeRects, high: number, highRects: MaybeRects, result: number[]): void {
183+
if (low === high) {
184+
return;
185+
}
186+
187+
lowRects = lowRects || readClientRect(range, textContentNode, charOffsets[low], charOffsets[low + 1]);
188+
highRects = highRects || readClientRect(range, textContentNode, charOffsets[high], charOffsets[high + 1]);
189+
190+
if (Math.abs(lowRects[0].top - highRects[0].top) <= 0.1) {
191+
// same line
192+
return;
193+
}
194+
195+
// there is at least one line break between these two offsets
196+
if (low + 1 === high) {
197+
// the two characters are adjacent, so the line break must be exactly between them
198+
result.push(high);
199+
return;
200+
}
201+
202+
const mid = low + ((high - low) / 2) | 0;
203+
const midRects = readClientRect(range, textContentNode, charOffsets[mid], charOffsets[mid + 1]);
204+
discoverBreaks(range, textContentNode, charOffsets, low, lowRects, mid, midRects, result);
205+
discoverBreaks(range, textContentNode, charOffsets, mid, midRects, high, highRects, result);
206+
}
207+
208+
function readClientRect(range: Range, textContentNode: Node, startOffset: number, endOffset: number): ClientRectList | DOMRectList {
209+
range.setStart(textContentNode, startOffset);
210+
range.setEnd(textContentNode, endOffset);
211+
return range.getClientRects();
212+
}

src/vs/editor/browser/widget/codeEditorWidget.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,10 @@ import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/com
5151
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
5252
import { withNullAsUndefined } from 'vs/base/common/types';
5353
import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer';
54+
import { DOMLineBreaksComputerFactory } from 'vs/editor/browser/view/domLineBreaksComputer';
5455

5556
let EDITOR_ID = 0;
57+
const useDOMLineBreaksComputerFactory = false;
5658

5759
export interface ICodeEditorWidgetOptions {
5860
/**
@@ -1337,7 +1339,17 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
13371339

13381340
model.onBeforeAttached();
13391341

1340-
const viewModel = new ViewModel(this._id, this._configuration, model, MonospaceLineBreaksComputerFactory.create(this._configuration.options), (callback) => dom.scheduleAtNextAnimationFrame(callback));
1342+
const viewModel = new ViewModel(
1343+
this._id,
1344+
this._configuration,
1345+
model,
1346+
(
1347+
useDOMLineBreaksComputerFactory
1348+
? DOMLineBreaksComputerFactory.create(this._configuration.options)
1349+
: MonospaceLineBreaksComputerFactory.create(this._configuration.options)
1350+
),
1351+
(callback) => dom.scheduleAtNextAnimationFrame(callback)
1352+
);
13411353

13421354
listenersToRemove.push(model.onDidChangeDecorations((e) => this._onDidChangeModelDecorations.fire(e)));
13431355
listenersToRemove.push(model.onDidChangeLanguage((e) => {

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ class WrappingCharacterClassifier extends CharacterClassifier<CharacterClass> {
5151
}
5252
}
5353

54+
let arrPool1: number[] = [];
55+
let arrPool2: number[] = [];
56+
5457
export class MonospaceLineBreaksComputerFactory implements ILineBreaksComputerFactory {
5558

5659
public static create(options: IComputedEditorOptions): MonospaceLineBreaksComputerFactory {
@@ -88,14 +91,14 @@ export class MonospaceLineBreaksComputerFactory implements ILineBreaksComputerFa
8891
result[i] = createLineBreaks(this.classifier, requests[i], tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent);
8992
}
9093
}
94+
arrPool1.length = 0;
95+
arrPool2.length = 0;
9196
return result;
9297
}
9398
};
9499
}
95100
}
96101

97-
let arrPool1: number[] = [];
98-
let arrPool2: number[] = [];
99102
function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterClassifier, previousBreakingData: LineBreakData, lineText: string, tabSize: number, firstLineBreakColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): LineBreakData | null {
100103
if (firstLineBreakColumn === -1) {
101104
return null;

0 commit comments

Comments
 (0)