@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/src/offsets.ts
CHANGED
|
@@ -14,3 +14,27 @@ export function mapOffset(offset: number, changes: readonly DocumentChange[]): n
|
|
|
14
14
|
}
|
|
15
15
|
return offset + delta;
|
|
16
16
|
}
|
|
17
|
+
|
|
18
|
+
/* Build once per sorted, nonoverlapping batch when many offsets need to follow it. */
|
|
19
|
+
export function offsetMapper(changes: readonly DocumentChange[]): (offset: number) => number {
|
|
20
|
+
const deltas = new Float64Array(changes.length + 1);
|
|
21
|
+
for (let index = 0; index < changes.length; index++) {
|
|
22
|
+
const change = changes[index]!;
|
|
23
|
+
deltas[index + 1] = deltas[index]! + change.insertedLength - (change.to - change.from);
|
|
24
|
+
}
|
|
25
|
+
return (offset) => {
|
|
26
|
+
let low = 0,
|
|
27
|
+
high = changes.length;
|
|
28
|
+
while (low < high) {
|
|
29
|
+
const middle = (low + high) >>> 1;
|
|
30
|
+
const change = changes[middle]!;
|
|
31
|
+
if (change.to < offset || (change.to === offset && change.from !== change.to)) {
|
|
32
|
+
low = middle + 1;
|
|
33
|
+
} else {
|
|
34
|
+
high = middle;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const change = changes[low];
|
|
38
|
+
return change !== undefined && change.from <= offset ? change.from + deltas[low]! + change.insertedLength : offset + deltas[low]!;
|
|
39
|
+
};
|
|
40
|
+
}
|
package/src/outline.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DocumentChange, FoldingRange } from '@adecore/editor-core';
|
|
2
|
-
import {
|
|
2
|
+
import { offsetMapper } from './offsets.ts';
|
|
3
3
|
import type { EditorBlock } from './types.ts';
|
|
4
4
|
|
|
5
5
|
/* A block with a header line, in zero-based lines and both ends inclusive. */
|
|
@@ -168,13 +168,14 @@ export class Outline {
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
edited(batches: readonly (readonly DocumentChange[])[]): void {
|
|
171
|
+
const mappers = batches.map(offsetMapper);
|
|
171
172
|
const move = (entries: OutlineEntry[]): OutlineEntry[] =>
|
|
172
173
|
entries.map((entry) => {
|
|
173
174
|
let from = entry.from;
|
|
174
175
|
let to = entry.to;
|
|
175
|
-
for (const
|
|
176
|
-
from =
|
|
177
|
-
to =
|
|
176
|
+
for (const map of mappers) {
|
|
177
|
+
from = map(from);
|
|
178
|
+
to = map(to);
|
|
178
179
|
}
|
|
179
180
|
return { ...entry, from, to };
|
|
180
181
|
});
|
package/src/paint.ts
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { type
|
|
3
|
-
import
|
|
1
|
+
import { offsetOfStop, rowOfStop, stopAtOffset, stopCount, xOfStop } from './geometry-stops.ts';
|
|
2
|
+
import { type AttributedLines, colorValue, tintValue } from './attribution.ts';
|
|
3
|
+
import {
|
|
4
|
+
type BlockReplacement,
|
|
5
|
+
type BlockWidget,
|
|
6
|
+
type EditorLayout,
|
|
7
|
+
type LayoutRect,
|
|
8
|
+
type LayoutRow,
|
|
9
|
+
type LineGeometry,
|
|
10
|
+
scanLine,
|
|
11
|
+
subRowBand,
|
|
12
|
+
type SubRowBand,
|
|
13
|
+
type VerticalWindow,
|
|
14
|
+
type TextRow
|
|
15
|
+
} from './layout.ts';
|
|
16
|
+
import type { EditorChangeKind, EditorGutterIcon, LineToken } from './types.ts';
|
|
17
|
+
import { BIDI_CONTROLS, BIDI_PATTERN, visibleBidi } from './bidi.ts';
|
|
4
18
|
|
|
5
19
|
const ITALIC = 1;
|
|
6
20
|
const BOLD = 2;
|
|
@@ -19,6 +33,7 @@ interface PaintedRow {
|
|
|
19
33
|
tailTokens: readonly LineToken[] | null;
|
|
20
34
|
/* The revision of the widget a block row was drawn from. */
|
|
21
35
|
revision: number;
|
|
36
|
+
band?: SubRowBand;
|
|
22
37
|
}
|
|
23
38
|
|
|
24
39
|
/* The gap around a collapsed fold's placeholder, so it reads as `{…}` and not as three separate things. */
|
|
@@ -32,6 +47,7 @@ export interface RowPaint {
|
|
|
32
47
|
whitespace: boolean;
|
|
33
48
|
tokensOf(line: number): readonly LineToken[] | null;
|
|
34
49
|
viewportWidth: number;
|
|
50
|
+
window: VerticalWindow;
|
|
35
51
|
/* Where a row of the host's own DOM starts and how wide it is: the whole width of the editor, over the gutter, wherever the text is scrolled to. */
|
|
36
52
|
hostLeft: number;
|
|
37
53
|
hostWidth: number;
|
|
@@ -106,43 +122,66 @@ function fillRun(
|
|
|
106
122
|
}
|
|
107
123
|
}
|
|
108
124
|
|
|
109
|
-
/* One line of text as absolutely placed runs and inlays. `firstRowOnly` leaves out what a wrapped line carries onto later visual lines. */
|
|
110
125
|
export function lineElement(
|
|
111
126
|
document: Document,
|
|
112
127
|
geometry: LineGeometry,
|
|
113
128
|
tokens: readonly LineToken[] | null,
|
|
114
129
|
lineHeight: number,
|
|
115
|
-
|
|
130
|
+
band: SubRowBand,
|
|
116
131
|
whitespace = false
|
|
117
132
|
): HTMLElement {
|
|
118
133
|
const element = document.createElement('div');
|
|
119
134
|
element.className = 'se-line';
|
|
120
135
|
element.style.width = `${geometry.width}px`;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
136
|
+
let first = 0;
|
|
137
|
+
let last = geometry.runs.length;
|
|
138
|
+
while (first < last) {
|
|
139
|
+
const middle = (first + last) >>> 1;
|
|
140
|
+
if (geometry.runs[middle]!.subRow < band.first) {
|
|
141
|
+
first = middle + 1;
|
|
142
|
+
} else {
|
|
143
|
+
last = middle;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
for (let index = first; index < geometry.runs.length; index++) {
|
|
147
|
+
const run = geometry.runs[index]!;
|
|
148
|
+
if (run.subRow > band.last) {
|
|
149
|
+
break;
|
|
124
150
|
}
|
|
125
151
|
const span = document.createElement('span');
|
|
126
152
|
span.className = 'se-run';
|
|
127
153
|
span.style.left = `${run.x}px`;
|
|
128
154
|
span.style.top = `${run.subRow * lineHeight}px`;
|
|
129
|
-
fillRun(span, geometry, run.from, run.to, tokens, whitespace);
|
|
155
|
+
fillRun(span, geometry, geometry.start + run.from, geometry.start + run.to, tokens, whitespace);
|
|
130
156
|
element.append(span);
|
|
131
157
|
}
|
|
132
158
|
if (whitespace) {
|
|
133
|
-
appendTabSigns(element, geometry, lineHeight,
|
|
159
|
+
appendTabSigns(element, geometry, lineHeight, band);
|
|
134
160
|
}
|
|
161
|
+
appendBidiSigns(element, geometry, lineHeight, band);
|
|
135
162
|
for (const box of geometry.inlays) {
|
|
136
|
-
if (
|
|
163
|
+
if (box.subRow < band.first || box.subRow > band.last) {
|
|
137
164
|
continue;
|
|
138
165
|
}
|
|
139
166
|
const span = document.createElement('span');
|
|
140
167
|
span.className = box.inlay.ghost ? 'se-ghost' : box.inlay.composition ? 'se-composition' : 'se-inlay';
|
|
141
168
|
span.dataset.inlayId = box.inlay.id;
|
|
142
|
-
|
|
143
|
-
|
|
169
|
+
const { parts } = box.inlay;
|
|
170
|
+
if (parts === undefined) {
|
|
171
|
+
span.textContent = visibleBidi(box.inlay.text);
|
|
172
|
+
} else {
|
|
173
|
+
parts.forEach((part, index) => {
|
|
174
|
+
const element = document.createElement('span');
|
|
175
|
+
element.className = 'se-inlay-part';
|
|
176
|
+
element.dataset.part = String(index);
|
|
177
|
+
element.textContent = visibleBidi(part);
|
|
178
|
+
span.append(element);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
const gapLeft = box.inlay.gapLeft ?? 0;
|
|
182
|
+
span.style.left = `${box.x + gapLeft}px`;
|
|
144
183
|
span.style.top = `${box.subRow * lineHeight}px`;
|
|
145
|
-
span.style.width = `${box.width}px`;
|
|
184
|
+
span.style.width = `${box.width - gapLeft - (box.inlay.gapRight ?? 0)}px`;
|
|
146
185
|
element.append(span);
|
|
147
186
|
}
|
|
148
187
|
return element;
|
|
@@ -177,7 +216,9 @@ export class RowPainter {
|
|
|
177
216
|
}
|
|
178
217
|
let measured = false;
|
|
179
218
|
for (const row of rows) {
|
|
180
|
-
if (row.kind === 'text') {
|
|
219
|
+
if (row.kind === 'text' && row.replacement !== undefined) {
|
|
220
|
+
measured = this.paintReplacement(row, row.replacement, paint) || measured;
|
|
221
|
+
} else if (row.kind === 'text') {
|
|
181
222
|
this.paintText(row, paint);
|
|
182
223
|
} else {
|
|
183
224
|
measured = this.paintBlock(row, paint) || measured;
|
|
@@ -189,6 +230,11 @@ export class RowPainter {
|
|
|
189
230
|
private paintText(row: TextRow, paint: RowPaint): void {
|
|
190
231
|
const geometry = this.layout.geometry(row);
|
|
191
232
|
const tokens = paint.tokensOf(row.line);
|
|
233
|
+
// Block boundaries keep a scroll within the band from rebuilding its text DOM.
|
|
234
|
+
const band =
|
|
235
|
+
geometry.subRows <= 64
|
|
236
|
+
? { first: 0, last: geometry.subRows - 1 }
|
|
237
|
+
: subRowBand(row.top, geometry.subRows, this.layout.metrics.lineHeight, paint.window, 64);
|
|
192
238
|
const folded = row.lastLine > row.line;
|
|
193
239
|
const lastLine = folded && row.tail !== null ? this.layout.document.getLine(row.lastLine) : null;
|
|
194
240
|
const tail = lastLine === null ? '' : lastLine.text.slice(row.tail! - lastLine.start);
|
|
@@ -202,9 +248,11 @@ export class RowPainter {
|
|
|
202
248
|
entry.whitespace !== paint.whitespace ||
|
|
203
249
|
entry.folded !== folded ||
|
|
204
250
|
entry.tail !== tail ||
|
|
205
|
-
entry.tailTokens !== tailTokens
|
|
251
|
+
entry.tailTokens !== tailTokens ||
|
|
252
|
+
entry.band?.first !== band.first ||
|
|
253
|
+
entry.band?.last !== band.last
|
|
206
254
|
) {
|
|
207
|
-
const element = this.buildText(row, geometry, tokens, paint, tail, tailTokens);
|
|
255
|
+
const element = this.buildText(row, geometry, tokens, paint, tail, tailTokens, band);
|
|
208
256
|
if (entry) {
|
|
209
257
|
entry.element.replaceWith(element);
|
|
210
258
|
} else {
|
|
@@ -219,7 +267,8 @@ export class RowPainter {
|
|
|
219
267
|
folded,
|
|
220
268
|
tail,
|
|
221
269
|
tailTokens,
|
|
222
|
-
revision: 0
|
|
270
|
+
revision: 0,
|
|
271
|
+
band
|
|
223
272
|
};
|
|
224
273
|
this.rows.set(row.key, entry);
|
|
225
274
|
}
|
|
@@ -234,12 +283,13 @@ export class RowPainter {
|
|
|
234
283
|
tokens: readonly LineToken[] | null,
|
|
235
284
|
paint: RowPaint,
|
|
236
285
|
tail: string,
|
|
237
|
-
tailTokens: readonly LineToken[] | null
|
|
286
|
+
tailTokens: readonly LineToken[] | null,
|
|
287
|
+
band: SubRowBand
|
|
238
288
|
): HTMLElement {
|
|
239
289
|
const document = this.code.ownerDocument;
|
|
240
290
|
const lineHeight = this.layout.metrics.lineHeight;
|
|
241
|
-
const element = lineElement(document, geometry, tokens, lineHeight,
|
|
242
|
-
if (row.lastLine > row.line) {
|
|
291
|
+
const element = lineElement(document, geometry, tokens, lineHeight, band, paint.whitespace);
|
|
292
|
+
if (row.lastLine > row.line && band.last === geometry.subRows - 1) {
|
|
243
293
|
const chip = document.createElement('button');
|
|
244
294
|
chip.type = 'button';
|
|
245
295
|
chip.className = 'se-fold-chip';
|
|
@@ -270,9 +320,42 @@ export class RowPainter {
|
|
|
270
320
|
span.className = 'se-run';
|
|
271
321
|
span.style.left = `${left + run.x}px`;
|
|
272
322
|
span.style.top = `${(header.subRows - 1) * this.layout.metrics.lineHeight}px`;
|
|
273
|
-
fillRun(span, geometry, run.from, run.to, tokens, false, lineStart);
|
|
323
|
+
fillRun(span, geometry, geometry.start + run.from, geometry.start + run.to, tokens, false, lineStart);
|
|
274
324
|
element.append(span);
|
|
275
325
|
}
|
|
326
|
+
appendBidiSigns(element, geometry, this.layout.metrics.lineHeight, { first: 0, last: 0 }, left, (header.subRows - 1) * this.layout.metrics.lineHeight);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/* The lines the host draws itself: an element of its own, indented as the first line is and as tall as its content. */
|
|
330
|
+
private paintReplacement(row: TextRow, replacement: BlockReplacement, paint: RowPaint): boolean {
|
|
331
|
+
const revision = replacement.revision ?? 0;
|
|
332
|
+
let entry = this.rows.get(row.key);
|
|
333
|
+
if (!entry) {
|
|
334
|
+
const element = this.code.ownerDocument.createElement('div');
|
|
335
|
+
element.className = 'se-block se-replacement';
|
|
336
|
+
element.dataset.rowKey = row.key;
|
|
337
|
+
element.dataset.replacementId = replacement.id;
|
|
338
|
+
element.setAttribute('role', 'group');
|
|
339
|
+
if (replacement.label !== undefined) {
|
|
340
|
+
element.setAttribute('aria-label', replacement.label);
|
|
341
|
+
}
|
|
342
|
+
replacement.render(element);
|
|
343
|
+
this.code.append(element);
|
|
344
|
+
entry = { element, text: '', tokens: null, layoutVersion: 0, whitespace: false, folded: false, tail: '', tailTokens: null, revision };
|
|
345
|
+
this.rows.set(row.key, entry);
|
|
346
|
+
} else if (entry.revision !== revision) {
|
|
347
|
+
entry.element.replaceChildren();
|
|
348
|
+
replacement.render(entry.element);
|
|
349
|
+
entry.revision = revision;
|
|
350
|
+
}
|
|
351
|
+
const indent = this.layout.indentX(row.line);
|
|
352
|
+
entry.element.style.top = `${row.top}px`;
|
|
353
|
+
entry.element.style.left = `${indent}px`;
|
|
354
|
+
entry.element.style.width = `${Math.max(80, paint.viewportWidth - indent)}px`;
|
|
355
|
+
entry.element.style.height = '';
|
|
356
|
+
this.observer?.observe(entry.element);
|
|
357
|
+
const height = entry.element.getBoundingClientRect?.().height ?? 0;
|
|
358
|
+
return height > 0 && this.layout.setMeasuredHeight(row.key, height);
|
|
276
359
|
}
|
|
277
360
|
|
|
278
361
|
private paintBlock(row: Extract<LayoutRow, { kind: 'block' }>, paint: RowPaint): boolean {
|
|
@@ -286,7 +369,7 @@ export class RowPainter {
|
|
|
286
369
|
element.dataset.widgetId = widget.id;
|
|
287
370
|
this.fillBlock(element, widget);
|
|
288
371
|
this.code.append(element);
|
|
289
|
-
if (!widget.lens) {
|
|
372
|
+
if (!widget.lens && !widget.fixed) {
|
|
290
373
|
this.observer?.observe(element);
|
|
291
374
|
}
|
|
292
375
|
entry = { element, text: '', tokens: null, layoutVersion: 0, whitespace: false, folded: false, tail: '', tailTokens: null, revision };
|
|
@@ -308,6 +391,13 @@ export class RowPainter {
|
|
|
308
391
|
} else {
|
|
309
392
|
entry.element.style.width = `${Math.max(80, paint.viewportWidth)}px`;
|
|
310
393
|
}
|
|
394
|
+
if (widget.fixed) {
|
|
395
|
+
this.observer?.unobserve(entry.element);
|
|
396
|
+
entry.element.style.height = `${row.height}px`;
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
entry.element.style.height = '';
|
|
400
|
+
this.observer?.observe(entry.element);
|
|
311
401
|
const height = entry.element.getBoundingClientRect?.().height ?? 0;
|
|
312
402
|
return height > 0 && this.layout.setMeasuredHeight(row.key, height);
|
|
313
403
|
}
|
|
@@ -330,6 +420,7 @@ export class RowPainter {
|
|
|
330
420
|
}
|
|
331
421
|
|
|
332
422
|
export interface GutterPaint {
|
|
423
|
+
lineNumber(line: number): string | null;
|
|
333
424
|
activeLines: ReadonlySet<number>;
|
|
334
425
|
/* How the lines the host marked differ from what it compares against. */
|
|
335
426
|
changes: ReadonlyMap<number, EditorChangeKind>;
|
|
@@ -339,10 +430,14 @@ export interface GutterPaint {
|
|
|
339
430
|
highlights: ReadonlyMap<number, AttributedLines>;
|
|
340
431
|
/* Lines a fold can start at, and whether each is collapsed. */
|
|
341
432
|
foldable: ReadonlyMap<number, boolean>;
|
|
433
|
+
/* What the fold control of a line is called, from its line and whether it is collapsed. */
|
|
434
|
+
foldName(line: number, collapsed: boolean): string;
|
|
435
|
+
/* A sentence read with the line, which then stands in for the drawn number and sign. */
|
|
436
|
+
lineLabel(line: number): string | null;
|
|
342
437
|
/* The line that carries the host's button, and what it is called. */
|
|
343
438
|
action: { line: number; label: string } | null;
|
|
344
439
|
/* The host's markers by line. */
|
|
345
|
-
markers: ReadonlyMap<number, { id: string; label: string }>;
|
|
440
|
+
markers: ReadonlyMap<number, { id: string; label: string; icon?: EditorGutterIcon; pressed?: boolean }>;
|
|
346
441
|
}
|
|
347
442
|
|
|
348
443
|
function gutterButton(document: Document, className: string, label: string): HTMLButtonElement {
|
|
@@ -366,13 +461,33 @@ export function paintGutter(container: HTMLElement, layout: EditorLayout, rows:
|
|
|
366
461
|
item.className = paint.activeLines.has(row.line) ? 'se-line-number se-active-number' : 'se-line-number';
|
|
367
462
|
item.style.top = `${row.top}px`;
|
|
368
463
|
item.style.height = `${layout.metrics.lineHeight}px`;
|
|
369
|
-
const
|
|
464
|
+
const highlight = paint.highlights.get(row.line);
|
|
465
|
+
const fill = highlight?.fill;
|
|
370
466
|
if (fill !== undefined) {
|
|
371
467
|
item.style.background = colorValue(fill);
|
|
372
468
|
}
|
|
469
|
+
if (highlight?.bar !== undefined) {
|
|
470
|
+
item.classList.add('se-line-marked');
|
|
471
|
+
item.style.height = `${row.height}px`;
|
|
472
|
+
item.style.setProperty('--se-line', `${layout.metrics.lineHeight}px`);
|
|
473
|
+
item.style.setProperty('--se-mark', colorValue(highlight.color));
|
|
474
|
+
if (fill === undefined) {
|
|
475
|
+
item.style.setProperty('--se-tint', tintValue(highlight.color));
|
|
476
|
+
}
|
|
477
|
+
}
|
|
373
478
|
const number = document.createElement('span');
|
|
374
|
-
number.textContent =
|
|
479
|
+
number.textContent = paint.lineNumber(row.line);
|
|
480
|
+
const label = paint.lineLabel(row.line);
|
|
481
|
+
if (label !== null) {
|
|
482
|
+
number.setAttribute('aria-hidden', 'true');
|
|
483
|
+
}
|
|
375
484
|
item.append(number);
|
|
485
|
+
if (label !== null) {
|
|
486
|
+
const hidden = document.createElement('span');
|
|
487
|
+
hidden.className = 'se-sr-only';
|
|
488
|
+
hidden.textContent = label;
|
|
489
|
+
item.append(hidden);
|
|
490
|
+
}
|
|
376
491
|
if (paint.action?.line === row.line) {
|
|
377
492
|
const button = gutterButton(document, 'se-gutter-action', paint.action.label);
|
|
378
493
|
button.dataset.gutterAction = String(row.line);
|
|
@@ -382,6 +497,12 @@ export function paintGutter(container: HTMLElement, layout: EditorLayout, rows:
|
|
|
382
497
|
if (marker !== undefined) {
|
|
383
498
|
const button = gutterButton(document, 'se-gutter-marker', marker.label);
|
|
384
499
|
button.dataset.gutterMarker = marker.id;
|
|
500
|
+
if (marker.icon !== undefined) {
|
|
501
|
+
button.dataset.icon = marker.icon;
|
|
502
|
+
}
|
|
503
|
+
if (marker.pressed !== undefined) {
|
|
504
|
+
button.setAttribute('aria-pressed', String(marker.pressed));
|
|
505
|
+
}
|
|
385
506
|
item.append(button);
|
|
386
507
|
}
|
|
387
508
|
const sign = paint.highlights.get(row.line)?.sign;
|
|
@@ -389,9 +510,18 @@ export function paintGutter(container: HTMLElement, layout: EditorLayout, rows:
|
|
|
389
510
|
const mark = document.createElement('span');
|
|
390
511
|
mark.className = 'se-line-sign';
|
|
391
512
|
mark.textContent = sign;
|
|
513
|
+
if (label !== null) {
|
|
514
|
+
mark.setAttribute('aria-hidden', 'true');
|
|
515
|
+
}
|
|
392
516
|
mark.style.color = colorValue(paint.highlights.get(row.line)!.color);
|
|
393
517
|
item.append(mark);
|
|
394
518
|
}
|
|
519
|
+
if (highlight?.bar !== undefined) {
|
|
520
|
+
const mark = document.createElement('span');
|
|
521
|
+
mark.className = `se-line-bar se-line-bar-${highlight.bar}`;
|
|
522
|
+
mark.setAttribute('aria-hidden', 'true');
|
|
523
|
+
item.append(mark);
|
|
524
|
+
}
|
|
395
525
|
const bar = paint.attribution.get(row.line);
|
|
396
526
|
if (bar) {
|
|
397
527
|
const mark = document.createElement('span');
|
|
@@ -403,7 +533,7 @@ export function paintGutter(container: HTMLElement, layout: EditorLayout, rows:
|
|
|
403
533
|
}
|
|
404
534
|
const change = paint.changes.get(row.line);
|
|
405
535
|
// The bar of an agent stands where the mark of a change would, and a removal is a triangle between lines that the bar never hides.
|
|
406
|
-
if (change && (!bar || change === 'deleted')) {
|
|
536
|
+
if (change && (!bar || change === 'deleted') && highlight?.bar === undefined) {
|
|
407
537
|
const mark = document.createElement('span');
|
|
408
538
|
mark.className = `se-change se-change-${change}`;
|
|
409
539
|
if (change !== 'deleted') {
|
|
@@ -413,7 +543,8 @@ export function paintGutter(container: HTMLElement, layout: EditorLayout, rows:
|
|
|
413
543
|
}
|
|
414
544
|
const collapsed = paint.foldable.get(row.line);
|
|
415
545
|
if (collapsed !== undefined) {
|
|
416
|
-
const button = gutterButton(document, collapsed ? 'se-fold-toggle se-folded' : 'se-fold-toggle', collapsed
|
|
546
|
+
const button = gutterButton(document, collapsed ? 'se-fold-toggle se-folded' : 'se-fold-toggle', paint.foldName(row.line, collapsed));
|
|
547
|
+
button.setAttribute('aria-expanded', String(!collapsed));
|
|
417
548
|
button.dataset.foldLine = String(row.line);
|
|
418
549
|
item.append(button);
|
|
419
550
|
}
|
|
@@ -515,6 +646,7 @@ export function paintRemoteCarets(container: HTMLElement, carets: readonly Remot
|
|
|
515
646
|
|
|
516
647
|
export interface StickyEntry {
|
|
517
648
|
line: number;
|
|
649
|
+
label: string | null;
|
|
518
650
|
geometry: LineGeometry;
|
|
519
651
|
tokens: readonly LineToken[] | null;
|
|
520
652
|
}
|
|
@@ -530,10 +662,10 @@ export function paintSticky(container: HTMLElement, entries: readonly StickyEntr
|
|
|
530
662
|
row.dataset.depth = String(depth);
|
|
531
663
|
const number = document.createElement('span');
|
|
532
664
|
number.className = 'se-sticky-number';
|
|
533
|
-
number.textContent =
|
|
665
|
+
number.textContent = entry.label;
|
|
534
666
|
const code = document.createElement('div');
|
|
535
667
|
code.className = 'se-sticky-code';
|
|
536
|
-
code.append(lineElement(document, entry.geometry, entry.tokens, lineHeight,
|
|
668
|
+
code.append(lineElement(document, entry.geometry, entry.tokens, lineHeight, { first: 0, last: 0 }));
|
|
537
669
|
row.append(number, code);
|
|
538
670
|
return row;
|
|
539
671
|
})
|
|
@@ -567,20 +699,54 @@ export function arrowSign(document: Document, width: number, lineHeight: number,
|
|
|
567
699
|
}
|
|
568
700
|
|
|
569
701
|
/* An arrow over each tab of a line. */
|
|
570
|
-
function appendTabSigns(element: HTMLElement, geometry: LineGeometry, lineHeight: number,
|
|
702
|
+
function appendTabSigns(element: HTMLElement, geometry: LineGeometry, lineHeight: number, band: SubRowBand): void {
|
|
703
|
+
if (geometry.uniform !== undefined) {
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
571
706
|
const document = element.ownerDocument;
|
|
572
|
-
|
|
573
|
-
|
|
707
|
+
const end = geometry.rowStarts[band.last + 1] ?? stopCount(geometry) - 1;
|
|
708
|
+
for (let stop = geometry.rowStarts[band.first]!; stop < end; stop++) {
|
|
709
|
+
if (geometry.text[offsetOfStop(geometry, stop)] !== '\t') {
|
|
574
710
|
continue;
|
|
575
711
|
}
|
|
576
|
-
const left = geometry
|
|
577
|
-
const right = geometry
|
|
712
|
+
const left = xOfStop(geometry, stop);
|
|
713
|
+
const right = rowOfStop(geometry, stop + 1) === rowOfStop(geometry, stop) ? xOfStop(geometry, stop + 1) : geometry.rowEnds[rowOfStop(geometry, stop)]!;
|
|
578
714
|
if (right - left < 3) {
|
|
579
715
|
continue;
|
|
580
716
|
}
|
|
581
717
|
const sign = arrowSign(document, right - left - 2, lineHeight, 'se-tab-sign');
|
|
582
718
|
sign.style.left = `${left + 1}px`;
|
|
583
|
-
sign.style.top = `${geometry
|
|
719
|
+
sign.style.top = `${rowOfStop(geometry, stop) * lineHeight}px`;
|
|
720
|
+
element.append(sign);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/*
|
|
725
|
+
* A boxed label over each bidirectional control character, which no run carries, so the control never reaches the text nodes
|
|
726
|
+
* where it would reorder the line. `top` and `originX` place a line that is drawn as part of another one.
|
|
727
|
+
*/
|
|
728
|
+
function appendBidiSigns(element: HTMLElement, geometry: LineGeometry, lineHeight: number, band: SubRowBand, originX = 0, top?: number): void {
|
|
729
|
+
if (geometry.uniform !== undefined) {
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
const document = element.ownerDocument;
|
|
733
|
+
const from = offsetOfStop(geometry, geometry.rowStarts[band.first]!);
|
|
734
|
+
const to = geometry.rowStarts[band.last + 1] === undefined ? geometry.text.length : offsetOfStop(geometry, geometry.rowStarts[band.last + 1]!);
|
|
735
|
+
const pattern = new RegExp(BIDI_PATTERN.source, 'g');
|
|
736
|
+
pattern.lastIndex = from;
|
|
737
|
+
for (let match = pattern.exec(geometry.text); match !== null && match.index < to; match = pattern.exec(geometry.text)) {
|
|
738
|
+
const stop = stopAtOffset(geometry, match.index);
|
|
739
|
+
const row = rowOfStop(geometry, stop);
|
|
740
|
+
const control = BIDI_CONTROLS[match[0] as keyof typeof BIDI_CONTROLS];
|
|
741
|
+
const left = xOfStop(geometry, stop);
|
|
742
|
+
const right = rowOfStop(geometry, stop + 1) === row ? xOfStop(geometry, stop + 1) : geometry.rowEnds[row]!;
|
|
743
|
+
const sign = document.createElement('span');
|
|
744
|
+
sign.className = 'se-bidi';
|
|
745
|
+
sign.textContent = control.label;
|
|
746
|
+
sign.style.left = `${originX + left}px`;
|
|
747
|
+
sign.style.top = `${top ?? row * lineHeight}px`;
|
|
748
|
+
sign.style.width = `${right - left}px`;
|
|
749
|
+
sign.style.height = `${lineHeight}px`;
|
|
584
750
|
element.append(sign);
|
|
585
751
|
}
|
|
586
752
|
}
|