@adecore/editor 0.22.0 → 0.23.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/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 +184 -41
- package/dist/fake.d.ts +43 -5
- package/dist/fake.js +117 -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 +84 -2
- package/dist/uniform-geometry.d.ts +7 -0
- package/dist/uniform-geometry.js +49 -0
- package/dist/view.d.ts +68 -17
- package/dist/view.js +424 -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 +216 -42
- package/src/fake.ts +152 -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 +174 -8
- package/src/uniform-geometry.ts +57 -0
- package/src/view.ts +500 -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,
|
|
@@ -95,11 +108,13 @@ class SmartEditor implements Editor {
|
|
|
95
108
|
guides: options.guides !== false,
|
|
96
109
|
whitespace: options.whitespace === true,
|
|
97
110
|
rightMargin: options.rightMargin ?? null,
|
|
98
|
-
foldOutline: options.foldOutline ?? 'hover'
|
|
111
|
+
foldOutline: options.foldOutline ?? 'hover',
|
|
112
|
+
folding: options.folding !== false
|
|
99
113
|
};
|
|
100
114
|
this.view = new EditorView(element, this.model, this.settings);
|
|
115
|
+
this.grammarPreparation = new GrammarPreparation(this.model, () => this.view.grammarChanged());
|
|
101
116
|
this.controller = new InputController(this.view, {
|
|
102
|
-
handBack: engine.handBack ?? [],
|
|
117
|
+
handBack: [...(engine.handBack ?? []), ...(options.handBack ?? [])],
|
|
103
118
|
keymap: engine.keymap,
|
|
104
119
|
apple: engine.apple ?? false,
|
|
105
120
|
save: () => emit(this.saves),
|
|
@@ -111,8 +126,9 @@ class SmartEditor implements Editor {
|
|
|
111
126
|
this.caretMoved(snapshot.selections.at(-1)!.head);
|
|
112
127
|
if (snapshot.revision !== this.revision) {
|
|
113
128
|
this.revision = snapshot.revision;
|
|
129
|
+
this.grammarPreparation.edited(snapshot.changes ?? []);
|
|
114
130
|
if (snapshot.contentEdits !== undefined && snapshot.contentEdits.length > 0) {
|
|
115
|
-
this.announceTextChange(snapshot.contentEdits, snapshot.source ?? 'external');
|
|
131
|
+
this.announceTextChange(snapshot.contentEdits, snapshot.source ?? 'external', snapshot.typed === true);
|
|
116
132
|
}
|
|
117
133
|
if (!this.settingText) {
|
|
118
134
|
emit(this.changes);
|
|
@@ -147,6 +163,7 @@ class SmartEditor implements Editor {
|
|
|
147
163
|
|
|
148
164
|
private loadTokenizer(): void {
|
|
149
165
|
const request = ++this.tokenizerRequest;
|
|
166
|
+
this.grammarPreparation.begin();
|
|
150
167
|
void this.engine
|
|
151
168
|
.scopeColors?.(this.theme)
|
|
152
169
|
.catch(() => null)
|
|
@@ -161,6 +178,7 @@ class SmartEditor implements Editor {
|
|
|
161
178
|
.then((tokenizer) => {
|
|
162
179
|
if (!this.disposed && request === this.tokenizerRequest) {
|
|
163
180
|
this.view.setTokenizer(tokenizer);
|
|
181
|
+
this.grammarPreparation.ready(tokenizer);
|
|
164
182
|
}
|
|
165
183
|
});
|
|
166
184
|
}
|
|
@@ -194,9 +212,10 @@ class SmartEditor implements Editor {
|
|
|
194
212
|
return subscribe(this.textChanges, listener);
|
|
195
213
|
}
|
|
196
214
|
|
|
197
|
-
private announceTextChange(edits: NonNullable<EditorSnapshot['contentEdits']>, source: EditorTextChange['source']): void {
|
|
215
|
+
private announceTextChange(edits: NonNullable<EditorSnapshot['contentEdits']>, source: EditorTextChange['source'], typed: boolean): void {
|
|
198
216
|
const change: EditorTextChange = {
|
|
199
217
|
source,
|
|
218
|
+
...(typed ? { typed } : {}),
|
|
200
219
|
changes: edits.map((edit) => ({
|
|
201
220
|
range: { start: { line: edit.start.line, character: edit.start.column }, end: { line: edit.end.line, character: edit.end.column } },
|
|
202
221
|
text: edit.text
|
|
@@ -222,6 +241,26 @@ class SmartEditor implements Editor {
|
|
|
222
241
|
return this.view.scrollPlace.y;
|
|
223
242
|
}
|
|
224
243
|
|
|
244
|
+
setScrollTop(top: number): void {
|
|
245
|
+
this.view.setScrollTop(top);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
getLineHeight(line: number): number {
|
|
249
|
+
return this.view.getLineHeight(line);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
getBaseLineHeight(): number {
|
|
253
|
+
return this.view.layout.metrics.lineHeight;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
onLineHeights(listener: Listener): () => void {
|
|
257
|
+
return this.view.onLineHeights(listener);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
getContentHeight(): number {
|
|
261
|
+
return this.view.contentHeight;
|
|
262
|
+
}
|
|
263
|
+
|
|
225
264
|
getSelection(): EditorRange {
|
|
226
265
|
return this.rangeOf(this.model.getPrimary());
|
|
227
266
|
}
|
|
@@ -295,14 +334,32 @@ class SmartEditor implements Editor {
|
|
|
295
334
|
this.controller.setSelectionRanges(provider);
|
|
296
335
|
}
|
|
297
336
|
|
|
298
|
-
applyEdits(edits: readonly EditorContentChange[]): boolean {
|
|
337
|
+
applyEdits(edits: readonly EditorContentChange[], options: EditorApplyEditsOptions = {}): boolean {
|
|
299
338
|
if (this.settings.readOnly || edits.length === 0) {
|
|
300
339
|
return false;
|
|
301
340
|
}
|
|
302
341
|
try {
|
|
342
|
+
const { mapSelections } = options;
|
|
303
343
|
return this.model.applyEdits(
|
|
304
344
|
edits.map((edit) => ({ from: this.offsetAt(edit.range.start), to: this.offsetAt(edit.range.end), text: edit.text })),
|
|
305
|
-
{
|
|
345
|
+
{
|
|
346
|
+
source: 'command',
|
|
347
|
+
joinPrevious: options.mergeUndo === true,
|
|
348
|
+
selectionPositions:
|
|
349
|
+
mapSelections &&
|
|
350
|
+
this.model.getSelections().map((selection) => {
|
|
351
|
+
const { start, end } = mapSelections(this.rangeOf(selection));
|
|
352
|
+
const from = { line: start.line, column: start.character };
|
|
353
|
+
const to = { line: end.line, column: end.character };
|
|
354
|
+
if (selection.head < selection.anchor) {
|
|
355
|
+
return { anchor: to, head: from };
|
|
356
|
+
}
|
|
357
|
+
return {
|
|
358
|
+
anchor: from,
|
|
359
|
+
head: selection.head === selection.anchor && start.line === end.line && start.character === end.character ? from : to
|
|
360
|
+
};
|
|
361
|
+
})
|
|
362
|
+
}
|
|
306
363
|
);
|
|
307
364
|
} catch {
|
|
308
365
|
return false;
|
|
@@ -323,7 +380,28 @@ class SmartEditor implements Editor {
|
|
|
323
380
|
}
|
|
324
381
|
|
|
325
382
|
setInlayHints(hints: readonly EditorInlayHint[]): void {
|
|
326
|
-
this.view.setInlays(
|
|
383
|
+
this.view.setInlays(
|
|
384
|
+
hints.map((hint, index) => ({
|
|
385
|
+
id: hint.id ?? `hint-${index}`,
|
|
386
|
+
at: this.offsetAt(hint.position),
|
|
387
|
+
text: hint.label,
|
|
388
|
+
...(hint.parts === undefined ? {} : { parts: hint.parts }),
|
|
389
|
+
...(hint.paddingLeft ? { gapLeft: INLAY_GAP } : {}),
|
|
390
|
+
...(hint.paddingRight ? { gapRight: INLAY_GAP } : {})
|
|
391
|
+
}))
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
onInlayHover(listener: (target: EditorInlayTarget | null) => void): () => void {
|
|
396
|
+
return this.view.onInlayHover(listener);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
onInlayClick(handler: EditorInlayClickHandler): () => void {
|
|
400
|
+
return this.controller.onInlayClick(handler);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
setInlayLink(target: { readonly id: string; readonly part: number | null } | null): void {
|
|
404
|
+
this.view.setInlayLink(target);
|
|
327
405
|
}
|
|
328
406
|
|
|
329
407
|
setGhostText(ghost: EditorGhostText | null): void {
|
|
@@ -336,7 +414,7 @@ class SmartEditor implements Editor {
|
|
|
336
414
|
? null
|
|
337
415
|
: tokens.map((token) => {
|
|
338
416
|
const from = this.offsetAt({ line: token.line, character: token.character });
|
|
339
|
-
return { from, to: from + token.length, scopes: token.scopes };
|
|
417
|
+
return { from, to: from + token.length, scopes: token.scopes, within: token.within };
|
|
340
418
|
})
|
|
341
419
|
);
|
|
342
420
|
}
|
|
@@ -393,19 +471,23 @@ class SmartEditor implements Editor {
|
|
|
393
471
|
|
|
394
472
|
replace(replacement: string, replaceOptions: EditorReplaceOptions = {}): boolean {
|
|
395
473
|
const { find } = this.view;
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
if (mark === null || query === null || this.blockedByReadOnly()) {
|
|
474
|
+
const match = find.currentMatch;
|
|
475
|
+
if (this.blockedByReadOnly()) {
|
|
399
476
|
return false;
|
|
400
477
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
const match = this.model.find(query.text, options)[0];
|
|
404
|
-
if (match === undefined) {
|
|
478
|
+
if (find.activeQuery?.regex === true) {
|
|
479
|
+
if (find.currentMark === null) {
|
|
405
480
|
return false;
|
|
406
481
|
}
|
|
482
|
+
void find.replaceOne(replacement, replaceOptions.preserveCase === true, () => !this.disposed && !this.settings.readOnly);
|
|
483
|
+
return true;
|
|
484
|
+
}
|
|
485
|
+
if (match === null) {
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
try {
|
|
407
489
|
// The next match is looked for after what was written, or a replacement that holds the query would find itself again.
|
|
408
|
-
find.pendingAnchor =
|
|
490
|
+
find.pendingAnchor = match.to;
|
|
409
491
|
if (!this.model.replace(match, replacement, { preserveCase: replaceOptions.preserveCase })) {
|
|
410
492
|
find.pendingAnchor = undefined;
|
|
411
493
|
return false;
|
|
@@ -420,23 +502,49 @@ class SmartEditor implements Editor {
|
|
|
420
502
|
}
|
|
421
503
|
|
|
422
504
|
replaceAll(replacement: string, options: EditorReplaceOptions = {}): number {
|
|
423
|
-
const
|
|
424
|
-
|
|
505
|
+
const { find } = this.view;
|
|
506
|
+
const query = find.activeQuery;
|
|
507
|
+
if (query === null || find.state.noSelection === true || this.blockedByReadOnly()) {
|
|
425
508
|
return 0;
|
|
426
509
|
}
|
|
427
510
|
try {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
511
|
+
const matches = find.completeMatches;
|
|
512
|
+
if (matches !== null) {
|
|
513
|
+
let characters = 0;
|
|
514
|
+
for (const match of matches) {
|
|
515
|
+
characters += replacementText('', match, replacement, false, options.preserveCase, MAX_REPLACEMENT_CHARACTERS).length;
|
|
516
|
+
if (characters > MAX_REPLACEMENT_CHARACTERS) {
|
|
517
|
+
find.reportReplaceFailed('limit');
|
|
518
|
+
return 0;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
const count = this.model.replaceMatches(matches, replacement, { preserveCase: options.preserveCase });
|
|
522
|
+
find.reportReplaced(count);
|
|
523
|
+
return count;
|
|
524
|
+
}
|
|
525
|
+
void this.replaceAllAsync(replacement, options);
|
|
526
|
+
return 0;
|
|
527
|
+
} catch (error) {
|
|
528
|
+
find.reportReplaceFailed(error instanceof RangeError ? 'limit' : 'error');
|
|
436
529
|
return 0;
|
|
437
530
|
}
|
|
438
531
|
}
|
|
439
532
|
|
|
533
|
+
async replaceAllAsync(replacement: string, options: EditorReplaceOptions = {}): Promise<number> {
|
|
534
|
+
const { find } = this.view;
|
|
535
|
+
if (this.disposed || this.blockedByReadOnly() || find.activeQuery === null || find.state.noSelection) {
|
|
536
|
+
return 0;
|
|
537
|
+
}
|
|
538
|
+
if (find.state.limited) {
|
|
539
|
+
find.reportReplaceFailed('limit');
|
|
540
|
+
return 0;
|
|
541
|
+
}
|
|
542
|
+
if (find.completeMatches !== null) {
|
|
543
|
+
return this.replaceAll(replacement, options);
|
|
544
|
+
}
|
|
545
|
+
return find.replaceAll(replacement, options.preserveCase === true, () => !this.disposed && !this.settings.readOnly);
|
|
546
|
+
}
|
|
547
|
+
|
|
440
548
|
setReplacePreview(replacement: string | null, options: EditorReplaceOptions = {}): void {
|
|
441
549
|
this.view.setReplacePreview(replacement, options.preserveCase === true);
|
|
442
550
|
}
|
|
@@ -462,24 +570,39 @@ class SmartEditor implements Editor {
|
|
|
462
570
|
if (query.text === '') {
|
|
463
571
|
return false;
|
|
464
572
|
}
|
|
465
|
-
|
|
573
|
+
const { anchor, head } = this.model.getPrimary();
|
|
574
|
+
const offset = direction === 1 ? Math.max(anchor, head) : Math.min(anchor, head);
|
|
575
|
+
const reveal = (target: { from: number; to: number } | null): boolean => {
|
|
576
|
+
if (target === null || this.disposed) {
|
|
577
|
+
return false;
|
|
578
|
+
}
|
|
579
|
+
this.view.ensureVisible(target.from);
|
|
580
|
+
this.model.setSelections([direction === 1 ? { anchor: target.from, head: target.to } : { anchor: target.to, head: target.from }]);
|
|
581
|
+
this.view.revealCaret(target.from > head ? 'centerDown' : 'centerUp');
|
|
582
|
+
return true;
|
|
583
|
+
};
|
|
584
|
+
if (query.regex) {
|
|
585
|
+
const revision = this.model.getRevision();
|
|
586
|
+
void this.view.find.nextFromCursor(query, offset, direction).then((target) => {
|
|
587
|
+
const current = this.model.getPrimary();
|
|
588
|
+
if (this.model.getRevision() === revision && current.anchor === anchor && current.head === head) {
|
|
589
|
+
reveal(target);
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
return true;
|
|
593
|
+
}
|
|
466
594
|
try {
|
|
467
|
-
|
|
595
|
+
return reveal(
|
|
596
|
+
this.model.findNext(query.text, offset, {
|
|
597
|
+
caseSensitive: query.caseSensitive,
|
|
598
|
+
wholeWord: query.wholeWord,
|
|
599
|
+
backwards: direction === -1,
|
|
600
|
+
wrap: true
|
|
601
|
+
})
|
|
602
|
+
);
|
|
468
603
|
} catch {
|
|
469
604
|
return false;
|
|
470
605
|
}
|
|
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
606
|
}
|
|
484
607
|
|
|
485
608
|
private blockedByReadOnly(): boolean {
|
|
@@ -514,8 +637,8 @@ class SmartEditor implements Editor {
|
|
|
514
637
|
this.view.applySettings();
|
|
515
638
|
}
|
|
516
639
|
|
|
517
|
-
setChangeMarks(marks: readonly EditorChangeMark[]): void {
|
|
518
|
-
this.view.setChangeMarks(marks);
|
|
640
|
+
setChangeMarks(marks: readonly EditorChangeMark[], layer?: string): void {
|
|
641
|
+
this.view.setChangeMarks(marks, layer);
|
|
519
642
|
}
|
|
520
643
|
|
|
521
644
|
setAttributionMarks(marks: readonly EditorAttributionMark[]): void {
|
|
@@ -526,6 +649,32 @@ class SmartEditor implements Editor {
|
|
|
526
649
|
this.view.setLineHighlights(highlights);
|
|
527
650
|
}
|
|
528
651
|
|
|
652
|
+
setLineLabels(labels: ((line: number) => string | null) | null): void {
|
|
653
|
+
this.view.setLineLabels(labels);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
setInputPopup(popup: EditorInputPopup | null): void {
|
|
657
|
+
this.view.setInputPopup(popup);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
announce(message: string): void {
|
|
661
|
+
this.view.announce(message);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
setMessages(messages: Partial<EditorMessages>): void {
|
|
665
|
+
this.settings.messages = { ...this.settings.messages, ...messages };
|
|
666
|
+
this.view.repaint();
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
setLineNumbers(labels: ((line: number) => string | null) | null): void {
|
|
670
|
+
this.view.setLineNumbers(labels);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
setLineTokens(tokens: ((line: number) => readonly LineToken[] | null | undefined) | null): void {
|
|
674
|
+
this.grammarPreparation.pause(tokens !== null);
|
|
675
|
+
this.view.setLineTokens(tokens);
|
|
676
|
+
}
|
|
677
|
+
|
|
529
678
|
renderCode(container: HTMLElement, text: string, options: EditorCodeBlockOptions = {}): void {
|
|
530
679
|
this.view.renderCode(container, text, options);
|
|
531
680
|
}
|
|
@@ -546,6 +695,7 @@ class SmartEditor implements Editor {
|
|
|
546
695
|
at: this.offsetAt({ line: widget.line, character: 0 }),
|
|
547
696
|
placement: widget.placement ?? 'below',
|
|
548
697
|
height: widget.height,
|
|
698
|
+
fixed: widget.fixed,
|
|
549
699
|
render: (container: HTMLElement) => {
|
|
550
700
|
container.classList.add('se-widget');
|
|
551
701
|
widget.render(container);
|
|
@@ -554,6 +704,25 @@ class SmartEditor implements Editor {
|
|
|
554
704
|
);
|
|
555
705
|
}
|
|
556
706
|
|
|
707
|
+
setReplacements(replacements: readonly EditorReplacement[], owner: string = DEFAULT_WIDGET_OWNER): void {
|
|
708
|
+
this.view.setReplacements(
|
|
709
|
+
owner,
|
|
710
|
+
replacements.map((replacement) => ({
|
|
711
|
+
id: `replacement:${owner}:${replacement.id}`,
|
|
712
|
+
from: this.offsetAt({ line: replacement.startLine, character: 0 }),
|
|
713
|
+
to: this.model.getLine(Math.max(0, Math.min(replacement.endLine, this.model.getLineCount() - 1))).end,
|
|
714
|
+
height: replacement.height,
|
|
715
|
+
label: replacement.label,
|
|
716
|
+
enterable: replacement.enterable,
|
|
717
|
+
onReveal: replacement.onReveal === undefined ? undefined : () => replacement.onReveal!(),
|
|
718
|
+
render: (container: HTMLElement) => {
|
|
719
|
+
container.classList.add('se-widget-replacement');
|
|
720
|
+
replacement.render(container);
|
|
721
|
+
}
|
|
722
|
+
}))
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
|
|
557
726
|
setLineActions(actions: readonly EditorLineAction[], owner: string = DEFAULT_WIDGET_OWNER): void {
|
|
558
727
|
this.view.setLineActions(
|
|
559
728
|
owner,
|
|
@@ -714,6 +883,10 @@ class SmartEditor implements Editor {
|
|
|
714
883
|
this.view.applySettings();
|
|
715
884
|
}
|
|
716
885
|
|
|
886
|
+
isReadOnly(): boolean {
|
|
887
|
+
return this.settings.readOnly;
|
|
888
|
+
}
|
|
889
|
+
|
|
717
890
|
runCommand(command: EditorRunCommand): boolean {
|
|
718
891
|
return this.controller.command(command);
|
|
719
892
|
}
|
|
@@ -731,6 +904,7 @@ class SmartEditor implements Editor {
|
|
|
731
904
|
return;
|
|
732
905
|
}
|
|
733
906
|
this.disposed = true;
|
|
907
|
+
this.grammarPreparation.dispose();
|
|
734
908
|
this.changes.clear();
|
|
735
909
|
this.textChanges.clear();
|
|
736
910
|
this.carets.clear();
|