@adecore/editor 0.41.1 → 0.43.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/controller.d.ts +13 -2
- package/dist/controller.js +110 -9
- package/dist/editor.css +19 -0
- package/dist/engine.js +82 -5
- package/dist/fake.d.ts +31 -2
- package/dist/fake.js +100 -3
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -0
- package/dist/keymap-table.d.ts +28 -1
- package/dist/keymap-table.js +34 -3
- package/dist/keymap.js +23 -1
- package/dist/layout.d.ts +2 -0
- package/dist/layout.js +6 -1
- package/dist/outline.d.ts +1 -0
- package/dist/outline.js +10 -0
- package/dist/overview.d.ts +1 -1
- package/dist/paint.js +12 -2
- package/dist/todos.d.ts +23 -0
- package/dist/todos.js +86 -0
- package/dist/types.d.ts +34 -3
- package/dist/view.d.ts +17 -0
- package/dist/view.js +113 -12
- package/dist/zoom.d.ts +18 -0
- package/dist/zoom.js +41 -0
- package/package.json +2 -2
- package/src/controller.ts +150 -11
- package/src/editor.css +19 -0
- package/src/engine.ts +103 -5
- package/src/fake.ts +132 -3
- package/src/index.ts +7 -0
- package/src/keymap-table.ts +63 -4
- package/src/keymap.ts +23 -1
- package/src/layout.ts +9 -1
- package/src/outline.ts +13 -0
- package/src/overview.ts +1 -1
- package/src/paint.ts +11 -2
- package/src/todos.ts +111 -0
- package/src/types.ts +76 -4
- package/src/view.ts +143 -13
- package/src/zoom.ts +62 -0
package/dist/controller.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Keymap } from './keymap-table.ts';
|
|
2
|
-
import type { EditorClickHandler, EditorInlayClickHandler, EditorContextMenu, EditorKeyHandler, EditorRunCommand, EditorSelectionRanges, KeyChord } from './types.ts';
|
|
2
|
+
import type { EditorClickHandler, EditorInlayClickHandler, EditorContextMenu, EditorCopy, EditorKeyHandler, EditorPasteOptions, EditorRunCommand, EditorSelectionRanges, KeyChord } from './types.ts';
|
|
3
3
|
import type { EditorView } from './view.ts';
|
|
4
4
|
export interface ControllerOptions {
|
|
5
5
|
keymap?: Keymap;
|
|
@@ -7,13 +7,19 @@ export interface ControllerOptions {
|
|
|
7
7
|
apple: boolean;
|
|
8
8
|
save(): void;
|
|
9
9
|
blur(): void;
|
|
10
|
+
zoom(step: -1 | 0 | 1): void;
|
|
11
|
+
wheelZooms(): boolean;
|
|
10
12
|
}
|
|
13
|
+
export declare function rememberCopy(text: string, wholeLines: boolean): void;
|
|
11
14
|
export declare class InputController {
|
|
12
15
|
private readonly native;
|
|
13
16
|
private readonly pointer;
|
|
14
17
|
private readonly touch;
|
|
15
18
|
private readonly cleanup;
|
|
16
19
|
private readonly keyHandlers;
|
|
20
|
+
private readonly navigateListeners;
|
|
21
|
+
private readonly copyListeners;
|
|
22
|
+
private wordSources;
|
|
17
23
|
private readonly gesture;
|
|
18
24
|
private readonly clickHandlers;
|
|
19
25
|
private readonly inlayHandlers;
|
|
@@ -25,6 +31,7 @@ export declare class InputController {
|
|
|
25
31
|
private selectionRanges;
|
|
26
32
|
private expansions;
|
|
27
33
|
private lastInputAt;
|
|
34
|
+
private readonly wheelZoom;
|
|
28
35
|
private desiredXs;
|
|
29
36
|
private keepDesiredXs;
|
|
30
37
|
private readonly view;
|
|
@@ -38,13 +45,17 @@ export declare class InputController {
|
|
|
38
45
|
private applyEdits;
|
|
39
46
|
private replaceSelections;
|
|
40
47
|
private unfoldCarets;
|
|
41
|
-
|
|
48
|
+
pasteText(text: string, options?: EditorPasteOptions): boolean;
|
|
49
|
+
onCopy(listener: (copy: EditorCopy) => void): () => void;
|
|
42
50
|
private commandOptions;
|
|
51
|
+
setWordSources(source: (() => readonly string[]) | null): void;
|
|
43
52
|
private viewCommand;
|
|
53
|
+
private wheel;
|
|
44
54
|
command(name: EditorRunCommand): boolean;
|
|
45
55
|
setSelectionRanges(provider: EditorSelectionRanges | null): void;
|
|
46
56
|
private expandThrough;
|
|
47
57
|
private modelCommand;
|
|
58
|
+
onNavigate(listener: (from: number) => void): () => void;
|
|
48
59
|
private typeText;
|
|
49
60
|
private movePages;
|
|
50
61
|
private toggleColumnMode;
|
package/dist/controller.js
CHANGED
|
@@ -6,6 +6,7 @@ import { NativeInput } from './native-input.js';
|
|
|
6
6
|
import { columnSelections, sameCell } from './column-selection.js';
|
|
7
7
|
import { PointerSelection } from './pointer.js';
|
|
8
8
|
import { TouchSelection } from './touch.js';
|
|
9
|
+
import { WheelZoom } from './zoom.js';
|
|
9
10
|
const MODIFYING = new Set([
|
|
10
11
|
'deleteWordLeft',
|
|
11
12
|
'deleteWordRight',
|
|
@@ -15,9 +16,12 @@ const MODIFYING = new Set([
|
|
|
15
16
|
'deleteLine',
|
|
16
17
|
'moveLineUp',
|
|
17
18
|
'moveLineDown',
|
|
19
|
+
'moveStatementUp',
|
|
20
|
+
'moveStatementDown',
|
|
18
21
|
'toggleLineComment',
|
|
19
22
|
'toggleBlockComment',
|
|
20
23
|
'startNewLine',
|
|
24
|
+
'completeCurrentStatement',
|
|
21
25
|
'startNewLineBefore',
|
|
22
26
|
'splitLine',
|
|
23
27
|
'joinLines',
|
|
@@ -28,8 +32,16 @@ const MODIFYING = new Set([
|
|
|
28
32
|
'outdent',
|
|
29
33
|
'insertNewline',
|
|
30
34
|
'smartBackspace',
|
|
31
|
-
'deleteForward'
|
|
35
|
+
'deleteForward',
|
|
36
|
+
'sortLines',
|
|
37
|
+
'reverseLines',
|
|
38
|
+
'uniqueLines',
|
|
39
|
+
'convertIndentsToSpaces',
|
|
40
|
+
'convertIndentsToTabs',
|
|
41
|
+
'hippieCompletion',
|
|
42
|
+
'hippieBackwardCompletion'
|
|
32
43
|
]);
|
|
44
|
+
const CODE_BLOCK_COMMANDS = new Set(['codeBlockStart', 'codeBlockEnd', 'codeBlockStartWithSelection', 'codeBlockEndWithSelection']);
|
|
33
45
|
const VIEW_COMMANDS = new Set([
|
|
34
46
|
'collapseRegion',
|
|
35
47
|
'expandRegion',
|
|
@@ -45,10 +57,26 @@ const VIEW_COMMANDS = new Set([
|
|
|
45
57
|
'expandAllToLevel3',
|
|
46
58
|
'expandAllToLevel4',
|
|
47
59
|
'expandAllToLevel5',
|
|
48
|
-
'toggleColumnMode'
|
|
60
|
+
'toggleColumnMode',
|
|
61
|
+
'increaseFontSize',
|
|
62
|
+
'decreaseFontSize',
|
|
63
|
+
'resetFontSize',
|
|
64
|
+
'methodUp',
|
|
65
|
+
'methodDown',
|
|
66
|
+
'nextChange',
|
|
67
|
+
'previousChange',
|
|
68
|
+
'pasteAsPlainText'
|
|
49
69
|
]);
|
|
70
|
+
const ZOOM_STEPS = { increaseFontSize: 1, decreaseFontSize: -1, resetFontSize: 0 };
|
|
50
71
|
/* 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. */
|
|
51
72
|
let lineClip = null;
|
|
73
|
+
/*
|
|
74
|
+
* Makes `text` the copy that pastes back as whole lines (or not), for a paste from a history that kept what the
|
|
75
|
+
* copy was. The clipboard itself is the caller's.
|
|
76
|
+
*/
|
|
77
|
+
export function rememberCopy(text, wholeLines) {
|
|
78
|
+
lineClip = wholeLines ? text : null;
|
|
79
|
+
}
|
|
52
80
|
function selectionSignature(selections) {
|
|
53
81
|
return selections.map((selection) => `${selection.anchor}:${selection.head}`).join(',');
|
|
54
82
|
}
|
|
@@ -66,6 +94,9 @@ export class InputController {
|
|
|
66
94
|
touch;
|
|
67
95
|
cleanup = [];
|
|
68
96
|
keyHandlers = new Set();
|
|
97
|
+
navigateListeners = new Set();
|
|
98
|
+
copyListeners = new Set();
|
|
99
|
+
wordSources = null;
|
|
69
100
|
gesture;
|
|
70
101
|
clickHandlers = new Set();
|
|
71
102
|
inlayHandlers = new Set();
|
|
@@ -79,6 +110,7 @@ export class InputController {
|
|
|
79
110
|
/* The Extend Selection presses that wait for a server's ranges, which are answered one after the other. */
|
|
80
111
|
expansions = Promise.resolve();
|
|
81
112
|
lastInputAt = 0;
|
|
113
|
+
wheelZoom = new WheelZoom();
|
|
82
114
|
desiredXs = [];
|
|
83
115
|
keepDesiredXs = false;
|
|
84
116
|
view;
|
|
@@ -175,12 +207,12 @@ export class InputController {
|
|
|
175
207
|
});
|
|
176
208
|
this.listen(input, 'paste', (event) => {
|
|
177
209
|
event.preventDefault();
|
|
178
|
-
this.
|
|
210
|
+
this.pasteText(event.clipboardData?.getData('text/plain') ?? '');
|
|
179
211
|
});
|
|
180
212
|
this.listen(input, 'copy', (event) => this.copy(event, false));
|
|
181
213
|
this.listen(input, 'cut', (event) => this.copy(event, true));
|
|
182
214
|
this.listen(viewport, 'scroll', () => this.view.scrolled());
|
|
183
|
-
this.listen(viewport, 'wheel', () => this.
|
|
215
|
+
this.listen(viewport, 'wheel', (event) => this.wheel(event));
|
|
184
216
|
this.listen(viewport, 'pointermove', (event) => this.view.hoverMoved(event));
|
|
185
217
|
this.listen(viewport, 'pointerleave', () => {
|
|
186
218
|
this.view.setHover(null);
|
|
@@ -234,6 +266,8 @@ export class InputController {
|
|
|
234
266
|
}
|
|
235
267
|
this.cleanup.length = 0;
|
|
236
268
|
this.keyHandlers.clear();
|
|
269
|
+
this.navigateListeners.clear();
|
|
270
|
+
this.copyListeners.clear();
|
|
237
271
|
}
|
|
238
272
|
blockedByReadOnly() {
|
|
239
273
|
return this.view.blockedByReadOnly();
|
|
@@ -267,15 +301,25 @@ export class InputController {
|
|
|
267
301
|
this.view.ensureVisible(selection.head);
|
|
268
302
|
}
|
|
269
303
|
}
|
|
270
|
-
paste
|
|
304
|
+
/* Pastes as a paste from the clipboard does; false when a read-only editor refused. */
|
|
305
|
+
pasteText(text, options = {}) {
|
|
271
306
|
if (this.blockedByReadOnly()) {
|
|
272
|
-
return;
|
|
307
|
+
return false;
|
|
273
308
|
}
|
|
274
309
|
this.unfoldCarets();
|
|
275
310
|
this.historyGroup++;
|
|
276
|
-
this.view.model.paste(this.native.normalizeNewlines(text), {
|
|
311
|
+
const pasted = this.view.model.paste(this.native.normalizeNewlines(text), {
|
|
312
|
+
...this.commandOptions(),
|
|
313
|
+
...(options.reindent === false ? { indentOnPaste: false } : {}),
|
|
314
|
+
wholeLines: options.wholeLines ?? (text !== '' && text === lineClip)
|
|
315
|
+
});
|
|
277
316
|
this.historyGroup++;
|
|
278
317
|
this.view.revealCaret();
|
|
318
|
+
return pasted;
|
|
319
|
+
}
|
|
320
|
+
/* Told what a copy or cut put on the clipboard. */
|
|
321
|
+
onCopy(listener) {
|
|
322
|
+
return subscribe(this.copyListeners, listener);
|
|
279
323
|
}
|
|
280
324
|
commandOptions() {
|
|
281
325
|
const { settings } = this.view;
|
|
@@ -289,17 +333,47 @@ export class InputController {
|
|
|
289
333
|
tabOutOfClosers: keys.tabOutOfClosers,
|
|
290
334
|
smartEnter: keys.smartIndentOnEnter,
|
|
291
335
|
indentOnPaste: keys.indentOnPaste,
|
|
336
|
+
indentGuides: settings.guides,
|
|
292
337
|
smartSemicolon: keys.smartSemicolon,
|
|
293
338
|
smartArrow: keys.smartArrow,
|
|
294
339
|
language: settings.language ?? 'plaintext',
|
|
295
340
|
camelCase: keys.camelHumps,
|
|
296
341
|
visualLine: (offset) => this.view.rowOf(offset),
|
|
297
|
-
lineSpan: (line) => this.view.lineSpan(line)
|
|
342
|
+
lineSpan: (line) => this.view.lineSpan(line),
|
|
343
|
+
structure: () => this.view.structureRanges(),
|
|
344
|
+
...(this.wordSources === null ? {} : { wordSources: this.wordSources })
|
|
298
345
|
};
|
|
299
346
|
}
|
|
347
|
+
/* The texts Hippie completion takes words from after those of this document; null takes them away. */
|
|
348
|
+
setWordSources(source) {
|
|
349
|
+
this.wordSources = source;
|
|
350
|
+
}
|
|
300
351
|
/* Folding is the view's, since it knows where the ranges are. */
|
|
301
352
|
viewCommand(name) {
|
|
302
353
|
const { view } = this;
|
|
354
|
+
if (name in ZOOM_STEPS) {
|
|
355
|
+
this.options.zoom(ZOOM_STEPS[name]);
|
|
356
|
+
return true;
|
|
357
|
+
}
|
|
358
|
+
if (name === 'methodUp' || name === 'methodDown') {
|
|
359
|
+
const from = view.model.getPrimary().head;
|
|
360
|
+
const moved = view.goToMethod(name === 'methodUp' ? -1 : 1);
|
|
361
|
+
if (moved) {
|
|
362
|
+
emit(this.navigateListeners, from);
|
|
363
|
+
}
|
|
364
|
+
return moved;
|
|
365
|
+
}
|
|
366
|
+
if (name === 'pasteAsPlainText') {
|
|
367
|
+
// The clipboard is read once a person asks, and an editor with no way to read it does nothing.
|
|
368
|
+
if (this.blockedByReadOnly() || globalThis.navigator?.clipboard?.readText === undefined) {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
void navigator.clipboard.readText().then((text) => this.pasteText(text, { reindent: false }), () => undefined);
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
if (name === 'nextChange' || name === 'previousChange') {
|
|
375
|
+
return view.goToChange(name === 'nextChange' ? 1 : -1);
|
|
376
|
+
}
|
|
303
377
|
if (view.settings.folding === false && name !== 'toggleColumnMode') {
|
|
304
378
|
return false;
|
|
305
379
|
}
|
|
@@ -323,6 +397,18 @@ export class InputController {
|
|
|
323
397
|
run[name]();
|
|
324
398
|
return true;
|
|
325
399
|
}
|
|
400
|
+
/* A pinch comes as the wheel with Ctrl, on every platform. */
|
|
401
|
+
wheel(event) {
|
|
402
|
+
this.view.cancelScroll();
|
|
403
|
+
if (!(event.ctrlKey || (this.options.apple && event.metaKey)) || !this.options.wheelZooms()) {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
event.preventDefault();
|
|
407
|
+
const step = this.wheelZoom.step(event);
|
|
408
|
+
if (step !== 0) {
|
|
409
|
+
this.options.zoom(step);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
326
412
|
/* Runs a model command on every caret, with the folds that would hide its result opened first. */
|
|
327
413
|
command(name) {
|
|
328
414
|
if (VIEW_COMMANDS.has(name)) {
|
|
@@ -386,7 +472,10 @@ export class InputController {
|
|
|
386
472
|
}
|
|
387
473
|
}
|
|
388
474
|
}
|
|
389
|
-
const
|
|
475
|
+
const before = model.getPrimary().head;
|
|
476
|
+
const folded = name === 'moveLineUp' || name === 'moveLineDown' || name === 'moveStatementUp' || name === 'moveStatementDown'
|
|
477
|
+
? this.view.getFolds().collapsed
|
|
478
|
+
: [];
|
|
390
479
|
const result = model.execute(name, {
|
|
391
480
|
...this.commandOptions(),
|
|
392
481
|
onLinesMoved: (moves) => this.view.refoldMoved(folded, moves)
|
|
@@ -397,9 +486,19 @@ export class InputController {
|
|
|
397
486
|
if (name === 'smartEnd' || name === 'selectSmartEnd') {
|
|
398
487
|
this.view.markRowEnd(model.getPrimary().head);
|
|
399
488
|
}
|
|
489
|
+
if (name === 'hippieCompletion' || name === 'hippieBackwardCompletion') {
|
|
490
|
+
this.view.setHippieSource(model.hippieSource);
|
|
491
|
+
}
|
|
400
492
|
this.view.revealCaret();
|
|
493
|
+
if (CODE_BLOCK_COMMANDS.has(name) && model.getPrimary().head !== before) {
|
|
494
|
+
emit(this.navigateListeners, before);
|
|
495
|
+
}
|
|
401
496
|
return result;
|
|
402
497
|
}
|
|
498
|
+
/* Told the offset the primary caret left when a command that navigates moved it. */
|
|
499
|
+
onNavigate(listener) {
|
|
500
|
+
return subscribe(this.navigateListeners, listener);
|
|
501
|
+
}
|
|
403
502
|
typeText(text) {
|
|
404
503
|
if (this.blockedByReadOnly()) {
|
|
405
504
|
return;
|
|
@@ -624,6 +723,7 @@ export class InputController {
|
|
|
624
723
|
if (event.isComposing || this.native.composing) {
|
|
625
724
|
return;
|
|
626
725
|
}
|
|
726
|
+
this.view.setHippieSource(null);
|
|
627
727
|
const added = this.gesture.keydown(event);
|
|
628
728
|
if (added !== null) {
|
|
629
729
|
event.preventDefault();
|
|
@@ -699,6 +799,7 @@ export class InputController {
|
|
|
699
799
|
: selections.map((selection) => model.slice(Math.min(selection.anchor, selection.head), Math.max(selection.anchor, selection.head))).join('\n');
|
|
700
800
|
event.clipboardData?.setData('text/plain', text);
|
|
701
801
|
lineClip = empty ? text : null;
|
|
802
|
+
emit(this.copyListeners, { text, wholeLines: empty, cut });
|
|
702
803
|
if (!cut || this.blockedByReadOnly()) {
|
|
703
804
|
return;
|
|
704
805
|
}
|
package/dist/editor.css
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
--editor-added: var(--status-idle);
|
|
18
18
|
--editor-modified: var(--accent);
|
|
19
19
|
--editor-deleted: var(--status-error);
|
|
20
|
+
--editor-todo: var(--status-running);
|
|
20
21
|
--editor-mark-number-mix: 72%;
|
|
21
22
|
--editor-mark-bar-width: 3px;
|
|
22
23
|
--editor-mark-gap: 4px;
|
|
@@ -38,6 +39,7 @@
|
|
|
38
39
|
--editor-margin: color-mix(in srgb, var(--text) 16%, transparent);
|
|
39
40
|
--editor-whitespace: color-mix(in srgb, var(--text) 32%, transparent);
|
|
40
41
|
--editor-hatch: color-mix(in srgb, var(--text) 10%, transparent);
|
|
42
|
+
--editor-todo: var(--status-idle);
|
|
41
43
|
--editor-mark-number-mix: 82%;
|
|
42
44
|
--editor-sticky-shadow: color-mix(in srgb, black 60%, transparent);
|
|
43
45
|
}
|
|
@@ -478,11 +480,15 @@
|
|
|
478
480
|
left: 6px;
|
|
479
481
|
background: var(--editor-occurrence);
|
|
480
482
|
}
|
|
483
|
+
.se-tick-todo,
|
|
481
484
|
.se-tick-error,
|
|
482
485
|
.se-tick-warning,
|
|
483
486
|
.se-tick-info {
|
|
484
487
|
left: 10px;
|
|
485
488
|
}
|
|
489
|
+
.se-tick-todo {
|
|
490
|
+
background: var(--editor-todo);
|
|
491
|
+
}
|
|
486
492
|
/* The only part of the track that takes the pointer, with a wider reach than the 4px it draws. */
|
|
487
493
|
.se-tick[data-offset] {
|
|
488
494
|
pointer-events: auto;
|
|
@@ -646,11 +652,16 @@
|
|
|
646
652
|
.se-find-match,
|
|
647
653
|
.se-find-current,
|
|
648
654
|
.se-occurrence,
|
|
655
|
+
.se-hippie-source,
|
|
649
656
|
.se-bracket-match,
|
|
650
657
|
.se-bracket-unmatched {
|
|
651
658
|
position: absolute;
|
|
652
659
|
border-radius: var(--radius-xs);
|
|
653
660
|
}
|
|
661
|
+
.se-hippie-source {
|
|
662
|
+
background: color-mix(in srgb, var(--accent) 22%, transparent);
|
|
663
|
+
border-radius: var(--radius-sm);
|
|
664
|
+
}
|
|
654
665
|
.se-find-match {
|
|
655
666
|
background: var(--find-match);
|
|
656
667
|
}
|
|
@@ -741,6 +752,14 @@
|
|
|
741
752
|
white-space: pre;
|
|
742
753
|
cursor: default;
|
|
743
754
|
}
|
|
755
|
+
.se-swatch {
|
|
756
|
+
height: 12px;
|
|
757
|
+
margin-top: calc((var(--code-line-height, 20px) - 12px) / 2);
|
|
758
|
+
padding: 0;
|
|
759
|
+
border-radius: var(--radius-sm);
|
|
760
|
+
box-shadow: inset 0 0 0 1px var(--border);
|
|
761
|
+
cursor: pointer;
|
|
762
|
+
}
|
|
744
763
|
.se-inlay-link {
|
|
745
764
|
text-decoration: underline;
|
|
746
765
|
text-underline-offset: 2px;
|
package/dist/engine.js
CHANGED
|
@@ -6,6 +6,7 @@ import { resolveSmartKeys } from './smart-keys.js';
|
|
|
6
6
|
import { emit, subscribe } from './listeners.js';
|
|
7
7
|
import { fillLens, lensSignature } from './lens.js';
|
|
8
8
|
import { EditorView } from './view.js';
|
|
9
|
+
import { clampFontSize, fontSizeRange } from './zoom.js';
|
|
9
10
|
const DEFAULT_TAB_SIZE = 4;
|
|
10
11
|
const DEFAULT_LABEL = 'Code editor';
|
|
11
12
|
const DEFAULT_WIDGET_OWNER = 'default';
|
|
@@ -23,6 +24,9 @@ class SmartEditor {
|
|
|
23
24
|
caretOffset = 0;
|
|
24
25
|
saves = new Set();
|
|
25
26
|
blurs = new Set();
|
|
27
|
+
fontSizes = new Set();
|
|
28
|
+
fontSizeRange;
|
|
29
|
+
fontSize = null;
|
|
26
30
|
subscription;
|
|
27
31
|
language;
|
|
28
32
|
theme;
|
|
@@ -31,10 +35,13 @@ class SmartEditor {
|
|
|
31
35
|
lensRevisions = new Map();
|
|
32
36
|
tokenizerRequest = 0;
|
|
33
37
|
settingText = false;
|
|
38
|
+
/* The hints of each owner, in the order the owners first set theirs. */
|
|
39
|
+
inlayOwners = new Map();
|
|
34
40
|
disposed = false;
|
|
35
41
|
engine;
|
|
36
42
|
constructor(engine, element, options) {
|
|
37
43
|
this.engine = engine;
|
|
44
|
+
this.fontSizeRange = fontSizeRange(options.zoom);
|
|
38
45
|
this.model = new DocumentModel(options.text);
|
|
39
46
|
this.language = options.language;
|
|
40
47
|
this.theme = options.theme;
|
|
@@ -54,7 +61,8 @@ class SmartEditor {
|
|
|
54
61
|
whitespace: options.whitespace === true,
|
|
55
62
|
rightMargin: options.rightMargin ?? null,
|
|
56
63
|
foldOutline: options.foldOutline ?? 'hover',
|
|
57
|
-
folding: options.folding !== false
|
|
64
|
+
folding: options.folding !== false,
|
|
65
|
+
todoWords: options.todoWords
|
|
58
66
|
};
|
|
59
67
|
this.view = new EditorView(element, this.model, this.settings);
|
|
60
68
|
this.grammarPreparation = new GrammarPreparation(this.model, () => this.view.grammarChanged());
|
|
@@ -63,8 +71,13 @@ class SmartEditor {
|
|
|
63
71
|
keymap: engine.keymap,
|
|
64
72
|
apple: engine.apple ?? false,
|
|
65
73
|
save: () => emit(this.saves),
|
|
66
|
-
blur: () => emit(this.blurs)
|
|
74
|
+
blur: () => emit(this.blurs),
|
|
75
|
+
zoom: (step) => this.zoom(step),
|
|
76
|
+
wheelZooms: () => options.zoom?.wheel !== false
|
|
67
77
|
});
|
|
78
|
+
if (options.fontSize !== undefined) {
|
|
79
|
+
this.setFontSize(options.fontSize);
|
|
80
|
+
}
|
|
68
81
|
this.revision = this.model.getRevision();
|
|
69
82
|
this.caretOffset = this.model.getPrimary().head;
|
|
70
83
|
this.subscription = this.model.subscribe((snapshot) => {
|
|
@@ -273,6 +286,14 @@ class SmartEditor {
|
|
|
273
286
|
return false;
|
|
274
287
|
}
|
|
275
288
|
}
|
|
289
|
+
cleanUpWhitespace(options) {
|
|
290
|
+
const changed = !this.settings.readOnly && this.model.cleanUpWhitespace({ ...options, language: this.settings.language });
|
|
291
|
+
this.model.markSaved();
|
|
292
|
+
return changed;
|
|
293
|
+
}
|
|
294
|
+
markSaved() {
|
|
295
|
+
this.model.markSaved();
|
|
296
|
+
}
|
|
276
297
|
setMarkers(markers) {
|
|
277
298
|
this.view.setMarkers(markers.map((marker) => ({
|
|
278
299
|
severity: marker.severity,
|
|
@@ -283,14 +304,21 @@ class SmartEditor {
|
|
|
283
304
|
...(marker.message === undefined ? {} : { message: marker.message })
|
|
284
305
|
})));
|
|
285
306
|
}
|
|
286
|
-
setInlayHints(hints) {
|
|
287
|
-
|
|
307
|
+
setInlayHints(hints, owner = DEFAULT_WIDGET_OWNER) {
|
|
308
|
+
if (hints.length === 0) {
|
|
309
|
+
this.inlayOwners.delete(owner);
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
this.inlayOwners.set(owner, hints);
|
|
313
|
+
}
|
|
314
|
+
this.view.setInlays([...this.inlayOwners.values()].flat().map((hint, index) => ({
|
|
288
315
|
id: hint.id ?? `hint-${index}`,
|
|
289
316
|
at: this.offsetAt(hint.position),
|
|
290
317
|
text: hint.label,
|
|
291
318
|
...(hint.parts === undefined ? {} : { parts: hint.parts }),
|
|
292
319
|
...(hint.paddingLeft ? { gapLeft: INLAY_GAP } : {}),
|
|
293
|
-
...(hint.paddingRight ? { gapRight: INLAY_GAP } : {})
|
|
320
|
+
...(hint.paddingRight ? { gapRight: INLAY_GAP } : {}),
|
|
321
|
+
...(hint.swatch === undefined ? {} : { swatch: hint.swatch })
|
|
294
322
|
})));
|
|
295
323
|
}
|
|
296
324
|
onInlayHover(listener) {
|
|
@@ -655,9 +683,31 @@ class SmartEditor {
|
|
|
655
683
|
this.settings.label = label;
|
|
656
684
|
this.view.applySettings();
|
|
657
685
|
}
|
|
686
|
+
setTodoWords(words) {
|
|
687
|
+
this.settings.todoWords = [...words];
|
|
688
|
+
this.view.applySettings();
|
|
689
|
+
}
|
|
658
690
|
setFoldHints(hints) {
|
|
659
691
|
this.view.setFoldHints(hints === null ? null : this.foldHintsByOffset(hints));
|
|
660
692
|
}
|
|
693
|
+
setDeclarations(positions) {
|
|
694
|
+
this.view.setDeclarations(positions === null ? null : positions.map((position) => this.offsetAt(position)));
|
|
695
|
+
}
|
|
696
|
+
onNavigate(listener) {
|
|
697
|
+
return this.controller.onNavigate((from) => listener(this.positionAt(from)));
|
|
698
|
+
}
|
|
699
|
+
hasFocus() {
|
|
700
|
+
return this.view.focused;
|
|
701
|
+
}
|
|
702
|
+
setWordSources(source) {
|
|
703
|
+
this.controller.setWordSources(source);
|
|
704
|
+
}
|
|
705
|
+
onCopy(listener) {
|
|
706
|
+
return this.controller.onCopy(listener);
|
|
707
|
+
}
|
|
708
|
+
paste(text, options) {
|
|
709
|
+
return this.controller.pasteText(text, options);
|
|
710
|
+
}
|
|
661
711
|
foldHintsByOffset(hints) {
|
|
662
712
|
const lineOf = (line) => this.model.getLine(Math.min(line, this.model.getLineCount() - 1));
|
|
663
713
|
const within = (line, character) => Math.min(line.end, line.start + Math.max(0, character));
|
|
@@ -700,6 +750,32 @@ class SmartEditor {
|
|
|
700
750
|
refreshFont() {
|
|
701
751
|
this.view.refreshFont();
|
|
702
752
|
}
|
|
753
|
+
setFontSize(size) {
|
|
754
|
+
const next = size === null || !Number.isFinite(size) ? null : clampFontSize(size, this.fontSizeRange);
|
|
755
|
+
if (next !== this.fontSize) {
|
|
756
|
+
this.fontSize = next;
|
|
757
|
+
this.view.setFontSize(next);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
getFontSize() {
|
|
761
|
+
return this.view.getFontSize();
|
|
762
|
+
}
|
|
763
|
+
onFontSizeChange(listener) {
|
|
764
|
+
return subscribe(this.fontSizes, listener);
|
|
765
|
+
}
|
|
766
|
+
/* Steps from the size on screen, so the first step from the page's size is one pixel away from it. */
|
|
767
|
+
zoom(step) {
|
|
768
|
+
const before = this.fontSize;
|
|
769
|
+
const current = this.view.getFontSize();
|
|
770
|
+
// A page size outside the range must not make a step the other way.
|
|
771
|
+
if (step !== 0 && Math.sign(clampFontSize(current + step, this.fontSizeRange) - current) !== step) {
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
this.setFontSize(step === 0 ? null : current + step);
|
|
775
|
+
if (this.fontSize !== before) {
|
|
776
|
+
emit(this.fontSizes, this.fontSize);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
703
779
|
setReadOnly(readOnly, reason) {
|
|
704
780
|
this.settings.readOnly = readOnly;
|
|
705
781
|
this.settings.readOnlyReason = reason;
|
|
@@ -728,6 +804,7 @@ class SmartEditor {
|
|
|
728
804
|
this.carets.clear();
|
|
729
805
|
this.saves.clear();
|
|
730
806
|
this.blurs.clear();
|
|
807
|
+
this.fontSizes.clear();
|
|
731
808
|
this.subscription.dispose();
|
|
732
809
|
this.controller.dispose();
|
|
733
810
|
this.view.dispose();
|
package/dist/fake.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Editor, EditorAttributionHover, EditorAttributionMark, EditorBlock, EditorCodeBlockOptions, EditorLineAction, EditorReplacement, EditorLineHighlight, LineToken, EditorChangeMark, EditorCodeVision, EditorClick, EditorClickHandler, EditorContextMenu, EditorContentChange, EditorEngine, EditorFindQuery, EditorFindState, EditorReplaceOptions, EditorGutterAction, EditorGutterMarker, EditorHighlight, EditorHover, EditorIndentation, EditorGhostText, EditorInlayClick, EditorInlayClickHandler, EditorInlayHint, EditorInlayTarget, EditorKeyHandler, EditorInputPopup, EditorMarker, EditorMessages, EditorOptions, EditorPosition, EditorRemoteCursor, EditorRange, EditorReveal, EditorRect, EditorRunCommand, EditorSelectionRanges, EditorFoldHints, EditorFoldOutline, EditorFolds, EditorSemanticToken, EditorSmartKeys, EditorApplyEditsOptions, EditorTextChange, EditorTrackedRange, EditorTheme, EditorWidget } from './types.ts';
|
|
1
|
+
import type { Editor, EditorAttributionHover, EditorAttributionMark, EditorBlock, EditorCodeBlockOptions, EditorLineAction, EditorReplacement, EditorLineHighlight, LineToken, EditorChangeMark, EditorCodeVision, EditorClick, EditorClickHandler, EditorContextMenu, EditorContentChange, EditorEngine, EditorFindQuery, EditorFindState, EditorReplaceOptions, EditorGutterAction, EditorGutterMarker, EditorHighlight, EditorHover, EditorIndentation, EditorGhostText, EditorInlayClick, EditorInlayClickHandler, EditorInlayHint, EditorInlayTarget, EditorKeyHandler, EditorInputPopup, EditorMarker, EditorMessages, EditorOptions, EditorPosition, EditorRemoteCursor, EditorRange, EditorReveal, EditorRect, EditorRunCommand, EditorSelectionRanges, EditorFoldHints, EditorFoldOutline, EditorFolds, EditorSemanticToken, EditorSmartKeys, EditorApplyEditsOptions, EditorCopy, EditorPasteOptions, EditorTextChange, EditorTrackedRange, EditorTheme, EditorWhitespaceCleanup, EditorWidget } from './types.ts';
|
|
2
2
|
import { type Listener } from './listeners.ts';
|
|
3
3
|
export declare class FakeEditor implements Editor {
|
|
4
4
|
readonly element: HTMLElement;
|
|
@@ -139,7 +139,25 @@ export declare class FakeEditor implements Editor {
|
|
|
139
139
|
setFoldOutline(outline: EditorFoldOutline): void;
|
|
140
140
|
label: string;
|
|
141
141
|
setLabel(label: string): void;
|
|
142
|
+
todoWords: readonly string[] | undefined;
|
|
143
|
+
setTodoWords(words: readonly string[]): void;
|
|
142
144
|
setFoldHints(hints: EditorFoldHints | null): void;
|
|
145
|
+
declarations: readonly EditorPosition[] | null;
|
|
146
|
+
setDeclarations(positions: readonly EditorPosition[] | null): void;
|
|
147
|
+
private readonly navigations;
|
|
148
|
+
onNavigate(listener: (from: EditorPosition) => void): () => void;
|
|
149
|
+
navigate(from: EditorPosition): void;
|
|
150
|
+
hasFocus(): boolean;
|
|
151
|
+
private readonly copies;
|
|
152
|
+
onCopy(listener: (copy: EditorCopy) => void): () => void;
|
|
153
|
+
copy(text: string, wholeLines?: boolean, cut?: boolean): void;
|
|
154
|
+
readonly pastes: Array<{
|
|
155
|
+
text: string;
|
|
156
|
+
options: EditorPasteOptions | undefined;
|
|
157
|
+
}>;
|
|
158
|
+
paste(text: string, options?: EditorPasteOptions): boolean;
|
|
159
|
+
wordSources: (() => readonly string[]) | null;
|
|
160
|
+
setWordSources(source: (() => readonly string[]) | null): void;
|
|
143
161
|
setSmartKeys(keys: Partial<EditorSmartKeys>): void;
|
|
144
162
|
setIndentation(indentation: EditorIndentation): void;
|
|
145
163
|
markers: readonly EditorMarker[];
|
|
@@ -159,7 +177,8 @@ export declare class FakeEditor implements Editor {
|
|
|
159
177
|
private readonly keys;
|
|
160
178
|
setMarkers(markers: readonly EditorMarker[]): void;
|
|
161
179
|
inlayHints: readonly EditorInlayHint[];
|
|
162
|
-
|
|
180
|
+
private readonly inlayOwners;
|
|
181
|
+
setInlayHints(hints: readonly EditorInlayHint[], owner?: string): void;
|
|
163
182
|
private readonly inlayHovers;
|
|
164
183
|
private readonly inlayHandlers;
|
|
165
184
|
inlayLink: {
|
|
@@ -224,8 +243,18 @@ export declare class FakeEditor implements Editor {
|
|
|
224
243
|
openContextMenu(menu: EditorContextMenu): void;
|
|
225
244
|
press(event: Pick<KeyboardEvent, 'key'> & Partial<KeyboardEvent>): boolean;
|
|
226
245
|
applyEdits(edits: readonly EditorContentChange[], options?: EditorApplyEditsOptions): boolean;
|
|
246
|
+
readonly cleanups: EditorWhitespaceCleanup[];
|
|
247
|
+
savedMarks: number;
|
|
248
|
+
cleanUpWhitespace(options: EditorWhitespaceCleanup): boolean;
|
|
249
|
+
markSaved(): void;
|
|
227
250
|
setTheme(theme: EditorTheme): void;
|
|
228
251
|
refreshFont(): void;
|
|
252
|
+
fontSize: number | null;
|
|
253
|
+
private readonly fontSizeListeners;
|
|
254
|
+
setFontSize(size: number | null): void;
|
|
255
|
+
getFontSize(): number;
|
|
256
|
+
onFontSizeChange(listener: (size: number | null) => void): () => void;
|
|
257
|
+
zoomTo(size: number | null): void;
|
|
229
258
|
setReadOnly(readOnly: boolean, reason?: string): void;
|
|
230
259
|
isReadOnly(): boolean;
|
|
231
260
|
readonly commands: EditorRunCommand[];
|