@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/editor.css
CHANGED
|
@@ -12,10 +12,14 @@
|
|
|
12
12
|
--editor-guide-active: color-mix(in srgb, var(--text) 24%, transparent);
|
|
13
13
|
--editor-margin: color-mix(in srgb, var(--text) 14%, transparent);
|
|
14
14
|
--editor-whitespace: color-mix(in srgb, var(--text) 30%, transparent);
|
|
15
|
+
--editor-bidi: var(--status-needs-you);
|
|
15
16
|
--editor-hatch: color-mix(in srgb, var(--text) 12%, transparent);
|
|
16
17
|
--editor-added: var(--status-idle);
|
|
17
18
|
--editor-modified: var(--accent);
|
|
18
19
|
--editor-deleted: var(--status-error);
|
|
20
|
+
--editor-mark-number-mix: 72%;
|
|
21
|
+
--editor-mark-bar-width: 3px;
|
|
22
|
+
--editor-mark-gap: 4px;
|
|
19
23
|
--editor-sticky-shadow: color-mix(in srgb, black 12%, transparent);
|
|
20
24
|
--agent-1: #c4602f;
|
|
21
25
|
--agent-2: #7c4dd8;
|
|
@@ -34,6 +38,7 @@
|
|
|
34
38
|
--editor-margin: color-mix(in srgb, var(--text) 16%, transparent);
|
|
35
39
|
--editor-whitespace: color-mix(in srgb, var(--text) 32%, transparent);
|
|
36
40
|
--editor-hatch: color-mix(in srgb, var(--text) 10%, transparent);
|
|
41
|
+
--editor-mark-number-mix: 82%;
|
|
37
42
|
--editor-sticky-shadow: color-mix(in srgb, black 60%, transparent);
|
|
38
43
|
}
|
|
39
44
|
|
|
@@ -69,6 +74,7 @@
|
|
|
69
74
|
overflow: auto;
|
|
70
75
|
overflow-anchor: none;
|
|
71
76
|
overscroll-behavior: contain;
|
|
77
|
+
touch-action: pan-x pan-y pinch-zoom;
|
|
72
78
|
scrollbar-width: thin;
|
|
73
79
|
scrollbar-color: color-mix(in srgb, var(--text) 25%, transparent) transparent;
|
|
74
80
|
}
|
|
@@ -177,10 +183,23 @@
|
|
|
177
183
|
white-space: pre;
|
|
178
184
|
letter-spacing: 0;
|
|
179
185
|
direction: ltr;
|
|
186
|
+
unicode-bidi: bidi-override;
|
|
180
187
|
}
|
|
181
188
|
.se-run {
|
|
182
189
|
position: absolute;
|
|
183
190
|
white-space: pre;
|
|
191
|
+
unicode-bidi: bidi-override;
|
|
192
|
+
}
|
|
193
|
+
.se-bidi {
|
|
194
|
+
position: absolute;
|
|
195
|
+
box-sizing: border-box;
|
|
196
|
+
overflow: hidden;
|
|
197
|
+
border-radius: var(--radius-xs);
|
|
198
|
+
box-shadow: inset 0 0 0 1px var(--editor-bidi);
|
|
199
|
+
color: var(--editor-bidi);
|
|
200
|
+
text-align: center;
|
|
201
|
+
user-select: none;
|
|
202
|
+
pointer-events: none;
|
|
184
203
|
}
|
|
185
204
|
|
|
186
205
|
.se-gutter {
|
|
@@ -216,6 +235,31 @@
|
|
|
216
235
|
.se-active-number {
|
|
217
236
|
color: var(--text-muted);
|
|
218
237
|
}
|
|
238
|
+
/* A line a host marked with a bar: the number in a shade of the mark that reads on the tint, and a bar along the edge. */
|
|
239
|
+
/* The tint stops short of the code, so the cell and the line read as two surfaces. */
|
|
240
|
+
.se-line-marked {
|
|
241
|
+
align-items: flex-start;
|
|
242
|
+
background: linear-gradient(to right, var(--se-tint, transparent) calc(100% - var(--editor-mark-gap)), transparent 0);
|
|
243
|
+
}
|
|
244
|
+
.se-line-marked > span:first-child {
|
|
245
|
+
display: flex;
|
|
246
|
+
align-items: center;
|
|
247
|
+
height: var(--se-line);
|
|
248
|
+
color: color-mix(in srgb, var(--se-mark) var(--editor-mark-number-mix), var(--text));
|
|
249
|
+
}
|
|
250
|
+
.se-line-bar {
|
|
251
|
+
position: absolute;
|
|
252
|
+
top: 0;
|
|
253
|
+
left: 0;
|
|
254
|
+
width: var(--editor-mark-bar-width);
|
|
255
|
+
height: 100%;
|
|
256
|
+
}
|
|
257
|
+
.se-line-bar-solid {
|
|
258
|
+
background: var(--se-mark);
|
|
259
|
+
}
|
|
260
|
+
.se-line-bar-hatched {
|
|
261
|
+
background: repeating-linear-gradient(135deg, var(--se-mark) 0 2px, transparent 2px 4px);
|
|
262
|
+
}
|
|
219
263
|
.se-fold-toggle {
|
|
220
264
|
position: absolute;
|
|
221
265
|
top: 0;
|
|
@@ -299,6 +343,14 @@
|
|
|
299
343
|
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z'/%3E%3C/svg%3E")
|
|
300
344
|
center / contain no-repeat;
|
|
301
345
|
}
|
|
346
|
+
.se-gutter-marker[aria-pressed='true'] {
|
|
347
|
+
color: var(--accent);
|
|
348
|
+
}
|
|
349
|
+
/* Lucide's book-open. */
|
|
350
|
+
.se-gutter-marker[data-icon='book-open']::before {
|
|
351
|
+
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 7v14'/%3E%3Cpath d='M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z'/%3E%3C/svg%3E")
|
|
352
|
+
center / contain no-repeat;
|
|
353
|
+
}
|
|
302
354
|
.se-change {
|
|
303
355
|
position: absolute;
|
|
304
356
|
top: 0;
|
|
@@ -337,9 +389,13 @@
|
|
|
337
389
|
font-size: max(12px, calc(var(--code-font-size, 13px) - 2px));
|
|
338
390
|
line-height: 1;
|
|
339
391
|
}
|
|
340
|
-
.se-line-highlight
|
|
392
|
+
.se-line-highlight,
|
|
393
|
+
.se-line-emphasis {
|
|
341
394
|
position: absolute;
|
|
342
395
|
}
|
|
396
|
+
.se-line-emphasis {
|
|
397
|
+
border-radius: 2px;
|
|
398
|
+
}
|
|
343
399
|
|
|
344
400
|
/* Code a host draws in a widget row: the editor's face, with the gutter column the document has. */
|
|
345
401
|
.se-code-block {
|
|
@@ -685,6 +741,11 @@
|
|
|
685
741
|
white-space: pre;
|
|
686
742
|
cursor: default;
|
|
687
743
|
}
|
|
744
|
+
.se-inlay-link {
|
|
745
|
+
text-decoration: underline;
|
|
746
|
+
text-underline-offset: 2px;
|
|
747
|
+
cursor: pointer;
|
|
748
|
+
}
|
|
688
749
|
.se-composition {
|
|
689
750
|
position: absolute;
|
|
690
751
|
white-space: pre;
|
|
@@ -717,6 +778,13 @@
|
|
|
717
778
|
white-space: pre-wrap;
|
|
718
779
|
overflow-wrap: anywhere;
|
|
719
780
|
}
|
|
781
|
+
/* Lines the host drew in place of the text: a row in the text column that scrolls with the code, selectable as the page's own text is. */
|
|
782
|
+
.se-block.se-replacement {
|
|
783
|
+
padding: 0;
|
|
784
|
+
color: var(--text);
|
|
785
|
+
white-space: normal;
|
|
786
|
+
user-select: text;
|
|
787
|
+
}
|
|
720
788
|
/* The host's own row runs the width of the editor over the gutter and under the pinned headers, so the code layer has no stacking of its own. */
|
|
721
789
|
.se-block.se-widget {
|
|
722
790
|
z-index: 4;
|
|
@@ -790,6 +858,18 @@
|
|
|
790
858
|
white-space: pre;
|
|
791
859
|
pointer-events: none;
|
|
792
860
|
}
|
|
861
|
+
.se-sr-only,
|
|
862
|
+
.se-live {
|
|
863
|
+
position: absolute;
|
|
864
|
+
width: 1px;
|
|
865
|
+
height: 1px;
|
|
866
|
+
margin: -1px;
|
|
867
|
+
padding: 0;
|
|
868
|
+
overflow: hidden;
|
|
869
|
+
clip-path: inset(50%);
|
|
870
|
+
white-space: nowrap;
|
|
871
|
+
border: 0;
|
|
872
|
+
}
|
|
793
873
|
.se-notice {
|
|
794
874
|
position: absolute;
|
|
795
875
|
right: 16px;
|
package/src/engine.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { changedSpans, DocumentModel, type EditorSnapshot, type FoldHints, replacementText } from '@adecore/editor-core';
|
|
2
|
+
import { MAX_REPLACEMENT_CHARACTERS } from './search-protocol.ts';
|
|
3
|
+
import { GrammarPreparation } from './grammar-preparation.ts';
|
|
2
4
|
import { InputController } from './controller.ts';
|
|
3
5
|
import { resolveSmartKeys } from './smart-keys.ts';
|
|
4
6
|
import { emit, type Listener, subscribe } from './listeners.ts';
|
|
@@ -10,6 +12,7 @@ import type {
|
|
|
10
12
|
EditorCodeBlockOptions,
|
|
11
13
|
EditorLineAction,
|
|
12
14
|
EditorLineHighlight,
|
|
15
|
+
LineToken,
|
|
13
16
|
EditorChangeMark,
|
|
14
17
|
EditorCodeVision,
|
|
15
18
|
EditorClickHandler,
|
|
@@ -25,12 +28,17 @@ import type {
|
|
|
25
28
|
EditorReplaceOptions,
|
|
26
29
|
EditorGutterAction,
|
|
27
30
|
EditorGutterMarker,
|
|
31
|
+
EditorReplacement,
|
|
28
32
|
EditorHighlight,
|
|
29
33
|
EditorHover,
|
|
30
34
|
EditorGhostText,
|
|
35
|
+
EditorInlayClickHandler,
|
|
31
36
|
EditorInlayHint,
|
|
37
|
+
EditorInlayTarget,
|
|
32
38
|
EditorKeyHandler,
|
|
39
|
+
EditorInputPopup,
|
|
33
40
|
EditorMarker,
|
|
41
|
+
EditorMessages,
|
|
34
42
|
EditorOptions,
|
|
35
43
|
EditorPosition,
|
|
36
44
|
EditorRemoteCursor,
|
|
@@ -41,6 +49,7 @@ import type {
|
|
|
41
49
|
EditorSelectionRanges,
|
|
42
50
|
EditorSemanticToken,
|
|
43
51
|
EditorSmartKeys,
|
|
52
|
+
EditorApplyEditsOptions,
|
|
44
53
|
EditorTextChange,
|
|
45
54
|
EditorTrackedRange,
|
|
46
55
|
EditorTheme,
|
|
@@ -53,12 +62,15 @@ import { EditorView, type ViewSettings } from './view.ts';
|
|
|
53
62
|
const DEFAULT_TAB_SIZE = 4;
|
|
54
63
|
const DEFAULT_LABEL = 'Code editor';
|
|
55
64
|
const DEFAULT_WIDGET_OWNER = 'default';
|
|
65
|
+
/* The space a hint's padding keeps clear; a whole pixel count so the pill stays on the pixel grid. */
|
|
66
|
+
const INLAY_GAP = 4;
|
|
56
67
|
|
|
57
68
|
class SmartEditor implements Editor {
|
|
58
69
|
private readonly model: DocumentModel;
|
|
59
70
|
private readonly settings: ViewSettings;
|
|
60
71
|
private readonly view: EditorView;
|
|
61
72
|
private readonly controller: InputController;
|
|
73
|
+
private readonly grammarPreparation: GrammarPreparation;
|
|
62
74
|
private readonly changes = new Set<Listener>();
|
|
63
75
|
private readonly textChanges = new Set<(change: EditorTextChange) => void>();
|
|
64
76
|
private readonly carets = new Set<(position: EditorPosition) => void>();
|
|
@@ -83,6 +95,7 @@ class SmartEditor implements Editor {
|
|
|
83
95
|
this.language = options.language;
|
|
84
96
|
this.theme = options.theme;
|
|
85
97
|
this.settings = {
|
|
98
|
+
searchWorker: engine.searchWorker,
|
|
86
99
|
language: options.language,
|
|
87
100
|
tabSize: options.indentation?.tabSize ?? DEFAULT_TAB_SIZE,
|
|
88
101
|
insertSpaces: options.indentation?.insertSpaces ?? true,
|
|
@@ -93,13 +106,16 @@ class SmartEditor implements Editor {
|
|
|
93
106
|
messages: options.messages ?? {},
|
|
94
107
|
label: options.label ?? DEFAULT_LABEL,
|
|
95
108
|
guides: options.guides !== false,
|
|
109
|
+
stickyScroll: options.stickyScroll !== false,
|
|
96
110
|
whitespace: options.whitespace === true,
|
|
97
111
|
rightMargin: options.rightMargin ?? null,
|
|
98
|
-
foldOutline: options.foldOutline ?? 'hover'
|
|
112
|
+
foldOutline: options.foldOutline ?? 'hover',
|
|
113
|
+
folding: options.folding !== false
|
|
99
114
|
};
|
|
100
115
|
this.view = new EditorView(element, this.model, this.settings);
|
|
116
|
+
this.grammarPreparation = new GrammarPreparation(this.model, () => this.view.grammarChanged());
|
|
101
117
|
this.controller = new InputController(this.view, {
|
|
102
|
-
handBack: engine.handBack ?? [],
|
|
118
|
+
handBack: [...(engine.handBack ?? []), ...(options.handBack ?? [])],
|
|
103
119
|
keymap: engine.keymap,
|
|
104
120
|
apple: engine.apple ?? false,
|
|
105
121
|
save: () => emit(this.saves),
|
|
@@ -111,8 +127,9 @@ class SmartEditor implements Editor {
|
|
|
111
127
|
this.caretMoved(snapshot.selections.at(-1)!.head);
|
|
112
128
|
if (snapshot.revision !== this.revision) {
|
|
113
129
|
this.revision = snapshot.revision;
|
|
130
|
+
this.grammarPreparation.edited(snapshot.changes ?? []);
|
|
114
131
|
if (snapshot.contentEdits !== undefined && snapshot.contentEdits.length > 0) {
|
|
115
|
-
this.announceTextChange(snapshot.contentEdits, snapshot.source ?? 'external');
|
|
132
|
+
this.announceTextChange(snapshot.contentEdits, snapshot.source ?? 'external', snapshot.typed === true);
|
|
116
133
|
}
|
|
117
134
|
if (!this.settingText) {
|
|
118
135
|
emit(this.changes);
|
|
@@ -147,6 +164,7 @@ class SmartEditor implements Editor {
|
|
|
147
164
|
|
|
148
165
|
private loadTokenizer(): void {
|
|
149
166
|
const request = ++this.tokenizerRequest;
|
|
167
|
+
this.grammarPreparation.begin();
|
|
150
168
|
void this.engine
|
|
151
169
|
.scopeColors?.(this.theme)
|
|
152
170
|
.catch(() => null)
|
|
@@ -161,6 +179,7 @@ class SmartEditor implements Editor {
|
|
|
161
179
|
.then((tokenizer) => {
|
|
162
180
|
if (!this.disposed && request === this.tokenizerRequest) {
|
|
163
181
|
this.view.setTokenizer(tokenizer);
|
|
182
|
+
this.grammarPreparation.ready(tokenizer);
|
|
164
183
|
}
|
|
165
184
|
});
|
|
166
185
|
}
|
|
@@ -194,9 +213,10 @@ class SmartEditor implements Editor {
|
|
|
194
213
|
return subscribe(this.textChanges, listener);
|
|
195
214
|
}
|
|
196
215
|
|
|
197
|
-
private announceTextChange(edits: NonNullable<EditorSnapshot['contentEdits']>, source: EditorTextChange['source']): void {
|
|
216
|
+
private announceTextChange(edits: NonNullable<EditorSnapshot['contentEdits']>, source: EditorTextChange['source'], typed: boolean): void {
|
|
198
217
|
const change: EditorTextChange = {
|
|
199
218
|
source,
|
|
219
|
+
...(typed ? { typed } : {}),
|
|
200
220
|
changes: edits.map((edit) => ({
|
|
201
221
|
range: { start: { line: edit.start.line, character: edit.start.column }, end: { line: edit.end.line, character: edit.end.column } },
|
|
202
222
|
text: edit.text
|
|
@@ -222,6 +242,26 @@ class SmartEditor implements Editor {
|
|
|
222
242
|
return this.view.scrollPlace.y;
|
|
223
243
|
}
|
|
224
244
|
|
|
245
|
+
setScrollTop(top: number): void {
|
|
246
|
+
this.view.setScrollTop(top);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
getLineHeight(line: number): number {
|
|
250
|
+
return this.view.getLineHeight(line);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
getBaseLineHeight(): number {
|
|
254
|
+
return this.view.layout.metrics.lineHeight;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
onLineHeights(listener: Listener): () => void {
|
|
258
|
+
return this.view.onLineHeights(listener);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
getContentHeight(): number {
|
|
262
|
+
return this.view.contentHeight;
|
|
263
|
+
}
|
|
264
|
+
|
|
225
265
|
getSelection(): EditorRange {
|
|
226
266
|
return this.rangeOf(this.model.getPrimary());
|
|
227
267
|
}
|
|
@@ -295,14 +335,32 @@ class SmartEditor implements Editor {
|
|
|
295
335
|
this.controller.setSelectionRanges(provider);
|
|
296
336
|
}
|
|
297
337
|
|
|
298
|
-
applyEdits(edits: readonly EditorContentChange[]): boolean {
|
|
338
|
+
applyEdits(edits: readonly EditorContentChange[], options: EditorApplyEditsOptions = {}): boolean {
|
|
299
339
|
if (this.settings.readOnly || edits.length === 0) {
|
|
300
340
|
return false;
|
|
301
341
|
}
|
|
302
342
|
try {
|
|
343
|
+
const { mapSelections } = options;
|
|
303
344
|
return this.model.applyEdits(
|
|
304
345
|
edits.map((edit) => ({ from: this.offsetAt(edit.range.start), to: this.offsetAt(edit.range.end), text: edit.text })),
|
|
305
|
-
{
|
|
346
|
+
{
|
|
347
|
+
source: 'command',
|
|
348
|
+
joinPrevious: options.mergeUndo === true,
|
|
349
|
+
selectionPositions:
|
|
350
|
+
mapSelections &&
|
|
351
|
+
this.model.getSelections().map((selection) => {
|
|
352
|
+
const { start, end } = mapSelections(this.rangeOf(selection));
|
|
353
|
+
const from = { line: start.line, column: start.character };
|
|
354
|
+
const to = { line: end.line, column: end.character };
|
|
355
|
+
if (selection.head < selection.anchor) {
|
|
356
|
+
return { anchor: to, head: from };
|
|
357
|
+
}
|
|
358
|
+
return {
|
|
359
|
+
anchor: from,
|
|
360
|
+
head: selection.head === selection.anchor && start.line === end.line && start.character === end.character ? from : to
|
|
361
|
+
};
|
|
362
|
+
})
|
|
363
|
+
}
|
|
306
364
|
);
|
|
307
365
|
} catch {
|
|
308
366
|
return false;
|
|
@@ -323,7 +381,28 @@ class SmartEditor implements Editor {
|
|
|
323
381
|
}
|
|
324
382
|
|
|
325
383
|
setInlayHints(hints: readonly EditorInlayHint[]): void {
|
|
326
|
-
this.view.setInlays(
|
|
384
|
+
this.view.setInlays(
|
|
385
|
+
hints.map((hint, index) => ({
|
|
386
|
+
id: hint.id ?? `hint-${index}`,
|
|
387
|
+
at: this.offsetAt(hint.position),
|
|
388
|
+
text: hint.label,
|
|
389
|
+
...(hint.parts === undefined ? {} : { parts: hint.parts }),
|
|
390
|
+
...(hint.paddingLeft ? { gapLeft: INLAY_GAP } : {}),
|
|
391
|
+
...(hint.paddingRight ? { gapRight: INLAY_GAP } : {})
|
|
392
|
+
}))
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
onInlayHover(listener: (target: EditorInlayTarget | null) => void): () => void {
|
|
397
|
+
return this.view.onInlayHover(listener);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
onInlayClick(handler: EditorInlayClickHandler): () => void {
|
|
401
|
+
return this.controller.onInlayClick(handler);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
setInlayLink(target: { readonly id: string; readonly part: number | null } | null): void {
|
|
405
|
+
this.view.setInlayLink(target);
|
|
327
406
|
}
|
|
328
407
|
|
|
329
408
|
setGhostText(ghost: EditorGhostText | null): void {
|
|
@@ -336,7 +415,7 @@ class SmartEditor implements Editor {
|
|
|
336
415
|
? null
|
|
337
416
|
: tokens.map((token) => {
|
|
338
417
|
const from = this.offsetAt({ line: token.line, character: token.character });
|
|
339
|
-
return { from, to: from + token.length, scopes: token.scopes };
|
|
418
|
+
return { from, to: from + token.length, scopes: token.scopes, within: token.within };
|
|
340
419
|
})
|
|
341
420
|
);
|
|
342
421
|
}
|
|
@@ -393,19 +472,23 @@ class SmartEditor implements Editor {
|
|
|
393
472
|
|
|
394
473
|
replace(replacement: string, replaceOptions: EditorReplaceOptions = {}): boolean {
|
|
395
474
|
const { find } = this.view;
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
if (mark === null || query === null || this.blockedByReadOnly()) {
|
|
475
|
+
const match = find.currentMatch;
|
|
476
|
+
if (this.blockedByReadOnly()) {
|
|
399
477
|
return false;
|
|
400
478
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
const match = this.model.find(query.text, options)[0];
|
|
404
|
-
if (match === undefined) {
|
|
479
|
+
if (find.activeQuery?.regex === true) {
|
|
480
|
+
if (find.currentMark === null) {
|
|
405
481
|
return false;
|
|
406
482
|
}
|
|
483
|
+
void find.replaceOne(replacement, replaceOptions.preserveCase === true, () => !this.disposed && !this.settings.readOnly);
|
|
484
|
+
return true;
|
|
485
|
+
}
|
|
486
|
+
if (match === null) {
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
489
|
+
try {
|
|
407
490
|
// The next match is looked for after what was written, or a replacement that holds the query would find itself again.
|
|
408
|
-
find.pendingAnchor =
|
|
491
|
+
find.pendingAnchor = match.to;
|
|
409
492
|
if (!this.model.replace(match, replacement, { preserveCase: replaceOptions.preserveCase })) {
|
|
410
493
|
find.pendingAnchor = undefined;
|
|
411
494
|
return false;
|
|
@@ -420,21 +503,47 @@ class SmartEditor implements Editor {
|
|
|
420
503
|
}
|
|
421
504
|
|
|
422
505
|
replaceAll(replacement: string, options: EditorReplaceOptions = {}): number {
|
|
423
|
-
const
|
|
424
|
-
|
|
506
|
+
const { find } = this.view;
|
|
507
|
+
const query = find.activeQuery;
|
|
508
|
+
if (query === null || find.state.noSelection === true || this.blockedByReadOnly()) {
|
|
425
509
|
return 0;
|
|
426
510
|
}
|
|
427
511
|
try {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
512
|
+
const matches = find.completeMatches;
|
|
513
|
+
if (matches !== null) {
|
|
514
|
+
let characters = 0;
|
|
515
|
+
for (const match of matches) {
|
|
516
|
+
characters += replacementText('', match, replacement, false, options.preserveCase, MAX_REPLACEMENT_CHARACTERS).length;
|
|
517
|
+
if (characters > MAX_REPLACEMENT_CHARACTERS) {
|
|
518
|
+
find.reportReplaceFailed('limit');
|
|
519
|
+
return 0;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
const count = this.model.replaceMatches(matches, replacement, { preserveCase: options.preserveCase });
|
|
523
|
+
find.reportReplaced(count);
|
|
524
|
+
return count;
|
|
525
|
+
}
|
|
526
|
+
void this.replaceAllAsync(replacement, options);
|
|
527
|
+
return 0;
|
|
528
|
+
} catch (error) {
|
|
529
|
+
find.reportReplaceFailed(error instanceof RangeError ? 'limit' : 'error');
|
|
530
|
+
return 0;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
async replaceAllAsync(replacement: string, options: EditorReplaceOptions = {}): Promise<number> {
|
|
535
|
+
const { find } = this.view;
|
|
536
|
+
if (this.disposed || this.blockedByReadOnly() || find.activeQuery === null || find.state.noSelection) {
|
|
537
|
+
return 0;
|
|
538
|
+
}
|
|
539
|
+
if (find.state.limited) {
|
|
540
|
+
find.reportReplaceFailed('limit');
|
|
436
541
|
return 0;
|
|
437
542
|
}
|
|
543
|
+
if (find.completeMatches !== null) {
|
|
544
|
+
return this.replaceAll(replacement, options);
|
|
545
|
+
}
|
|
546
|
+
return find.replaceAll(replacement, options.preserveCase === true, () => !this.disposed && !this.settings.readOnly);
|
|
438
547
|
}
|
|
439
548
|
|
|
440
549
|
setReplacePreview(replacement: string | null, options: EditorReplaceOptions = {}): void {
|
|
@@ -462,24 +571,39 @@ class SmartEditor implements Editor {
|
|
|
462
571
|
if (query.text === '') {
|
|
463
572
|
return false;
|
|
464
573
|
}
|
|
465
|
-
|
|
574
|
+
const { anchor, head } = this.model.getPrimary();
|
|
575
|
+
const offset = direction === 1 ? Math.max(anchor, head) : Math.min(anchor, head);
|
|
576
|
+
const reveal = (target: { from: number; to: number } | null): boolean => {
|
|
577
|
+
if (target === null || this.disposed) {
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
580
|
+
this.view.ensureVisible(target.from);
|
|
581
|
+
this.model.setSelections([direction === 1 ? { anchor: target.from, head: target.to } : { anchor: target.to, head: target.from }]);
|
|
582
|
+
this.view.revealCaret(target.from > head ? 'centerDown' : 'centerUp');
|
|
583
|
+
return true;
|
|
584
|
+
};
|
|
585
|
+
if (query.regex) {
|
|
586
|
+
const revision = this.model.getRevision();
|
|
587
|
+
void this.view.find.nextFromCursor(query, offset, direction).then((target) => {
|
|
588
|
+
const current = this.model.getPrimary();
|
|
589
|
+
if (this.model.getRevision() === revision && current.anchor === anchor && current.head === head) {
|
|
590
|
+
reveal(target);
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
return true;
|
|
594
|
+
}
|
|
466
595
|
try {
|
|
467
|
-
|
|
596
|
+
return reveal(
|
|
597
|
+
this.model.findNext(query.text, offset, {
|
|
598
|
+
caseSensitive: query.caseSensitive,
|
|
599
|
+
wholeWord: query.wholeWord,
|
|
600
|
+
backwards: direction === -1,
|
|
601
|
+
wrap: true
|
|
602
|
+
})
|
|
603
|
+
);
|
|
468
604
|
} catch {
|
|
469
605
|
return false;
|
|
470
606
|
}
|
|
471
|
-
if (matches.length === 0) {
|
|
472
|
-
return false;
|
|
473
|
-
}
|
|
474
|
-
const { anchor, head } = this.model.getPrimary();
|
|
475
|
-
const from = Math.min(anchor, head);
|
|
476
|
-
const to = Math.max(anchor, head);
|
|
477
|
-
const target =
|
|
478
|
-
direction === 1 ? (matches.find((match) => match.from >= to) ?? matches[0]!) : (matches.findLast((match) => match.to <= from) ?? matches.at(-1)!);
|
|
479
|
-
this.view.ensureVisible(target.from);
|
|
480
|
-
this.model.setSelections([direction === 1 ? { anchor: target.from, head: target.to } : { anchor: target.to, head: target.from }]);
|
|
481
|
-
this.view.revealCaret(target.from > head ? 'centerDown' : 'centerUp');
|
|
482
|
-
return true;
|
|
483
607
|
}
|
|
484
608
|
|
|
485
609
|
private blockedByReadOnly(): boolean {
|
|
@@ -514,8 +638,8 @@ class SmartEditor implements Editor {
|
|
|
514
638
|
this.view.applySettings();
|
|
515
639
|
}
|
|
516
640
|
|
|
517
|
-
setChangeMarks(marks: readonly EditorChangeMark[]): void {
|
|
518
|
-
this.view.setChangeMarks(marks);
|
|
641
|
+
setChangeMarks(marks: readonly EditorChangeMark[], layer?: string): void {
|
|
642
|
+
this.view.setChangeMarks(marks, layer);
|
|
519
643
|
}
|
|
520
644
|
|
|
521
645
|
setAttributionMarks(marks: readonly EditorAttributionMark[]): void {
|
|
@@ -526,6 +650,32 @@ class SmartEditor implements Editor {
|
|
|
526
650
|
this.view.setLineHighlights(highlights);
|
|
527
651
|
}
|
|
528
652
|
|
|
653
|
+
setLineLabels(labels: ((line: number) => string | null) | null): void {
|
|
654
|
+
this.view.setLineLabels(labels);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
setInputPopup(popup: EditorInputPopup | null): void {
|
|
658
|
+
this.view.setInputPopup(popup);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
announce(message: string): void {
|
|
662
|
+
this.view.announce(message);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
setMessages(messages: Partial<EditorMessages>): void {
|
|
666
|
+
this.settings.messages = { ...this.settings.messages, ...messages };
|
|
667
|
+
this.view.repaint();
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
setLineNumbers(labels: ((line: number) => string | null) | null): void {
|
|
671
|
+
this.view.setLineNumbers(labels);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
setLineTokens(tokens: ((line: number) => readonly LineToken[] | null | undefined) | null): void {
|
|
675
|
+
this.grammarPreparation.pause(tokens !== null);
|
|
676
|
+
this.view.setLineTokens(tokens);
|
|
677
|
+
}
|
|
678
|
+
|
|
529
679
|
renderCode(container: HTMLElement, text: string, options: EditorCodeBlockOptions = {}): void {
|
|
530
680
|
this.view.renderCode(container, text, options);
|
|
531
681
|
}
|
|
@@ -546,6 +696,7 @@ class SmartEditor implements Editor {
|
|
|
546
696
|
at: this.offsetAt({ line: widget.line, character: 0 }),
|
|
547
697
|
placement: widget.placement ?? 'below',
|
|
548
698
|
height: widget.height,
|
|
699
|
+
fixed: widget.fixed,
|
|
549
700
|
render: (container: HTMLElement) => {
|
|
550
701
|
container.classList.add('se-widget');
|
|
551
702
|
widget.render(container);
|
|
@@ -554,6 +705,25 @@ class SmartEditor implements Editor {
|
|
|
554
705
|
);
|
|
555
706
|
}
|
|
556
707
|
|
|
708
|
+
setReplacements(replacements: readonly EditorReplacement[], owner: string = DEFAULT_WIDGET_OWNER): void {
|
|
709
|
+
this.view.setReplacements(
|
|
710
|
+
owner,
|
|
711
|
+
replacements.map((replacement) => ({
|
|
712
|
+
id: `replacement:${owner}:${replacement.id}`,
|
|
713
|
+
from: this.offsetAt({ line: replacement.startLine, character: 0 }),
|
|
714
|
+
to: this.model.getLine(Math.max(0, Math.min(replacement.endLine, this.model.getLineCount() - 1))).end,
|
|
715
|
+
height: replacement.height,
|
|
716
|
+
label: replacement.label,
|
|
717
|
+
enterable: replacement.enterable,
|
|
718
|
+
onReveal: replacement.onReveal === undefined ? undefined : () => replacement.onReveal!(),
|
|
719
|
+
render: (container: HTMLElement) => {
|
|
720
|
+
container.classList.add('se-widget-replacement');
|
|
721
|
+
replacement.render(container);
|
|
722
|
+
}
|
|
723
|
+
}))
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
|
|
557
727
|
setLineActions(actions: readonly EditorLineAction[], owner: string = DEFAULT_WIDGET_OWNER): void {
|
|
558
728
|
this.view.setLineActions(
|
|
559
729
|
owner,
|
|
@@ -637,6 +807,11 @@ class SmartEditor implements Editor {
|
|
|
637
807
|
this.view.applySettings();
|
|
638
808
|
}
|
|
639
809
|
|
|
810
|
+
setStickyScroll(sticky: boolean): void {
|
|
811
|
+
this.settings.stickyScroll = sticky;
|
|
812
|
+
this.view.applySettings();
|
|
813
|
+
}
|
|
814
|
+
|
|
640
815
|
setWhitespace(whitespace: boolean): void {
|
|
641
816
|
this.settings.whitespace = whitespace;
|
|
642
817
|
this.view.applySettings();
|
|
@@ -714,6 +889,10 @@ class SmartEditor implements Editor {
|
|
|
714
889
|
this.view.applySettings();
|
|
715
890
|
}
|
|
716
891
|
|
|
892
|
+
isReadOnly(): boolean {
|
|
893
|
+
return this.settings.readOnly;
|
|
894
|
+
}
|
|
895
|
+
|
|
717
896
|
runCommand(command: EditorRunCommand): boolean {
|
|
718
897
|
return this.controller.command(command);
|
|
719
898
|
}
|
|
@@ -731,6 +910,7 @@ class SmartEditor implements Editor {
|
|
|
731
910
|
return;
|
|
732
911
|
}
|
|
733
912
|
this.disposed = true;
|
|
913
|
+
this.grammarPreparation.dispose();
|
|
734
914
|
this.changes.clear();
|
|
735
915
|
this.textChanges.clear();
|
|
736
916
|
this.carets.clear();
|