@adecore/editor 0.39.0 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/controller.d.ts +1 -0
- package/dist/controller.js +17 -28
- package/dist/engine.js +2 -9
- package/dist/find.js +12 -13
- package/dist/layout.js +7 -2
- package/dist/paint.d.ts +2 -0
- package/dist/paint.js +23 -20
- package/dist/view.d.ts +3 -2
- package/dist/view.js +46 -59
- package/package.json +6 -6
- package/src/attribution.ts +14 -0
- package/src/code-block.ts +25 -0
- package/src/column-selection.ts +10 -0
- package/src/controller.ts +189 -28
- package/src/engine.ts +87 -9
- package/src/fake.ts +79 -0
- package/src/find-search.ts +20 -0
- package/src/find.ts +135 -14
- package/src/geometry-stops.ts +10 -0
- package/src/grammar-preparation.ts +23 -0
- package/src/index.ts +1 -0
- package/src/input.ts +13 -0
- package/src/keymap-table.ts +9 -0
- package/src/keymap.ts +25 -0
- package/src/layout-changes.ts +7 -0
- package/src/layout.ts +219 -3
- package/src/lens.ts +3 -0
- package/src/listeners.ts +1 -0
- package/src/metrics.ts +14 -0
- package/src/modifier-gesture.ts +17 -0
- package/src/native-input.ts +27 -0
- package/src/offsets.ts +10 -0
- package/src/outline.ts +38 -0
- package/src/overview.ts +13 -0
- package/src/paint.ts +140 -20
- package/src/php-html.ts +1 -0
- package/src/pointer.ts +50 -0
- package/src/scroll-animation.ts +10 -0
- package/src/scroll.ts +7 -0
- package/src/search-client.ts +32 -0
- package/src/search-worker.ts +38 -0
- package/src/semantic.ts +12 -0
- package/src/shiki-document.ts +19 -0
- package/src/shiki-worker.ts +68 -0
- package/src/shiki.ts +37 -0
- package/src/testing.ts +17 -0
- package/src/theme-scopes.ts +14 -0
- package/src/tokens.ts +31 -0
- package/src/touch.ts +16 -0
- package/src/tracked-range.ts +5 -0
- package/src/uniform-geometry.ts +10 -0
- package/src/view.ts +481 -75
- package/src/worker-client.ts +54 -0
package/src/testing.ts
CHANGED
|
@@ -10,32 +10,40 @@ export function createPage(): { document: Document; window: Window & typeof glob
|
|
|
10
10
|
const { document, window } = parseHTML('<html><body><div id="host"></div></body></html>');
|
|
11
11
|
const prototype = (window as unknown as { HTMLTextAreaElement: { prototype: Record<string, unknown> } }).HTMLTextAreaElement.prototype;
|
|
12
12
|
const selections = new WeakMap<object, { start: number; end: number; direction: string }>();
|
|
13
|
+
|
|
13
14
|
const read = (element: object): { start: number; end: number; direction: string } => selections.get(element) ?? { start: 0, end: 0, direction: 'none' };
|
|
15
|
+
|
|
14
16
|
Object.defineProperty(prototype, 'selectionStart', {
|
|
15
17
|
get: function (this: object) {
|
|
16
18
|
return read(this).start;
|
|
17
19
|
},
|
|
18
20
|
configurable: true
|
|
19
21
|
});
|
|
22
|
+
|
|
20
23
|
Object.defineProperty(prototype, 'selectionEnd', {
|
|
21
24
|
get: function (this: object) {
|
|
22
25
|
return read(this).end;
|
|
23
26
|
},
|
|
24
27
|
configurable: true
|
|
25
28
|
});
|
|
29
|
+
|
|
26
30
|
Object.defineProperty(prototype, 'selectionDirection', {
|
|
27
31
|
get: function (this: object) {
|
|
28
32
|
return read(this).direction;
|
|
29
33
|
},
|
|
30
34
|
configurable: true
|
|
31
35
|
});
|
|
36
|
+
|
|
32
37
|
prototype.setSelectionRange = function (this: object, start: number, end: number, direction = 'none'): void {
|
|
33
38
|
selections.set(this, { start, end, direction });
|
|
34
39
|
};
|
|
40
|
+
|
|
35
41
|
const element = (window as unknown as { HTMLElement: { prototype: object } }).HTMLElement.prototype;
|
|
42
|
+
|
|
36
43
|
for (const name of ['scrollTop', 'scrollLeft']) {
|
|
37
44
|
Object.defineProperty(element, name, { value: 0, writable: true, configurable: true });
|
|
38
45
|
}
|
|
46
|
+
|
|
39
47
|
return {
|
|
40
48
|
document: document as unknown as Document,
|
|
41
49
|
window: window as unknown as Window & typeof globalThis,
|
|
@@ -61,6 +69,7 @@ export function press(window: Window, target: Element, key: string, options: Key
|
|
|
61
69
|
const event = createEvent(window, 'keydown');
|
|
62
70
|
Object.assign(event, { key, ctrlKey: false, metaKey: false, altKey: false, shiftKey: false, isComposing: false, ...options });
|
|
63
71
|
target.dispatchEvent(event);
|
|
72
|
+
|
|
64
73
|
return event.defaultPrevented;
|
|
65
74
|
}
|
|
66
75
|
|
|
@@ -86,6 +95,7 @@ export function clipboard(
|
|
|
86
95
|
data: { text: string } = { text: '' }
|
|
87
96
|
): { prevented: boolean; text: string } {
|
|
88
97
|
const event = createEvent(window, type);
|
|
98
|
+
|
|
89
99
|
Object.assign(event, {
|
|
90
100
|
clipboardData: {
|
|
91
101
|
getData: () => data.text,
|
|
@@ -94,7 +104,9 @@ export function clipboard(
|
|
|
94
104
|
}
|
|
95
105
|
}
|
|
96
106
|
});
|
|
107
|
+
|
|
97
108
|
target.dispatchEvent(event);
|
|
109
|
+
|
|
98
110
|
return { prevented: event.defaultPrevented, text: data.text };
|
|
99
111
|
}
|
|
100
112
|
|
|
@@ -108,6 +120,7 @@ export function pointer(
|
|
|
108
120
|
options: KeyOptions = {}
|
|
109
121
|
): void {
|
|
110
122
|
const event = createEvent(window, type);
|
|
123
|
+
|
|
111
124
|
Object.assign(event, {
|
|
112
125
|
clientX: x,
|
|
113
126
|
clientY: y,
|
|
@@ -120,6 +133,7 @@ export function pointer(
|
|
|
120
133
|
metaKey: false,
|
|
121
134
|
...options
|
|
122
135
|
});
|
|
136
|
+
|
|
123
137
|
target.dispatchEvent(event);
|
|
124
138
|
}
|
|
125
139
|
|
|
@@ -139,13 +153,16 @@ export interface MountedEditor {
|
|
|
139
153
|
/* An editor on a page of its own, with the helpers that drive it the way a person would. */
|
|
140
154
|
export function mountEditor(options: Partial<EditorOptions> = {}, engineOptions: Partial<SmartEditorEngineOptions> = {}): MountedEditor {
|
|
141
155
|
const page = createPage();
|
|
156
|
+
|
|
142
157
|
const editor = createSmartEditorEngine({ tokenizer: async () => null, ...engineOptions }).mount(page.host, {
|
|
143
158
|
text: 'one\ntwo\nthree',
|
|
144
159
|
theme: 'dark',
|
|
145
160
|
...options
|
|
146
161
|
});
|
|
162
|
+
|
|
147
163
|
const input = page.host.querySelector('textarea')!;
|
|
148
164
|
const viewport = page.host.querySelector('.se-viewport') as HTMLElement;
|
|
165
|
+
|
|
149
166
|
return {
|
|
150
167
|
editor,
|
|
151
168
|
page,
|
package/src/theme-scopes.ts
CHANGED
|
@@ -26,19 +26,25 @@ function selectorScore(selector: string, stack: readonly string[]): number | nul
|
|
|
26
26
|
const words = selector.trim().split(/\s+/);
|
|
27
27
|
const last = words[words.length - 1]!;
|
|
28
28
|
const innermost = stack[stack.length - 1];
|
|
29
|
+
|
|
29
30
|
if (innermost === undefined || !nameMatches(last, innermost)) {
|
|
30
31
|
return null;
|
|
31
32
|
}
|
|
33
|
+
|
|
32
34
|
let at = stack.length - 2;
|
|
35
|
+
|
|
33
36
|
for (let word = words.length - 2; word >= 0; word--) {
|
|
34
37
|
while (at >= 0 && !nameMatches(words[word]!, stack[at]!)) {
|
|
35
38
|
at--;
|
|
36
39
|
}
|
|
40
|
+
|
|
37
41
|
if (at < 0) {
|
|
38
42
|
return null;
|
|
39
43
|
}
|
|
44
|
+
|
|
40
45
|
at--;
|
|
41
46
|
}
|
|
47
|
+
|
|
42
48
|
return last.length * 100 + words.length;
|
|
43
49
|
}
|
|
44
50
|
|
|
@@ -55,21 +61,29 @@ export function scopeColorsOf(rules: readonly ThemeRule[]): ScopeColors {
|
|
|
55
61
|
order
|
|
56
62
|
}))
|
|
57
63
|
);
|
|
64
|
+
|
|
58
65
|
const cache = new Map<string, ScopeStyle | undefined>();
|
|
66
|
+
|
|
59
67
|
return (stack) => {
|
|
60
68
|
const key = stack.join(' ');
|
|
69
|
+
|
|
61
70
|
if (cache.has(key)) {
|
|
62
71
|
return cache.get(key);
|
|
63
72
|
}
|
|
73
|
+
|
|
64
74
|
let best: { score: number; order: number; style: ScopeStyle } | undefined;
|
|
75
|
+
|
|
65
76
|
for (const entry of flat) {
|
|
66
77
|
const score = entry.selector === '' ? null : selectorScore(entry.selector, stack);
|
|
78
|
+
|
|
67
79
|
if (score !== null && (best === undefined || score > best.score || (score === best.score && entry.order > best.order))) {
|
|
68
80
|
best = { score, order: entry.order, style: entry.style };
|
|
69
81
|
}
|
|
70
82
|
}
|
|
83
|
+
|
|
71
84
|
const style = best !== undefined && best.style.color !== '' ? best.style : undefined;
|
|
72
85
|
cache.set(key, style);
|
|
86
|
+
|
|
73
87
|
return style;
|
|
74
88
|
};
|
|
75
89
|
}
|
package/src/tokens.ts
CHANGED
|
@@ -63,16 +63,21 @@ export class TokenCache {
|
|
|
63
63
|
/* The colors of text that is not part of the document, a line at a time, or null without a tokenizer. */
|
|
64
64
|
tokenizeText(lines: readonly string[]): (readonly LineToken[])[] | null {
|
|
65
65
|
const tokenizer = this.tokenizer;
|
|
66
|
+
|
|
66
67
|
if (tokenizer === null) {
|
|
67
68
|
return null;
|
|
68
69
|
}
|
|
70
|
+
|
|
69
71
|
let state: unknown = null;
|
|
72
|
+
|
|
70
73
|
return lines.map((text) => {
|
|
71
74
|
if (text.length > LONGEST_COLORED_LINE) {
|
|
72
75
|
return [{ length: text.length, color: '', fontStyle: 0 }];
|
|
73
76
|
}
|
|
77
|
+
|
|
74
78
|
const result = tokenizer.tokenizeLine(text, state);
|
|
75
79
|
state = result.state;
|
|
80
|
+
|
|
76
81
|
return result.tokens;
|
|
77
82
|
});
|
|
78
83
|
}
|
|
@@ -87,27 +92,35 @@ export class TokenCache {
|
|
|
87
92
|
const lineCount = this.source.getLineCount();
|
|
88
93
|
const delta = lineCount - this.lines;
|
|
89
94
|
this.lines = lineCount;
|
|
95
|
+
|
|
90
96
|
if (this.tokenizer === null) {
|
|
91
97
|
return;
|
|
92
98
|
}
|
|
99
|
+
|
|
93
100
|
const first = batches[0]?.[0];
|
|
101
|
+
|
|
94
102
|
if (first === undefined) {
|
|
95
103
|
this.entries = [];
|
|
96
104
|
this.staleFrom = Number.POSITIVE_INFINITY;
|
|
97
105
|
this.frontier = 0;
|
|
98
106
|
this.forgetPivot();
|
|
107
|
+
|
|
99
108
|
return;
|
|
100
109
|
}
|
|
110
|
+
|
|
101
111
|
const firstLine = lineOf(first.from);
|
|
102
112
|
const changes = batches.length === 1 ? batches[0]! : [];
|
|
103
113
|
let shift = 0;
|
|
114
|
+
|
|
104
115
|
for (let i = 0; i < changes.length - 1; i++) {
|
|
105
116
|
shift += changes[i]!.insertedLength - (changes[i]!.to - changes[i]!.from);
|
|
106
117
|
}
|
|
118
|
+
|
|
107
119
|
const last = changes.at(-1);
|
|
108
120
|
const lastNew = last === undefined ? -1 : lineOf(last.from + shift + last.insertedLength);
|
|
109
121
|
const lastOld = lastNew - delta;
|
|
110
122
|
this.frontier = Math.min(this.frontier, firstLine);
|
|
123
|
+
|
|
111
124
|
if (last === undefined && Number.isFinite(this.staleFrom)) {
|
|
112
125
|
// A history batch has no single mapping for the old grammar boundary.
|
|
113
126
|
this.staleFrom = 0;
|
|
@@ -117,14 +130,19 @@ export class TokenCache {
|
|
|
117
130
|
} else if (this.staleFrom > firstLine) {
|
|
118
131
|
this.staleFrom = firstLine;
|
|
119
132
|
}
|
|
133
|
+
|
|
120
134
|
if (last === undefined || lastOld < firstLine || this.entries.length <= firstLine) {
|
|
121
135
|
this.entries.length = Math.min(this.entries.length, firstLine);
|
|
136
|
+
|
|
122
137
|
if (this.staleFrom >= this.entries.length) {
|
|
123
138
|
this.staleFrom = Number.POSITIVE_INFINITY;
|
|
124
139
|
}
|
|
140
|
+
|
|
125
141
|
this.forgetPivot();
|
|
142
|
+
|
|
126
143
|
return;
|
|
127
144
|
}
|
|
145
|
+
|
|
128
146
|
const old = this.entries[lastOld];
|
|
129
147
|
this.pivotLine = old ? lastNew : -1;
|
|
130
148
|
this.pivotState = old?.state;
|
|
@@ -138,9 +156,11 @@ export class TokenCache {
|
|
|
138
156
|
if (this.tokenizer?.stale?.() !== true) {
|
|
139
157
|
return false;
|
|
140
158
|
}
|
|
159
|
+
|
|
141
160
|
this.frontier = 0;
|
|
142
161
|
this.staleFrom = 0;
|
|
143
162
|
this.forgetPivot();
|
|
163
|
+
|
|
144
164
|
return true;
|
|
145
165
|
}
|
|
146
166
|
|
|
@@ -151,45 +171,56 @@ export class TokenCache {
|
|
|
151
171
|
*/
|
|
152
172
|
advance(until: number, budget: number, now: () => number = () => performance.now()): { from: number; to: number } | null {
|
|
153
173
|
const tokenizer = this.tokenizer;
|
|
174
|
+
|
|
154
175
|
if (tokenizer === null) {
|
|
155
176
|
return null;
|
|
156
177
|
}
|
|
178
|
+
|
|
157
179
|
this.revalidate();
|
|
158
180
|
const count = this.source.getLineCount();
|
|
159
181
|
const limit = Math.min(until, count - 1);
|
|
160
182
|
const deadline = now() + budget;
|
|
161
183
|
const from = this.frontier;
|
|
184
|
+
|
|
162
185
|
while (this.frontier <= limit && this.frontier < count) {
|
|
163
186
|
const line = this.frontier;
|
|
164
187
|
const text = this.source.getLineText(line);
|
|
165
188
|
const state = line === 0 ? null : (this.entries[line - 1]?.state ?? null);
|
|
166
189
|
const previous = this.entries[line];
|
|
167
190
|
let next: Entry;
|
|
191
|
+
|
|
168
192
|
if (text.length > LONGEST_COLORED_LINE) {
|
|
169
193
|
next = { tokens: [{ length: text.length, color: '', fontStyle: 0 }], state };
|
|
170
194
|
} else {
|
|
171
195
|
const result = tokenizer.tokenizeLine(text, state);
|
|
172
196
|
next = { tokens: result.tokens, state: result.state };
|
|
173
197
|
}
|
|
198
|
+
|
|
174
199
|
this.entries[line] = next;
|
|
175
200
|
this.frontier = line + 1;
|
|
176
201
|
const fresh = line < this.staleFrom;
|
|
202
|
+
|
|
177
203
|
if (line === this.staleFrom) {
|
|
178
204
|
this.staleFrom = line + 1 >= this.entries.length ? Number.POSITIVE_INFINITY : line + 1;
|
|
179
205
|
}
|
|
206
|
+
|
|
180
207
|
const settled =
|
|
181
208
|
fresh &&
|
|
182
209
|
((line === this.pivotLine && tokenizer.sameState(this.pivotState, next.state)) ||
|
|
183
210
|
(previous !== undefined && line >= this.reusableFrom && tokenizer.sameState(previous.state, next.state)));
|
|
211
|
+
|
|
184
212
|
if (settled) {
|
|
185
213
|
this.frontier = Math.min(this.entries.length, this.staleFrom);
|
|
186
214
|
this.forgetPivot();
|
|
215
|
+
|
|
187
216
|
break;
|
|
188
217
|
}
|
|
218
|
+
|
|
189
219
|
if (now() > deadline) {
|
|
190
220
|
break;
|
|
191
221
|
}
|
|
192
222
|
}
|
|
223
|
+
|
|
193
224
|
return this.frontier > from ? { from, to: this.frontier - 1 } : null;
|
|
194
225
|
}
|
|
195
226
|
|
package/src/touch.ts
CHANGED
|
@@ -50,6 +50,7 @@ export class TouchSelection {
|
|
|
50
50
|
start(event: PointerEvent, area: 'text' | 'gutter' = 'text', taken?: () => boolean): void {
|
|
51
51
|
this.cancel();
|
|
52
52
|
this.contact = { id: event.pointerId, x: event.clientX, y: event.clientY, area, moved: false, pressed: false, taken };
|
|
53
|
+
|
|
53
54
|
if (area === 'text') {
|
|
54
55
|
this.timer = setTimeout(() => this.press(), LONG_PRESS_MS);
|
|
55
56
|
}
|
|
@@ -57,9 +58,11 @@ export class TouchSelection {
|
|
|
57
58
|
|
|
58
59
|
move(event: PointerEvent): void {
|
|
59
60
|
const contact = this.contact;
|
|
61
|
+
|
|
60
62
|
if (contact === undefined || event.pointerId !== contact.id || contact.moved) {
|
|
61
63
|
return;
|
|
62
64
|
}
|
|
65
|
+
|
|
63
66
|
if (Math.abs(event.clientX - contact.x) > TAP_SLOP || Math.abs(event.clientY - contact.y) > TAP_SLOP) {
|
|
64
67
|
contact.moved = true;
|
|
65
68
|
this.clearTimer();
|
|
@@ -69,13 +72,17 @@ export class TouchSelection {
|
|
|
69
72
|
/* The finger lifted: a tap that did not scroll and was not a long press lands here. */
|
|
70
73
|
end(event: PointerEvent): void {
|
|
71
74
|
const contact = this.contact;
|
|
75
|
+
|
|
72
76
|
if (contact === undefined || event.pointerId !== contact.id) {
|
|
73
77
|
return;
|
|
74
78
|
}
|
|
79
|
+
|
|
75
80
|
this.cancel();
|
|
81
|
+
|
|
76
82
|
if (event.type === 'pointercancel' || contact.moved || contact.pressed) {
|
|
77
83
|
return;
|
|
78
84
|
}
|
|
85
|
+
|
|
79
86
|
this.tap(contact, event);
|
|
80
87
|
}
|
|
81
88
|
|
|
@@ -87,20 +94,26 @@ export class TouchSelection {
|
|
|
87
94
|
const { model } = this.host;
|
|
88
95
|
const offset = this.host.offsetAt(contact.x, contact.y);
|
|
89
96
|
this.host.focus();
|
|
97
|
+
|
|
90
98
|
if (contact.area === 'gutter') {
|
|
91
99
|
const line = this.host.lineAt(offset);
|
|
92
100
|
model.setSelections([{ anchor: line.from, head: line.to }]);
|
|
101
|
+
|
|
93
102
|
return;
|
|
94
103
|
}
|
|
104
|
+
|
|
95
105
|
if (contact.taken?.()) {
|
|
96
106
|
return;
|
|
97
107
|
}
|
|
108
|
+
|
|
98
109
|
const last = this.lastTap;
|
|
110
|
+
|
|
99
111
|
const double =
|
|
100
112
|
last !== undefined &&
|
|
101
113
|
event.timeStamp - last.time <= DOUBLE_TAP_MS &&
|
|
102
114
|
Math.abs(contact.x - last.x) <= DOUBLE_TAP_DISTANCE &&
|
|
103
115
|
Math.abs(contact.y - last.y) <= DOUBLE_TAP_DISTANCE;
|
|
116
|
+
|
|
104
117
|
this.lastTap = double ? undefined : { time: event.timeStamp, x: contact.x, y: contact.y };
|
|
105
118
|
const word = double ? model.wordSelectionAt(offset, this.host.camelHumps()) : null;
|
|
106
119
|
model.setSelections([word === null ? { anchor: offset, head: offset } : { anchor: word.from, head: word.to }]);
|
|
@@ -109,9 +122,11 @@ export class TouchSelection {
|
|
|
109
122
|
private press(): void {
|
|
110
123
|
const contact = this.contact;
|
|
111
124
|
this.timer = undefined;
|
|
125
|
+
|
|
112
126
|
if (contact === undefined || contact.moved) {
|
|
113
127
|
return;
|
|
114
128
|
}
|
|
129
|
+
|
|
115
130
|
contact.pressed = true;
|
|
116
131
|
this.lastTap = undefined;
|
|
117
132
|
const offset = this.host.offsetAt(contact.x, contact.y);
|
|
@@ -124,6 +139,7 @@ export class TouchSelection {
|
|
|
124
139
|
if (this.timer !== undefined) {
|
|
125
140
|
clearTimeout(this.timer);
|
|
126
141
|
}
|
|
142
|
+
|
|
127
143
|
this.timer = undefined;
|
|
128
144
|
}
|
|
129
145
|
|
package/src/tracked-range.ts
CHANGED
|
@@ -8,15 +8,20 @@ import type { DocumentChange } from '@adecore/editor-core';
|
|
|
8
8
|
*/
|
|
9
9
|
export function mapTrackedRange(from: number, to: number, changes: readonly DocumentChange[]): { from: number; to: number } | null {
|
|
10
10
|
let delta = 0;
|
|
11
|
+
|
|
11
12
|
for (const change of changes) {
|
|
12
13
|
if (change.to <= from) {
|
|
13
14
|
delta += change.insertedLength - (change.to - change.from);
|
|
15
|
+
|
|
14
16
|
continue;
|
|
15
17
|
}
|
|
18
|
+
|
|
16
19
|
if (change.from >= to) {
|
|
17
20
|
break;
|
|
18
21
|
}
|
|
22
|
+
|
|
19
23
|
return null;
|
|
20
24
|
}
|
|
25
|
+
|
|
21
26
|
return { from: from + delta, to: to + delta };
|
|
22
27
|
}
|
package/src/uniform-geometry.ts
CHANGED
|
@@ -10,21 +10,28 @@ export function uniformGeometry(
|
|
|
10
10
|
const rowStarts = [0, ...breaks];
|
|
11
11
|
const firstLength = rowStarts[1] ?? line.text.length;
|
|
12
12
|
let continuationLength = 0;
|
|
13
|
+
|
|
13
14
|
for (let row = 1; row < rowStarts.length; row++) {
|
|
14
15
|
continuationLength = Math.max(continuationLength, (rowStarts[row + 1] ?? line.text.length) - rowStarts[row]!);
|
|
15
16
|
}
|
|
17
|
+
|
|
16
18
|
// A shared prefix preserves repeated floating-point addition exactly, even for fallback font metrics.
|
|
17
19
|
if (Math.max(firstLength, continuationLength) > 4096) {
|
|
18
20
|
return null;
|
|
19
21
|
}
|
|
22
|
+
|
|
20
23
|
const positions = (start: number, length: number): number[] => {
|
|
21
24
|
const result = [start];
|
|
25
|
+
|
|
22
26
|
for (let index = 0; index < length; index++) {
|
|
23
27
|
result.push(result.at(-1)! + metrics.charWidth);
|
|
24
28
|
}
|
|
29
|
+
|
|
25
30
|
return result;
|
|
26
31
|
};
|
|
32
|
+
|
|
27
33
|
const uniform = { first: positions(0, firstLength), continuation: positions(indent, continuationLength) };
|
|
34
|
+
|
|
28
35
|
const geometry: LineGeometry = {
|
|
29
36
|
start: line.start,
|
|
30
37
|
end: line.end,
|
|
@@ -43,15 +50,18 @@ export function uniformGeometry(
|
|
|
43
50
|
width: 0,
|
|
44
51
|
uniform
|
|
45
52
|
};
|
|
53
|
+
|
|
46
54
|
for (let row = 0; row < rowStarts.length; row++) {
|
|
47
55
|
const from = rowStarts[row]!;
|
|
48
56
|
const to = rowStarts[row + 1] ?? line.text.length;
|
|
49
57
|
const end = uniform[row === 0 ? 'first' : 'continuation'][to - from]!;
|
|
50
58
|
geometry.rowEnds.push(end);
|
|
51
59
|
geometry.width = Math.max(geometry.width, end);
|
|
60
|
+
|
|
52
61
|
if (from < to) {
|
|
53
62
|
geometry.runs.push({ from, to, x: row === 0 ? 0 : indent, subRow: row, text: line.text.slice(from, to) });
|
|
54
63
|
}
|
|
55
64
|
}
|
|
65
|
+
|
|
56
66
|
return geometry;
|
|
57
67
|
}
|