@adecore/editor 0.21.0 → 0.21.2
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/code-block.js +26 -22
- package/dist/controller.d.ts +1 -0
- package/dist/controller.js +29 -43
- package/dist/engine.js +34 -42
- package/dist/fake.js +44 -97
- package/dist/keymap.d.ts +1 -2
- package/dist/keymap.js +18 -15
- package/dist/layout.d.ts +1 -1
- package/dist/layout.js +7 -6
- package/dist/listeners.d.ts +2 -2
- package/dist/listeners.js +2 -2
- package/dist/modifier-gesture.js +2 -2
- package/dist/native-input.d.ts +1 -0
- package/dist/native-input.js +5 -3
- package/dist/outline.js +2 -2
- package/dist/overview.js +0 -1
- package/dist/paint.js +11 -15
- package/dist/pointer.js +9 -2
- package/dist/testing.js +8 -5
- package/dist/tokens.d.ts +1 -0
- package/dist/tokens.js +9 -10
- package/dist/view.d.ts +1 -0
- package/dist/view.js +63 -92
- package/package.json +2 -2
- package/src/code-block.ts +27 -22
- package/src/controller.ts +32 -43
- package/src/engine.ts +37 -46
- package/src/fake.ts +44 -97
- package/src/keymap.ts +20 -17
- package/src/layout.ts +7 -6
- package/src/listeners.ts +3 -3
- package/src/modifier-gesture.ts +2 -2
- package/src/native-input.ts +6 -3
- package/src/outline.ts +2 -2
- package/src/overview.ts +0 -1
- package/src/paint.ts +12 -15
- package/src/pointer.ts +10 -2
- package/src/testing.ts +9 -5
- package/src/tokens.ts +10 -10
- package/src/types.ts +14 -8
- package/src/view.ts +73 -99
package/dist/fake.js
CHANGED
|
@@ -6,7 +6,7 @@ function positionIn(text, offset) {
|
|
|
6
6
|
const lines = before.split('\n');
|
|
7
7
|
return { line: lines.length - 1, character: lines.at(-1).length };
|
|
8
8
|
}
|
|
9
|
-
/* An editor without a DOM, for tests: `type`, `save` and `blur` do what a person would, the rest says what the
|
|
9
|
+
/* An editor without a DOM, for tests: `type`, `save` and `blur` do what a person would, the rest says what the host asked of it. */
|
|
10
10
|
export class FakeEditor {
|
|
11
11
|
element;
|
|
12
12
|
language;
|
|
@@ -20,10 +20,10 @@ export class FakeEditor {
|
|
|
20
20
|
revealedLine;
|
|
21
21
|
focused = false;
|
|
22
22
|
indentation;
|
|
23
|
-
/* How often the
|
|
23
|
+
/* How often the host asked it to read the code face off the page again. */
|
|
24
24
|
fontRefreshes = 0;
|
|
25
25
|
disposed = false;
|
|
26
|
-
/* What the
|
|
26
|
+
/* What the host asked to find last; null once it ended the find. */
|
|
27
27
|
findQuery = null;
|
|
28
28
|
findState = { count: 0, current: null };
|
|
29
29
|
text;
|
|
@@ -93,9 +93,7 @@ export class FakeEditor {
|
|
|
93
93
|
source,
|
|
94
94
|
changes: [{ range: { start: positionIn(before, span.start), end: positionIn(before, span.end) }, text: span.text }]
|
|
95
95
|
};
|
|
96
|
-
|
|
97
|
-
listener(change);
|
|
98
|
-
}
|
|
96
|
+
emit(this.textChanges, change);
|
|
99
97
|
}
|
|
100
98
|
tracked = new Set();
|
|
101
99
|
/* The fake sees one change per edit, the stretch that differs, so a range it tracks is lost a little sooner than the real editor's. */
|
|
@@ -123,10 +121,7 @@ export class FakeEditor {
|
|
|
123
121
|
};
|
|
124
122
|
}
|
|
125
123
|
onTextChange(listener) {
|
|
126
|
-
this.textChanges
|
|
127
|
-
return () => {
|
|
128
|
-
this.textChanges.delete(listener);
|
|
129
|
-
};
|
|
124
|
+
return subscribe(this.textChanges, listener);
|
|
130
125
|
}
|
|
131
126
|
positionAt(offset) {
|
|
132
127
|
return positionIn(this.text, offset);
|
|
@@ -152,13 +147,13 @@ export class FakeEditor {
|
|
|
152
147
|
onBlur(listener) {
|
|
153
148
|
return subscribe(this.blurs, listener);
|
|
154
149
|
}
|
|
155
|
-
/* How the
|
|
150
|
+
/* How the host last asked the view to follow a jump. */
|
|
156
151
|
lastReveal = null;
|
|
157
152
|
revealLine(line, reveal = 'center') {
|
|
158
153
|
this.revealedLine = line;
|
|
159
154
|
this.lastReveal = reveal;
|
|
160
155
|
}
|
|
161
|
-
/* Counts plain occurrences only; the matcher itself is the editor's, and a test here is about the
|
|
156
|
+
/* Counts plain occurrences only; the matcher itself is the editor's, and a test here is about the host's side. */
|
|
162
157
|
find(query) {
|
|
163
158
|
this.findQuery = query;
|
|
164
159
|
const needle = query === null || query.text === '' ? '' : query.caseSensitive ? query.text : query.text.toLowerCase();
|
|
@@ -166,12 +161,12 @@ export class FakeEditor {
|
|
|
166
161
|
const count = needle === '' ? 0 : haystack.split(needle).length - 1;
|
|
167
162
|
this.announceFind({ count, current: count === 0 ? null : 0 });
|
|
168
163
|
}
|
|
169
|
-
/* What the
|
|
164
|
+
/* What the host asked to replace, one match at a time and all at once. */
|
|
170
165
|
replacements = [];
|
|
171
166
|
replaceOptions = [];
|
|
172
|
-
/* What the
|
|
167
|
+
/* What the host last asked to show under the match. */
|
|
173
168
|
replacePreview = null;
|
|
174
|
-
/* What the
|
|
169
|
+
/* What the host asked to select all matches of, and how many it was told there were. */
|
|
175
170
|
selectedMatches = 0;
|
|
176
171
|
lastFindFromCursor = null;
|
|
177
172
|
replacementsOfAll = [];
|
|
@@ -204,10 +199,7 @@ export class FakeEditor {
|
|
|
204
199
|
}
|
|
205
200
|
}
|
|
206
201
|
onFind(listener) {
|
|
207
|
-
this.finds
|
|
208
|
-
return () => {
|
|
209
|
-
this.finds.delete(listener);
|
|
210
|
-
};
|
|
202
|
+
return subscribe(this.finds, listener);
|
|
211
203
|
}
|
|
212
204
|
endFind() {
|
|
213
205
|
this.find(null);
|
|
@@ -215,40 +207,37 @@ export class FakeEditor {
|
|
|
215
207
|
setWrap(wrap) {
|
|
216
208
|
this.wrap = wrap;
|
|
217
209
|
}
|
|
218
|
-
/* What the
|
|
210
|
+
/* What the host handed it last; null while it reads the structure itself. */
|
|
219
211
|
blocks = null;
|
|
220
212
|
scopes = new Set();
|
|
221
|
-
/* What the
|
|
213
|
+
/* What the host marked as changed last. */
|
|
222
214
|
changeMarks = [];
|
|
223
215
|
setChangeMarks(marks) {
|
|
224
216
|
this.changeMarks = marks;
|
|
225
217
|
}
|
|
226
|
-
/* The tinted lines the
|
|
218
|
+
/* The tinted lines the host set last. */
|
|
227
219
|
lineHighlights = [];
|
|
228
220
|
setLineHighlights(highlights) {
|
|
229
221
|
this.lineHighlights = highlights;
|
|
230
222
|
}
|
|
231
|
-
/* What the
|
|
223
|
+
/* What the host drew as code outside the document: the text and how, and into which element. */
|
|
232
224
|
codeBlocks = [];
|
|
233
225
|
renderCode(container, text, options = {}) {
|
|
234
226
|
this.codeBlocks.push({ container, text, options });
|
|
235
227
|
}
|
|
236
|
-
/* The cursors of agents the
|
|
228
|
+
/* The cursors of agents the host set last. */
|
|
237
229
|
remoteCursors = [];
|
|
238
230
|
setRemoteCursors(cursors) {
|
|
239
231
|
this.remoteCursors = cursors;
|
|
240
232
|
}
|
|
241
|
-
/* What the
|
|
233
|
+
/* What the host marked as written by an agent last. */
|
|
242
234
|
attributionMarks = [];
|
|
243
235
|
attributionHovers = new Set();
|
|
244
236
|
setAttributionMarks(marks) {
|
|
245
237
|
this.attributionMarks = marks;
|
|
246
238
|
}
|
|
247
239
|
onAttributionHover(listener) {
|
|
248
|
-
this.attributionHovers
|
|
249
|
-
return () => {
|
|
250
|
-
this.attributionHovers.delete(listener);
|
|
251
|
-
};
|
|
240
|
+
return subscribe(this.attributionHovers, listener);
|
|
252
241
|
}
|
|
253
242
|
/* The pointer on the bar of a mark, or leaving it. The rect is where `rectAt` puts the mark's first line unless given. */
|
|
254
243
|
hoverAttribution(id, rect) {
|
|
@@ -256,9 +245,7 @@ export class FakeEditor {
|
|
|
256
245
|
const hover = mark === undefined
|
|
257
246
|
? null
|
|
258
247
|
: { id: mark.id, rect: rect ?? this.rectAt({ line: mark.startLine - 1, character: 0 }) ?? { left: 0, top: 0, right: 0, bottom: 0 } };
|
|
259
|
-
|
|
260
|
-
listener(hover);
|
|
261
|
-
}
|
|
248
|
+
emit(this.attributionHovers, hover);
|
|
262
249
|
}
|
|
263
250
|
/* What each owner put next to lines of the text last. */
|
|
264
251
|
widgetsByOwner = new Map();
|
|
@@ -292,22 +279,19 @@ export class FakeEditor {
|
|
|
292
279
|
this.lineActionsByOwner.set(owner, actions);
|
|
293
280
|
}
|
|
294
281
|
}
|
|
295
|
-
/* The code vision rows the
|
|
282
|
+
/* The code vision rows the host set last. */
|
|
296
283
|
codeVision = [];
|
|
297
284
|
setCodeVision(rows) {
|
|
298
285
|
this.codeVision = rows;
|
|
299
286
|
}
|
|
300
|
-
/* The button the
|
|
287
|
+
/* The button the host set in the gutter last. */
|
|
301
288
|
gutterAction = null;
|
|
302
289
|
gutterActionListeners = new Set();
|
|
303
290
|
setGutterAction(action) {
|
|
304
291
|
this.gutterAction = action;
|
|
305
292
|
}
|
|
306
293
|
onGutterAction(listener) {
|
|
307
|
-
this.gutterActionListeners
|
|
308
|
-
return () => {
|
|
309
|
-
this.gutterActionListeners.delete(listener);
|
|
310
|
-
};
|
|
294
|
+
return subscribe(this.gutterActionListeners, listener);
|
|
311
295
|
}
|
|
312
296
|
/* What each owner put in the gutter last. */
|
|
313
297
|
gutterMarkersByOwner = new Map();
|
|
@@ -321,39 +305,27 @@ export class FakeEditor {
|
|
|
321
305
|
}
|
|
322
306
|
}
|
|
323
307
|
onGutterMarker(listener) {
|
|
324
|
-
this.gutterMarkerListeners
|
|
325
|
-
return () => {
|
|
326
|
-
this.gutterMarkerListeners.delete(listener);
|
|
327
|
-
};
|
|
308
|
+
return subscribe(this.gutterMarkerListeners, listener);
|
|
328
309
|
}
|
|
329
310
|
/* A press on a gutter marker. */
|
|
330
311
|
pressGutterMarker(id) {
|
|
331
|
-
|
|
332
|
-
listener(id);
|
|
333
|
-
}
|
|
312
|
+
emit(this.gutterMarkerListeners, id);
|
|
334
313
|
}
|
|
335
314
|
/* A press on the gutter button, if there is one. */
|
|
336
315
|
pressGutterAction() {
|
|
337
316
|
if (this.gutterAction !== null) {
|
|
338
|
-
|
|
339
|
-
listener(this.gutterAction.line);
|
|
340
|
-
}
|
|
317
|
+
emit(this.gutterActionListeners, this.gutterAction.line);
|
|
341
318
|
}
|
|
342
319
|
}
|
|
343
320
|
setBlocks(blocks) {
|
|
344
321
|
this.blocks = blocks;
|
|
345
322
|
}
|
|
346
323
|
onScope(listener) {
|
|
347
|
-
this.scopes
|
|
348
|
-
return () => {
|
|
349
|
-
this.scopes.delete(listener);
|
|
350
|
-
};
|
|
324
|
+
return subscribe(this.scopes, listener);
|
|
351
325
|
}
|
|
352
|
-
/* The caret moving into blocks, for a test of what the
|
|
326
|
+
/* The caret moving into blocks, for a test of what the host draws from it. */
|
|
353
327
|
enterScope(scope) {
|
|
354
|
-
|
|
355
|
-
listener(scope);
|
|
356
|
-
}
|
|
328
|
+
emit(this.scopes, scope);
|
|
357
329
|
}
|
|
358
330
|
smartKeys;
|
|
359
331
|
guides = true;
|
|
@@ -386,7 +358,7 @@ export class FakeEditor {
|
|
|
386
358
|
setIndentation(indentation) {
|
|
387
359
|
this.indentation = indentation;
|
|
388
360
|
}
|
|
389
|
-
/* What the
|
|
361
|
+
/* What the host marked and highlighted last. */
|
|
390
362
|
markers = [];
|
|
391
363
|
highlights = [];
|
|
392
364
|
caret = { line: 0, character: 0 };
|
|
@@ -400,7 +372,7 @@ export class FakeEditor {
|
|
|
400
372
|
setMarkers(markers) {
|
|
401
373
|
this.markers = markers;
|
|
402
374
|
}
|
|
403
|
-
/* What the
|
|
375
|
+
/* What the host drew as hints last. */
|
|
404
376
|
inlayHints = [];
|
|
405
377
|
setInlayHints(hints) {
|
|
406
378
|
this.inlayHints = hints;
|
|
@@ -410,12 +382,12 @@ export class FakeEditor {
|
|
|
410
382
|
setGhostText(ghost) {
|
|
411
383
|
this.ghost = ghost;
|
|
412
384
|
}
|
|
413
|
-
/* What the
|
|
385
|
+
/* What the host classified last. */
|
|
414
386
|
semanticTokens = null;
|
|
415
387
|
setSemanticTokens(tokens) {
|
|
416
388
|
this.semanticTokens = tokens;
|
|
417
389
|
}
|
|
418
|
-
/* What the
|
|
390
|
+
/* What the host drew as a link last. */
|
|
419
391
|
link = null;
|
|
420
392
|
setLink(range) {
|
|
421
393
|
this.link = range;
|
|
@@ -426,7 +398,7 @@ export class FakeEditor {
|
|
|
426
398
|
getCaret() {
|
|
427
399
|
return this.caret;
|
|
428
400
|
}
|
|
429
|
-
/* The folds the
|
|
401
|
+
/* The folds the host asked to keep. */
|
|
430
402
|
folds = { collapsed: [], custom: [] };
|
|
431
403
|
getFolds() {
|
|
432
404
|
return this.folds;
|
|
@@ -472,36 +444,24 @@ export class FakeEditor {
|
|
|
472
444
|
this.lastReveal = reveal;
|
|
473
445
|
this.caret = range.end;
|
|
474
446
|
this.selection = range;
|
|
475
|
-
|
|
476
|
-
listener(range.end);
|
|
477
|
-
}
|
|
447
|
+
emit(this.carets, range.end);
|
|
478
448
|
}
|
|
479
449
|
/* The caret a person's click or key moved. */
|
|
480
450
|
moveCaret(position) {
|
|
481
451
|
this.caret = position;
|
|
482
452
|
this.selection = null;
|
|
483
|
-
|
|
484
|
-
listener(position);
|
|
485
|
-
}
|
|
453
|
+
emit(this.carets, position);
|
|
486
454
|
}
|
|
487
455
|
onCaret(listener) {
|
|
488
|
-
this.carets
|
|
489
|
-
return () => {
|
|
490
|
-
this.carets.delete(listener);
|
|
491
|
-
};
|
|
456
|
+
return subscribe(this.carets, listener);
|
|
492
457
|
}
|
|
493
458
|
onHover(listener) {
|
|
494
|
-
this.hovers
|
|
495
|
-
return () => {
|
|
496
|
-
this.hovers.delete(listener);
|
|
497
|
-
};
|
|
459
|
+
return subscribe(this.hovers, listener);
|
|
498
460
|
}
|
|
499
461
|
/* The pointer resting on a position, or leaving the text. */
|
|
500
462
|
hover(position) {
|
|
501
463
|
const hover = position === null ? null : { position, rect: this.rectAt(position) };
|
|
502
|
-
|
|
503
|
-
listener(hover);
|
|
504
|
-
}
|
|
464
|
+
emit(this.hovers, hover);
|
|
505
465
|
}
|
|
506
466
|
rectAt(position) {
|
|
507
467
|
const left = this.origin.left + position.character * this.charSize.width;
|
|
@@ -529,24 +489,15 @@ export class FakeEditor {
|
|
|
529
489
|
this.selectionRanges = provider;
|
|
530
490
|
}
|
|
531
491
|
onKeyDown(handler) {
|
|
532
|
-
this.keys
|
|
533
|
-
return () => {
|
|
534
|
-
this.keys.delete(handler);
|
|
535
|
-
};
|
|
492
|
+
return subscribe(this.keys, handler);
|
|
536
493
|
}
|
|
537
494
|
clickHandlers = new Set();
|
|
538
495
|
contextListeners = new Set();
|
|
539
496
|
onClick(handler) {
|
|
540
|
-
this.clickHandlers
|
|
541
|
-
return () => {
|
|
542
|
-
this.clickHandlers.delete(handler);
|
|
543
|
-
};
|
|
497
|
+
return subscribe(this.clickHandlers, handler);
|
|
544
498
|
}
|
|
545
499
|
onContextMenu(listener) {
|
|
546
|
-
this.contextListeners
|
|
547
|
-
return () => {
|
|
548
|
-
this.contextListeners.delete(listener);
|
|
549
|
-
};
|
|
500
|
+
return subscribe(this.contextListeners, listener);
|
|
550
501
|
}
|
|
551
502
|
/* A press on a character as the editor sees it first; true when a handler took it, and the caret stays where it is otherwise only until the test moves it. */
|
|
552
503
|
click(click) {
|
|
@@ -554,9 +505,7 @@ export class FakeEditor {
|
|
|
554
505
|
}
|
|
555
506
|
/* The context menu asked for at a position. */
|
|
556
507
|
openContextMenu(menu) {
|
|
557
|
-
|
|
558
|
-
listener(menu);
|
|
559
|
-
}
|
|
508
|
+
emit(this.contextListeners, menu);
|
|
560
509
|
}
|
|
561
510
|
/* A key as the editor sees it first; true when a handler took it. */
|
|
562
511
|
press(event) {
|
|
@@ -634,11 +583,9 @@ export class FakeEditor {
|
|
|
634
583
|
}
|
|
635
584
|
announceFind(state) {
|
|
636
585
|
this.findState = state;
|
|
637
|
-
|
|
638
|
-
listener(state);
|
|
639
|
-
}
|
|
586
|
+
emit(this.finds, state);
|
|
640
587
|
}
|
|
641
|
-
/* A test that drives an editor its
|
|
588
|
+
/* A test that drives an editor its host already disposed has found a leak. */
|
|
642
589
|
assertLive() {
|
|
643
590
|
if (this.disposed) {
|
|
644
591
|
throw new Error('The editor is disposed');
|
package/dist/keymap.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { EditorCommand } from '@adecore/editor-core';
|
|
2
|
-
import type { EditorViewCommand } from './types.ts';
|
|
3
2
|
import { type Keymap } from './keymap-table.ts';
|
|
4
|
-
import type { KeyChord } from './types.ts';
|
|
3
|
+
import type { EditorViewCommand, KeyChord } from './types.ts';
|
|
5
4
|
export interface KeyLike {
|
|
6
5
|
key: string;
|
|
7
6
|
code?: string;
|
package/dist/keymap.js
CHANGED
|
@@ -3,6 +3,9 @@ const MOVES = new Set(['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'PageU
|
|
|
3
3
|
function command(name) {
|
|
4
4
|
return { type: 'command', command: name };
|
|
5
5
|
}
|
|
6
|
+
function view(name) {
|
|
7
|
+
return { type: 'view', command: name };
|
|
8
|
+
}
|
|
6
9
|
/* What each key of the table does in the editor. A key of the table that is not here is a language command, which the host answers. */
|
|
7
10
|
const ACTIONS = {
|
|
8
11
|
undo: command('undo'),
|
|
@@ -42,21 +45,21 @@ const ACTIONS = {
|
|
|
42
45
|
selectTextStart: { type: 'edge', end: false, extend: true },
|
|
43
46
|
textEnd: { type: 'edge', end: true, extend: false },
|
|
44
47
|
selectTextEnd: { type: 'edge', end: true, extend: true },
|
|
45
|
-
collapse:
|
|
46
|
-
expand:
|
|
47
|
-
collapseAll:
|
|
48
|
-
expandAll:
|
|
49
|
-
collapseRecursively:
|
|
50
|
-
expandRecursively:
|
|
51
|
-
foldSelection:
|
|
52
|
-
collapseDocComments:
|
|
53
|
-
expandDocComments:
|
|
54
|
-
expandAllToLevel1:
|
|
55
|
-
expandAllToLevel2:
|
|
56
|
-
expandAllToLevel3:
|
|
57
|
-
expandAllToLevel4:
|
|
58
|
-
expandAllToLevel5:
|
|
59
|
-
toggleColumnMode:
|
|
48
|
+
collapse: view('collapseRegion'),
|
|
49
|
+
expand: view('expandRegion'),
|
|
50
|
+
collapseAll: view('collapseAllRegions'),
|
|
51
|
+
expandAll: view('expandAllRegions'),
|
|
52
|
+
collapseRecursively: view('collapseRegionRecursively'),
|
|
53
|
+
expandRecursively: view('expandRegionRecursively'),
|
|
54
|
+
foldSelection: view('foldSelection'),
|
|
55
|
+
collapseDocComments: view('collapseDocComments'),
|
|
56
|
+
expandDocComments: view('expandDocComments'),
|
|
57
|
+
expandAllToLevel1: view('expandAllToLevel1'),
|
|
58
|
+
expandAllToLevel2: view('expandAllToLevel2'),
|
|
59
|
+
expandAllToLevel3: view('expandAllToLevel3'),
|
|
60
|
+
expandAllToLevel4: view('expandAllToLevel4'),
|
|
61
|
+
expandAllToLevel5: view('expandAllToLevel5'),
|
|
62
|
+
toggleColumnMode: view('toggleColumnMode')
|
|
60
63
|
};
|
|
61
64
|
const PUNCTUATION_CODES = {
|
|
62
65
|
',': 'Comma',
|
package/dist/layout.d.ts
CHANGED
|
@@ -133,7 +133,7 @@ export declare class EditorLayout {
|
|
|
133
133
|
configure(config: LayoutConfig): void;
|
|
134
134
|
private rowCountOf;
|
|
135
135
|
private rebuild;
|
|
136
|
-
private
|
|
136
|
+
private clampToLine;
|
|
137
137
|
private reflow;
|
|
138
138
|
setMeasuredHeight(key: string, height: number): boolean;
|
|
139
139
|
syncRows(rows: readonly LayoutRow[]): boolean;
|
package/dist/layout.js
CHANGED
|
@@ -8,6 +8,7 @@ const DEFAULT_BLOCK_HEIGHT = 40;
|
|
|
8
8
|
const WRAP_INDENT_STEP = 2;
|
|
9
9
|
const GEOMETRY_CACHE_SIZE = 400;
|
|
10
10
|
const PLAIN = /^[\x20-\x7e\t]*$/;
|
|
11
|
+
const GRAPHEME_LIMIT = 20000;
|
|
11
12
|
const SEGMENTER = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
|
|
12
13
|
function lowerBound(values, value) {
|
|
13
14
|
let low = 0;
|
|
@@ -23,10 +24,10 @@ function lowerBound(values, value) {
|
|
|
23
24
|
}
|
|
24
25
|
return low;
|
|
25
26
|
}
|
|
26
|
-
/* Where each character starts, with the length last. A
|
|
27
|
+
/* 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. */
|
|
27
28
|
function characterOffsets(text) {
|
|
28
29
|
const offsets = [];
|
|
29
|
-
if (text.length >
|
|
30
|
+
if (text.length > GRAPHEME_LIMIT || PLAIN.test(text)) {
|
|
30
31
|
for (let i = 0; i < text.length; i++) {
|
|
31
32
|
offsets.push(i);
|
|
32
33
|
}
|
|
@@ -277,8 +278,8 @@ export class EditorLayout {
|
|
|
277
278
|
}
|
|
278
279
|
const fold = folds.get(line);
|
|
279
280
|
const last = Math.max(line, Math.min(count - 1, fold?.endLine ?? line));
|
|
280
|
-
const tail = fold?.closer !== undefined && last > line ? this.
|
|
281
|
-
const head = fold?.head !== undefined && last > line ? this.
|
|
281
|
+
const tail = fold?.closer !== undefined && last > line ? this.clampToLine(fold.closer, last) : null;
|
|
282
|
+
const head = fold?.head !== undefined && last > line ? this.clampToLine(fold.head, line) : null;
|
|
282
283
|
const index = this.rows.length;
|
|
283
284
|
const bounds = this.wrapWidth === null ? null : this.document.getLine(line);
|
|
284
285
|
const subRows = this.rowCountOf(line, bounds === null ? 0 : bounds.end - bounds.start);
|
|
@@ -305,9 +306,9 @@ export class EditorLayout {
|
|
|
305
306
|
this.reflow();
|
|
306
307
|
}
|
|
307
308
|
/* An offset of a fold clamped to its line, since the fold may be a moment older than an edit. */
|
|
308
|
-
|
|
309
|
+
clampToLine(offset, line) {
|
|
309
310
|
const bounds = this.document.getLine(line);
|
|
310
|
-
return Math.min(bounds.end, Math.max(bounds.start,
|
|
311
|
+
return Math.min(bounds.end, Math.max(bounds.start, offset));
|
|
311
312
|
}
|
|
312
313
|
reflow() {
|
|
313
314
|
let top = 0;
|
package/dist/listeners.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export type Listener = () => void;
|
|
2
|
-
export declare function subscribe(listeners: Set<
|
|
3
|
-
export declare function emit(listeners: Set<
|
|
2
|
+
export declare function subscribe<T>(listeners: Set<T>, listener: T): () => void;
|
|
3
|
+
export declare function emit<Args extends unknown[]>(listeners: Set<(...args: Args) => void>, ...args: Args): void;
|
package/dist/listeners.js
CHANGED
|
@@ -5,8 +5,8 @@ export function subscribe(listeners, listener) {
|
|
|
5
5
|
};
|
|
6
6
|
}
|
|
7
7
|
/* Over a copy, so a listener that unsubscribes itself does not skip the next one. */
|
|
8
|
-
export function emit(listeners) {
|
|
8
|
+
export function emit(listeners, ...args) {
|
|
9
9
|
for (const listener of [...listeners]) {
|
|
10
|
-
listener();
|
|
10
|
+
listener(...args);
|
|
11
11
|
}
|
|
12
12
|
}
|
package/dist/modifier-gesture.js
CHANGED
|
@@ -27,8 +27,8 @@ export class DoubleModifierGesture {
|
|
|
27
27
|
}
|
|
28
28
|
/* Another modifier held with the one that counts spoils the gesture. */
|
|
29
29
|
spoiled(event) {
|
|
30
|
-
const
|
|
31
|
-
return
|
|
30
|
+
const other = this.modifier === 'Alt' ? event.ctrlKey : event.altKey;
|
|
31
|
+
return other || event.metaKey || event.shiftKey;
|
|
32
32
|
}
|
|
33
33
|
keydown(event) {
|
|
34
34
|
if (event.key === this.modifier) {
|
package/dist/native-input.d.ts
CHANGED
package/dist/native-input.js
CHANGED
|
@@ -90,8 +90,7 @@ export class NativeInput {
|
|
|
90
90
|
return;
|
|
91
91
|
}
|
|
92
92
|
if (this.model.getSelections().length > 1) {
|
|
93
|
-
|
|
94
|
-
this.host.replace(this.element.value.slice(context.start, Math.max(context.start, this.element.value.length - suffix)));
|
|
93
|
+
this.host.replace(this.replacedText(context));
|
|
95
94
|
return;
|
|
96
95
|
}
|
|
97
96
|
const edit = {
|
|
@@ -140,7 +139,10 @@ export class NativeInput {
|
|
|
140
139
|
return textareaText(text).replace(/\n/g, newline);
|
|
141
140
|
}
|
|
142
141
|
composedText() {
|
|
143
|
-
|
|
142
|
+
return this.replacedText(this.compositionContext);
|
|
143
|
+
}
|
|
144
|
+
/* What the textarea now holds where the selection of `context` was, the text after it kept. */
|
|
145
|
+
replacedText(context) {
|
|
144
146
|
const suffix = context.value.length - context.end;
|
|
145
147
|
return this.element.value.slice(context.start, Math.max(context.start, this.element.value.length - suffix));
|
|
146
148
|
}
|
package/dist/outline.js
CHANGED
|
@@ -82,7 +82,7 @@ export function structuralEntries(ranges, document, slice, mate) {
|
|
|
82
82
|
}
|
|
83
83
|
return entries;
|
|
84
84
|
}
|
|
85
|
-
function
|
|
85
|
+
function toBlock(entry, document) {
|
|
86
86
|
const startLine = document.positionAt(entry.from).line;
|
|
87
87
|
return {
|
|
88
88
|
startLine,
|
|
@@ -143,7 +143,7 @@ export class Outline {
|
|
|
143
143
|
blocks(document) {
|
|
144
144
|
if (this.cache?.version !== this.version) {
|
|
145
145
|
const sorted = (this.provided ?? this.structure)
|
|
146
|
-
.map((entry) =>
|
|
146
|
+
.map((entry) => toBlock(entry, document))
|
|
147
147
|
.sort((left, right) => left.startLine - right.startLine || right.endLine - left.endLine);
|
|
148
148
|
this.cache = { version: this.version, blocks: sorted.filter((block, index) => index === 0 || block.startLine !== sorted[index - 1].startLine) };
|
|
149
149
|
}
|
package/dist/overview.js
CHANGED
|
@@ -33,7 +33,6 @@ export function overviewTicks(spans, contentHeight, trackHeight) {
|
|
|
33
33
|
}
|
|
34
34
|
/* What is painted later is on top: the current find match, and the worse a problem is the later it goes. */
|
|
35
35
|
const PAINT_ORDER = { warning: 1, error: 2, 'find-current': 3 };
|
|
36
|
-
/* Draws the ticks. */
|
|
37
36
|
export function paintOverview(container, ticks) {
|
|
38
37
|
const document = container.ownerDocument;
|
|
39
38
|
const order = (tick) => PAINT_ORDER[tick.kind] ?? 0;
|
package/dist/paint.js
CHANGED
|
@@ -266,6 +266,14 @@ export class RowPainter {
|
|
|
266
266
|
this.rows.clear();
|
|
267
267
|
}
|
|
268
268
|
}
|
|
269
|
+
function gutterButton(document, className, label) {
|
|
270
|
+
const button = document.createElement('button');
|
|
271
|
+
button.type = 'button';
|
|
272
|
+
button.className = className;
|
|
273
|
+
button.tabIndex = -1;
|
|
274
|
+
button.setAttribute('aria-label', label);
|
|
275
|
+
return button;
|
|
276
|
+
}
|
|
269
277
|
/* The line numbers of the visible rows, and the fold control of the lines that have one. */
|
|
270
278
|
export function paintGutter(container, layout, rows, paint) {
|
|
271
279
|
const document = container.ownerDocument;
|
|
@@ -286,22 +294,14 @@ export function paintGutter(container, layout, rows, paint) {
|
|
|
286
294
|
number.textContent = String(row.line + 1);
|
|
287
295
|
item.append(number);
|
|
288
296
|
if (paint.action?.line === row.line) {
|
|
289
|
-
const button = document
|
|
290
|
-
button.type = 'button';
|
|
291
|
-
button.className = 'se-gutter-action';
|
|
297
|
+
const button = gutterButton(document, 'se-gutter-action', paint.action.label);
|
|
292
298
|
button.dataset.gutterAction = String(row.line);
|
|
293
|
-
button.tabIndex = -1;
|
|
294
|
-
button.setAttribute('aria-label', paint.action.label);
|
|
295
299
|
item.append(button);
|
|
296
300
|
}
|
|
297
301
|
const marker = paint.markers.get(row.line);
|
|
298
302
|
if (marker !== undefined) {
|
|
299
|
-
const button = document
|
|
300
|
-
button.type = 'button';
|
|
301
|
-
button.className = 'se-gutter-marker';
|
|
303
|
+
const button = gutterButton(document, 'se-gutter-marker', marker.label);
|
|
302
304
|
button.dataset.gutterMarker = marker.id;
|
|
303
|
-
button.tabIndex = -1;
|
|
304
|
-
button.setAttribute('aria-label', marker.label);
|
|
305
305
|
item.append(button);
|
|
306
306
|
}
|
|
307
307
|
const sign = paint.highlights.get(row.line)?.sign;
|
|
@@ -333,12 +333,8 @@ export function paintGutter(container, layout, rows, paint) {
|
|
|
333
333
|
}
|
|
334
334
|
const collapsed = paint.foldable.get(row.line);
|
|
335
335
|
if (collapsed !== undefined) {
|
|
336
|
-
const button = document
|
|
337
|
-
button.type = 'button';
|
|
338
|
-
button.className = collapsed ? 'se-fold-toggle se-folded' : 'se-fold-toggle';
|
|
336
|
+
const button = gutterButton(document, collapsed ? 'se-fold-toggle se-folded' : 'se-fold-toggle', collapsed ? 'Expand' : 'Collapse');
|
|
339
337
|
button.dataset.foldLine = String(row.line);
|
|
340
|
-
button.tabIndex = -1;
|
|
341
|
-
button.setAttribute('aria-label', collapsed ? 'Expand' : 'Collapse');
|
|
342
338
|
item.append(button);
|
|
343
339
|
}
|
|
344
340
|
fragment.append(item);
|
package/dist/pointer.js
CHANGED
|
@@ -4,6 +4,13 @@ const MULTI_CLICK_MS = 500;
|
|
|
4
4
|
const MULTI_CLICK_DISTANCE = 4;
|
|
5
5
|
/* How far the pointer goes, in pixels, before a press on a selection starts carrying it. */
|
|
6
6
|
const CARRY_THRESHOLD = 5;
|
|
7
|
+
/* How far `value` lies outside `[low, high]`, negative below it and zero inside. */
|
|
8
|
+
function overshoot(value, low, high) {
|
|
9
|
+
if (value < low) {
|
|
10
|
+
return value - low;
|
|
11
|
+
}
|
|
12
|
+
return value > high ? value - high : 0;
|
|
13
|
+
}
|
|
7
14
|
/* The selection a mouse makes: a click, a drag, shift to extend, alt to add a caret or, dragged, a column, a double click for a word and a triple for a line, which a drag then grows by words and lines. */
|
|
8
15
|
export class PointerSelection {
|
|
9
16
|
drag;
|
|
@@ -230,8 +237,8 @@ export class PointerSelection {
|
|
|
230
237
|
return;
|
|
231
238
|
}
|
|
232
239
|
const rect = viewport.getBoundingClientRect();
|
|
233
|
-
const dy = this.drag.y
|
|
234
|
-
const dx = this.drag.x
|
|
240
|
+
const dy = overshoot(this.drag.y, rect.top, rect.bottom);
|
|
241
|
+
const dx = overshoot(this.drag.x, rect.left, rect.right);
|
|
235
242
|
if (dy || dx) {
|
|
236
243
|
viewport.scrollTop += Math.max(-SCROLL_STEP, Math.min(SCROLL_STEP, dy));
|
|
237
244
|
viewport.scrollLeft += Math.max(-SCROLL_STEP, Math.min(SCROLL_STEP, dx));
|