@adecore/editor 0.21.1 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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/src/controller.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Keymap } from './keymap-table.ts';
|
|
2
2
|
import type { CommandOptions, EditorCommand, Selection, TextEdit } from '@adecore/editor-core';
|
|
3
3
|
import { replacementEdits } from './input.ts';
|
|
4
|
+
import { emit, subscribe } from './listeners.ts';
|
|
4
5
|
import { chordMatches, type KeyAction, keyAction, type MoveKey } from './keymap.ts';
|
|
5
6
|
import { DoubleModifierGesture } from './modifier-gesture.ts';
|
|
6
7
|
import { NativeInput } from './native-input.ts';
|
|
@@ -28,8 +29,6 @@ export interface ControllerOptions {
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
const MODIFYING = new Set<EditorCommand>([
|
|
31
|
-
'undo',
|
|
32
|
-
'redo',
|
|
33
32
|
'deleteWordLeft',
|
|
34
33
|
'deleteWordRight',
|
|
35
34
|
'deleteCamelLeft',
|
|
@@ -75,6 +74,10 @@ const VIEW_COMMANDS = new Set<EditorViewCommand>([
|
|
|
75
74
|
/* The clipboard text of the last copy or cut of a bare caret, which pastes back as whole lines. Shared, since it can be pasted in another editor. */
|
|
76
75
|
let lineClip: string | null = null;
|
|
77
76
|
|
|
77
|
+
function selectionSignature(selections: readonly Selection[]): string {
|
|
78
|
+
return selections.map((selection) => `${selection.anchor}:${selection.head}`).join(',');
|
|
79
|
+
}
|
|
80
|
+
|
|
78
81
|
/* Typing in the same stretch is one undo step until a pause this long. */
|
|
79
82
|
const TYPING_PAUSE_MS = 750;
|
|
80
83
|
|
|
@@ -375,7 +378,7 @@ export class InputController {
|
|
|
375
378
|
|
|
376
379
|
private modelCommand(name: EditorCommand): boolean {
|
|
377
380
|
const { model } = this.view;
|
|
378
|
-
const modifies = MODIFYING.has(name)
|
|
381
|
+
const modifies = MODIFYING.has(name);
|
|
379
382
|
if ((modifies || name === 'undo' || name === 'redo') && this.blockedByReadOnly()) {
|
|
380
383
|
return false;
|
|
381
384
|
}
|
|
@@ -449,7 +452,7 @@ export class InputController {
|
|
|
449
452
|
/* Column mode on drops every caret but the first and keeps a selection as a box; off it makes one selection of the box. */
|
|
450
453
|
private toggleColumnMode(): void {
|
|
451
454
|
const { view } = this;
|
|
452
|
-
const { model
|
|
455
|
+
const { model } = view;
|
|
453
456
|
this.block = null;
|
|
454
457
|
if (view.columnMode) {
|
|
455
458
|
const first = model.getSelections()[0]!;
|
|
@@ -462,9 +465,7 @@ export class InputController {
|
|
|
462
465
|
const primary = model.getPrimary();
|
|
463
466
|
model.setSelections([primary]);
|
|
464
467
|
if (primary.anchor !== primary.head) {
|
|
465
|
-
|
|
466
|
-
const head = layout.caret(primary.head);
|
|
467
|
-
this.block = { anchor: { x: anchor.x, y: anchor.y }, head: { x: head.x, y: head.y }, signature: '' };
|
|
468
|
+
this.block = this.blockOf(primary, '');
|
|
468
469
|
this.extendBlock({ x: 0, y: 0 });
|
|
469
470
|
}
|
|
470
471
|
}
|
|
@@ -472,13 +473,9 @@ export class InputController {
|
|
|
472
473
|
/* Moves the corner of the box by a step and selects the columns between it and the other corner. */
|
|
473
474
|
private extendBlock(step: { x: number; y: number }): void {
|
|
474
475
|
const { model, layout } = this.view;
|
|
475
|
-
const
|
|
476
|
-
const signature = selections.map((selection) => `${selection.anchor}:${selection.head}`).join(',');
|
|
476
|
+
const signature = selectionSignature(model.getSelections());
|
|
477
477
|
if (this.block === null || (this.block.signature !== '' && this.block.signature !== signature)) {
|
|
478
|
-
|
|
479
|
-
const anchor = layout.caret(primary.anchor);
|
|
480
|
-
const head = layout.caret(primary.head);
|
|
481
|
-
this.block = { anchor: { x: anchor.x, y: anchor.y }, head: { x: head.x, y: head.y }, signature };
|
|
478
|
+
this.block = this.blockOf(model.getPrimary(), signature);
|
|
482
479
|
}
|
|
483
480
|
const block = this.block;
|
|
484
481
|
block.head = {
|
|
@@ -486,10 +483,15 @@ export class InputController {
|
|
|
486
483
|
y: Math.max(0, Math.min(layout.height - layout.metrics.lineHeight, block.head.y + step.y))
|
|
487
484
|
};
|
|
488
485
|
model.setSelections(columnSelections(layout, block.anchor, block.head));
|
|
489
|
-
block.signature = model
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
486
|
+
block.signature = selectionSignature(model.getSelections());
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/* A box between the two ends of a selection. */
|
|
490
|
+
private blockOf(selection: Selection, signature: string): NonNullable<InputController['block']> {
|
|
491
|
+
const { layout } = this.view;
|
|
492
|
+
const anchor = layout.caret(selection.anchor);
|
|
493
|
+
const head = layout.caret(selection.head);
|
|
494
|
+
return { anchor: { x: anchor.x, y: anchor.y }, head: { x: head.x, y: head.y }, signature };
|
|
493
495
|
}
|
|
494
496
|
|
|
495
497
|
private moveCarets(key: MoveKey, extend: boolean): void {
|
|
@@ -509,19 +511,17 @@ export class InputController {
|
|
|
509
511
|
const { model, layout } = this.view;
|
|
510
512
|
const vertical = key === 'ArrowUp' || key === 'ArrowDown';
|
|
511
513
|
const direction = key === 'ArrowLeft' || key === 'ArrowUp' ? -1 : 1;
|
|
514
|
+
const edge = (selection: Selection): number =>
|
|
515
|
+
direction < 0 ? Math.min(selection.anchor, selection.head) : Math.max(selection.anchor, selection.head);
|
|
512
516
|
const selections = model.getSelections().map((selection, index) => {
|
|
517
|
+
const collapses = !extend && selection.anchor !== selection.head;
|
|
513
518
|
let head: number;
|
|
514
|
-
if (!vertical &&
|
|
515
|
-
head =
|
|
519
|
+
if (!vertical && collapses) {
|
|
520
|
+
head = edge(selection);
|
|
516
521
|
} else if (vertical) {
|
|
517
522
|
// A selection first gives way to the edge in the direction of the move, as the platform's Up and Down do.
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
? direction < 0
|
|
521
|
-
? Math.min(selection.anchor, selection.head)
|
|
522
|
-
: Math.max(selection.anchor, selection.head)
|
|
523
|
-
: selection.head;
|
|
524
|
-
if (hasSelection) {
|
|
523
|
+
const from = collapses ? edge(selection) : selection.head;
|
|
524
|
+
if (collapses) {
|
|
525
525
|
delete this.desiredXs[index];
|
|
526
526
|
}
|
|
527
527
|
this.desiredXs[index] ??= layout.caret(from).x;
|
|
@@ -634,24 +634,15 @@ export class InputController {
|
|
|
634
634
|
|
|
635
635
|
/* A handler sees the key before the editor does, the page's own shortcuts included, since a list of suggestions owns the arrows while it is open. */
|
|
636
636
|
onKeyDown(handler: EditorKeyHandler): () => void {
|
|
637
|
-
this.keyHandlers
|
|
638
|
-
return () => {
|
|
639
|
-
this.keyHandlers.delete(handler);
|
|
640
|
-
};
|
|
637
|
+
return subscribe(this.keyHandlers, handler);
|
|
641
638
|
}
|
|
642
639
|
|
|
643
640
|
onClick(handler: EditorClickHandler): () => void {
|
|
644
|
-
this.clickHandlers
|
|
645
|
-
return () => {
|
|
646
|
-
this.clickHandlers.delete(handler);
|
|
647
|
-
};
|
|
641
|
+
return subscribe(this.clickHandlers, handler);
|
|
648
642
|
}
|
|
649
643
|
|
|
650
644
|
onContextMenu(listener: (menu: EditorContextMenu) => void): () => void {
|
|
651
|
-
this.contextListeners
|
|
652
|
-
return () => {
|
|
653
|
-
this.contextListeners.delete(listener);
|
|
654
|
-
};
|
|
645
|
+
return subscribe(this.contextListeners, listener);
|
|
655
646
|
}
|
|
656
647
|
|
|
657
648
|
private keydown(event: KeyboardEvent): void {
|
|
@@ -728,9 +719,9 @@ export class InputController {
|
|
|
728
719
|
? selections
|
|
729
720
|
.map((selection) => {
|
|
730
721
|
const { first, last } = this.view.lineSpan(model.positionAt(selection.head).line);
|
|
731
|
-
|
|
722
|
+
const end = model.getLine(last);
|
|
723
|
+
return model.slice(model.getLine(first).start, end.next) + (end.next === end.end ? '\n' : '');
|
|
732
724
|
})
|
|
733
|
-
.map((lines) => model.slice(lines.start, lines.next) + (lines.next === lines.end ? '\n' : ''))
|
|
734
725
|
.join('')
|
|
735
726
|
: selections.map((selection) => model.slice(Math.min(selection.anchor, selection.head), Math.max(selection.anchor, selection.head))).join('\n');
|
|
736
727
|
event.clipboardData?.setData('text/plain', text);
|
|
@@ -794,9 +785,7 @@ export class InputController {
|
|
|
794
785
|
.getSelections()
|
|
795
786
|
.some((candidate) => Math.min(candidate.anchor, candidate.head) <= offset && offset <= Math.max(candidate.anchor, candidate.head));
|
|
796
787
|
const menu: EditorContextMenu = { position: this.positionOf(offset), inSelection: selection, x: event.clientX, y: event.clientY };
|
|
797
|
-
|
|
798
|
-
listener(menu);
|
|
799
|
-
}
|
|
788
|
+
emit(this.contextListeners, menu);
|
|
800
789
|
}
|
|
801
790
|
|
|
802
791
|
/* A press on a pinned header goes to that header. */
|
package/src/engine.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { changedSpans, DocumentModel, type EditorSnapshot, replacementText } from '@adecore/editor-core';
|
|
1
|
+
import { changedSpans, DocumentModel, type EditorSnapshot, type FoldHints, replacementText } from '@adecore/editor-core';
|
|
2
2
|
import { InputController } from './controller.ts';
|
|
3
3
|
import { resolveSmartKeys } from './smart-keys.ts';
|
|
4
4
|
import { emit, type Listener, subscribe } from './listeners.ts';
|
|
@@ -191,10 +191,7 @@ class SmartEditor implements Editor {
|
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
onTextChange(listener: (change: EditorTextChange) => void): () => void {
|
|
194
|
-
this.textChanges
|
|
195
|
-
return () => {
|
|
196
|
-
this.textChanges.delete(listener);
|
|
197
|
-
};
|
|
194
|
+
return subscribe(this.textChanges, listener);
|
|
198
195
|
}
|
|
199
196
|
|
|
200
197
|
private announceTextChange(edits: NonNullable<EditorSnapshot['contentEdits']>, source: EditorTextChange['source']): void {
|
|
@@ -205,9 +202,7 @@ class SmartEditor implements Editor {
|
|
|
205
202
|
text: edit.text
|
|
206
203
|
}))
|
|
207
204
|
};
|
|
208
|
-
|
|
209
|
-
listener(change);
|
|
210
|
-
}
|
|
205
|
+
emit(this.textChanges, change);
|
|
211
206
|
}
|
|
212
207
|
|
|
213
208
|
private caretMoved(head: number): void {
|
|
@@ -216,9 +211,7 @@ class SmartEditor implements Editor {
|
|
|
216
211
|
}
|
|
217
212
|
this.caretOffset = head;
|
|
218
213
|
const position = this.positionAt(head);
|
|
219
|
-
|
|
220
|
-
listener(position);
|
|
221
|
-
}
|
|
214
|
+
emit(this.carets, position);
|
|
222
215
|
}
|
|
223
216
|
|
|
224
217
|
getCaret(): EditorPosition {
|
|
@@ -230,15 +223,16 @@ class SmartEditor implements Editor {
|
|
|
230
223
|
}
|
|
231
224
|
|
|
232
225
|
getSelection(): EditorRange {
|
|
233
|
-
|
|
234
|
-
return { start: this.positionAt(Math.min(anchor, head)), end: this.positionAt(Math.max(anchor, head)) };
|
|
226
|
+
return this.rangeOf(this.model.getPrimary());
|
|
235
227
|
}
|
|
236
228
|
|
|
237
229
|
getSelections(): EditorRange[] {
|
|
238
|
-
return this.model.getSelections().map((
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
230
|
+
return this.model.getSelections().map((selection) => this.rangeOf(selection));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/* A selection as a range, start before end. */
|
|
234
|
+
private rangeOf({ anchor, head }: { anchor: number; head: number }): EditorRange {
|
|
235
|
+
return { start: this.positionAt(Math.min(anchor, head)), end: this.positionAt(Math.max(anchor, head)) };
|
|
242
236
|
}
|
|
243
237
|
|
|
244
238
|
getIndentation(): EditorIndentation {
|
|
@@ -267,10 +261,7 @@ class SmartEditor implements Editor {
|
|
|
267
261
|
}
|
|
268
262
|
|
|
269
263
|
onCaret(listener: (position: EditorPosition) => void): () => void {
|
|
270
|
-
this.carets
|
|
271
|
-
return () => {
|
|
272
|
-
this.carets.delete(listener);
|
|
273
|
-
};
|
|
264
|
+
return subscribe(this.carets, listener);
|
|
274
265
|
}
|
|
275
266
|
|
|
276
267
|
onHover(listener: (hover: EditorHover | null) => void): () => void {
|
|
@@ -625,7 +616,7 @@ class SmartEditor implements Editor {
|
|
|
625
616
|
return this.view.onGutterAction(listener);
|
|
626
617
|
}
|
|
627
618
|
|
|
628
|
-
setGutterMarkers(markers: readonly EditorGutterMarker[], owner: string =
|
|
619
|
+
setGutterMarkers(markers: readonly EditorGutterMarker[], owner: string = DEFAULT_WIDGET_OWNER): void {
|
|
629
620
|
this.view.setGutterMarkers(owner, markers);
|
|
630
621
|
}
|
|
631
622
|
|
|
@@ -667,30 +658,30 @@ class SmartEditor implements Editor {
|
|
|
667
658
|
}
|
|
668
659
|
|
|
669
660
|
setFoldHints(hints: EditorFoldHints | null): void {
|
|
670
|
-
this.view.setFoldHints(
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
661
|
+
this.view.setFoldHints(hints === null ? null : this.foldHintsByOffset(hints));
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
private foldHintsByOffset(hints: EditorFoldHints): FoldHints {
|
|
665
|
+
const lineOf = (line: number): { start: number; end: number } => this.model.getLine(Math.min(line, this.model.getLineCount() - 1));
|
|
666
|
+
const within = (line: { start: number; end: number }, character: number): number => Math.min(line.end, line.start + Math.max(0, character));
|
|
667
|
+
return {
|
|
668
|
+
symbols: (hints.symbols ?? []).map((symbol) => ({
|
|
669
|
+
from: this.offsetAt(symbol.range.start),
|
|
670
|
+
to: this.offsetAt(symbol.range.end),
|
|
671
|
+
body: symbol.body
|
|
672
|
+
})),
|
|
673
|
+
ranges: (hints.ranges ?? []).map((range) => {
|
|
674
|
+
const first = lineOf(range.startLine);
|
|
675
|
+
const last = lineOf(range.endLine);
|
|
676
|
+
return {
|
|
677
|
+
from: first.start,
|
|
678
|
+
to: last.start,
|
|
679
|
+
...(range.kind === undefined ? {} : { kind: range.kind }),
|
|
680
|
+
...(range.startCharacter === undefined ? {} : { head: within(first, range.startCharacter) }),
|
|
681
|
+
...(range.endCharacter === undefined ? {} : { tail: within(last, range.endCharacter) })
|
|
682
|
+
};
|
|
683
|
+
})
|
|
684
|
+
};
|
|
694
685
|
}
|
|
695
686
|
|
|
696
687
|
setSmartKeys(keys: Partial<EditorSmartKeys>): void {
|