@adecore/editor 0.21.1 → 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/src/testing.ts
CHANGED
|
@@ -43,6 +43,10 @@ export function createPage(): { document: Document; window: Window & typeof glob
|
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
function createEvent(window: Window, type: string): Event {
|
|
47
|
+
return new (window as unknown as { Event: typeof Event }).Event(type, { bubbles: true, cancelable: true });
|
|
48
|
+
}
|
|
49
|
+
|
|
46
50
|
export interface KeyOptions {
|
|
47
51
|
ctrlKey?: boolean;
|
|
48
52
|
metaKey?: boolean;
|
|
@@ -52,7 +56,7 @@ export interface KeyOptions {
|
|
|
52
56
|
|
|
53
57
|
/* Fires a keydown at the element and says whether the editor took the key. */
|
|
54
58
|
export function press(window: Window, target: Element, key: string, options: KeyOptions = {}): boolean {
|
|
55
|
-
const event =
|
|
59
|
+
const event = createEvent(window, 'keydown');
|
|
56
60
|
Object.assign(event, { key, ctrlKey: false, metaKey: false, altKey: false, shiftKey: false, isComposing: false, ...options });
|
|
57
61
|
target.dispatchEvent(event);
|
|
58
62
|
return event.defaultPrevented;
|
|
@@ -60,14 +64,14 @@ export function press(window: Window, target: Element, key: string, options: Key
|
|
|
60
64
|
|
|
61
65
|
/* Fires a keyup at the element, the other half of `press`. */
|
|
62
66
|
export function release(window: Window, target: Element, key: string, options: KeyOptions = {}): void {
|
|
63
|
-
const event =
|
|
67
|
+
const event = createEvent(window, 'keyup');
|
|
64
68
|
Object.assign(event, { key, ctrlKey: false, metaKey: false, altKey: false, shiftKey: false, isComposing: false, ...options });
|
|
65
69
|
target.dispatchEvent(event);
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
/* What a browser sends before it puts text in a textarea. */
|
|
69
73
|
export function typeInto(window: Window, target: Element, text: string): void {
|
|
70
|
-
const event =
|
|
74
|
+
const event = createEvent(window, 'beforeinput');
|
|
71
75
|
Object.assign(event, { inputType: 'insertText', data: text, isComposing: false });
|
|
72
76
|
target.dispatchEvent(event);
|
|
73
77
|
}
|
|
@@ -79,7 +83,7 @@ export function clipboard(
|
|
|
79
83
|
type: 'copy' | 'cut' | 'paste',
|
|
80
84
|
data: { text: string } = { text: '' }
|
|
81
85
|
): { prevented: boolean; text: string } {
|
|
82
|
-
const event =
|
|
86
|
+
const event = createEvent(window, type);
|
|
83
87
|
Object.assign(event, {
|
|
84
88
|
clipboardData: {
|
|
85
89
|
getData: () => data.text,
|
|
@@ -101,7 +105,7 @@ export function pointer(
|
|
|
101
105
|
y: number,
|
|
102
106
|
options: KeyOptions = {}
|
|
103
107
|
): void {
|
|
104
|
-
const event =
|
|
108
|
+
const event = createEvent(window, type);
|
|
105
109
|
Object.assign(event, {
|
|
106
110
|
clientX: x,
|
|
107
111
|
clientY: y,
|
package/src/tokens.ts
CHANGED
|
@@ -49,8 +49,7 @@ export class TokenCache {
|
|
|
49
49
|
this.lines = this.source.getLineCount();
|
|
50
50
|
this.entries = [];
|
|
51
51
|
this.frontier = 0;
|
|
52
|
-
this.
|
|
53
|
-
this.pivotLine = -1;
|
|
52
|
+
this.forgetPivot();
|
|
54
53
|
}
|
|
55
54
|
|
|
56
55
|
/* How far down the document has colors that are right for the text. */
|
|
@@ -92,8 +91,7 @@ export class TokenCache {
|
|
|
92
91
|
if (first === undefined) {
|
|
93
92
|
this.entries = [];
|
|
94
93
|
this.frontier = 0;
|
|
95
|
-
this.
|
|
96
|
-
this.pivotLine = -1;
|
|
94
|
+
this.forgetPivot();
|
|
97
95
|
return;
|
|
98
96
|
}
|
|
99
97
|
const firstLine = lineOf(first.from);
|
|
@@ -108,8 +106,7 @@ export class TokenCache {
|
|
|
108
106
|
this.frontier = Math.min(this.frontier, firstLine);
|
|
109
107
|
if (last === undefined || lastOld < firstLine || this.entries.length <= firstLine) {
|
|
110
108
|
this.entries.length = Math.min(this.entries.length, firstLine);
|
|
111
|
-
this.
|
|
112
|
-
this.pivotLine = -1;
|
|
109
|
+
this.forgetPivot();
|
|
113
110
|
return;
|
|
114
111
|
}
|
|
115
112
|
const old = this.entries[lastOld];
|
|
@@ -131,8 +128,7 @@ export class TokenCache {
|
|
|
131
128
|
}
|
|
132
129
|
if (tokenizer.stale?.() === true) {
|
|
133
130
|
this.frontier = 0;
|
|
134
|
-
this.
|
|
135
|
-
this.pivotLine = -1;
|
|
131
|
+
this.forgetPivot();
|
|
136
132
|
}
|
|
137
133
|
const count = this.source.getLineCount();
|
|
138
134
|
const limit = Math.min(until, count - 1);
|
|
@@ -157,8 +153,7 @@ export class TokenCache {
|
|
|
157
153
|
(previous !== undefined && line >= this.reusableFrom && tokenizer.sameState(previous.state, next.state));
|
|
158
154
|
if (settled) {
|
|
159
155
|
this.frontier = this.entries.length;
|
|
160
|
-
this.
|
|
161
|
-
this.pivotLine = -1;
|
|
156
|
+
this.forgetPivot();
|
|
162
157
|
break;
|
|
163
158
|
}
|
|
164
159
|
if (now() > deadline) {
|
|
@@ -168,6 +163,11 @@ export class TokenCache {
|
|
|
168
163
|
return this.frontier > from ? { from, to: this.frontier - 1 } : null;
|
|
169
164
|
}
|
|
170
165
|
|
|
166
|
+
private forgetPivot(): void {
|
|
167
|
+
this.reusableFrom = Number.POSITIVE_INFINITY;
|
|
168
|
+
this.pivotLine = -1;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
171
|
/* Whether lines through `line` are colored for the text as it is now. */
|
|
172
172
|
covers(line: number): boolean {
|
|
173
173
|
return this.frontier > line || this.frontier >= this.source.getLineCount();
|
package/src/types.ts
CHANGED
|
@@ -45,7 +45,7 @@ export type EditorTheme = string;
|
|
|
45
45
|
|
|
46
46
|
export interface EditorOptions {
|
|
47
47
|
readonly text: string;
|
|
48
|
-
/*
|
|
48
|
+
/* A Shiki language id. Without one, or with one Shiki does not know, it is plain text. */
|
|
49
49
|
readonly language?: string;
|
|
50
50
|
/* The file's path, absolute or only a name. A language service reads the dialect off its extension, a `.tsx` from a `.ts`. */
|
|
51
51
|
readonly path?: string;
|
|
@@ -459,8 +459,10 @@ export interface EditorTrackedRange {
|
|
|
459
459
|
|
|
460
460
|
export interface Editor {
|
|
461
461
|
getText(): string;
|
|
462
|
-
/*
|
|
463
|
-
|
|
462
|
+
/*
|
|
463
|
+
* A change from outside, such as a reload after the file changed on disk. It is never reported as a
|
|
464
|
+
* change, and the cursor and the scroll stay put wherever the text around them did.
|
|
465
|
+
*/
|
|
464
466
|
setText(text: string): void;
|
|
465
467
|
onChange(listener: () => void): () => void;
|
|
466
468
|
/* Every change of the text, `setText` and undo included, with what changed in the positions a language server expects. */
|
|
@@ -470,14 +472,16 @@ export interface Editor {
|
|
|
470
472
|
offsetAt(position: EditorPosition): number;
|
|
471
473
|
/* The text of a range, such as the word before the caret. */
|
|
472
474
|
textInRange(range: EditorRange): string;
|
|
473
|
-
/* Mod+S from inside the editor; what happens then is the
|
|
475
|
+
/* Mod+S from inside the editor; what happens then is the host's. */
|
|
474
476
|
onSave(listener: () => void): () => void;
|
|
475
477
|
/* The focus left the editor and every widget of its own, such as its suggestions. */
|
|
476
478
|
onBlur(listener: () => void): () => void;
|
|
477
479
|
/* Puts the cursor at the start of a one-based line and scrolls it into view, a third from the top when it was out of view. */
|
|
478
480
|
revealLine(line: number, reveal?: EditorReveal): void;
|
|
479
|
-
/*
|
|
480
|
-
|
|
481
|
+
/*
|
|
482
|
+
* Marks every match and moves to the first one from the cursor on; null takes the marks away. The
|
|
483
|
+
* count comes back through `onFind`, and again whenever an edit changes it.
|
|
484
|
+
*/
|
|
481
485
|
find(query: EditorFindQuery | null): void;
|
|
482
486
|
findStep(direction: 1 | -1): void;
|
|
483
487
|
/* Replaces the match the find is on, with `$1` and the like expanded for a regular expression, and moves to the next one. False when there is none or the editor is read only. */
|
|
@@ -507,8 +511,10 @@ export interface Editor {
|
|
|
507
511
|
setFoldHints(hints: EditorFoldHints | null): void;
|
|
508
512
|
/* Marks that follow their lines through edits until the host sets them again. */
|
|
509
513
|
setChangeMarks(marks: readonly EditorChangeMark[]): void;
|
|
510
|
-
/*
|
|
511
|
-
|
|
514
|
+
/*
|
|
515
|
+
* The blocks sticky scroll and the breadcrumb go by. The editor reads them from brackets and
|
|
516
|
+
* indentation until the host has better, such as a language server's symbols; null goes back.
|
|
517
|
+
*/
|
|
512
518
|
setBlocks(blocks: readonly EditorBlock[] | null): void;
|
|
513
519
|
/* The named blocks around the caret, outermost first, said again only when they change. */
|
|
514
520
|
onScope(listener: (scope: readonly EditorBlock[]) => void): () => void;
|
package/src/view.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type FoldHints,
|
|
6
6
|
type FoldRole,
|
|
7
7
|
type FoldingRange,
|
|
8
|
+
indentationColumn,
|
|
8
9
|
replacementText,
|
|
9
10
|
scanBrackets,
|
|
10
11
|
type BracketIndex,
|
|
@@ -15,6 +16,7 @@ import { type BlockWidget, EditorLayout, type FoldState, type Inlay, type Layout
|
|
|
15
16
|
import { createMetrics, type EditorFont, readEditorFont } from './metrics.ts';
|
|
16
17
|
import { type AttributedLines, AttributionRuns, colorValue, tintValue } from './attribution.ts';
|
|
17
18
|
import { renderCodeBlock } from './code-block.ts';
|
|
19
|
+
import { emit, subscribe } from './listeners.ts';
|
|
18
20
|
import { mapOffset } from './offsets.ts';
|
|
19
21
|
import { mapTrackedRange } from './tracked-range.ts';
|
|
20
22
|
import { type ScrollKind, scrollPosition } from './scroll.ts';
|
|
@@ -35,7 +37,6 @@ import {
|
|
|
35
37
|
type WrapSign
|
|
36
38
|
} from './paint.ts';
|
|
37
39
|
import { overlayTokens, type SemanticSpan } from './semantic.ts';
|
|
38
|
-
import { indentationColumn } from '@adecore/editor-core';
|
|
39
40
|
import { TokenCache } from './tokens.ts';
|
|
40
41
|
import type {
|
|
41
42
|
EditorBlock,
|
|
@@ -58,6 +59,22 @@ function sameRect(left: EditorRect, right: EditorRect): boolean {
|
|
|
58
59
|
return left.left === right.left && left.top === right.top && left.right === right.right && left.bottom === right.bottom;
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
function mapRange<Range extends { from: number; to: number }>(range: Range, changes: readonly DocumentChange[]): Range {
|
|
63
|
+
return { ...range, from: mapOffset(range.from, changes), to: mapOffset(range.to, changes) };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function mapFoldHints(hints: FoldHints, changes: readonly DocumentChange[]): FoldHints {
|
|
67
|
+
return {
|
|
68
|
+
symbols: hints.symbols?.map((hint) => mapRange(hint, changes)) ?? [],
|
|
69
|
+
ranges:
|
|
70
|
+
hints.ranges?.map((hint) => ({
|
|
71
|
+
...mapRange(hint, changes),
|
|
72
|
+
...(hint.head === undefined ? {} : { head: mapOffset(hint.head, changes) }),
|
|
73
|
+
...(hint.tail === undefined ? {} : { tail: mapOffset(hint.tail, changes) })
|
|
74
|
+
})) ?? []
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
61
78
|
export interface ViewSettings {
|
|
62
79
|
language: string | undefined;
|
|
63
80
|
tabSize: number;
|
|
@@ -556,45 +573,7 @@ export class EditorView {
|
|
|
556
573
|
this.layoutGhostParts();
|
|
557
574
|
}
|
|
558
575
|
for (const changes of batches) {
|
|
559
|
-
this.
|
|
560
|
-
for (const [owner, blocks] of this.blocks) {
|
|
561
|
-
this.blocks.set(
|
|
562
|
-
owner,
|
|
563
|
-
blocks.map((block) => ({ ...block, at: mapOffset(block.at, changes) }))
|
|
564
|
-
);
|
|
565
|
-
}
|
|
566
|
-
this.lenses = this.lenses.map((lens) => ({ ...lens, at: mapOffset(lens.at, changes) }));
|
|
567
|
-
this.collapsed = new Set([...this.collapsed].map((anchor) => mapOffset(anchor, changes)));
|
|
568
|
-
this.settled = new Set([...this.settled].map((anchor) => mapOffset(anchor, changes)));
|
|
569
|
-
if (this.foldHints !== null) {
|
|
570
|
-
this.foldHints = {
|
|
571
|
-
symbols:
|
|
572
|
-
this.foldHints.symbols?.map((hint) => ({ ...hint, from: mapOffset(hint.from, changes), to: mapOffset(hint.to, changes) })) ?? [],
|
|
573
|
-
ranges:
|
|
574
|
-
this.foldHints.ranges?.map((hint) => ({
|
|
575
|
-
...hint,
|
|
576
|
-
from: mapOffset(hint.from, changes),
|
|
577
|
-
to: mapOffset(hint.to, changes),
|
|
578
|
-
...(hint.head === undefined ? {} : { head: mapOffset(hint.head, changes) }),
|
|
579
|
-
...(hint.tail === undefined ? {} : { tail: mapOffset(hint.tail, changes) })
|
|
580
|
-
})) ?? []
|
|
581
|
-
};
|
|
582
|
-
}
|
|
583
|
-
this.customFolds = this.customFolds.map((fold) => ({ from: mapOffset(fold.from, changes), to: mapOffset(fold.to, changes) }));
|
|
584
|
-
this.find.mapBounds(changes);
|
|
585
|
-
this.changeMarks = this.changeMarks.map((mark) => ({ ...mark, from: mapOffset(mark.from, changes), to: mapOffset(mark.to, changes) }));
|
|
586
|
-
this.markers = this.markers.map((marker) => ({ ...marker, from: mapOffset(marker.from, changes), to: mapOffset(marker.to, changes) }));
|
|
587
|
-
this.mapTracked(changes);
|
|
588
|
-
this.attribution.map(changes);
|
|
589
|
-
this.highlights.map(changes);
|
|
590
|
-
this.remoteCarets = this.remoteCarets.map((caret) => ({ ...caret, at: mapOffset(caret.at, changes) }));
|
|
591
|
-
for (const [owner, entries] of this.lineActions) {
|
|
592
|
-
this.lineActions.set(
|
|
593
|
-
owner,
|
|
594
|
-
entries.map((entry) => ({ ...entry, at: mapOffset(entry.at, changes) }))
|
|
595
|
-
);
|
|
596
|
-
}
|
|
597
|
-
this.semantic = this.semantic.map((token) => ({ ...token, from: mapOffset(token.from, changes), to: mapOffset(token.to, changes) }));
|
|
576
|
+
this.followEdit(changes);
|
|
598
577
|
}
|
|
599
578
|
this.foldRanges = this.mappedFolds(batches);
|
|
600
579
|
this.semanticVersion++;
|
|
@@ -625,6 +604,38 @@ export class EditorView {
|
|
|
625
604
|
this.requestRender();
|
|
626
605
|
}
|
|
627
606
|
|
|
607
|
+
/* Moves everything anchored to an offset along with one batch of changes. */
|
|
608
|
+
private followEdit(changes: readonly DocumentChange[]): void {
|
|
609
|
+
this.inlays = this.inlays.map((inlay) => ({ ...inlay, at: mapOffset(inlay.at, changes) }));
|
|
610
|
+
for (const [owner, blocks] of this.blocks) {
|
|
611
|
+
this.blocks.set(
|
|
612
|
+
owner,
|
|
613
|
+
blocks.map((block) => ({ ...block, at: mapOffset(block.at, changes) }))
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
this.lenses = this.lenses.map((lens) => ({ ...lens, at: mapOffset(lens.at, changes) }));
|
|
617
|
+
this.collapsed = new Set([...this.collapsed].map((anchor) => mapOffset(anchor, changes)));
|
|
618
|
+
this.settled = new Set([...this.settled].map((anchor) => mapOffset(anchor, changes)));
|
|
619
|
+
if (this.foldHints !== null) {
|
|
620
|
+
this.foldHints = mapFoldHints(this.foldHints, changes);
|
|
621
|
+
}
|
|
622
|
+
this.customFolds = this.customFolds.map((fold) => mapRange(fold, changes));
|
|
623
|
+
this.find.mapBounds(changes);
|
|
624
|
+
this.changeMarks = this.changeMarks.map((mark) => mapRange(mark, changes));
|
|
625
|
+
this.markers = this.markers.map((marker) => mapRange(marker, changes));
|
|
626
|
+
this.mapTracked(changes);
|
|
627
|
+
this.attribution.map(changes);
|
|
628
|
+
this.highlights.map(changes);
|
|
629
|
+
this.remoteCarets = this.remoteCarets.map((caret) => ({ ...caret, at: mapOffset(caret.at, changes) }));
|
|
630
|
+
for (const [owner, entries] of this.lineActions) {
|
|
631
|
+
this.lineActions.set(
|
|
632
|
+
owner,
|
|
633
|
+
entries.map((entry) => ({ ...entry, at: mapOffset(entry.at, changes) }))
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
this.semantic = this.semantic.map((token) => mapRange(token, changes));
|
|
637
|
+
}
|
|
638
|
+
|
|
628
639
|
private mapTracked(changes: readonly DocumentChange[]): void {
|
|
629
640
|
for (const entry of this.tracked) {
|
|
630
641
|
const mapped = mapTrackedRange(entry.from, entry.to, changes);
|
|
@@ -773,9 +784,7 @@ export class EditorView {
|
|
|
773
784
|
);
|
|
774
785
|
const anchors = new Set(this.foldRanges.map((range) => range.from));
|
|
775
786
|
this.collapsed = new Set([...this.collapsed].filter((anchor) => anchors.has(anchor)));
|
|
776
|
-
this.
|
|
777
|
-
this.moveHiddenCarets();
|
|
778
|
-
this.render();
|
|
787
|
+
this.refoldLayout();
|
|
779
788
|
}
|
|
780
789
|
|
|
781
790
|
/* The folds a person made of a selection, which the document's own structure does not know, among the ones it does. */
|
|
@@ -1149,9 +1158,11 @@ export class EditorView {
|
|
|
1149
1158
|
if (!rect) {
|
|
1150
1159
|
return { x: clientX - this.gutterWidth + this.viewport.scrollLeft, y: clientY + this.viewport.scrollTop };
|
|
1151
1160
|
}
|
|
1152
|
-
const
|
|
1153
|
-
|
|
1154
|
-
|
|
1161
|
+
const scale = this.screenScale(rect);
|
|
1162
|
+
return {
|
|
1163
|
+
x: (clientX - rect.left) / scale.x - this.gutterWidth + this.viewport.scrollLeft,
|
|
1164
|
+
y: (clientY - rect.top) / scale.y + this.viewport.scrollTop
|
|
1165
|
+
};
|
|
1155
1166
|
}
|
|
1156
1167
|
|
|
1157
1168
|
offsetAtPoint(clientX: number, clientY: number): number {
|
|
@@ -1250,13 +1261,9 @@ export class EditorView {
|
|
|
1250
1261
|
}
|
|
1251
1262
|
|
|
1252
1263
|
onHover(listener: (offset: number | null) => void): () => void {
|
|
1253
|
-
this.hoverListeners
|
|
1254
|
-
return () => {
|
|
1255
|
-
this.hoverListeners.delete(listener);
|
|
1256
|
-
};
|
|
1264
|
+
return subscribe(this.hoverListeners, listener);
|
|
1257
1265
|
}
|
|
1258
1266
|
|
|
1259
|
-
/* The pointer moved over the editor; a drag in progress is not a hover. */
|
|
1260
1267
|
/*
|
|
1261
1268
|
* The other places the selected text is, found without a language server: only for one selection that
|
|
1262
1269
|
* holds no line break and is not blank, and not at all when there are more than a few of them.
|
|
@@ -1480,6 +1487,7 @@ export class EditorView {
|
|
|
1480
1487
|
this.render();
|
|
1481
1488
|
}
|
|
1482
1489
|
|
|
1490
|
+
/* The pointer moved over the editor; a drag in progress is not a hover. */
|
|
1483
1491
|
hoverMoved(event: PointerEvent): void {
|
|
1484
1492
|
this.content.classList.toggle(
|
|
1485
1493
|
'se-over-selection',
|
|
@@ -1493,16 +1501,11 @@ export class EditorView {
|
|
|
1493
1501
|
return;
|
|
1494
1502
|
}
|
|
1495
1503
|
this.hoverOffset = offset;
|
|
1496
|
-
|
|
1497
|
-
listener(offset);
|
|
1498
|
-
}
|
|
1504
|
+
emit(this.hoverListeners, offset);
|
|
1499
1505
|
}
|
|
1500
1506
|
|
|
1501
1507
|
onViewChange(listener: () => void): () => void {
|
|
1502
|
-
this.viewListeners
|
|
1503
|
-
return () => {
|
|
1504
|
-
this.viewListeners.delete(listener);
|
|
1505
|
-
};
|
|
1508
|
+
return subscribe(this.viewListeners, listener);
|
|
1506
1509
|
}
|
|
1507
1510
|
|
|
1508
1511
|
private announceView(): void {
|
|
@@ -1511,9 +1514,7 @@ export class EditorView {
|
|
|
1511
1514
|
return;
|
|
1512
1515
|
}
|
|
1513
1516
|
this.viewKey = key;
|
|
1514
|
-
|
|
1515
|
-
listener();
|
|
1516
|
-
}
|
|
1517
|
+
emit(this.viewListeners);
|
|
1517
1518
|
}
|
|
1518
1519
|
|
|
1519
1520
|
private caretOf(head: number): LayoutRect {
|
|
@@ -1525,7 +1526,6 @@ export class EditorView {
|
|
|
1525
1526
|
return this.scrollRun?.to ?? { x: this.viewport.scrollLeft, y: this.viewport.scrollTop };
|
|
1526
1527
|
}
|
|
1527
1528
|
|
|
1528
|
-
/* Stops a scroll on its way where it is. */
|
|
1529
1529
|
cancelScroll(): void {
|
|
1530
1530
|
if (this.scrollRun !== null) {
|
|
1531
1531
|
this.document.defaultView?.cancelAnimationFrame?.(this.scrollRun.frame);
|
|
@@ -1533,7 +1533,6 @@ export class EditorView {
|
|
|
1533
1533
|
}
|
|
1534
1534
|
}
|
|
1535
1535
|
|
|
1536
|
-
/* Whether a person asked their system for less motion. */
|
|
1537
1536
|
private reducedMotion(): boolean {
|
|
1538
1537
|
return this.document.defaultView?.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches === true;
|
|
1539
1538
|
}
|
|
@@ -1672,16 +1671,11 @@ export class EditorView {
|
|
|
1672
1671
|
}
|
|
1673
1672
|
|
|
1674
1673
|
onFind(listener: (state: EditorFindState) => void): () => void {
|
|
1675
|
-
this.findListeners
|
|
1676
|
-
return () => {
|
|
1677
|
-
this.findListeners.delete(listener);
|
|
1678
|
-
};
|
|
1674
|
+
return subscribe(this.findListeners, listener);
|
|
1679
1675
|
}
|
|
1680
1676
|
|
|
1681
1677
|
private announceFind(): void {
|
|
1682
|
-
|
|
1683
|
-
listener(this.find.state);
|
|
1684
|
-
}
|
|
1678
|
+
emit(this.findListeners, this.find.state);
|
|
1685
1679
|
}
|
|
1686
1680
|
|
|
1687
1681
|
revealFind(): void {
|
|
@@ -2114,10 +2108,7 @@ export class EditorView {
|
|
|
2114
2108
|
}
|
|
2115
2109
|
|
|
2116
2110
|
onAttributionHover(listener: (hover: { id: string; rect: EditorRect } | null) => void): () => void {
|
|
2117
|
-
this.attributionListeners
|
|
2118
|
-
return () => {
|
|
2119
|
-
this.attributionListeners.delete(listener);
|
|
2120
|
-
};
|
|
2111
|
+
return subscribe(this.attributionListeners, listener);
|
|
2121
2112
|
}
|
|
2122
2113
|
|
|
2123
2114
|
/* The bar is three pixels wide, so the pointer finds it a few pixels to either side. */
|
|
@@ -2139,9 +2130,7 @@ export class EditorView {
|
|
|
2139
2130
|
return;
|
|
2140
2131
|
}
|
|
2141
2132
|
this.attributionHover = next;
|
|
2142
|
-
|
|
2143
|
-
listener(next);
|
|
2144
|
-
}
|
|
2133
|
+
emit(this.attributionListeners, next);
|
|
2145
2134
|
}
|
|
2146
2135
|
|
|
2147
2136
|
private attributionUnder(clientX: number, clientY: number): { id: string; rect: EditorRect } | null {
|
|
@@ -2259,10 +2248,7 @@ export class EditorView {
|
|
|
2259
2248
|
}
|
|
2260
2249
|
|
|
2261
2250
|
onScope(listener: (scope: readonly EditorBlock[]) => void): () => void {
|
|
2262
|
-
this.scopeListeners
|
|
2263
|
-
return () => {
|
|
2264
|
-
this.scopeListeners.delete(listener);
|
|
2265
|
-
};
|
|
2251
|
+
return subscribe(this.scopeListeners, listener);
|
|
2266
2252
|
}
|
|
2267
2253
|
|
|
2268
2254
|
private announceScope(): void {
|
|
@@ -2278,9 +2264,7 @@ export class EditorView {
|
|
|
2278
2264
|
return;
|
|
2279
2265
|
}
|
|
2280
2266
|
this.scopeKey = key;
|
|
2281
|
-
|
|
2282
|
-
listener(scope);
|
|
2283
|
-
}
|
|
2267
|
+
emit(this.scopeListeners, scope);
|
|
2284
2268
|
}
|
|
2285
2269
|
|
|
2286
2270
|
private stickyLimit(): number {
|
|
@@ -2356,10 +2340,7 @@ export class EditorView {
|
|
|
2356
2340
|
}
|
|
2357
2341
|
|
|
2358
2342
|
onGutterAction(listener: (line: number) => void): () => void {
|
|
2359
|
-
this.gutterActionListeners
|
|
2360
|
-
return () => {
|
|
2361
|
-
this.gutterActionListeners.delete(listener);
|
|
2362
|
-
};
|
|
2343
|
+
return subscribe(this.gutterActionListeners, listener);
|
|
2363
2344
|
}
|
|
2364
2345
|
|
|
2365
2346
|
setGutterMarkers(owner: string, markers: readonly { id: string; line: number; label: string }[]): void {
|
|
@@ -2379,10 +2360,7 @@ export class EditorView {
|
|
|
2379
2360
|
}
|
|
2380
2361
|
|
|
2381
2362
|
onGutterMarker(listener: (id: string) => void): () => void {
|
|
2382
|
-
this.gutterMarkerListeners
|
|
2383
|
-
return () => {
|
|
2384
|
-
this.gutterMarkerListeners.delete(listener);
|
|
2385
|
-
};
|
|
2363
|
+
return subscribe(this.gutterMarkerListeners, listener);
|
|
2386
2364
|
}
|
|
2387
2365
|
|
|
2388
2366
|
/* The id of the host's gutter marker when the target is it. */
|
|
@@ -2392,12 +2370,10 @@ export class EditorView {
|
|
|
2392
2370
|
}
|
|
2393
2371
|
|
|
2394
2372
|
pressGutterMarker(id: string): void {
|
|
2395
|
-
|
|
2396
|
-
listener(id);
|
|
2397
|
-
}
|
|
2373
|
+
emit(this.gutterMarkerListeners, id);
|
|
2398
2374
|
}
|
|
2399
2375
|
|
|
2400
|
-
/* One marker per line: where two owners mark the same line, the owner
|
|
2376
|
+
/* One marker per line: where two owners mark the same line, the owner that sorts first stays. */
|
|
2401
2377
|
private markersByLine(): ReadonlyMap<number, { id: string; label: string }> {
|
|
2402
2378
|
const byLine = new Map<number, { id: string; label: string }>();
|
|
2403
2379
|
for (const owner of [...this.gutterMarkers.keys()].sort()) {
|
|
@@ -2417,9 +2393,7 @@ export class EditorView {
|
|
|
2417
2393
|
}
|
|
2418
2394
|
|
|
2419
2395
|
pressGutterAction(line: number): void {
|
|
2420
|
-
|
|
2421
|
-
listener(line);
|
|
2422
|
-
}
|
|
2396
|
+
emit(this.gutterActionListeners, line);
|
|
2423
2397
|
}
|
|
2424
2398
|
|
|
2425
2399
|
/* Whether a click at this point of the gutter is on a fold control, and which line it folds. */
|