@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/dist/attribution.d.ts
CHANGED
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
import type { DocumentChange } from '@adecore/editor-core';
|
|
2
|
+
import type { EditorLineHighlight } from './types.ts';
|
|
2
3
|
export declare const AGENT_COLORS: readonly ['--agent-1', '--agent-2', '--agent-3', '--agent-4', '--agent-5', '--agent-6'];
|
|
3
4
|
export interface AttributedLines {
|
|
4
5
|
readonly id: string;
|
|
5
6
|
readonly color: string;
|
|
6
7
|
readonly sign?: string;
|
|
7
8
|
readonly fill?: string;
|
|
9
|
+
readonly bar?: 'solid' | 'hatched';
|
|
10
|
+
readonly emphasis?: readonly {
|
|
11
|
+
from: number;
|
|
12
|
+
to: number;
|
|
13
|
+
}[];
|
|
8
14
|
}
|
|
9
15
|
export declare function tintValue(color: string): string;
|
|
10
16
|
export declare function colorValue(color: string): string;
|
|
11
17
|
export declare class AttributionRuns {
|
|
12
18
|
private runs;
|
|
13
19
|
get size(): number;
|
|
14
|
-
set(marks: readonly {
|
|
20
|
+
set(marks: readonly (EditorLineHighlight & {
|
|
15
21
|
id: string;
|
|
16
|
-
|
|
17
|
-
endLine: number;
|
|
18
|
-
color: string;
|
|
19
|
-
sign?: string;
|
|
20
|
-
fill?: string;
|
|
21
|
-
}[], lineCount: number, lineBounds: (line: number) => {
|
|
22
|
+
})[], lineCount: number, lineBounds: (line: number) => {
|
|
22
23
|
start: number;
|
|
23
24
|
end: number;
|
|
24
25
|
}): void;
|
|
25
26
|
clear(): void;
|
|
26
|
-
map(changes: readonly DocumentChange[]): void;
|
|
27
|
+
map(changes: readonly DocumentChange[], map?: (offset: number) => number): void;
|
|
27
28
|
linesIn(first: number, last: number, from: number, to: number, lineOf: (offset: number) => number): Map<number, AttributedLines>;
|
|
28
29
|
}
|
package/dist/attribution.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { offsetMapper } from './offsets.js';
|
|
2
2
|
/* The custom properties of the agent palette, defined in `editor.css` for both themes. */
|
|
3
3
|
export const AGENT_COLORS = ['--agent-1', '--agent-2', '--agent-3', '--agent-4', '--agent-5', '--agent-6'];
|
|
4
4
|
/* A color washed out to a background that text stays readable on in either theme. */
|
|
@@ -11,8 +11,8 @@ export function colorValue(color) {
|
|
|
11
11
|
}
|
|
12
12
|
/*
|
|
13
13
|
* The runs of lines a host attributed, anchored by offsets so they follow their text through edits. An
|
|
14
|
-
* edit
|
|
15
|
-
* reach the lines asked for.
|
|
14
|
+
* edit maps each run and its emphasized ranges, and a read works out the lines of only the runs
|
|
15
|
+
* that reach the lines asked for.
|
|
16
16
|
*/
|
|
17
17
|
export class AttributionRuns {
|
|
18
18
|
runs = [];
|
|
@@ -25,16 +25,38 @@ export class AttributionRuns {
|
|
|
25
25
|
this.runs = marks.map((mark) => {
|
|
26
26
|
const start = clamp(mark.startLine);
|
|
27
27
|
const end = Math.max(start, clamp(mark.endLine));
|
|
28
|
-
|
|
28
|
+
const emphasis = (mark.emphasis ?? []).slice(0, end - start + 1).flatMap((ranges, index) => {
|
|
29
|
+
const bounds = lineBounds(start + index);
|
|
30
|
+
const length = bounds.end - bounds.start;
|
|
31
|
+
return ranges.flatMap(([from, to]) => {
|
|
32
|
+
if (!Number.isFinite(from) || !Number.isFinite(to)) {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
const first = Math.min(length, Math.max(0, Math.trunc(from)));
|
|
36
|
+
const last = Math.min(length, Math.max(0, Math.trunc(to)));
|
|
37
|
+
return last > first ? [{ from: bounds.start + first, to: bounds.start + last }] : [];
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
id: mark.id,
|
|
42
|
+
color: mark.color,
|
|
43
|
+
sign: mark.sign,
|
|
44
|
+
fill: mark.fill,
|
|
45
|
+
bar: mark.bar,
|
|
46
|
+
from: lineBounds(start).start,
|
|
47
|
+
to: lineBounds(end).end,
|
|
48
|
+
emphasis
|
|
49
|
+
};
|
|
29
50
|
});
|
|
30
51
|
}
|
|
31
52
|
clear() {
|
|
32
53
|
this.runs = [];
|
|
33
54
|
}
|
|
34
|
-
map(changes) {
|
|
55
|
+
map(changes, map = offsetMapper(changes)) {
|
|
35
56
|
for (const run of this.runs) {
|
|
36
|
-
run.from =
|
|
37
|
-
run.to =
|
|
57
|
+
run.from = map(run.from);
|
|
58
|
+
run.to = map(run.to);
|
|
59
|
+
run.emphasis = run.emphasis.map((range) => ({ from: map(range.from), to: map(range.to) })).filter((range) => range.to > range.from);
|
|
38
60
|
}
|
|
39
61
|
}
|
|
40
62
|
/* The run on each line from `first` to `last`, zero-based, where `from` and `to` are the offsets they span; where runs overlap the one set last wins. */
|
|
@@ -44,10 +66,23 @@ export class AttributionRuns {
|
|
|
44
66
|
if (run.to < from || run.from > to) {
|
|
45
67
|
continue;
|
|
46
68
|
}
|
|
47
|
-
const value = { id: run.id, color: run.color, sign: run.sign, fill: run.fill };
|
|
69
|
+
const value = { id: run.id, color: run.color, sign: run.sign, fill: run.fill, bar: run.bar };
|
|
70
|
+
const emphasis = new Map();
|
|
71
|
+
for (const range of run.emphasis) {
|
|
72
|
+
if (range.to <= from || range.from > to) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const endLine = Math.min(lineOf(range.to - 1), last);
|
|
76
|
+
for (let line = Math.max(lineOf(range.from), first); line <= endLine; line++) {
|
|
77
|
+
const ranges = emphasis.get(line) ?? [];
|
|
78
|
+
ranges.push(range);
|
|
79
|
+
emphasis.set(line, ranges);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
48
82
|
const end = Math.min(Math.max(lineOf(run.from), lineOf(run.to)), last);
|
|
49
83
|
for (let line = Math.max(lineOf(run.from), first); line <= end; line++) {
|
|
50
|
-
|
|
84
|
+
const ranges = emphasis.get(line);
|
|
85
|
+
lines.set(line, ranges === undefined ? value : { ...value, emphasis: ranges });
|
|
51
86
|
}
|
|
52
87
|
}
|
|
53
88
|
return lines;
|
package/dist/bidi.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export declare const BIDI_CONTROLS: {
|
|
2
|
+
readonly '': {
|
|
3
|
+
readonly label: 'LRE';
|
|
4
|
+
};
|
|
5
|
+
readonly '': {
|
|
6
|
+
readonly label: 'RLE';
|
|
7
|
+
};
|
|
8
|
+
readonly '': {
|
|
9
|
+
readonly label: 'PDF';
|
|
10
|
+
};
|
|
11
|
+
readonly '': {
|
|
12
|
+
readonly label: 'LRO';
|
|
13
|
+
};
|
|
14
|
+
readonly '': {
|
|
15
|
+
readonly label: 'RLO';
|
|
16
|
+
};
|
|
17
|
+
readonly '': {
|
|
18
|
+
readonly label: 'LRI';
|
|
19
|
+
};
|
|
20
|
+
readonly '': {
|
|
21
|
+
readonly label: 'RLI';
|
|
22
|
+
};
|
|
23
|
+
readonly '': {
|
|
24
|
+
readonly label: 'FSI';
|
|
25
|
+
};
|
|
26
|
+
readonly '': {
|
|
27
|
+
readonly label: 'PDI';
|
|
28
|
+
};
|
|
29
|
+
readonly '': {
|
|
30
|
+
readonly label: 'LRM';
|
|
31
|
+
};
|
|
32
|
+
readonly '': {
|
|
33
|
+
readonly label: 'RLM';
|
|
34
|
+
};
|
|
35
|
+
readonly '': {
|
|
36
|
+
readonly label: 'ALM';
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
type BidiControl = keyof typeof BIDI_CONTROLS;
|
|
40
|
+
export declare const BIDI_COLUMNS = 3;
|
|
41
|
+
export declare const BIDI_PATTERN: RegExp;
|
|
42
|
+
export declare function bidiControl(character: string): (typeof BIDI_CONTROLS)[BidiControl] | null;
|
|
43
|
+
export declare function visibleBidi(text: string): string;
|
|
44
|
+
export {};
|
package/dist/bidi.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/* The label drawn for each control character. The boxes are visual: assistive technology reads the raw characters from the input. */
|
|
2
|
+
export const BIDI_CONTROLS = {
|
|
3
|
+
'': { label: 'LRE' },
|
|
4
|
+
'': { label: 'RLE' },
|
|
5
|
+
'': { label: 'PDF' },
|
|
6
|
+
'': { label: 'LRO' },
|
|
7
|
+
'': { label: 'RLO' },
|
|
8
|
+
'': { label: 'LRI' },
|
|
9
|
+
'': { label: 'RLI' },
|
|
10
|
+
'': { label: 'FSI' },
|
|
11
|
+
'': { label: 'PDI' },
|
|
12
|
+
'': { label: 'LRM' },
|
|
13
|
+
'': { label: 'RLM' },
|
|
14
|
+
'': { label: 'ALM' }
|
|
15
|
+
};
|
|
16
|
+
/* Every label is this many columns wide, so the placeholder is a box of fixed size in the grid. */
|
|
17
|
+
export const BIDI_COLUMNS = 3;
|
|
18
|
+
export const BIDI_PATTERN = /[--]/g;
|
|
19
|
+
/* The placeholder of a bidirectional control character, or null for any other text. */
|
|
20
|
+
export function bidiControl(character) {
|
|
21
|
+
return character.length === 1 ? (BIDI_CONTROLS[character] ?? null) : null;
|
|
22
|
+
}
|
|
23
|
+
/* Text a person reads but the editor never edits, such as a hint or a suggestion: each control is spelled out in brackets so none reaches the DOM. */
|
|
24
|
+
export function visibleBidi(text) {
|
|
25
|
+
return text.replace(BIDI_PATTERN, (character) => `[${BIDI_CONTROLS[character].label}]`);
|
|
26
|
+
}
|
package/dist/column-selection.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { stopCount, xOfStop } from './geometry-stops.js';
|
|
1
2
|
import { TEXT_PADDING } from './layout.js';
|
|
2
3
|
/* The cell of a monospaced grid an x falls in, which stands in for the visual column since a tab is a whole number of cells. */
|
|
3
4
|
export function cellAt(layout, x) {
|
|
@@ -25,7 +26,7 @@ export function columnSelections(layout, from, to) {
|
|
|
25
26
|
continue;
|
|
26
27
|
}
|
|
27
28
|
const geometry = layout.geometry(row);
|
|
28
|
-
const endX = geometry.rowStarts.length > 1 ? geometry.rowEnds[0] : geometry
|
|
29
|
+
const endX = geometry.rowStarts.length > 1 ? geometry.rowEnds[0] : xOfStop(geometry, stopCount(geometry) - 1);
|
|
29
30
|
const offsetAt = (cell) => layout.offsetAtX(geometry, 0, TEXT_PADDING + cell * charWidth).offset;
|
|
30
31
|
if (cellAt(layout, endX + TEXT_PADDING) < left) {
|
|
31
32
|
if (left === right) {
|
package/dist/controller.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Keymap } from './keymap-table.ts';
|
|
2
|
-
import type { EditorClickHandler, EditorContextMenu, EditorKeyHandler, EditorRunCommand, EditorSelectionRanges, KeyChord } from './types.ts';
|
|
2
|
+
import type { EditorClickHandler, EditorInlayClickHandler, EditorContextMenu, EditorKeyHandler, EditorRunCommand, EditorSelectionRanges, KeyChord } from './types.ts';
|
|
3
3
|
import type { EditorView } from './view.ts';
|
|
4
4
|
export interface ControllerOptions {
|
|
5
5
|
keymap?: Keymap;
|
|
@@ -11,10 +11,13 @@ export interface ControllerOptions {
|
|
|
11
11
|
export declare class InputController {
|
|
12
12
|
private readonly native;
|
|
13
13
|
private readonly pointer;
|
|
14
|
+
private readonly touch;
|
|
14
15
|
private readonly cleanup;
|
|
15
16
|
private readonly keyHandlers;
|
|
16
17
|
private readonly gesture;
|
|
17
18
|
private readonly clickHandlers;
|
|
19
|
+
private readonly inlayHandlers;
|
|
20
|
+
private lastInlayPress;
|
|
18
21
|
private readonly contextListeners;
|
|
19
22
|
private readonly subscription;
|
|
20
23
|
private historyGroup;
|
|
@@ -52,11 +55,13 @@ export declare class InputController {
|
|
|
52
55
|
private run;
|
|
53
56
|
onKeyDown(handler: EditorKeyHandler): () => void;
|
|
54
57
|
onClick(handler: EditorClickHandler): () => void;
|
|
58
|
+
onInlayClick(handler: EditorInlayClickHandler): () => void;
|
|
55
59
|
onContextMenu(listener: (menu: EditorContextMenu) => void): () => void;
|
|
56
60
|
private keydown;
|
|
57
61
|
private beforeInput;
|
|
58
62
|
private copy;
|
|
59
63
|
private pointerDown;
|
|
64
|
+
private takenByInlayHandler;
|
|
60
65
|
private takenByHost;
|
|
61
66
|
private positionOf;
|
|
62
67
|
private contextMenu;
|
package/dist/controller.js
CHANGED
|
@@ -5,6 +5,7 @@ import { DoubleModifierGesture } from './modifier-gesture.js';
|
|
|
5
5
|
import { NativeInput } from './native-input.js';
|
|
6
6
|
import { columnSelections, sameCell } from './column-selection.js';
|
|
7
7
|
import { PointerSelection } from './pointer.js';
|
|
8
|
+
import { TouchSelection } from './touch.js';
|
|
8
9
|
const MODIFYING = new Set([
|
|
9
10
|
'deleteWordLeft',
|
|
10
11
|
'deleteWordRight',
|
|
@@ -53,6 +54,8 @@ function selectionSignature(selections) {
|
|
|
53
54
|
}
|
|
54
55
|
/* Typing in the same stretch is one undo step until a pause this long. */
|
|
55
56
|
const TYPING_PAUSE_MS = 750;
|
|
57
|
+
/* A second press on the same hint within this many milliseconds is a double click. */
|
|
58
|
+
const DOUBLE_PRESS_MS = 500;
|
|
56
59
|
/*
|
|
57
60
|
* What a person does to the editor: keys, typing, the clipboard, an input method, the mouse. It turns
|
|
58
61
|
* each into a command on the model and leaves the drawing to the view.
|
|
@@ -60,10 +63,13 @@ const TYPING_PAUSE_MS = 750;
|
|
|
60
63
|
export class InputController {
|
|
61
64
|
native;
|
|
62
65
|
pointer;
|
|
66
|
+
touch;
|
|
63
67
|
cleanup = [];
|
|
64
68
|
keyHandlers = new Set();
|
|
65
69
|
gesture;
|
|
66
70
|
clickHandlers = new Set();
|
|
71
|
+
inlayHandlers = new Set();
|
|
72
|
+
lastInlayPress = null;
|
|
67
73
|
contextListeners = new Set();
|
|
68
74
|
subscription;
|
|
69
75
|
historyGroup = 0;
|
|
@@ -117,6 +123,16 @@ export class InputController {
|
|
|
117
123
|
focus: () => view.focus(),
|
|
118
124
|
scrolled: () => view.requestRender()
|
|
119
125
|
});
|
|
126
|
+
this.touch = new TouchSelection({
|
|
127
|
+
model,
|
|
128
|
+
offsetAt: (x, y) => view.offsetAtPoint(x, y),
|
|
129
|
+
camelHumps: () => view.settings.smartKeys.camelHumps,
|
|
130
|
+
lineAt: (offset) => {
|
|
131
|
+
const row = view.layout.rowForLine(model.positionAt(offset).line);
|
|
132
|
+
return row.kind === 'text' ? { from: model.getLine(row.line).start, to: model.getLine(row.lastLine).next } : { from: offset, to: offset };
|
|
133
|
+
},
|
|
134
|
+
focus: () => view.focus()
|
|
135
|
+
});
|
|
120
136
|
this.subscription = model.subscribe(() => {
|
|
121
137
|
if (!this.keepDesiredXs) {
|
|
122
138
|
this.desiredXs = [];
|
|
@@ -165,22 +181,45 @@ export class InputController {
|
|
|
165
181
|
this.listen(input, 'cut', (event) => this.copy(event, true));
|
|
166
182
|
this.listen(viewport, 'scroll', () => this.view.scrolled());
|
|
167
183
|
this.listen(viewport, 'wheel', () => this.view.cancelScroll());
|
|
168
|
-
this.listen(viewport, 'touchstart', () => this.view.cancelScroll());
|
|
169
184
|
this.listen(viewport, 'pointermove', (event) => this.view.hoverMoved(event));
|
|
170
|
-
this.listen(viewport, 'pointerleave', () =>
|
|
185
|
+
this.listen(viewport, 'pointerleave', () => {
|
|
186
|
+
this.view.setHover(null);
|
|
187
|
+
this.view.setInlayHover(null);
|
|
188
|
+
});
|
|
171
189
|
this.listen(viewport, 'pointerdown', (event) => this.pointerDown(event));
|
|
172
190
|
this.listen(viewport, 'contextmenu', (event) => this.contextMenu(event));
|
|
173
191
|
this.listen(this.view.gutterElement, 'pointerdown', (event) => this.gutterDown(event));
|
|
174
192
|
this.listen(this.view.stickyElement, 'pointerdown', (event) => this.stickyDown(event));
|
|
175
|
-
const move = (event) =>
|
|
193
|
+
const move = (event) => {
|
|
194
|
+
this.pointer.move(event);
|
|
195
|
+
this.touch.move(event);
|
|
196
|
+
};
|
|
176
197
|
const end = (event) => {
|
|
177
198
|
this.pointer.end(event);
|
|
199
|
+
this.touch.end(event);
|
|
178
200
|
this.historyGroup++;
|
|
179
201
|
};
|
|
180
202
|
document.addEventListener('pointermove', move);
|
|
181
203
|
document.addEventListener('pointerup', end);
|
|
182
204
|
document.addEventListener('pointercancel', end);
|
|
205
|
+
// The on-screen keyboard shrinks the visual viewport and not the layout one, which would leave the caret behind it.
|
|
206
|
+
const visualViewport = document.defaultView?.visualViewport;
|
|
207
|
+
let keyboardHeight = visualViewport?.height ?? 0;
|
|
208
|
+
let keyboardScale = visualViewport?.scale ?? 1;
|
|
209
|
+
const keyboard = () => {
|
|
210
|
+
const height = visualViewport?.height ?? 0;
|
|
211
|
+
const scale = visualViewport?.scale ?? 1;
|
|
212
|
+
// Zooming in shrinks the visual viewport too, and must not pull the view back to the caret.
|
|
213
|
+
const shrunk = height < keyboardHeight && scale === keyboardScale;
|
|
214
|
+
keyboardHeight = height;
|
|
215
|
+
keyboardScale = scale;
|
|
216
|
+
if (shrunk && this.view.focused) {
|
|
217
|
+
this.view.revealCaret();
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
visualViewport?.addEventListener('resize', keyboard);
|
|
183
221
|
this.cleanup.push(() => {
|
|
222
|
+
visualViewport?.removeEventListener('resize', keyboard);
|
|
184
223
|
document.removeEventListener('pointermove', move);
|
|
185
224
|
document.removeEventListener('pointerup', end);
|
|
186
225
|
document.removeEventListener('pointercancel', end);
|
|
@@ -189,6 +228,7 @@ export class InputController {
|
|
|
189
228
|
dispose() {
|
|
190
229
|
this.subscription.dispose();
|
|
191
230
|
this.pointer.dispose();
|
|
231
|
+
this.touch.dispose();
|
|
192
232
|
for (const remove of this.cleanup) {
|
|
193
233
|
remove();
|
|
194
234
|
}
|
|
@@ -267,6 +307,9 @@ export class InputController {
|
|
|
267
307
|
/* Folding is the view's, since it knows where the ranges are. */
|
|
268
308
|
viewCommand(name) {
|
|
269
309
|
const { view } = this;
|
|
310
|
+
if (view.settings.folding === false && name !== 'toggleColumnMode') {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
270
313
|
const run = {
|
|
271
314
|
collapseRegion: () => view.collapseRegion(),
|
|
272
315
|
expandRegion: () => view.expandRegion(),
|
|
@@ -580,6 +623,9 @@ export class InputController {
|
|
|
580
623
|
onClick(handler) {
|
|
581
624
|
return subscribe(this.clickHandlers, handler);
|
|
582
625
|
}
|
|
626
|
+
onInlayClick(handler) {
|
|
627
|
+
return subscribe(this.inlayHandlers, handler);
|
|
628
|
+
}
|
|
583
629
|
onContextMenu(listener) {
|
|
584
630
|
return subscribe(this.contextListeners, listener);
|
|
585
631
|
}
|
|
@@ -680,13 +726,48 @@ export class InputController {
|
|
|
680
726
|
if (event.button !== 0 || target.closest('button, a, input, textarea, [contenteditable], .se-block, .se-gutter, .se-sticky')) {
|
|
681
727
|
return;
|
|
682
728
|
}
|
|
729
|
+
// Cancels the compatibility mouse events, whose focus change would close the keyboard a tap opened; scrolling is not affected.
|
|
683
730
|
event.preventDefault();
|
|
731
|
+
if (this.takenByInlayHandler(event)) {
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
if (event.pointerType === 'touch') {
|
|
735
|
+
this.touch.start(event, 'text', () => this.takenByHost(event));
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
684
738
|
if (this.takenByHost(event)) {
|
|
685
739
|
this.view.focus();
|
|
686
740
|
return;
|
|
687
741
|
}
|
|
688
742
|
this.pointer.start(event);
|
|
689
743
|
}
|
|
744
|
+
/* Whether a handler of the host took a press on a hint, which then moves neither the caret nor the focus. */
|
|
745
|
+
takenByInlayHandler(event) {
|
|
746
|
+
const inlay = this.inlayHandlers.size === 0 ? null : this.view.inlayAt(event.target);
|
|
747
|
+
if (inlay === null) {
|
|
748
|
+
return false;
|
|
749
|
+
}
|
|
750
|
+
const now = Date.now();
|
|
751
|
+
const again = this.lastInlayPress?.id === inlay.id && now - this.lastInlayPress.at <= DOUBLE_PRESS_MS;
|
|
752
|
+
this.lastInlayPress = again ? null : { id: inlay.id, at: now };
|
|
753
|
+
const touch = event.pointerType === 'touch';
|
|
754
|
+
const click = {
|
|
755
|
+
id: inlay.id,
|
|
756
|
+
part: inlay.part,
|
|
757
|
+
mod: this.options.apple ? event.metaKey : event.ctrlKey,
|
|
758
|
+
alt: event.altKey,
|
|
759
|
+
shift: event.shiftKey,
|
|
760
|
+
clicks: again ? 2 : 1,
|
|
761
|
+
touch
|
|
762
|
+
};
|
|
763
|
+
if (![...this.inlayHandlers].some((handler) => handler(click))) {
|
|
764
|
+
return false;
|
|
765
|
+
}
|
|
766
|
+
if (!touch) {
|
|
767
|
+
this.view.focus();
|
|
768
|
+
}
|
|
769
|
+
return true;
|
|
770
|
+
}
|
|
690
771
|
/* Whether a handler of the host took a press on a character, such as a Mod+click that follows a name. */
|
|
691
772
|
takenByHost(event) {
|
|
692
773
|
if (this.clickHandlers.size === 0) {
|
|
@@ -709,6 +790,10 @@ export class InputController {
|
|
|
709
790
|
return { line, character: column };
|
|
710
791
|
}
|
|
711
792
|
contextMenu(event) {
|
|
793
|
+
if (this.contextListeners.size === 0 && this.touch.pressed) {
|
|
794
|
+
event.preventDefault();
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
712
797
|
if (this.contextListeners.size === 0 || event.target?.closest?.('.se-sticky')) {
|
|
713
798
|
return;
|
|
714
799
|
}
|
|
@@ -732,6 +817,7 @@ export class InputController {
|
|
|
732
817
|
}
|
|
733
818
|
/* A press on a line number selects the line. */
|
|
734
819
|
gutterDown(event) {
|
|
820
|
+
this.view.cancelScroll();
|
|
735
821
|
if (event.button !== 0) {
|
|
736
822
|
return;
|
|
737
823
|
}
|
|
@@ -753,6 +839,10 @@ export class InputController {
|
|
|
753
839
|
this.view.focus();
|
|
754
840
|
return;
|
|
755
841
|
}
|
|
842
|
+
if (event.pointerType === 'touch') {
|
|
843
|
+
this.touch.start(event, 'gutter');
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
756
846
|
this.pointer.start(event, 'gutter');
|
|
757
847
|
this.view.focus();
|
|
758
848
|
}
|
package/dist/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;
|