@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
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { shikiDocumentTokens } from './shiki-document.js';
|
|
2
|
+
import { documentLanguageOf } from './shiki.js';
|
|
3
|
+
function yieldToHost() {
|
|
4
|
+
return new Promise((resolve) => {
|
|
5
|
+
const channel = new MessageChannel();
|
|
6
|
+
channel.port1.onmessage = () => {
|
|
7
|
+
channel.port1.close();
|
|
8
|
+
channel.port2.close();
|
|
9
|
+
resolve();
|
|
10
|
+
};
|
|
11
|
+
channel.port2.postMessage(null);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
function packedTokens(generation, index, code, tokens, appearance) {
|
|
15
|
+
const colors = [''];
|
|
16
|
+
const colorIds = new Map([['', 0]]);
|
|
17
|
+
const starts = [0];
|
|
18
|
+
for (const match of code.matchAll(/\r\n|\n/g)) {
|
|
19
|
+
starts.push(match.index + match[0].length);
|
|
20
|
+
}
|
|
21
|
+
const lines = new Uint32Array(tokens.length + 1);
|
|
22
|
+
const packed = new Uint32Array(tokens.reduce((count, line) => count + line.length, 0) * 3);
|
|
23
|
+
let cursor = 0;
|
|
24
|
+
for (const [index, line] of tokens.entries()) {
|
|
25
|
+
lines[index] = cursor / 3;
|
|
26
|
+
for (const token of line) {
|
|
27
|
+
const color = token.color ?? '';
|
|
28
|
+
let colorId = colorIds.get(color);
|
|
29
|
+
if (colorId === undefined) {
|
|
30
|
+
colorId = colors.length;
|
|
31
|
+
colors.push(color);
|
|
32
|
+
colorIds.set(color, colorId);
|
|
33
|
+
}
|
|
34
|
+
packed[cursor++] = token.offset - starts[index];
|
|
35
|
+
packed[cursor++] = colorId;
|
|
36
|
+
packed[cursor++] = token.fontStyle ?? 0;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
lines[tokens.length] = cursor / 3;
|
|
40
|
+
return { type: 'tokens', generation, index, colors, ...appearance, lines, tokens: packed };
|
|
41
|
+
}
|
|
42
|
+
/* The packet omits text and token lengths; its source line supplies the final token's end. */
|
|
43
|
+
export function lineTokensOf(packet, line, text) {
|
|
44
|
+
if (!Number.isInteger(line) || line < 0 || line + 1 >= packet.lines.length) {
|
|
45
|
+
throw new RangeError('Unknown syntax line');
|
|
46
|
+
}
|
|
47
|
+
const result = [];
|
|
48
|
+
const end = packet.lines[line + 1];
|
|
49
|
+
for (let index = packet.lines[line]; index < end; index++) {
|
|
50
|
+
const from = packet.tokens[index * 3];
|
|
51
|
+
const to = index + 1 < end ? packet.tokens[(index + 1) * 3] : text.length;
|
|
52
|
+
if (to > from) {
|
|
53
|
+
result.push({ length: to - from, color: packet.colors[packet.tokens[index * 3 + 1]] ?? '', fontStyle: packet.tokens[index * 3 + 2] });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
export function createSyntaxService(options) {
|
|
59
|
+
const entries = new Map();
|
|
60
|
+
const requests = new Map();
|
|
61
|
+
const pause = options.yield ?? yieldToHost;
|
|
62
|
+
let maxLines = 100000;
|
|
63
|
+
let count = 0;
|
|
64
|
+
let ready;
|
|
65
|
+
let running = false;
|
|
66
|
+
let stopped = false;
|
|
67
|
+
let active;
|
|
68
|
+
const key = (generation, index) => `${generation}:${index}`;
|
|
69
|
+
const cancel = (generation) => {
|
|
70
|
+
for (const [id, request] of requests) {
|
|
71
|
+
if (request.generation === generation) {
|
|
72
|
+
requests.delete(id);
|
|
73
|
+
for (const resolve of request.resolve) {
|
|
74
|
+
resolve();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const trim = () => {
|
|
80
|
+
for (const [generation, entry] of entries) {
|
|
81
|
+
if (count <= maxLines) {
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
if (entry !== active) {
|
|
85
|
+
entries.delete(generation);
|
|
86
|
+
count -= entry.count;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
const finish = (request) => {
|
|
91
|
+
const id = key(request.generation, request.index);
|
|
92
|
+
if (requests.get(id) === request) {
|
|
93
|
+
requests.delete(id);
|
|
94
|
+
}
|
|
95
|
+
for (const resolve of request.resolve) {
|
|
96
|
+
resolve();
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
const failed = (request, reason, message) => {
|
|
100
|
+
options.send({ type: 'failed', generation: request.generation, index: request.index, reason, message });
|
|
101
|
+
finish(request);
|
|
102
|
+
};
|
|
103
|
+
const grammarOf = (entry, loaded) => {
|
|
104
|
+
const language = documentLanguageOf(entry.document.language);
|
|
105
|
+
return loaded.getLoadedLanguages().includes(language) ? loaded.getLanguage(language) : undefined;
|
|
106
|
+
};
|
|
107
|
+
const pump = async () => {
|
|
108
|
+
if (running) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
running = true;
|
|
112
|
+
try {
|
|
113
|
+
while (!stopped && requests.size > 0) {
|
|
114
|
+
const request = [...requests.values()].at(-1);
|
|
115
|
+
const id = key(request.generation, request.index);
|
|
116
|
+
const entry = entries.get(request.generation);
|
|
117
|
+
if (entry === undefined) {
|
|
118
|
+
failed(request, 'missing', 'The syntax document was evicted; resend its definition.');
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
active = entry;
|
|
122
|
+
entries.delete(request.generation);
|
|
123
|
+
entries.set(request.generation, entry);
|
|
124
|
+
try {
|
|
125
|
+
const loaded = await (ready ??= options.highlighter());
|
|
126
|
+
if (requests.get(id) !== request || entries.get(request.generation) !== entry || stopped) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (entry.done >= 0 && (entry.grammar !== grammarOf(entry, loaded) || entry.theme !== loaded.getTheme(entry.document.theme))) {
|
|
130
|
+
entry.done = -1;
|
|
131
|
+
}
|
|
132
|
+
if (request.index >= entry.document.blocks.length) {
|
|
133
|
+
throw new RangeError('The syntax block index is outside the document.');
|
|
134
|
+
}
|
|
135
|
+
if ([...requests.values()].at(-1) !== request) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const index = Math.min(entry.done + 1, request.index);
|
|
139
|
+
const tokens = await entry.tokens(index);
|
|
140
|
+
entry.done = Math.max(entry.done, index);
|
|
141
|
+
entry.grammar = grammarOf(entry, loaded);
|
|
142
|
+
const theme = loaded.getTheme(entry.document.theme);
|
|
143
|
+
entry.theme = theme;
|
|
144
|
+
if (requests.get(id) === request && entries.get(request.generation) === entry && !stopped && index === request.index) {
|
|
145
|
+
const raw = entry.document.blocks[index];
|
|
146
|
+
const code = index < entry.document.blocks.length - 1 ? raw.replace(/\r$/, '') : raw;
|
|
147
|
+
const packed = packedTokens(request.generation, index, code, tokens, { foreground: theme.fg, background: theme.bg });
|
|
148
|
+
options.send(packed, [packed.lines.buffer, packed.tokens.buffer]);
|
|
149
|
+
finish(request);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
if (requests.get(id) === request && !stopped) {
|
|
154
|
+
failed(request, 'highlight', error instanceof Error ? error.message : String(error));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
trim();
|
|
158
|
+
if (requests.size > 0 && !stopped) {
|
|
159
|
+
await pause();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
active = undefined;
|
|
165
|
+
running = false;
|
|
166
|
+
trim();
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
const dispose = () => {
|
|
170
|
+
stopped = true;
|
|
171
|
+
for (const generation of entries.keys()) {
|
|
172
|
+
cancel(generation);
|
|
173
|
+
}
|
|
174
|
+
for (const request of requests.values()) {
|
|
175
|
+
finish(request);
|
|
176
|
+
}
|
|
177
|
+
entries.clear();
|
|
178
|
+
count = 0;
|
|
179
|
+
};
|
|
180
|
+
return {
|
|
181
|
+
dispose,
|
|
182
|
+
handle(message) {
|
|
183
|
+
if (stopped) {
|
|
184
|
+
return Promise.resolve();
|
|
185
|
+
}
|
|
186
|
+
if (message.type === 'start') {
|
|
187
|
+
if (!Number.isSafeInteger(message.maxLines) || message.maxLines < 1) {
|
|
188
|
+
return Promise.reject(new RangeError('The syntax cache limit must be a positive safe integer.'));
|
|
189
|
+
}
|
|
190
|
+
maxLines = message.maxLines;
|
|
191
|
+
for (const generation of entries.keys()) {
|
|
192
|
+
cancel(generation);
|
|
193
|
+
}
|
|
194
|
+
entries.clear();
|
|
195
|
+
count = 0;
|
|
196
|
+
ready = options.highlighter().then(async (loaded) => {
|
|
197
|
+
const known = new Set(loaded.getLoadedThemes());
|
|
198
|
+
const missing = message.themes.filter((theme) => theme.name === undefined || !known.has(theme.name));
|
|
199
|
+
if (missing.length > 0) {
|
|
200
|
+
await loaded.loadTheme(...missing);
|
|
201
|
+
}
|
|
202
|
+
return loaded;
|
|
203
|
+
});
|
|
204
|
+
return ready.then(() => options.send({ type: 'ready' }), (error) => options.send({ type: 'error', message: error instanceof Error ? error.message : String(error) }));
|
|
205
|
+
}
|
|
206
|
+
if (message.type === 'cancel' || message.type === 'dispose') {
|
|
207
|
+
cancel(message.generation);
|
|
208
|
+
if (message.type === 'dispose') {
|
|
209
|
+
const entry = entries.get(message.generation);
|
|
210
|
+
if (entry !== undefined) {
|
|
211
|
+
entries.delete(message.generation);
|
|
212
|
+
count -= entry.count;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return Promise.resolve();
|
|
216
|
+
}
|
|
217
|
+
if (!Number.isSafeInteger(message.generation) || message.generation < 1 || !Number.isSafeInteger(message.index) || message.index < 0) {
|
|
218
|
+
return Promise.reject(new RangeError('Syntax generations must be positive integers and block indexes nonnegative integers.'));
|
|
219
|
+
}
|
|
220
|
+
if (message.document !== undefined && !entries.has(message.generation)) {
|
|
221
|
+
const document = { ...message.document, blocks: [...message.document.blocks], resets: [...message.document.resets] };
|
|
222
|
+
const entry = {
|
|
223
|
+
document,
|
|
224
|
+
tokens: shikiDocumentTokens(() => (ready ??= options.highlighter()), document.blocks, document.language, document.theme, {
|
|
225
|
+
reset: new Set(document.resets),
|
|
226
|
+
// Shiki skips at the cutoff; include source lines of exactly 10,000 code units.
|
|
227
|
+
tokenizeMaxLineLength: 10001,
|
|
228
|
+
tokenizeTimeLimit: 20
|
|
229
|
+
}),
|
|
230
|
+
count: document.blocks.reduce((count, block) => count + block.split(/\r\n|\n/).length, 0),
|
|
231
|
+
done: -1
|
|
232
|
+
};
|
|
233
|
+
count += entry.count;
|
|
234
|
+
entries.set(message.generation, entry);
|
|
235
|
+
}
|
|
236
|
+
return new Promise((resolve) => {
|
|
237
|
+
const id = key(message.generation, message.index);
|
|
238
|
+
const existing = requests.get(id);
|
|
239
|
+
const request = existing ?? { generation: message.generation, index: message.index, resolve: [] };
|
|
240
|
+
request.resolve.push(resolve);
|
|
241
|
+
requests.delete(id);
|
|
242
|
+
requests.set(id, request);
|
|
243
|
+
void pump();
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
export function serveDiffSyntax(endpoint) {
|
|
249
|
+
let highlighter;
|
|
250
|
+
const service = createSyntaxService({
|
|
251
|
+
highlighter: () => (highlighter ??= import('shiki').then(({ createHighlighter }) => createHighlighter({ langs: [], themes: [] }))),
|
|
252
|
+
send: (message, transfer) => endpoint.postMessage(message, { transfer: transfer ?? [] })
|
|
253
|
+
});
|
|
254
|
+
const listener = (event) => {
|
|
255
|
+
void service.handle(event.data).catch((error) => {
|
|
256
|
+
endpoint.postMessage({ type: 'error', message: error instanceof Error ? error.message : String(error) });
|
|
257
|
+
});
|
|
258
|
+
};
|
|
259
|
+
endpoint.addEventListener('message', listener);
|
|
260
|
+
return () => {
|
|
261
|
+
endpoint.removeEventListener('message', listener);
|
|
262
|
+
service.dispose();
|
|
263
|
+
void highlighter?.then((loaded) => loaded.dispose(), () => undefined);
|
|
264
|
+
};
|
|
265
|
+
}
|
package/dist/shiki.d.ts
CHANGED
|
@@ -5,3 +5,4 @@ export { documentLanguageOf };
|
|
|
5
5
|
export declare function loadGrammar(highlighter: Highlighter, grammar: string, text?: string): Promise<boolean>;
|
|
6
6
|
export declare function shikiTokenizers(highlighter: () => Promise<Highlighter>): TokenizerSource;
|
|
7
7
|
export declare function shikiScopeColors(highlighter: () => Promise<Highlighter>): ScopeColorSource;
|
|
8
|
+
export { shikiDocumentHighlighter, shikiDocumentTokens, type ShikiDocumentTokenOptions } from './shiki-document.ts';
|
package/dist/shiki.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { documentLanguageOf, loadDocumentLanguage } from './php-html.js';
|
|
2
2
|
import { scopeColorsOf } from './theme-scopes.js';
|
|
3
3
|
export { documentLanguageOf };
|
|
4
|
+
/* The standard token types of TextMate: 1 comment, 2 string, 3 regex. */
|
|
5
|
+
const KINDS = { 1: 'comment', 2: 'string', 3: 'regex' };
|
|
4
6
|
const PLAIN_TEXT = new Set(['', 'text', 'txt', 'plain', 'plaintext']);
|
|
5
7
|
/* Colors one line at a time and hands the grammar's state from one line to the next. A grammar Shiki rebuilt does not know the states of the old one. */
|
|
6
|
-
function lineTokenizer(highlighter, language, theme) {
|
|
8
|
+
function lineTokenizer(highlighter, language, theme, embedded) {
|
|
7
9
|
let grammar = highlighter.getLanguage(language);
|
|
8
10
|
return {
|
|
11
|
+
prepare: embedded.matcher === null ? undefined : (text) => loadEmbedded(highlighter, embedded, text),
|
|
9
12
|
stale() {
|
|
10
13
|
const current = highlighter.getLanguage(language);
|
|
11
14
|
const changed = current !== grammar;
|
|
@@ -13,12 +16,18 @@ function lineTokenizer(highlighter, language, theme) {
|
|
|
13
16
|
return changed;
|
|
14
17
|
},
|
|
15
18
|
tokenizeLine(text, state) {
|
|
16
|
-
const options = {
|
|
19
|
+
const options = {
|
|
20
|
+
lang: language,
|
|
21
|
+
theme,
|
|
22
|
+
grammarState: (state ?? undefined),
|
|
23
|
+
includeExplanation: 'tokenType'
|
|
24
|
+
};
|
|
17
25
|
const lines = highlighter.codeToTokensBase(text, options);
|
|
18
26
|
const tokens = (lines[0] ?? []).map((token) => ({
|
|
19
27
|
length: token.content.length,
|
|
20
28
|
color: token.color ?? '',
|
|
21
|
-
fontStyle: token.fontStyle ?? 0
|
|
29
|
+
fontStyle: token.fontStyle ?? 0,
|
|
30
|
+
kind: KINDS[token.type ?? 0]
|
|
22
31
|
}));
|
|
23
32
|
return { tokens, state: highlighter.getLastGrammarState(lines) };
|
|
24
33
|
},
|
|
@@ -32,39 +41,92 @@ function lineTokenizer(highlighter, language, theme) {
|
|
|
32
41
|
}
|
|
33
42
|
};
|
|
34
43
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
* and Shiki builds the host grammar anew each time one of them loads later, which drops the states
|
|
42
|
-
* of every line colored before. So the ones a document names are loaded together with the host.
|
|
43
|
-
*/
|
|
44
|
-
async function loadEmbedded(highlighter, grammar, text) {
|
|
45
|
-
const { bundledLanguages, bundledLanguagesInfo } = await import('shiki');
|
|
46
|
-
const bundled = bundledLanguages[grammar];
|
|
47
|
-
if (bundled === undefined) {
|
|
48
|
-
return;
|
|
44
|
+
const languageLoads = new WeakMap();
|
|
45
|
+
const embeddedIndexes = new Map();
|
|
46
|
+
/* Concurrent documents await one load, so each learns the grammar was rebuilt. */
|
|
47
|
+
function loadOnce(highlighter, id) {
|
|
48
|
+
if (highlighter.getLoadedLanguages().includes(id)) {
|
|
49
|
+
return Promise.resolve();
|
|
49
50
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
let loads = languageLoads.get(highlighter);
|
|
52
|
+
if (loads === undefined) {
|
|
53
|
+
loads = new Map();
|
|
54
|
+
languageLoads.set(highlighter, loads);
|
|
55
|
+
}
|
|
56
|
+
const pending = loads.get(id);
|
|
57
|
+
if (pending !== undefined) {
|
|
58
|
+
return pending;
|
|
59
|
+
}
|
|
60
|
+
const load = (async () => {
|
|
61
|
+
await loadDocumentLanguage(highlighter, id);
|
|
62
|
+
if (!highlighter.getLoadedLanguages().includes(id)) {
|
|
63
|
+
await highlighter.loadLanguage(id);
|
|
64
|
+
}
|
|
65
|
+
})();
|
|
66
|
+
loads.set(id, load);
|
|
67
|
+
void load.catch(() => {
|
|
68
|
+
if (loads.get(id) === load) {
|
|
69
|
+
loads.delete(id);
|
|
70
|
+
}
|
|
55
71
|
});
|
|
56
|
-
|
|
72
|
+
return load;
|
|
57
73
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
74
|
+
function embedIndex(grammar) {
|
|
75
|
+
const cached = embeddedIndexes.get(grammar);
|
|
76
|
+
if (cached !== undefined) {
|
|
77
|
+
return cached;
|
|
78
|
+
}
|
|
79
|
+
const pending = (async () => {
|
|
80
|
+
const { bundledLanguages, bundledLanguagesInfo } = await import('shiki');
|
|
81
|
+
const bundled = bundledLanguages[grammar];
|
|
82
|
+
const registrations = bundled === undefined ? [] : (await bundled()).default;
|
|
83
|
+
const lazy = registrations.find((registration) => registration.name === grammar)?.embeddedLangsLazy ?? [];
|
|
84
|
+
const names = new Map();
|
|
85
|
+
for (const id of lazy) {
|
|
86
|
+
const aliases = bundledLanguagesInfo.find((info) => info.id === id)?.aliases ?? [];
|
|
87
|
+
for (const name of [id, ...aliases]) {
|
|
88
|
+
names.set(name.toLowerCase(), id);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (names.size === 0) {
|
|
92
|
+
return { names, matcher: null };
|
|
93
|
+
}
|
|
94
|
+
let matcher;
|
|
95
|
+
if (grammar === 'markdown' || grammar === 'mdx') {
|
|
96
|
+
matcher = /^\s*(?:`{3,}|~{3,})[ \t]*([\w#+.-]+)/gm;
|
|
97
|
+
}
|
|
98
|
+
else if (grammar === 'vue' || grammar === 'svelte' || grammar === 'astro') {
|
|
99
|
+
matcher = /\blang\s*=\s*["']([\w#+.-]+)["']/gi;
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
const alternatives = [...names.keys()].map((name) => name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
|
|
103
|
+
matcher = new RegExp(`(?<![\\w-])(${alternatives})(?![\\w-])`, 'gi');
|
|
104
|
+
}
|
|
105
|
+
return { names, matcher };
|
|
106
|
+
})();
|
|
107
|
+
embeddedIndexes.set(grammar, pending);
|
|
108
|
+
void pending.catch(() => embeddedIndexes.delete(grammar));
|
|
109
|
+
return pending;
|
|
110
|
+
}
|
|
111
|
+
async function loadEmbedded(highlighter, embedded, text) {
|
|
112
|
+
if (embedded.matcher === null) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
const loaded = new Set(highlighter.getLoadedLanguages());
|
|
116
|
+
const named = new Set();
|
|
117
|
+
for (const match of text.matchAll(embedded.matcher)) {
|
|
118
|
+
const id = embedded.names.get(match[1].toLowerCase());
|
|
119
|
+
if (id !== undefined && !loaded.has(id)) {
|
|
120
|
+
named.add(id);
|
|
121
|
+
}
|
|
66
122
|
}
|
|
67
|
-
await
|
|
123
|
+
const results = await Promise.allSettled([...named].map((id) => loadOnce(highlighter, id)));
|
|
124
|
+
return results.some((result) => result.status === 'fulfilled');
|
|
125
|
+
}
|
|
126
|
+
/* Text names the optional embedded grammars that must load before the host hands out states. */
|
|
127
|
+
export async function loadGrammar(highlighter, grammar, text = '') {
|
|
128
|
+
await loadOnce(highlighter, grammar);
|
|
129
|
+
await loadEmbedded(highlighter, await embedIndex(grammar), text);
|
|
68
130
|
return highlighter.getLoadedLanguages().includes(grammar);
|
|
69
131
|
}
|
|
70
132
|
/*
|
|
@@ -82,7 +144,7 @@ export function shikiTokenizers(highlighter) {
|
|
|
82
144
|
if (!shiki.getLoadedThemes().includes(theme)) {
|
|
83
145
|
await shiki.loadTheme(theme);
|
|
84
146
|
}
|
|
85
|
-
return (await loadGrammar(shiki, grammar, text)) ? lineTokenizer(shiki, grammar, theme) : null;
|
|
147
|
+
return (await loadGrammar(shiki, grammar, text)) ? lineTokenizer(shiki, grammar, theme, await embedIndex(grammar)) : null;
|
|
86
148
|
}
|
|
87
149
|
catch {
|
|
88
150
|
return null;
|
|
@@ -104,3 +166,4 @@ export function shikiScopeColors(highlighter) {
|
|
|
104
166
|
}
|
|
105
167
|
};
|
|
106
168
|
}
|
|
169
|
+
export { shikiDocumentHighlighter, shikiDocumentTokens } from './shiki-document.js';
|
package/dist/testing.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export interface KeyOptions {
|
|
|
9
9
|
metaKey?: boolean;
|
|
10
10
|
altKey?: boolean;
|
|
11
11
|
shiftKey?: boolean;
|
|
12
|
+
pointerType?: string;
|
|
12
13
|
}
|
|
13
14
|
export declare function press(window: Window, target: Element, key: string, options?: KeyOptions): boolean;
|
|
14
15
|
export declare function release(window: Window, target: Element, key: string, options?: KeyOptions): void;
|
package/dist/tokens.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export declare class TokenCache {
|
|
|
8
8
|
private tokenizer;
|
|
9
9
|
private entries;
|
|
10
10
|
private frontier;
|
|
11
|
+
private staleFrom;
|
|
11
12
|
private reusableFrom;
|
|
12
13
|
private pivotLine;
|
|
13
14
|
private pivotState;
|
|
@@ -20,6 +21,7 @@ export declare class TokenCache {
|
|
|
20
21
|
tokenizeText(lines: readonly string[]): (readonly LineToken[])[] | null;
|
|
21
22
|
tokensOf(line: number): readonly LineToken[] | null;
|
|
22
23
|
edited(batches: readonly (readonly DocumentChange[])[], lineOf: (offset: number) => number): void;
|
|
24
|
+
revalidate(): boolean;
|
|
23
25
|
advance(until: number, budget: number, now?: () => number): {
|
|
24
26
|
from: number;
|
|
25
27
|
to: number;
|
package/dist/tokens.js
CHANGED
|
@@ -12,6 +12,8 @@ export class TokenCache {
|
|
|
12
12
|
entries = [];
|
|
13
13
|
/* Every line above it is colored for the text as it is now. */
|
|
14
14
|
frontier = 0;
|
|
15
|
+
/* Colors survive a rebuild, but the states below this boundary cannot settle recoloring. */
|
|
16
|
+
staleFrom = Number.POSITIVE_INFINITY;
|
|
15
17
|
/* The last line the edit touched; lines below it may be told right by their old state. */
|
|
16
18
|
reusableFrom = Number.POSITIVE_INFINITY;
|
|
17
19
|
/* The state the last line of the edited stretch ended in before the edit; the same state now means the lines below are right. */
|
|
@@ -31,6 +33,7 @@ export class TokenCache {
|
|
|
31
33
|
this.tokenizer = tokenizer;
|
|
32
34
|
this.lines = this.source.getLineCount();
|
|
33
35
|
this.entries = [];
|
|
36
|
+
this.staleFrom = Number.POSITIVE_INFINITY;
|
|
34
37
|
this.frontier = 0;
|
|
35
38
|
this.forgetPivot();
|
|
36
39
|
}
|
|
@@ -69,6 +72,7 @@ export class TokenCache {
|
|
|
69
72
|
const first = batches[0]?.[0];
|
|
70
73
|
if (first === undefined) {
|
|
71
74
|
this.entries = [];
|
|
75
|
+
this.staleFrom = Number.POSITIVE_INFINITY;
|
|
72
76
|
this.frontier = 0;
|
|
73
77
|
this.forgetPivot();
|
|
74
78
|
return;
|
|
@@ -83,8 +87,22 @@ export class TokenCache {
|
|
|
83
87
|
const lastNew = last === undefined ? -1 : lineOf(last.from + shift + last.insertedLength);
|
|
84
88
|
const lastOld = lastNew - delta;
|
|
85
89
|
this.frontier = Math.min(this.frontier, firstLine);
|
|
90
|
+
if (last === undefined && Number.isFinite(this.staleFrom)) {
|
|
91
|
+
// A history batch has no single mapping for the old grammar boundary.
|
|
92
|
+
this.staleFrom = 0;
|
|
93
|
+
this.frontier = 0;
|
|
94
|
+
}
|
|
95
|
+
else if (this.staleFrom > lastOld) {
|
|
96
|
+
this.staleFrom += delta;
|
|
97
|
+
}
|
|
98
|
+
else if (this.staleFrom > firstLine) {
|
|
99
|
+
this.staleFrom = firstLine;
|
|
100
|
+
}
|
|
86
101
|
if (last === undefined || lastOld < firstLine || this.entries.length <= firstLine) {
|
|
87
102
|
this.entries.length = Math.min(this.entries.length, firstLine);
|
|
103
|
+
if (this.staleFrom >= this.entries.length) {
|
|
104
|
+
this.staleFrom = Number.POSITIVE_INFINITY;
|
|
105
|
+
}
|
|
88
106
|
this.forgetPivot();
|
|
89
107
|
return;
|
|
90
108
|
}
|
|
@@ -94,6 +112,16 @@ export class TokenCache {
|
|
|
94
112
|
this.entries.splice(firstLine, lastOld - firstLine + 1, ...new Array(lastNew - firstLine + 1));
|
|
95
113
|
this.reusableFrom = lastNew + 1;
|
|
96
114
|
}
|
|
115
|
+
/* Old colors stay visible until the rebuilt grammar reaches each line. */
|
|
116
|
+
revalidate() {
|
|
117
|
+
if (this.tokenizer?.stale?.() !== true) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
this.frontier = 0;
|
|
121
|
+
this.staleFrom = 0;
|
|
122
|
+
this.forgetPivot();
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
97
125
|
/*
|
|
98
126
|
* Colors lines from the frontier until `until` is reached, the colors are right for the rest of
|
|
99
127
|
* the document, or `budget` milliseconds are spent. Returns the first line that changed, so a
|
|
@@ -104,10 +132,7 @@ export class TokenCache {
|
|
|
104
132
|
if (tokenizer === null) {
|
|
105
133
|
return null;
|
|
106
134
|
}
|
|
107
|
-
|
|
108
|
-
this.frontier = 0;
|
|
109
|
-
this.forgetPivot();
|
|
110
|
-
}
|
|
135
|
+
this.revalidate();
|
|
111
136
|
const count = this.source.getLineCount();
|
|
112
137
|
const limit = Math.min(until, count - 1);
|
|
113
138
|
const deadline = now() + budget;
|
|
@@ -127,10 +152,15 @@ export class TokenCache {
|
|
|
127
152
|
}
|
|
128
153
|
this.entries[line] = next;
|
|
129
154
|
this.frontier = line + 1;
|
|
130
|
-
const
|
|
131
|
-
|
|
155
|
+
const fresh = line < this.staleFrom;
|
|
156
|
+
if (line === this.staleFrom) {
|
|
157
|
+
this.staleFrom = line + 1 >= this.entries.length ? Number.POSITIVE_INFINITY : line + 1;
|
|
158
|
+
}
|
|
159
|
+
const settled = fresh &&
|
|
160
|
+
((line === this.pivotLine && tokenizer.sameState(this.pivotState, next.state)) ||
|
|
161
|
+
(previous !== undefined && line >= this.reusableFrom && tokenizer.sameState(previous.state, next.state)));
|
|
132
162
|
if (settled) {
|
|
133
|
-
this.frontier = this.entries.length;
|
|
163
|
+
this.frontier = Math.min(this.entries.length, this.staleFrom);
|
|
134
164
|
this.forgetPivot();
|
|
135
165
|
break;
|
|
136
166
|
}
|
package/dist/touch.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { DocumentModel } from '@adecore/editor-core';
|
|
2
|
+
export interface TouchHost {
|
|
3
|
+
readonly model: DocumentModel;
|
|
4
|
+
offsetAt(clientX: number, clientY: number): number;
|
|
5
|
+
camelHumps(): boolean;
|
|
6
|
+
lineAt(offset: number): {
|
|
7
|
+
from: number;
|
|
8
|
+
to: number;
|
|
9
|
+
};
|
|
10
|
+
focus(): void;
|
|
11
|
+
}
|
|
12
|
+
export declare class TouchSelection {
|
|
13
|
+
private contact;
|
|
14
|
+
private timer;
|
|
15
|
+
private lastTap;
|
|
16
|
+
private readonly host;
|
|
17
|
+
constructor(host: TouchHost);
|
|
18
|
+
get pressed(): boolean;
|
|
19
|
+
start(event: PointerEvent, area?: 'text' | 'gutter', taken?: () => boolean): void;
|
|
20
|
+
move(event: PointerEvent): void;
|
|
21
|
+
end(event: PointerEvent): void;
|
|
22
|
+
dispose(): void;
|
|
23
|
+
private tap;
|
|
24
|
+
private press;
|
|
25
|
+
private clearTimer;
|
|
26
|
+
private cancel;
|
|
27
|
+
}
|