@adecore/editor 0.22.1 → 0.23.1
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/attribution.d.ts +9 -8
- package/dist/attribution.js +44 -9
- package/dist/bidi.d.ts +44 -0
- package/dist/bidi.js +26 -0
- package/dist/column-selection.js +2 -1
- package/dist/controller.d.ts +6 -1
- package/dist/controller.js +93 -3
- package/dist/editor.css +81 -1
- package/dist/engine.js +189 -41
- package/dist/fake.d.ts +45 -5
- package/dist/fake.js +121 -13
- package/dist/find-search.d.ts +26 -0
- package/dist/find-search.js +130 -0
- package/dist/find.d.ts +50 -2
- package/dist/find.js +575 -25
- package/dist/geometry-stops.d.ts +6 -0
- package/dist/geometry-stops.js +47 -0
- package/dist/grammar-preparation.d.ts +29 -0
- package/dist/grammar-preparation.js +147 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/keymap-table.d.ts +10 -1
- package/dist/keymap-table.js +37 -1
- package/dist/keymap.d.ts +1 -0
- package/dist/keymap.js +4 -1
- package/dist/layout-changes.d.ts +6 -0
- package/dist/layout-changes.js +27 -0
- package/dist/layout.d.ts +45 -2
- package/dist/layout.js +202 -58
- package/dist/offsets.d.ts +1 -0
- package/dist/offsets.js +23 -0
- package/dist/outline.js +5 -4
- package/dist/paint.d.ts +11 -3
- package/dist/paint.js +180 -34
- package/dist/search-client.d.ts +27 -0
- package/dist/search-client.js +210 -0
- package/dist/search-protocol.d.ts +103 -0
- package/dist/search-protocol.js +3 -0
- package/dist/search-worker.d.ts +11 -0
- package/dist/search-worker.js +137 -0
- package/dist/semantic.d.ts +2 -1
- package/dist/semantic.js +6 -1
- package/dist/shiki-document.d.ts +8 -0
- package/dist/shiki-document.js +93 -0
- package/dist/shiki-worker.d.ts +65 -0
- package/dist/shiki-worker.js +265 -0
- package/dist/shiki.d.ts +1 -0
- package/dist/shiki.js +96 -33
- package/dist/testing.d.ts +1 -0
- package/dist/tokens.d.ts +2 -0
- package/dist/tokens.js +37 -7
- package/dist/touch.d.ts +27 -0
- package/dist/touch.js +100 -0
- package/dist/types.d.ts +86 -2
- package/dist/uniform-geometry.d.ts +7 -0
- package/dist/uniform-geometry.js +49 -0
- package/dist/view.d.ts +69 -17
- package/dist/view.js +427 -97
- package/dist/worker-client.d.ts +37 -0
- package/dist/worker-client.js +195 -0
- package/package.json +17 -2
- package/src/attribution.ts +50 -14
- package/src/bidi.ts +32 -0
- package/src/column-selection.ts +2 -1
- package/src/controller.ts +97 -3
- package/src/editor.css +81 -1
- package/src/engine.ts +222 -42
- package/src/fake.ts +157 -14
- package/src/find-search.ts +165 -0
- package/src/find.ts +622 -21
- package/src/geometry-stops.ts +51 -0
- package/src/grammar-preparation.ts +158 -0
- package/src/index.ts +10 -0
- package/src/keymap-table.ts +44 -2
- package/src/keymap.ts +5 -1
- package/src/layout-changes.ts +33 -0
- package/src/layout.ts +262 -63
- package/src/offsets.ts +24 -0
- package/src/outline.ts +5 -4
- package/src/paint.ts +202 -36
- package/src/search-client.ts +306 -0
- package/src/search-protocol.ts +100 -0
- package/src/search-worker.ts +171 -0
- package/src/semantic.ts +8 -2
- package/src/shiki-document.ts +143 -0
- package/src/shiki-worker.ts +344 -0
- package/src/shiki.ts +103 -33
- package/src/testing.ts +2 -0
- package/src/tokens.ts +36 -7
- package/src/touch.ts +134 -0
- package/src/types.ts +178 -8
- package/src/uniform-geometry.ts +57 -0
- package/src/view.ts +505 -102
- package/src/worker-client.ts +212 -0
package/dist/layout.js
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
import { offsetOfStop, rowOfStop, stopAtOffset, stopCount, xOfStop } from './geometry-stops.js';
|
|
2
|
+
import { BIDI_COLUMNS, bidiControl, visibleBidi } from './bidi.js';
|
|
3
|
+
import { uniformGeometry } from './uniform-geometry.js';
|
|
4
|
+
import { lineChangeMapper } from './layout-changes.js';
|
|
5
|
+
export function subRowBand(top, subRows, lineHeight, window, block = 1) {
|
|
6
|
+
const first = Math.max(0, Math.min(subRows - 1, Math.floor((window.top - top) / lineHeight)));
|
|
7
|
+
const last = Math.max(first, Math.min(subRows - 1, Math.ceil((window.bottom - top) / lineHeight) - 1));
|
|
8
|
+
return {
|
|
9
|
+
first: Math.floor(first / block) * block,
|
|
10
|
+
last: Math.min(subRows - 1, Math.ceil((last + 1) / block) * block - 1)
|
|
11
|
+
};
|
|
12
|
+
}
|
|
1
13
|
export const TEXT_PADDING = 0;
|
|
2
14
|
export const BOTTOM_PADDING = 8;
|
|
3
15
|
export const RIGHT_PADDING = 16;
|
|
@@ -10,20 +22,6 @@ const GEOMETRY_CACHE_SIZE = 400;
|
|
|
10
22
|
const PLAIN = /^[\x20-\x7e\t]*$/;
|
|
11
23
|
const GRAPHEME_LIMIT = 20000;
|
|
12
24
|
const SEGMENTER = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
|
|
13
|
-
function lowerBound(values, value) {
|
|
14
|
-
let low = 0;
|
|
15
|
-
let high = values.length;
|
|
16
|
-
while (low < high) {
|
|
17
|
-
const middle = (low + high) >>> 1;
|
|
18
|
-
if (values[middle] < value) {
|
|
19
|
-
low = middle + 1;
|
|
20
|
-
}
|
|
21
|
-
else {
|
|
22
|
-
high = middle;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return low;
|
|
26
|
-
}
|
|
27
25
|
/* Where each character starts, with the length last. A very long line is cut by code unit, since splitting it by grapheme would cost more than it shows. */
|
|
28
26
|
function characterOffsets(text) {
|
|
29
27
|
const offsets = [];
|
|
@@ -48,7 +46,10 @@ function characterWidth(metrics, text, x) {
|
|
|
48
46
|
const tab = metrics.tabSize * metrics.charWidth;
|
|
49
47
|
return (Math.floor(x / tab) + 1) * tab - x;
|
|
50
48
|
}
|
|
51
|
-
|
|
49
|
+
if (text.length === 1 && text >= ' ' && text <= '~') {
|
|
50
|
+
return metrics.charWidth;
|
|
51
|
+
}
|
|
52
|
+
return bidiControl(text) !== null ? BIDI_COLUMNS * metrics.charWidth : metrics.measureText(text);
|
|
52
53
|
}
|
|
53
54
|
/* Where the continuation rows of a wrapped line start, kept to half the row so a deep indent never leaves no room for text. */
|
|
54
55
|
export function wrapIndent(text, metrics, limit) {
|
|
@@ -65,20 +66,22 @@ export function wrapIndent(text, metrics, limit) {
|
|
|
65
66
|
* The stops at which a wrapped line starts a new visual line, after the space a word ends in when
|
|
66
67
|
* there is one on the visual line and in the middle of a word when there is not.
|
|
67
68
|
*/
|
|
68
|
-
|
|
69
|
+
function wrapRows(text, offsets, metrics, limit) {
|
|
69
70
|
const indent = wrapIndent(text, metrics, limit);
|
|
70
71
|
const breaks = [];
|
|
71
|
-
const
|
|
72
|
+
const triggers = [];
|
|
73
|
+
const count = offsets === null ? text.length : offsets.length - 1;
|
|
72
74
|
let rowStart = 0;
|
|
73
75
|
let opportunity = -1;
|
|
74
76
|
let x = 0;
|
|
75
77
|
let index = 0;
|
|
76
78
|
while (index < count) {
|
|
77
|
-
const character = text.slice(offsets[index], offsets[index + 1]);
|
|
79
|
+
const character = offsets === null ? text[index] : text.slice(offsets[index], offsets[index + 1]);
|
|
78
80
|
const width = characterWidth(metrics, character, x);
|
|
79
81
|
if (index > rowStart && x + width > limit && !isSpace(character)) {
|
|
80
82
|
const target = opportunity > rowStart ? opportunity : index;
|
|
81
83
|
breaks.push(target);
|
|
84
|
+
triggers.push(index);
|
|
82
85
|
rowStart = target;
|
|
83
86
|
opportunity = -1;
|
|
84
87
|
x = indent;
|
|
@@ -86,22 +89,28 @@ export function wrapStops(text, offsets, metrics, limit) {
|
|
|
86
89
|
continue;
|
|
87
90
|
}
|
|
88
91
|
x += width;
|
|
89
|
-
if (isSpace(character) && index + 1 < count && !isSpace(text[offsets[index + 1]])) {
|
|
92
|
+
if (isSpace(character) && index + 1 < count && !isSpace(text[offsets === null ? index + 1 : offsets[index + 1]])) {
|
|
90
93
|
opportunity = index + 1;
|
|
91
94
|
}
|
|
92
95
|
index++;
|
|
93
96
|
}
|
|
94
|
-
|
|
97
|
+
triggers.push(count);
|
|
98
|
+
return { breaks, triggers };
|
|
99
|
+
}
|
|
100
|
+
export function wrapStops(text, offsets, metrics, limit) {
|
|
101
|
+
return wrapRows(text, offsets, metrics, limit).breaks;
|
|
95
102
|
}
|
|
96
103
|
/* A line of text laid out against its metrics; wrapped at `limit` pixels unless that is null. */
|
|
97
|
-
export function
|
|
104
|
+
export function scanDenseLine(line, metrics, limit, lineInlays) {
|
|
98
105
|
const text = line.text;
|
|
99
106
|
const offsets = characterOffsets(text);
|
|
100
|
-
const
|
|
107
|
+
const wrapped = limit === null ? { breaks: [], triggers: [offsets.length - 1] } : wrapRows(text, offsets, metrics, limit);
|
|
108
|
+
const breakStops = wrapped.breaks;
|
|
101
109
|
const indent = limit === null ? 0 : wrapIndent(text, metrics, limit);
|
|
102
110
|
const geometry = {
|
|
103
111
|
start: line.start,
|
|
104
112
|
end: line.end,
|
|
113
|
+
next: line.next ?? line.end,
|
|
105
114
|
text,
|
|
106
115
|
offsets,
|
|
107
116
|
before: [],
|
|
@@ -109,6 +118,7 @@ export function scanLine(line, metrics, limit, lineInlays) {
|
|
|
109
118
|
rowOf: [],
|
|
110
119
|
rowStarts: [0],
|
|
111
120
|
rowEnds: [],
|
|
121
|
+
rowTriggers: wrapped.triggers,
|
|
112
122
|
runs: [],
|
|
113
123
|
inlays: [],
|
|
114
124
|
subRows: breakStops.length + 1,
|
|
@@ -122,7 +132,7 @@ export function scanLine(line, metrics, limit, lineInlays) {
|
|
|
122
132
|
let runX = 0;
|
|
123
133
|
const flushRun = (end) => {
|
|
124
134
|
if (runStart >= 0 && end > runStart) {
|
|
125
|
-
geometry.runs.push({ from:
|
|
135
|
+
geometry.runs.push({ from: runStart, to: end, x: runX, subRow: row, text: text.slice(runStart, end) });
|
|
126
136
|
}
|
|
127
137
|
runStart = -1;
|
|
128
138
|
};
|
|
@@ -141,7 +151,10 @@ export function scanLine(line, metrics, limit, lineInlays) {
|
|
|
141
151
|
while (nextInlay < lineInlays.length && lineInlays[nextInlay].at - line.start <= offset) {
|
|
142
152
|
flushRun(offset);
|
|
143
153
|
const inlay = lineInlays[nextInlay++];
|
|
144
|
-
const
|
|
154
|
+
const shown = visibleBidi(inlay.text);
|
|
155
|
+
const width = inlay.composition || inlay.ghost
|
|
156
|
+
? metrics.measureText(shown)
|
|
157
|
+
: Math.max(8, metrics.measureInlay(shown) + 12) + (inlay.gapLeft ?? 0) + (inlay.gapRight ?? 0);
|
|
145
158
|
geometry.inlays.push({ inlay, x, width, subRow: row });
|
|
146
159
|
x += width;
|
|
147
160
|
}
|
|
@@ -151,7 +164,7 @@ export function scanLine(line, metrics, limit, lineInlays) {
|
|
|
151
164
|
}
|
|
152
165
|
const character = text.slice(offset, offsets[stop + 1]);
|
|
153
166
|
const width = characterWidth(metrics, character, x);
|
|
154
|
-
if (character === '\t') {
|
|
167
|
+
if (character === '\t' || bidiControl(character) !== null) {
|
|
155
168
|
flushRun(offset);
|
|
156
169
|
}
|
|
157
170
|
else if (runStart < 0) {
|
|
@@ -162,9 +175,21 @@ export function scanLine(line, metrics, limit, lineInlays) {
|
|
|
162
175
|
}
|
|
163
176
|
flushRun(text.length);
|
|
164
177
|
geometry.rowEnds.push(x);
|
|
165
|
-
|
|
178
|
+
for (const end of geometry.rowEnds) {
|
|
179
|
+
geometry.width = Math.max(geometry.width, end);
|
|
180
|
+
}
|
|
166
181
|
return geometry;
|
|
167
182
|
}
|
|
183
|
+
export function scanLine(line, metrics, limit, lineInlays) {
|
|
184
|
+
if (limit !== null && line.text.length > GRAPHEME_LIMIT && lineInlays.length === 0 && /^[\x20-\x7e]*$/.test(line.text)) {
|
|
185
|
+
const wrapped = wrapRows(line.text, null, metrics, limit);
|
|
186
|
+
const geometry = uniformGeometry(line, metrics, wrapped.breaks, wrapped.triggers, wrapIndent(line.text, metrics, limit));
|
|
187
|
+
if (geometry !== null) {
|
|
188
|
+
return geometry;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return scanDenseLine(line, metrics, limit, lineInlays);
|
|
192
|
+
}
|
|
168
193
|
/*
|
|
169
194
|
* The rows of the document as they are drawn: one per line, taller for a wrapped line, one for a
|
|
170
195
|
* widget, none for a line a fold hides. Only the rows that are on screen are ever measured; the
|
|
@@ -173,6 +198,7 @@ export function scanLine(line, metrics, limit, lineInlays) {
|
|
|
173
198
|
export class EditorLayout {
|
|
174
199
|
rows = [];
|
|
175
200
|
height = 0;
|
|
201
|
+
lineHeightsVersion = 0;
|
|
176
202
|
/* The widest line measured since the text last changed, and the room past it; what a horizontal scroll bar needs. */
|
|
177
203
|
width = 0;
|
|
178
204
|
lineRows = [];
|
|
@@ -183,6 +209,7 @@ export class EditorLayout {
|
|
|
183
209
|
inlays = [];
|
|
184
210
|
blocks = [];
|
|
185
211
|
folds = [];
|
|
212
|
+
replacements = [];
|
|
186
213
|
wrapWidth = null;
|
|
187
214
|
document;
|
|
188
215
|
metrics;
|
|
@@ -207,21 +234,79 @@ export class EditorLayout {
|
|
|
207
234
|
}
|
|
208
235
|
if (config.inlays) {
|
|
209
236
|
this.inlays = config.inlays;
|
|
210
|
-
measureAgain = true;
|
|
211
237
|
}
|
|
212
238
|
if (config.blocks) {
|
|
213
239
|
this.blocks = config.blocks;
|
|
214
240
|
}
|
|
215
241
|
if (config.folds) {
|
|
216
242
|
this.folds = config.folds;
|
|
243
|
+
this.lineHeightsVersion++;
|
|
217
244
|
}
|
|
218
|
-
if (
|
|
245
|
+
if (config.replacements) {
|
|
246
|
+
this.replacements = config.replacements;
|
|
247
|
+
this.lineHeightsVersion++;
|
|
248
|
+
}
|
|
249
|
+
if (measureAgain || (config.textChanged && !config.changes?.length)) {
|
|
250
|
+
this.lineHeightsVersion++;
|
|
219
251
|
this.geometryCache.clear();
|
|
220
252
|
this.measuredRows.clear();
|
|
221
253
|
this.width = 0;
|
|
222
254
|
}
|
|
255
|
+
else {
|
|
256
|
+
if (config.changes?.length) {
|
|
257
|
+
this.retainGeometry(config.changes);
|
|
258
|
+
this.lineHeightsVersion++;
|
|
259
|
+
}
|
|
260
|
+
if (config.inlays !== undefined) {
|
|
261
|
+
if (!config.changes?.length) {
|
|
262
|
+
for (const line of this.inlaysByLine.keys()) {
|
|
263
|
+
this.geometryCache.delete(line);
|
|
264
|
+
this.measuredRows.delete(line);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
for (const inlay of config.inlays) {
|
|
268
|
+
const line = this.document.positionAt(inlay.at).line;
|
|
269
|
+
this.geometryCache.delete(line);
|
|
270
|
+
this.measuredRows.delete(line);
|
|
271
|
+
}
|
|
272
|
+
this.lineHeightsVersion++;
|
|
273
|
+
}
|
|
274
|
+
this.width = 0;
|
|
275
|
+
if (this.wrapWidth === null) {
|
|
276
|
+
for (const geometry of this.geometryCache.values()) {
|
|
277
|
+
this.width = Math.max(this.width, geometry.width + ADDITIONAL_COLUMNS * this.metrics.charWidth);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
223
281
|
this.rebuild();
|
|
224
282
|
}
|
|
283
|
+
retainGeometry(batches) {
|
|
284
|
+
const map = lineChangeMapper(batches);
|
|
285
|
+
const remap = (bounds) => {
|
|
286
|
+
const next = map({ start: bounds.start, end: Math.max(bounds.end, bounds.next - 1) });
|
|
287
|
+
if (next === null) {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
const delta = next.start - bounds.start;
|
|
291
|
+
return { start: next.start, end: bounds.end + delta, next: bounds.next + delta, line: this.document.positionAt(next.start).line };
|
|
292
|
+
};
|
|
293
|
+
const geometries = new Map();
|
|
294
|
+
for (const geometry of this.geometryCache.values()) {
|
|
295
|
+
const next = remap(geometry);
|
|
296
|
+
if (next !== null && geometry.inlays.length === 0) {
|
|
297
|
+
geometries.set(next.line, next.start === geometry.start ? geometry : { ...geometry, start: next.start, end: next.end, next: next.next });
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
const measured = new Map();
|
|
301
|
+
for (const [line, bounds] of this.measuredRows) {
|
|
302
|
+
const next = remap(bounds);
|
|
303
|
+
if (next !== null && !this.inlaysByLine.has(line)) {
|
|
304
|
+
measured.set(next.line, { start: next.start, end: next.end, next: next.next, subRows: bounds.subRows });
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
this.geometryCache = geometries;
|
|
308
|
+
this.measuredRows = measured;
|
|
309
|
+
}
|
|
225
310
|
rowCountOf(line, length) {
|
|
226
311
|
const limit = this.wrapWidth;
|
|
227
312
|
if (limit === null) {
|
|
@@ -229,7 +314,7 @@ export class EditorLayout {
|
|
|
229
314
|
}
|
|
230
315
|
const measured = this.measuredRows.get(line);
|
|
231
316
|
if (measured !== undefined) {
|
|
232
|
-
return measured;
|
|
317
|
+
return measured.subRows;
|
|
233
318
|
}
|
|
234
319
|
return Math.max(1, Math.ceil((length * this.metrics.charWidth) / Math.max(limit, this.metrics.charWidth)));
|
|
235
320
|
}
|
|
@@ -257,6 +342,15 @@ export class EditorLayout {
|
|
|
257
342
|
folds.set(fold.startLine, fold);
|
|
258
343
|
}
|
|
259
344
|
}
|
|
345
|
+
const replaced = new Map();
|
|
346
|
+
for (const replacement of this.replacements) {
|
|
347
|
+
const startLine = this.document.positionAt(replacement.from).line;
|
|
348
|
+
const endLine = Math.max(startLine, this.document.positionAt(replacement.to).line);
|
|
349
|
+
// A fold the person collapsed shows its own placeholder, so folding a replaced comment keeps working.
|
|
350
|
+
if (!folds.has(startLine) && !replaced.has(startLine)) {
|
|
351
|
+
replaced.set(startLine, { replacement, endLine });
|
|
352
|
+
}
|
|
353
|
+
}
|
|
260
354
|
const count = this.document.getLineCount();
|
|
261
355
|
this.rows = [];
|
|
262
356
|
this.lineRows = new Array(count);
|
|
@@ -269,13 +363,44 @@ export class EditorLayout {
|
|
|
269
363
|
line,
|
|
270
364
|
widget,
|
|
271
365
|
top: 0,
|
|
272
|
-
height: widget.lens
|
|
366
|
+
height: widget.lens
|
|
367
|
+
? this.metrics.lineHeight
|
|
368
|
+
: widget.fixed
|
|
369
|
+
? Math.max(1, widget.height ?? DEFAULT_BLOCK_HEIGHT)
|
|
370
|
+
: (this.measuredHeights.get(key) ?? widget.height ?? DEFAULT_BLOCK_HEIGHT)
|
|
273
371
|
});
|
|
274
372
|
};
|
|
275
373
|
for (let line = 0; line < count; line++) {
|
|
276
374
|
for (const block of above.get(line) ?? []) {
|
|
277
375
|
addBlock(block, line);
|
|
278
376
|
}
|
|
377
|
+
const standIn = replaced.get(line);
|
|
378
|
+
if (standIn !== undefined) {
|
|
379
|
+
const last = Math.min(count - 1, standIn.endLine);
|
|
380
|
+
const key = `replaced:${standIn.replacement.id}`;
|
|
381
|
+
const index = this.rows.length;
|
|
382
|
+
this.rows.push({
|
|
383
|
+
key,
|
|
384
|
+
kind: 'text',
|
|
385
|
+
index,
|
|
386
|
+
line,
|
|
387
|
+
lastLine: last,
|
|
388
|
+
tail: null,
|
|
389
|
+
head: null,
|
|
390
|
+
subRows: 1,
|
|
391
|
+
top: 0,
|
|
392
|
+
height: this.measuredHeights.get(key) ?? standIn.replacement.height ?? DEFAULT_BLOCK_HEIGHT,
|
|
393
|
+
replacement: standIn.replacement
|
|
394
|
+
});
|
|
395
|
+
for (let covered = line; covered <= last; covered++) {
|
|
396
|
+
this.lineRows[covered] = index;
|
|
397
|
+
}
|
|
398
|
+
for (const block of below.get(last) ?? []) {
|
|
399
|
+
addBlock(block, last);
|
|
400
|
+
}
|
|
401
|
+
line = last;
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
279
404
|
const fold = folds.get(line);
|
|
280
405
|
const last = Math.max(line, Math.min(count - 1, fold?.endLine ?? line));
|
|
281
406
|
const tail = fold?.closer !== undefined && last > line ? this.clampToLine(fold.closer, last) : null;
|
|
@@ -322,7 +447,7 @@ export class EditorLayout {
|
|
|
322
447
|
setMeasuredHeight(key, height) {
|
|
323
448
|
const row = this.rows.find((candidate) => candidate.key === key);
|
|
324
449
|
const next = Math.max(1, Math.round(height));
|
|
325
|
-
if (!row || !Number.isFinite(next) || row.height === next) {
|
|
450
|
+
if (!row || (row.kind === 'block' && row.widget.fixed) || !Number.isFinite(next) || row.height === next) {
|
|
326
451
|
return false;
|
|
327
452
|
}
|
|
328
453
|
this.measuredHeights.set(key, next);
|
|
@@ -334,18 +459,28 @@ export class EditorLayout {
|
|
|
334
459
|
syncRows(rows) {
|
|
335
460
|
let changed = false;
|
|
336
461
|
for (const row of rows) {
|
|
337
|
-
if (row.kind !== 'text') {
|
|
462
|
+
if (row.kind !== 'text' || row.replacement !== undefined) {
|
|
338
463
|
continue;
|
|
339
464
|
}
|
|
340
|
-
const
|
|
465
|
+
const geometry = this.geometry(row);
|
|
466
|
+
const subRows = geometry.subRows;
|
|
467
|
+
if (row.head === null && this.wrapWidth !== null) {
|
|
468
|
+
if (!this.measuredRows.has(row.line) && this.measuredRows.size >= GEOMETRY_CACHE_SIZE) {
|
|
469
|
+
this.measuredRows.delete(this.measuredRows.keys().next().value);
|
|
470
|
+
}
|
|
471
|
+
this.measuredRows.set(row.line, { start: geometry.start, end: geometry.end, next: geometry.next, subRows });
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
this.measuredRows.delete(row.line);
|
|
475
|
+
}
|
|
341
476
|
if (subRows !== row.subRows) {
|
|
342
|
-
this.measuredRows.set(row.line, subRows);
|
|
343
477
|
row.subRows = subRows;
|
|
344
478
|
row.height = subRows * this.metrics.lineHeight;
|
|
345
479
|
changed = true;
|
|
346
480
|
}
|
|
347
481
|
}
|
|
348
482
|
if (changed) {
|
|
483
|
+
this.lineHeightsVersion++;
|
|
349
484
|
this.reflow();
|
|
350
485
|
}
|
|
351
486
|
return changed;
|
|
@@ -375,7 +510,7 @@ export class EditorLayout {
|
|
|
375
510
|
while (stop < geometry.text.length && isSpace(geometry.text[stop])) {
|
|
376
511
|
stop++;
|
|
377
512
|
}
|
|
378
|
-
return TEXT_PADDING + geometry
|
|
513
|
+
return TEXT_PADDING + xOfStop(geometry, Math.min(stop, stopCount(geometry) - 1));
|
|
379
514
|
}
|
|
380
515
|
rowForLine(line) {
|
|
381
516
|
return this.rows[this.lineRows[Math.max(0, Math.min(this.lineRows.length - 1, line))]];
|
|
@@ -420,10 +555,10 @@ export class EditorLayout {
|
|
|
420
555
|
return result;
|
|
421
556
|
}
|
|
422
557
|
stopOf(geometry, offset) {
|
|
423
|
-
return
|
|
558
|
+
return stopAtOffset(geometry, Math.max(0, offset - geometry.start));
|
|
424
559
|
}
|
|
425
560
|
xAt(geometry, offset, affinity = 'after') {
|
|
426
|
-
return TEXT_PADDING + geometry
|
|
561
|
+
return TEXT_PADDING + xOfStop(geometry, this.stopOf(geometry, offset), affinity);
|
|
427
562
|
}
|
|
428
563
|
/*
|
|
429
564
|
* The offset nearest an x on one visual line of a laid out line. `rowEnd` is set when that is the
|
|
@@ -437,20 +572,20 @@ export class EditorLayout {
|
|
|
437
572
|
}
|
|
438
573
|
}
|
|
439
574
|
const first = geometry.rowStarts[subRow] ?? 0;
|
|
440
|
-
const last = subRow + 1 < geometry.rowStarts.length ? geometry.rowStarts[subRow + 1] : geometry
|
|
575
|
+
const last = subRow + 1 < geometry.rowStarts.length ? geometry.rowStarts[subRow + 1] : stopCount(geometry) - 1;
|
|
441
576
|
const wrapped = subRow + 1 < geometry.rowStarts.length;
|
|
442
577
|
let best = first;
|
|
443
|
-
let distance = Math.abs(local - geometry
|
|
578
|
+
let distance = Math.abs(local - xOfStop(geometry, first));
|
|
444
579
|
// The stop that starts the next visual line stands in for the end of this one.
|
|
445
580
|
for (let stop = first + 1; stop <= last; stop++) {
|
|
446
|
-
const edge = stop === last && wrapped ? geometry.rowEnds[subRow] : geometry
|
|
581
|
+
const edge = stop === last && wrapped ? geometry.rowEnds[subRow] : xOfStop(geometry, stop);
|
|
447
582
|
const candidate = Math.abs(local - edge);
|
|
448
583
|
if (candidate < distance) {
|
|
449
584
|
distance = candidate;
|
|
450
585
|
best = stop;
|
|
451
586
|
}
|
|
452
587
|
}
|
|
453
|
-
return { offset: geometry.start + geometry
|
|
588
|
+
return { offset: geometry.start + offsetOfStop(geometry, best), rowEnd: wrapped && best === last };
|
|
454
589
|
}
|
|
455
590
|
/* With `rowEnd`, an offset a wrap starts a visual line at is drawn at the end of the line before it. */
|
|
456
591
|
caret(offset, affinity = 'after', rowEnd = false) {
|
|
@@ -461,7 +596,7 @@ export class EditorLayout {
|
|
|
461
596
|
}
|
|
462
597
|
const geometry = this.geometry(row);
|
|
463
598
|
const stop = this.stopOf(geometry, offset);
|
|
464
|
-
const subRow = geometry
|
|
599
|
+
const subRow = rowOfStop(geometry, stop);
|
|
465
600
|
if (rowEnd && subRow > 0 && geometry.rowStarts[subRow] === stop) {
|
|
466
601
|
return {
|
|
467
602
|
x: TEXT_PADDING + geometry.rowEnds[subRow - 1],
|
|
@@ -470,7 +605,12 @@ export class EditorLayout {
|
|
|
470
605
|
height: this.metrics.lineHeight
|
|
471
606
|
};
|
|
472
607
|
}
|
|
473
|
-
return {
|
|
608
|
+
return {
|
|
609
|
+
x: TEXT_PADDING + xOfStop(geometry, stop, affinity),
|
|
610
|
+
y: row.top + subRow * this.metrics.lineHeight,
|
|
611
|
+
width: 1,
|
|
612
|
+
height: this.metrics.lineHeight
|
|
613
|
+
};
|
|
474
614
|
}
|
|
475
615
|
/* A height snapped up to the top of the visual line it is in, so a page scrolls to whole lines. */
|
|
476
616
|
lineBase(y) {
|
|
@@ -495,10 +635,10 @@ export class EditorLayout {
|
|
|
495
635
|
const geometry = this.geometry(row);
|
|
496
636
|
const lastLine = this.document.getLine(row.lastLine);
|
|
497
637
|
const hidden = position.line > row.line;
|
|
498
|
-
const subRow = hidden ? geometry.subRows - 1 : geometry
|
|
638
|
+
const subRow = hidden ? geometry.subRows - 1 : rowOfStop(geometry, this.stopOf(geometry, offset));
|
|
499
639
|
const last = subRow + 1 >= geometry.subRows;
|
|
500
|
-
const start = line.start + geometry
|
|
501
|
-
const end = last ? lastLine.end : line.start + geometry
|
|
640
|
+
const start = line.start + offsetOfStop(geometry, geometry.rowStarts[subRow]);
|
|
641
|
+
const end = last ? lastLine.end : line.start + offsetOfStop(geometry, geometry.rowStarts[subRow + 1]);
|
|
502
642
|
return { start, end, next: last ? lastLine.next : end, y: row.top + subRow * this.metrics.lineHeight };
|
|
503
643
|
}
|
|
504
644
|
hitTest(x, y) {
|
|
@@ -514,21 +654,24 @@ export class EditorLayout {
|
|
|
514
654
|
return this.offsetAtX(geometry, subRow, x);
|
|
515
655
|
}
|
|
516
656
|
/* The boxes a range is drawn in, one per visual line it touches, among the rows given. */
|
|
517
|
-
rectangles(from, to, rows) {
|
|
657
|
+
rectangles(from, to, rows, window) {
|
|
518
658
|
const result = [];
|
|
519
659
|
const lineHeight = this.metrics.lineHeight;
|
|
520
660
|
for (const row of rows) {
|
|
521
|
-
if (row.kind !== 'text'
|
|
661
|
+
if (row.kind !== 'text' ||
|
|
662
|
+
row.replacement !== undefined ||
|
|
663
|
+
(window !== undefined && (row.top >= window.bottom || row.top + row.height <= window.top))) {
|
|
522
664
|
continue;
|
|
523
665
|
}
|
|
524
666
|
const geometry = this.geometry(row);
|
|
525
667
|
if (from === to || from > geometry.end || to <= geometry.start) {
|
|
526
668
|
continue;
|
|
527
669
|
}
|
|
528
|
-
const lastStop = geometry
|
|
670
|
+
const lastStop = stopCount(geometry) - 1;
|
|
529
671
|
const fromStop = this.stopOf(geometry, Math.max(from, geometry.start));
|
|
530
672
|
const toStop = this.stopOf(geometry, Math.min(to, geometry.end));
|
|
531
|
-
|
|
673
|
+
const band = window === undefined ? { first: 0, last: geometry.subRows - 1 } : subRowBand(row.top, geometry.subRows, lineHeight, window);
|
|
674
|
+
for (let subRow = band.first; subRow <= band.last; subRow++) {
|
|
532
675
|
const last = subRow + 1 === geometry.subRows;
|
|
533
676
|
const rowFirst = geometry.rowStarts[subRow];
|
|
534
677
|
const rowEnd = last ? lastStop : geometry.rowStarts[subRow + 1];
|
|
@@ -538,8 +681,8 @@ export class EditorLayout {
|
|
|
538
681
|
if (first >= stop && !newline) {
|
|
539
682
|
continue;
|
|
540
683
|
}
|
|
541
|
-
const startX = first === fromStop ? geometry
|
|
542
|
-
const endX = stop < rowEnd || last ? geometry
|
|
684
|
+
const startX = first === fromStop ? xOfStop(geometry, first, 'after') : xOfStop(geometry, first);
|
|
685
|
+
const endX = stop < rowEnd || last ? xOfStop(geometry, stop) : geometry.rowEnds[subRow];
|
|
543
686
|
result.push({
|
|
544
687
|
x: TEXT_PADDING + startX,
|
|
545
688
|
y: row.top + subRow * lineHeight,
|
|
@@ -551,8 +694,9 @@ export class EditorLayout {
|
|
|
551
694
|
return result;
|
|
552
695
|
}
|
|
553
696
|
/*
|
|
554
|
-
* The offset a visual line above or below, `distance` pixels away, at an x. Widgets
|
|
555
|
-
*
|
|
697
|
+
* The offset a visual line above or below, `distance` pixels away, at an x. Widgets and the rows of
|
|
698
|
+
* replaced lines, unless the host made them enterable, are walked over, and the document's ends stop
|
|
699
|
+
* the caret on the first or the last offset.
|
|
556
700
|
*/
|
|
557
701
|
verticalOffset(offset, direction, x, distance = this.metrics.lineHeight) {
|
|
558
702
|
const caret = this.caret(offset);
|
|
@@ -561,10 +705,10 @@ export class EditorLayout {
|
|
|
561
705
|
return 0;
|
|
562
706
|
}
|
|
563
707
|
let row = this.rowAt(y);
|
|
564
|
-
while (row.kind === 'block') {
|
|
708
|
+
while (row.kind === 'block' || (row.replacement !== undefined && row.replacement.enterable !== true)) {
|
|
565
709
|
const next = this.rows[row.index + direction];
|
|
566
710
|
if (!next) {
|
|
567
|
-
return
|
|
711
|
+
return direction < 0 ? 0 : this.document.getLine(this.document.getLineCount() - 1).end;
|
|
568
712
|
}
|
|
569
713
|
row = next;
|
|
570
714
|
y = direction < 0 ? row.top + row.height - 1 : row.top;
|
|
@@ -581,8 +725,8 @@ export class EditorLayout {
|
|
|
581
725
|
const geometry = this.geometry(row);
|
|
582
726
|
const stop = this.stopOf(geometry, offset);
|
|
583
727
|
const next = stop + direction;
|
|
584
|
-
if (next >= 0 && next < geometry
|
|
585
|
-
return geometry.start + geometry
|
|
728
|
+
if (next >= 0 && next < stopCount(geometry) && offset >= geometry.start && offset <= geometry.end) {
|
|
729
|
+
return geometry.start + offsetOfStop(geometry, next);
|
|
586
730
|
}
|
|
587
731
|
}
|
|
588
732
|
for (let index = row.index + direction; index >= 0 && index < this.rows.length; index += direction) {
|
package/dist/offsets.d.ts
CHANGED
package/dist/offsets.js
CHANGED
|
@@ -12,3 +12,26 @@ export function mapOffset(offset, changes) {
|
|
|
12
12
|
}
|
|
13
13
|
return offset + delta;
|
|
14
14
|
}
|
|
15
|
+
/* Build once per sorted, nonoverlapping batch when many offsets need to follow it. */
|
|
16
|
+
export function offsetMapper(changes) {
|
|
17
|
+
const deltas = new Float64Array(changes.length + 1);
|
|
18
|
+
for (let index = 0; index < changes.length; index++) {
|
|
19
|
+
const change = changes[index];
|
|
20
|
+
deltas[index + 1] = deltas[index] + change.insertedLength - (change.to - change.from);
|
|
21
|
+
}
|
|
22
|
+
return (offset) => {
|
|
23
|
+
let low = 0, high = changes.length;
|
|
24
|
+
while (low < high) {
|
|
25
|
+
const middle = (low + high) >>> 1;
|
|
26
|
+
const change = changes[middle];
|
|
27
|
+
if (change.to < offset || (change.to === offset && change.from !== change.to)) {
|
|
28
|
+
low = middle + 1;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
high = middle;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const change = changes[low];
|
|
35
|
+
return change !== undefined && change.from <= offset ? change.from + deltas[low] + change.insertedLength : offset + deltas[low];
|
|
36
|
+
};
|
|
37
|
+
}
|
package/dist/outline.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { offsetMapper } from './offsets.js';
|
|
2
2
|
const MODIFIERS = '(?:export\\s+|default\\s+|declare\\s+|abstract\\s+|async\\s+|pub(?:\\([^)]*\\))?\\s+|public\\s+|private\\s+|protected\\s+|static\\s+|final\\s+|readonly\\s+|override\\s+|unsafe\\s+|extern\\s+)*';
|
|
3
3
|
const DECLARATION = new RegExp(`^${MODIFIERS}(function\\*?|class|interface|enum|type|namespace|module|trait|impl|struct|fn|def|func|object|record)\\s+([A-Za-z_$][\\w$]*)`);
|
|
4
4
|
const VARIABLE = new RegExp(`^${MODIFIERS}(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)`);
|
|
@@ -126,12 +126,13 @@ export class Outline {
|
|
|
126
126
|
});
|
|
127
127
|
}
|
|
128
128
|
edited(batches) {
|
|
129
|
+
const mappers = batches.map(offsetMapper);
|
|
129
130
|
const move = (entries) => entries.map((entry) => {
|
|
130
131
|
let from = entry.from;
|
|
131
132
|
let to = entry.to;
|
|
132
|
-
for (const
|
|
133
|
-
from =
|
|
134
|
-
to =
|
|
133
|
+
for (const map of mappers) {
|
|
134
|
+
from = map(from);
|
|
135
|
+
to = map(to);
|
|
135
136
|
}
|
|
136
137
|
return { ...entry, from, to };
|
|
137
138
|
});
|
package/dist/paint.d.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { type AttributedLines } from './attribution.ts';
|
|
2
|
-
import { type EditorLayout, type LayoutRect, type LayoutRow, type LineGeometry } from './layout.ts';
|
|
3
|
-
import type { EditorChangeKind, LineToken } from './types.ts';
|
|
2
|
+
import { type EditorLayout, type LayoutRect, type LayoutRow, type LineGeometry, type SubRowBand, type VerticalWindow } from './layout.ts';
|
|
3
|
+
import type { EditorChangeKind, EditorGutterIcon, LineToken } from './types.ts';
|
|
4
4
|
export interface RowPaint {
|
|
5
5
|
layoutVersion: number;
|
|
6
6
|
whitespace: boolean;
|
|
7
7
|
tokensOf(line: number): readonly LineToken[] | null;
|
|
8
8
|
viewportWidth: number;
|
|
9
|
+
window: VerticalWindow;
|
|
9
10
|
hostLeft: number;
|
|
10
11
|
hostWidth: number;
|
|
11
12
|
onUnfold(line: number): void;
|
|
12
13
|
}
|
|
13
14
|
export declare function styleSpan(span: HTMLElement, token: LineToken): void;
|
|
14
|
-
export declare function lineElement(document: Document, geometry: LineGeometry, tokens: readonly LineToken[] | null, lineHeight: number,
|
|
15
|
+
export declare function lineElement(document: Document, geometry: LineGeometry, tokens: readonly LineToken[] | null, lineHeight: number, band: SubRowBand, whitespace?: boolean): HTMLElement;
|
|
15
16
|
export declare class RowPainter {
|
|
16
17
|
private readonly rows;
|
|
17
18
|
private readonly code;
|
|
@@ -22,16 +23,20 @@ export declare class RowPainter {
|
|
|
22
23
|
private paintText;
|
|
23
24
|
private buildText;
|
|
24
25
|
private appendTail;
|
|
26
|
+
private paintReplacement;
|
|
25
27
|
private paintBlock;
|
|
26
28
|
private fillBlock;
|
|
27
29
|
clear(): void;
|
|
28
30
|
}
|
|
29
31
|
export interface GutterPaint {
|
|
32
|
+
lineNumber(line: number): string | null;
|
|
30
33
|
activeLines: ReadonlySet<number>;
|
|
31
34
|
changes: ReadonlyMap<number, EditorChangeKind>;
|
|
32
35
|
attribution: ReadonlyMap<number, AttributedLines>;
|
|
33
36
|
highlights: ReadonlyMap<number, AttributedLines>;
|
|
34
37
|
foldable: ReadonlyMap<number, boolean>;
|
|
38
|
+
foldName(line: number, collapsed: boolean): string;
|
|
39
|
+
lineLabel(line: number): string | null;
|
|
35
40
|
action: {
|
|
36
41
|
line: number;
|
|
37
42
|
label: string;
|
|
@@ -39,6 +44,8 @@ export interface GutterPaint {
|
|
|
39
44
|
markers: ReadonlyMap<number, {
|
|
40
45
|
id: string;
|
|
41
46
|
label: string;
|
|
47
|
+
icon?: EditorGutterIcon;
|
|
48
|
+
pressed?: boolean;
|
|
42
49
|
}>;
|
|
43
50
|
}
|
|
44
51
|
export declare function paintGutter(container: HTMLElement, layout: EditorLayout, rows: readonly LayoutRow[], paint: GutterPaint): void;
|
|
@@ -73,6 +80,7 @@ export interface RemoteCaret {
|
|
|
73
80
|
export declare function paintRemoteCarets(container: HTMLElement, carets: readonly RemoteCaret[]): void;
|
|
74
81
|
export interface StickyEntry {
|
|
75
82
|
line: number;
|
|
83
|
+
label: string | null;
|
|
76
84
|
geometry: LineGeometry;
|
|
77
85
|
tokens: readonly LineToken[] | null;
|
|
78
86
|
}
|