@codemirror/language 0.19.2 → 0.19.6
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/CHANGELOG.md +30 -0
- package/dist/index.cjs +94 -59
- package/dist/index.d.ts +29 -4
- package/dist/index.js +95 -62
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
## 0.19.6 (2021-11-26)
|
|
2
|
+
|
|
3
|
+
### Bug fixes
|
|
4
|
+
|
|
5
|
+
Fixes an issue where the background parse work would be scheduled too aggressively, degrading responsiveness on a newly-created editor with a large document.
|
|
6
|
+
|
|
7
|
+
Improve initial highlight for mixed-language editors and limit the amount of parsing done on state creation for faster startup.
|
|
8
|
+
|
|
9
|
+
## 0.19.5 (2021-11-17)
|
|
10
|
+
|
|
11
|
+
### New features
|
|
12
|
+
|
|
13
|
+
The new function `syntaxTreeAvailable` can be used to check if a fully-parsed syntax tree is available up to a given document position.
|
|
14
|
+
|
|
15
|
+
The module now exports `syntaxParserRunning`, which tells you whether the background parser is still planning to do more work for a given editor view.
|
|
16
|
+
|
|
17
|
+
## 0.19.4 (2021-11-13)
|
|
18
|
+
|
|
19
|
+
### New features
|
|
20
|
+
|
|
21
|
+
`LanguageDescription.of` now takes an optional already-loaded extension.
|
|
22
|
+
|
|
23
|
+
## 0.19.3 (2021-09-13)
|
|
24
|
+
|
|
25
|
+
### Bug fixes
|
|
26
|
+
|
|
27
|
+
Fix an issue where a parse that skipped content with `skipUntilInView` would in some cases not be restarted when the range came into view.
|
|
28
|
+
|
|
1
29
|
## 0.19.2 (2021-08-11)
|
|
2
30
|
|
|
3
31
|
### Bug fixes
|
|
@@ -24,6 +52,8 @@ CodeMirror now uses lezer 0.15, which means different package names (scoped with
|
|
|
24
52
|
|
|
25
53
|
`LezerLanguage` was renamed to `LRLanguage` (because all languages must emit Lezer-style trees, the name was misleading).
|
|
26
54
|
|
|
55
|
+
`Language.parseString` no longer exists. You can just call `.parser.parse(...)` instead.
|
|
56
|
+
|
|
27
57
|
### New features
|
|
28
58
|
|
|
29
59
|
New `IndentContext.lineAt` method to access lines in a way that is aware of simulated line breaks.
|
package/dist/index.cjs
CHANGED
|
@@ -181,6 +181,30 @@ function ensureSyntaxTree(state, upto, timeout = 50) {
|
|
|
181
181
|
let parse = (_a = state.field(Language.state, false)) === null || _a === void 0 ? void 0 : _a.context;
|
|
182
182
|
return !parse ? null : parse.treeLen >= upto || parse.work(timeout, upto) ? parse.tree : null;
|
|
183
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
Queries whether there is a full syntax tree available up to the
|
|
186
|
+
given document position. If there isn't, the background parse
|
|
187
|
+
process _might_ still be working and update the tree further, but
|
|
188
|
+
there is no guarantee of that—the parser will [stop
|
|
189
|
+
working](https://codemirror.net/6/docs/ref/#language.syntaxParserStopped) when it has spent a
|
|
190
|
+
certain amount of time or has moved beyond the visible viewport.
|
|
191
|
+
Always returns false if no language has been enabled.
|
|
192
|
+
*/
|
|
193
|
+
function syntaxTreeAvailable(state, upto = state.doc.length) {
|
|
194
|
+
var _a;
|
|
195
|
+
return ((_a = state.field(Language.state, false)) === null || _a === void 0 ? void 0 : _a.context.isDone(upto)) || false;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
Tells you whether the language parser is planning to do more
|
|
199
|
+
parsing work (in a `requestIdleCallback` pseudo-thread) or has
|
|
200
|
+
stopped running, either because it parsed the entire document,
|
|
201
|
+
because it spent too much time and was cut off, or because there
|
|
202
|
+
is no language parser enabled.
|
|
203
|
+
*/
|
|
204
|
+
function syntaxParserRunning(view) {
|
|
205
|
+
var _a;
|
|
206
|
+
return ((_a = view.plugin(parseWorker)) === null || _a === void 0 ? void 0 : _a.isWorking()) || false;
|
|
207
|
+
}
|
|
184
208
|
// Lezer-style Input object for a Text document.
|
|
185
209
|
class DocInput {
|
|
186
210
|
constructor(doc, length = doc.length) {
|
|
@@ -233,7 +257,7 @@ class ParseContext {
|
|
|
233
257
|
The current editor viewport (or some overapproximation
|
|
234
258
|
thereof). Intended to be used for opportunistically avoiding
|
|
235
259
|
work (in which case
|
|
236
|
-
[`skipUntilInView`](https://codemirror.net/6/docs/ref/#language.
|
|
260
|
+
[`skipUntilInView`](https://codemirror.net/6/docs/ref/#language.ParseContext.skipUntilInView)
|
|
237
261
|
should be called to make sure the parser is restarted when the
|
|
238
262
|
skipped region becomes visible).
|
|
239
263
|
*/
|
|
@@ -271,18 +295,18 @@ class ParseContext {
|
|
|
271
295
|
work(time, upto) {
|
|
272
296
|
if (upto != null && upto >= this.state.doc.length)
|
|
273
297
|
upto = undefined;
|
|
274
|
-
if (this.tree != common.Tree.empty && (upto
|
|
298
|
+
if (this.tree != common.Tree.empty && this.isDone(upto !== null && upto !== void 0 ? upto : this.state.doc.length)) {
|
|
275
299
|
this.takeTree();
|
|
276
300
|
return true;
|
|
277
301
|
}
|
|
278
302
|
return this.withContext(() => {
|
|
279
303
|
var _a;
|
|
304
|
+
let endTime = Date.now() + time;
|
|
280
305
|
if (!this.parse)
|
|
281
306
|
this.parse = this.startParse();
|
|
282
307
|
if (upto != null && (this.parse.stoppedAt == null || this.parse.stoppedAt > upto) &&
|
|
283
308
|
upto < this.state.doc.length)
|
|
284
309
|
this.parse.stopAt(upto);
|
|
285
|
-
let endTime = Date.now() + time;
|
|
286
310
|
for (;;) {
|
|
287
311
|
let done = this.parse.advance();
|
|
288
312
|
if (done) {
|
|
@@ -305,10 +329,11 @@ class ParseContext {
|
|
|
305
329
|
*/
|
|
306
330
|
takeTree() {
|
|
307
331
|
let pos, tree;
|
|
308
|
-
if (this.parse && (pos = this.parse.parsedPos)
|
|
332
|
+
if (this.parse && (pos = this.parse.parsedPos) >= this.treeLen) {
|
|
309
333
|
if (this.parse.stoppedAt == null || this.parse.stoppedAt > pos)
|
|
310
334
|
this.parse.stopAt(pos);
|
|
311
335
|
this.withContext(() => { while (!(tree = this.parse.advance())) { } });
|
|
336
|
+
this.treeLen = pos;
|
|
312
337
|
this.tree = tree;
|
|
313
338
|
this.fragments = this.withoutTempSkipped(common.TreeFragment.addTree(this.tree, this.fragments, true));
|
|
314
339
|
this.parse = null;
|
|
@@ -357,6 +382,8 @@ class ParseContext {
|
|
|
357
382
|
@internal
|
|
358
383
|
*/
|
|
359
384
|
updateViewport(viewport) {
|
|
385
|
+
if (this.viewport.from == viewport.from && this.viewport.to == viewport.to)
|
|
386
|
+
return false;
|
|
360
387
|
this.viewport = viewport;
|
|
361
388
|
let startLen = this.skipped.length;
|
|
362
389
|
for (let i = 0; i < this.skipped.length; i++) {
|
|
@@ -366,7 +393,10 @@ class ParseContext {
|
|
|
366
393
|
this.skipped.splice(i--, 1);
|
|
367
394
|
}
|
|
368
395
|
}
|
|
369
|
-
|
|
396
|
+
if (this.skipped.length >= startLen)
|
|
397
|
+
return false;
|
|
398
|
+
this.reset();
|
|
399
|
+
return true;
|
|
370
400
|
}
|
|
371
401
|
/**
|
|
372
402
|
@internal
|
|
@@ -421,8 +451,9 @@ class ParseContext {
|
|
|
421
451
|
/**
|
|
422
452
|
@internal
|
|
423
453
|
*/
|
|
424
|
-
|
|
425
|
-
|
|
454
|
+
isDone(upto) {
|
|
455
|
+
let frags = this.fragments;
|
|
456
|
+
return this.treeLen >= upto && frags.length && frags[0].from == 0 && frags[0].to >= upto;
|
|
426
457
|
}
|
|
427
458
|
/**
|
|
428
459
|
Get the context for the current parse, or `null` if no editor
|
|
@@ -450,13 +481,14 @@ class LanguageState {
|
|
|
450
481
|
// state updates with parse work beyond the viewport.
|
|
451
482
|
let upto = this.context.treeLen == tr.startState.doc.length ? undefined
|
|
452
483
|
: Math.max(tr.changes.mapPos(this.context.treeLen), newCx.viewport.to);
|
|
453
|
-
if (!newCx.work(
|
|
484
|
+
if (!newCx.work(20 /* Apply */, upto))
|
|
454
485
|
newCx.takeTree();
|
|
455
486
|
return new LanguageState(newCx);
|
|
456
487
|
}
|
|
457
488
|
static init(state) {
|
|
458
|
-
let
|
|
459
|
-
|
|
489
|
+
let vpTo = Math.min(3000 /* InitViewport */, state.doc.length);
|
|
490
|
+
let parseState = new ParseContext(state.facet(language).parser, state, [], common.Tree.empty, 0, { from: 0, to: vpTo }, [], null);
|
|
491
|
+
if (!parseState.work(20 /* Apply */, vpTo))
|
|
460
492
|
parseState.takeTree();
|
|
461
493
|
return new LanguageState(parseState);
|
|
462
494
|
}
|
|
@@ -472,13 +504,22 @@ Language.state = state.StateField.define({
|
|
|
472
504
|
return value.apply(tr);
|
|
473
505
|
}
|
|
474
506
|
});
|
|
475
|
-
let requestIdle =
|
|
476
|
-
|
|
477
|
-
|
|
507
|
+
let requestIdle = (callback) => {
|
|
508
|
+
let timeout = setTimeout(() => callback(), 500 /* MaxPause */);
|
|
509
|
+
return () => clearTimeout(timeout);
|
|
510
|
+
};
|
|
511
|
+
if (typeof requestIdleCallback != "undefined")
|
|
512
|
+
requestIdle = (callback) => {
|
|
513
|
+
let idle = -1, timeout = setTimeout(() => {
|
|
514
|
+
idle = requestIdleCallback(callback, { timeout: 500 /* MaxPause */ - 100 /* MinPause */ });
|
|
515
|
+
}, 100 /* MinPause */);
|
|
516
|
+
return () => idle < 0 ? clearTimeout(timeout) : cancelIdleCallback(idle);
|
|
517
|
+
};
|
|
478
518
|
const parseWorker = view.ViewPlugin.fromClass(class ParseWorker {
|
|
479
519
|
constructor(view) {
|
|
480
520
|
this.view = view;
|
|
481
|
-
this.working =
|
|
521
|
+
this.working = null;
|
|
522
|
+
this.workScheduled = 0;
|
|
482
523
|
// End of the current time chunk
|
|
483
524
|
this.chunkEnd = -1;
|
|
484
525
|
// Milliseconds of budget left for this chunk
|
|
@@ -488,12 +529,8 @@ const parseWorker = view.ViewPlugin.fromClass(class ParseWorker {
|
|
|
488
529
|
}
|
|
489
530
|
update(update) {
|
|
490
531
|
let cx = this.view.state.field(Language.state).context;
|
|
491
|
-
if (update.
|
|
492
|
-
|
|
493
|
-
cx.reset();
|
|
494
|
-
if (this.view.viewport.to > cx.treeLen)
|
|
495
|
-
this.scheduleWork();
|
|
496
|
-
}
|
|
532
|
+
if (cx.updateViewport(update.view.viewport) || this.view.viewport.to > cx.treeLen)
|
|
533
|
+
this.scheduleWork();
|
|
497
534
|
if (update.docChanged) {
|
|
498
535
|
if (this.view.hasFocus)
|
|
499
536
|
this.chunkBudget += 50 /* ChangeBonus */;
|
|
@@ -502,16 +539,14 @@ const parseWorker = view.ViewPlugin.fromClass(class ParseWorker {
|
|
|
502
539
|
this.checkAsyncSchedule(cx);
|
|
503
540
|
}
|
|
504
541
|
scheduleWork() {
|
|
505
|
-
if (this.working
|
|
542
|
+
if (this.working)
|
|
506
543
|
return;
|
|
507
|
-
let { state } = this.view, field = state.field(Language.state)
|
|
508
|
-
if (field.tree
|
|
509
|
-
|
|
510
|
-
return;
|
|
511
|
-
this.working = requestIdle(this.work, { timeout: 500 /* Pause */ });
|
|
544
|
+
let { state } = this.view, field = state.field(Language.state);
|
|
545
|
+
if (field.tree != field.context.tree || !field.context.isDone(state.doc.length))
|
|
546
|
+
this.working = requestIdle(this.work);
|
|
512
547
|
}
|
|
513
548
|
work(deadline) {
|
|
514
|
-
this.working =
|
|
549
|
+
this.working = null;
|
|
515
550
|
let now = Date.now();
|
|
516
551
|
if (this.chunkEnd < now && (this.chunkEnd < 0 || this.view.hasFocus)) { // Start a new chunk
|
|
517
552
|
this.chunkEnd = now + 30000 /* ChunkTime */;
|
|
@@ -520,28 +555,36 @@ const parseWorker = view.ViewPlugin.fromClass(class ParseWorker {
|
|
|
520
555
|
if (this.chunkBudget <= 0)
|
|
521
556
|
return; // No more budget
|
|
522
557
|
let { state, viewport: { to: vpTo } } = this.view, field = state.field(Language.state);
|
|
523
|
-
if (field.tree == field.context.tree && field.context.treeLen >= vpTo +
|
|
558
|
+
if (field.tree == field.context.tree && field.context.treeLen >= vpTo + 100000 /* MaxParseAhead */)
|
|
524
559
|
return;
|
|
525
|
-
let time = Math.min(this.chunkBudget, deadline ? Math.max(25 /* MinSlice */, deadline.timeRemaining()) :
|
|
526
|
-
let
|
|
560
|
+
let time = Math.min(this.chunkBudget, 100 /* Slice */, deadline ? Math.max(25 /* MinSlice */, deadline.timeRemaining() - 5) : 1e9);
|
|
561
|
+
let viewportFirst = field.context.treeLen < vpTo && state.doc.length > vpTo + 1000;
|
|
562
|
+
let done = field.context.work(time, vpTo + (viewportFirst ? 0 : 100000 /* MaxParseAhead */));
|
|
527
563
|
this.chunkBudget -= Date.now() - now;
|
|
528
|
-
if (done || this.chunkBudget <= 0
|
|
564
|
+
if (done || this.chunkBudget <= 0) {
|
|
529
565
|
field.context.takeTree();
|
|
530
566
|
this.view.dispatch({ effects: Language.setState.of(new LanguageState(field.context)) });
|
|
531
567
|
}
|
|
532
|
-
if (
|
|
568
|
+
if (this.chunkBudget > 0 && !(done && !viewportFirst))
|
|
533
569
|
this.scheduleWork();
|
|
534
570
|
this.checkAsyncSchedule(field.context);
|
|
535
571
|
}
|
|
536
572
|
checkAsyncSchedule(cx) {
|
|
537
573
|
if (cx.scheduleOn) {
|
|
538
|
-
|
|
574
|
+
this.workScheduled++;
|
|
575
|
+
cx.scheduleOn
|
|
576
|
+
.then(() => this.scheduleWork())
|
|
577
|
+
.catch(err => view.logException(this.view.state, err))
|
|
578
|
+
.then(() => this.workScheduled--);
|
|
539
579
|
cx.scheduleOn = null;
|
|
540
580
|
}
|
|
541
581
|
}
|
|
542
582
|
destroy() {
|
|
543
|
-
if (this.working
|
|
544
|
-
|
|
583
|
+
if (this.working)
|
|
584
|
+
this.working();
|
|
585
|
+
}
|
|
586
|
+
isWorking() {
|
|
587
|
+
return this.working || this.workScheduled > 0;
|
|
545
588
|
}
|
|
546
589
|
}, {
|
|
547
590
|
eventHandlers: { focus() { this.scheduleWork(); } }
|
|
@@ -605,16 +648,17 @@ class LanguageDescription {
|
|
|
605
648
|
Optional filename pattern that should be associated with this
|
|
606
649
|
language.
|
|
607
650
|
*/
|
|
608
|
-
filename, loadFunc
|
|
651
|
+
filename, loadFunc,
|
|
652
|
+
/**
|
|
653
|
+
If the language has been loaded, this will hold its value.
|
|
654
|
+
*/
|
|
655
|
+
support = undefined) {
|
|
609
656
|
this.name = name;
|
|
610
657
|
this.alias = alias;
|
|
611
658
|
this.extensions = extensions;
|
|
612
659
|
this.filename = filename;
|
|
613
660
|
this.loadFunc = loadFunc;
|
|
614
|
-
|
|
615
|
-
If the language has been loaded, this will hold its value.
|
|
616
|
-
*/
|
|
617
|
-
this.support = undefined;
|
|
661
|
+
this.support = support;
|
|
618
662
|
this.loading = null;
|
|
619
663
|
}
|
|
620
664
|
/**
|
|
@@ -629,7 +673,13 @@ class LanguageDescription {
|
|
|
629
673
|
Create a language description.
|
|
630
674
|
*/
|
|
631
675
|
static of(spec) {
|
|
632
|
-
|
|
676
|
+
let { load, support } = spec;
|
|
677
|
+
if (!load) {
|
|
678
|
+
if (!support)
|
|
679
|
+
throw new RangeError("Must pass either 'load' or 'support' to LanguageDescription.of");
|
|
680
|
+
load = () => Promise.resolve(support);
|
|
681
|
+
}
|
|
682
|
+
return new LanguageDescription(spec.name, (spec.alias || []).concat(spec.name).map(s => s.toLowerCase()), spec.extensions || [], spec.filename, load, support);
|
|
633
683
|
}
|
|
634
684
|
/**
|
|
635
685
|
Look for a language in the given array of descriptions that
|
|
@@ -839,24 +889,7 @@ definitive indentation can be determined.
|
|
|
839
889
|
const indentNodeProp = new common.NodeProp();
|
|
840
890
|
// Compute the indentation for a given position from the syntax tree.
|
|
841
891
|
function syntaxIndentation(cx, ast, pos) {
|
|
842
|
-
|
|
843
|
-
// Enter previous nodes that end in empty error terms, which means
|
|
844
|
-
// they were broken off by error recovery, so that indentation
|
|
845
|
-
// works even if the constructs haven't been finished.
|
|
846
|
-
for (let scan = tree, scanPos = pos;;) {
|
|
847
|
-
let last = scan.childBefore(scanPos);
|
|
848
|
-
if (!last)
|
|
849
|
-
break;
|
|
850
|
-
if (last.type.isError && last.from == last.to) {
|
|
851
|
-
tree = scan;
|
|
852
|
-
scanPos = last.from;
|
|
853
|
-
}
|
|
854
|
-
else {
|
|
855
|
-
scan = last;
|
|
856
|
-
scanPos = scan.to + 1;
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
return indentFrom(tree, pos, cx);
|
|
892
|
+
return indentFrom(ast.resolveInner(pos).enterUnfinishedNodesBefore(pos), pos, cx);
|
|
860
893
|
}
|
|
861
894
|
function ignoreClosed(cx) {
|
|
862
895
|
return cx.pos == cx.options.simulateBreak && cx.options.simulateDoubleBreak;
|
|
@@ -1137,4 +1170,6 @@ exports.indentString = indentString;
|
|
|
1137
1170
|
exports.indentUnit = indentUnit;
|
|
1138
1171
|
exports.language = language;
|
|
1139
1172
|
exports.languageDataProp = languageDataProp;
|
|
1173
|
+
exports.syntaxParserRunning = syntaxParserRunning;
|
|
1140
1174
|
exports.syntaxTree = syntaxTree;
|
|
1175
|
+
exports.syntaxTreeAvailable = syntaxTreeAvailable;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NodeProp, NodeType, Parser, Tree, TreeFragment, Input, PartialParse, SyntaxNode } from '@lezer/common';
|
|
2
2
|
import { LRParser, ParserConfig } from '@lezer/lr';
|
|
3
3
|
import { Facet, Extension, EditorState } from '@codemirror/state';
|
|
4
|
+
import { EditorView } from '@codemirror/view';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
Node prop stored in a grammar's top syntax node to provide the
|
|
@@ -139,6 +140,24 @@ up to that point if the tree isn't already available.
|
|
|
139
140
|
*/
|
|
140
141
|
declare function ensureSyntaxTree(state: EditorState, upto: number, timeout?: number): Tree | null;
|
|
141
142
|
/**
|
|
143
|
+
Queries whether there is a full syntax tree available up to the
|
|
144
|
+
given document position. If there isn't, the background parse
|
|
145
|
+
process _might_ still be working and update the tree further, but
|
|
146
|
+
there is no guarantee of that—the parser will [stop
|
|
147
|
+
working](https://codemirror.net/6/docs/ref/#language.syntaxParserStopped) when it has spent a
|
|
148
|
+
certain amount of time or has moved beyond the visible viewport.
|
|
149
|
+
Always returns false if no language has been enabled.
|
|
150
|
+
*/
|
|
151
|
+
declare function syntaxTreeAvailable(state: EditorState, upto?: number): boolean;
|
|
152
|
+
/**
|
|
153
|
+
Tells you whether the language parser is planning to do more
|
|
154
|
+
parsing work (in a `requestIdleCallback` pseudo-thread) or has
|
|
155
|
+
stopped running, either because it parsed the entire document,
|
|
156
|
+
because it spent too much time and was cut off, or because there
|
|
157
|
+
is no language parser enabled.
|
|
158
|
+
*/
|
|
159
|
+
declare function syntaxParserRunning(view: EditorView): boolean | (() => void);
|
|
160
|
+
/**
|
|
142
161
|
A parse context provided to parsers working on the editor content.
|
|
143
162
|
*/
|
|
144
163
|
declare class ParseContext {
|
|
@@ -156,7 +175,7 @@ declare class ParseContext {
|
|
|
156
175
|
The current editor viewport (or some overapproximation
|
|
157
176
|
thereof). Intended to be used for opportunistically avoiding
|
|
158
177
|
work (in which case
|
|
159
|
-
[`skipUntilInView`](https://codemirror.net/6/docs/ref/#language.
|
|
178
|
+
[`skipUntilInView`](https://codemirror.net/6/docs/ref/#language.ParseContext.skipUntilInView)
|
|
160
179
|
should be called to make sure the parser is restarted when the
|
|
161
180
|
skipped region becomes visible).
|
|
162
181
|
*/
|
|
@@ -298,7 +317,8 @@ declare class LanguageDescription {
|
|
|
298
317
|
*/
|
|
299
318
|
alias?: readonly string[];
|
|
300
319
|
/**
|
|
301
|
-
An optional array of extensions associated with this
|
|
320
|
+
An optional array of filename extensions associated with this
|
|
321
|
+
language.
|
|
302
322
|
*/
|
|
303
323
|
extensions?: readonly string[];
|
|
304
324
|
/**
|
|
@@ -308,7 +328,12 @@ declare class LanguageDescription {
|
|
|
308
328
|
/**
|
|
309
329
|
A function that will asynchronously load the language.
|
|
310
330
|
*/
|
|
311
|
-
load
|
|
331
|
+
load?: () => Promise<LanguageSupport>;
|
|
332
|
+
/**
|
|
333
|
+
Alternatively to `load`, you can provide an already loaded
|
|
334
|
+
support object. Either this or `load` should be provided.
|
|
335
|
+
*/
|
|
336
|
+
support?: LanguageSupport;
|
|
312
337
|
}): LanguageDescription;
|
|
313
338
|
/**
|
|
314
339
|
Look for a language in the given array of descriptions that
|
|
@@ -584,4 +609,4 @@ declare function foldable(state: EditorState, lineStart: number, lineEnd: number
|
|
|
584
609
|
to: number;
|
|
585
610
|
} | null;
|
|
586
611
|
|
|
587
|
-
export { IndentContext, LRLanguage, Language, LanguageDescription, LanguageSupport, ParseContext, TreeIndentContext, continuedIndent, defineLanguageFacet, delimitedIndent, ensureSyntaxTree, flatIndent, foldInside, foldNodeProp, foldService, foldable, getIndentUnit, getIndentation, indentNodeProp, indentOnInput, indentService, indentString, indentUnit, language, languageDataProp, syntaxTree };
|
|
612
|
+
export { IndentContext, LRLanguage, Language, LanguageDescription, LanguageSupport, ParseContext, TreeIndentContext, continuedIndent, defineLanguageFacet, delimitedIndent, ensureSyntaxTree, flatIndent, foldInside, foldNodeProp, foldService, foldable, getIndentUnit, getIndentation, indentNodeProp, indentOnInput, indentService, indentString, indentUnit, language, languageDataProp, syntaxParserRunning, syntaxTree, syntaxTreeAvailable };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NodeProp, Tree, TreeFragment, Parser, NodeType } from '@lezer/common';
|
|
2
|
-
import {
|
|
3
|
-
import { ViewPlugin } from '@codemirror/view';
|
|
2
|
+
import { StateEffect, StateField, Facet, EditorState } from '@codemirror/state';
|
|
3
|
+
import { ViewPlugin, logException } from '@codemirror/view';
|
|
4
4
|
import { countColumn } from '@codemirror/text';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -177,6 +177,30 @@ function ensureSyntaxTree(state, upto, timeout = 50) {
|
|
|
177
177
|
let parse = (_a = state.field(Language.state, false)) === null || _a === void 0 ? void 0 : _a.context;
|
|
178
178
|
return !parse ? null : parse.treeLen >= upto || parse.work(timeout, upto) ? parse.tree : null;
|
|
179
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
Queries whether there is a full syntax tree available up to the
|
|
182
|
+
given document position. If there isn't, the background parse
|
|
183
|
+
process _might_ still be working and update the tree further, but
|
|
184
|
+
there is no guarantee of that—the parser will [stop
|
|
185
|
+
working](https://codemirror.net/6/docs/ref/#language.syntaxParserStopped) when it has spent a
|
|
186
|
+
certain amount of time or has moved beyond the visible viewport.
|
|
187
|
+
Always returns false if no language has been enabled.
|
|
188
|
+
*/
|
|
189
|
+
function syntaxTreeAvailable(state, upto = state.doc.length) {
|
|
190
|
+
var _a;
|
|
191
|
+
return ((_a = state.field(Language.state, false)) === null || _a === void 0 ? void 0 : _a.context.isDone(upto)) || false;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
Tells you whether the language parser is planning to do more
|
|
195
|
+
parsing work (in a `requestIdleCallback` pseudo-thread) or has
|
|
196
|
+
stopped running, either because it parsed the entire document,
|
|
197
|
+
because it spent too much time and was cut off, or because there
|
|
198
|
+
is no language parser enabled.
|
|
199
|
+
*/
|
|
200
|
+
function syntaxParserRunning(view) {
|
|
201
|
+
var _a;
|
|
202
|
+
return ((_a = view.plugin(parseWorker)) === null || _a === void 0 ? void 0 : _a.isWorking()) || false;
|
|
203
|
+
}
|
|
180
204
|
// Lezer-style Input object for a Text document.
|
|
181
205
|
class DocInput {
|
|
182
206
|
constructor(doc, length = doc.length) {
|
|
@@ -229,7 +253,7 @@ class ParseContext {
|
|
|
229
253
|
The current editor viewport (or some overapproximation
|
|
230
254
|
thereof). Intended to be used for opportunistically avoiding
|
|
231
255
|
work (in which case
|
|
232
|
-
[`skipUntilInView`](https://codemirror.net/6/docs/ref/#language.
|
|
256
|
+
[`skipUntilInView`](https://codemirror.net/6/docs/ref/#language.ParseContext.skipUntilInView)
|
|
233
257
|
should be called to make sure the parser is restarted when the
|
|
234
258
|
skipped region becomes visible).
|
|
235
259
|
*/
|
|
@@ -267,18 +291,18 @@ class ParseContext {
|
|
|
267
291
|
work(time, upto) {
|
|
268
292
|
if (upto != null && upto >= this.state.doc.length)
|
|
269
293
|
upto = undefined;
|
|
270
|
-
if (this.tree != Tree.empty && (upto
|
|
294
|
+
if (this.tree != Tree.empty && this.isDone(upto !== null && upto !== void 0 ? upto : this.state.doc.length)) {
|
|
271
295
|
this.takeTree();
|
|
272
296
|
return true;
|
|
273
297
|
}
|
|
274
298
|
return this.withContext(() => {
|
|
275
299
|
var _a;
|
|
300
|
+
let endTime = Date.now() + time;
|
|
276
301
|
if (!this.parse)
|
|
277
302
|
this.parse = this.startParse();
|
|
278
303
|
if (upto != null && (this.parse.stoppedAt == null || this.parse.stoppedAt > upto) &&
|
|
279
304
|
upto < this.state.doc.length)
|
|
280
305
|
this.parse.stopAt(upto);
|
|
281
|
-
let endTime = Date.now() + time;
|
|
282
306
|
for (;;) {
|
|
283
307
|
let done = this.parse.advance();
|
|
284
308
|
if (done) {
|
|
@@ -301,10 +325,11 @@ class ParseContext {
|
|
|
301
325
|
*/
|
|
302
326
|
takeTree() {
|
|
303
327
|
let pos, tree;
|
|
304
|
-
if (this.parse && (pos = this.parse.parsedPos)
|
|
328
|
+
if (this.parse && (pos = this.parse.parsedPos) >= this.treeLen) {
|
|
305
329
|
if (this.parse.stoppedAt == null || this.parse.stoppedAt > pos)
|
|
306
330
|
this.parse.stopAt(pos);
|
|
307
331
|
this.withContext(() => { while (!(tree = this.parse.advance())) { } });
|
|
332
|
+
this.treeLen = pos;
|
|
308
333
|
this.tree = tree;
|
|
309
334
|
this.fragments = this.withoutTempSkipped(TreeFragment.addTree(this.tree, this.fragments, true));
|
|
310
335
|
this.parse = null;
|
|
@@ -353,6 +378,8 @@ class ParseContext {
|
|
|
353
378
|
@internal
|
|
354
379
|
*/
|
|
355
380
|
updateViewport(viewport) {
|
|
381
|
+
if (this.viewport.from == viewport.from && this.viewport.to == viewport.to)
|
|
382
|
+
return false;
|
|
356
383
|
this.viewport = viewport;
|
|
357
384
|
let startLen = this.skipped.length;
|
|
358
385
|
for (let i = 0; i < this.skipped.length; i++) {
|
|
@@ -362,7 +389,10 @@ class ParseContext {
|
|
|
362
389
|
this.skipped.splice(i--, 1);
|
|
363
390
|
}
|
|
364
391
|
}
|
|
365
|
-
|
|
392
|
+
if (this.skipped.length >= startLen)
|
|
393
|
+
return false;
|
|
394
|
+
this.reset();
|
|
395
|
+
return true;
|
|
366
396
|
}
|
|
367
397
|
/**
|
|
368
398
|
@internal
|
|
@@ -417,8 +447,9 @@ class ParseContext {
|
|
|
417
447
|
/**
|
|
418
448
|
@internal
|
|
419
449
|
*/
|
|
420
|
-
|
|
421
|
-
|
|
450
|
+
isDone(upto) {
|
|
451
|
+
let frags = this.fragments;
|
|
452
|
+
return this.treeLen >= upto && frags.length && frags[0].from == 0 && frags[0].to >= upto;
|
|
422
453
|
}
|
|
423
454
|
/**
|
|
424
455
|
Get the context for the current parse, or `null` if no editor
|
|
@@ -446,13 +477,14 @@ class LanguageState {
|
|
|
446
477
|
// state updates with parse work beyond the viewport.
|
|
447
478
|
let upto = this.context.treeLen == tr.startState.doc.length ? undefined
|
|
448
479
|
: Math.max(tr.changes.mapPos(this.context.treeLen), newCx.viewport.to);
|
|
449
|
-
if (!newCx.work(
|
|
480
|
+
if (!newCx.work(20 /* Apply */, upto))
|
|
450
481
|
newCx.takeTree();
|
|
451
482
|
return new LanguageState(newCx);
|
|
452
483
|
}
|
|
453
484
|
static init(state) {
|
|
454
|
-
let
|
|
455
|
-
|
|
485
|
+
let vpTo = Math.min(3000 /* InitViewport */, state.doc.length);
|
|
486
|
+
let parseState = new ParseContext(state.facet(language).parser, state, [], Tree.empty, 0, { from: 0, to: vpTo }, [], null);
|
|
487
|
+
if (!parseState.work(20 /* Apply */, vpTo))
|
|
456
488
|
parseState.takeTree();
|
|
457
489
|
return new LanguageState(parseState);
|
|
458
490
|
}
|
|
@@ -468,13 +500,22 @@ Language.state = /*@__PURE__*/StateField.define({
|
|
|
468
500
|
return value.apply(tr);
|
|
469
501
|
}
|
|
470
502
|
});
|
|
471
|
-
let requestIdle =
|
|
472
|
-
|
|
473
|
-
|
|
503
|
+
let requestIdle = (callback) => {
|
|
504
|
+
let timeout = setTimeout(() => callback(), 500 /* MaxPause */);
|
|
505
|
+
return () => clearTimeout(timeout);
|
|
506
|
+
};
|
|
507
|
+
if (typeof requestIdleCallback != "undefined")
|
|
508
|
+
requestIdle = (callback) => {
|
|
509
|
+
let idle = -1, timeout = setTimeout(() => {
|
|
510
|
+
idle = requestIdleCallback(callback, { timeout: 500 /* MaxPause */ - 100 /* MinPause */ });
|
|
511
|
+
}, 100 /* MinPause */);
|
|
512
|
+
return () => idle < 0 ? clearTimeout(timeout) : cancelIdleCallback(idle);
|
|
513
|
+
};
|
|
474
514
|
const parseWorker = /*@__PURE__*/ViewPlugin.fromClass(class ParseWorker {
|
|
475
515
|
constructor(view) {
|
|
476
516
|
this.view = view;
|
|
477
|
-
this.working =
|
|
517
|
+
this.working = null;
|
|
518
|
+
this.workScheduled = 0;
|
|
478
519
|
// End of the current time chunk
|
|
479
520
|
this.chunkEnd = -1;
|
|
480
521
|
// Milliseconds of budget left for this chunk
|
|
@@ -484,12 +525,8 @@ const parseWorker = /*@__PURE__*/ViewPlugin.fromClass(class ParseWorker {
|
|
|
484
525
|
}
|
|
485
526
|
update(update) {
|
|
486
527
|
let cx = this.view.state.field(Language.state).context;
|
|
487
|
-
if (update.
|
|
488
|
-
|
|
489
|
-
cx.reset();
|
|
490
|
-
if (this.view.viewport.to > cx.treeLen)
|
|
491
|
-
this.scheduleWork();
|
|
492
|
-
}
|
|
528
|
+
if (cx.updateViewport(update.view.viewport) || this.view.viewport.to > cx.treeLen)
|
|
529
|
+
this.scheduleWork();
|
|
493
530
|
if (update.docChanged) {
|
|
494
531
|
if (this.view.hasFocus)
|
|
495
532
|
this.chunkBudget += 50 /* ChangeBonus */;
|
|
@@ -498,16 +535,14 @@ const parseWorker = /*@__PURE__*/ViewPlugin.fromClass(class ParseWorker {
|
|
|
498
535
|
this.checkAsyncSchedule(cx);
|
|
499
536
|
}
|
|
500
537
|
scheduleWork() {
|
|
501
|
-
if (this.working
|
|
538
|
+
if (this.working)
|
|
502
539
|
return;
|
|
503
|
-
let { state } = this.view, field = state.field(Language.state)
|
|
504
|
-
if (field.tree
|
|
505
|
-
|
|
506
|
-
return;
|
|
507
|
-
this.working = requestIdle(this.work, { timeout: 500 /* Pause */ });
|
|
540
|
+
let { state } = this.view, field = state.field(Language.state);
|
|
541
|
+
if (field.tree != field.context.tree || !field.context.isDone(state.doc.length))
|
|
542
|
+
this.working = requestIdle(this.work);
|
|
508
543
|
}
|
|
509
544
|
work(deadline) {
|
|
510
|
-
this.working =
|
|
545
|
+
this.working = null;
|
|
511
546
|
let now = Date.now();
|
|
512
547
|
if (this.chunkEnd < now && (this.chunkEnd < 0 || this.view.hasFocus)) { // Start a new chunk
|
|
513
548
|
this.chunkEnd = now + 30000 /* ChunkTime */;
|
|
@@ -516,28 +551,36 @@ const parseWorker = /*@__PURE__*/ViewPlugin.fromClass(class ParseWorker {
|
|
|
516
551
|
if (this.chunkBudget <= 0)
|
|
517
552
|
return; // No more budget
|
|
518
553
|
let { state, viewport: { to: vpTo } } = this.view, field = state.field(Language.state);
|
|
519
|
-
if (field.tree == field.context.tree && field.context.treeLen >= vpTo +
|
|
554
|
+
if (field.tree == field.context.tree && field.context.treeLen >= vpTo + 100000 /* MaxParseAhead */)
|
|
520
555
|
return;
|
|
521
|
-
let time = Math.min(this.chunkBudget, deadline ? Math.max(25 /* MinSlice */, deadline.timeRemaining()) :
|
|
522
|
-
let
|
|
556
|
+
let time = Math.min(this.chunkBudget, 100 /* Slice */, deadline ? Math.max(25 /* MinSlice */, deadline.timeRemaining() - 5) : 1e9);
|
|
557
|
+
let viewportFirst = field.context.treeLen < vpTo && state.doc.length > vpTo + 1000;
|
|
558
|
+
let done = field.context.work(time, vpTo + (viewportFirst ? 0 : 100000 /* MaxParseAhead */));
|
|
523
559
|
this.chunkBudget -= Date.now() - now;
|
|
524
|
-
if (done || this.chunkBudget <= 0
|
|
560
|
+
if (done || this.chunkBudget <= 0) {
|
|
525
561
|
field.context.takeTree();
|
|
526
562
|
this.view.dispatch({ effects: Language.setState.of(new LanguageState(field.context)) });
|
|
527
563
|
}
|
|
528
|
-
if (
|
|
564
|
+
if (this.chunkBudget > 0 && !(done && !viewportFirst))
|
|
529
565
|
this.scheduleWork();
|
|
530
566
|
this.checkAsyncSchedule(field.context);
|
|
531
567
|
}
|
|
532
568
|
checkAsyncSchedule(cx) {
|
|
533
569
|
if (cx.scheduleOn) {
|
|
534
|
-
|
|
570
|
+
this.workScheduled++;
|
|
571
|
+
cx.scheduleOn
|
|
572
|
+
.then(() => this.scheduleWork())
|
|
573
|
+
.catch(err => logException(this.view.state, err))
|
|
574
|
+
.then(() => this.workScheduled--);
|
|
535
575
|
cx.scheduleOn = null;
|
|
536
576
|
}
|
|
537
577
|
}
|
|
538
578
|
destroy() {
|
|
539
|
-
if (this.working
|
|
540
|
-
|
|
579
|
+
if (this.working)
|
|
580
|
+
this.working();
|
|
581
|
+
}
|
|
582
|
+
isWorking() {
|
|
583
|
+
return this.working || this.workScheduled > 0;
|
|
541
584
|
}
|
|
542
585
|
}, {
|
|
543
586
|
eventHandlers: { focus() { this.scheduleWork(); } }
|
|
@@ -601,16 +644,17 @@ class LanguageDescription {
|
|
|
601
644
|
Optional filename pattern that should be associated with this
|
|
602
645
|
language.
|
|
603
646
|
*/
|
|
604
|
-
filename, loadFunc
|
|
647
|
+
filename, loadFunc,
|
|
648
|
+
/**
|
|
649
|
+
If the language has been loaded, this will hold its value.
|
|
650
|
+
*/
|
|
651
|
+
support = undefined) {
|
|
605
652
|
this.name = name;
|
|
606
653
|
this.alias = alias;
|
|
607
654
|
this.extensions = extensions;
|
|
608
655
|
this.filename = filename;
|
|
609
656
|
this.loadFunc = loadFunc;
|
|
610
|
-
|
|
611
|
-
If the language has been loaded, this will hold its value.
|
|
612
|
-
*/
|
|
613
|
-
this.support = undefined;
|
|
657
|
+
this.support = support;
|
|
614
658
|
this.loading = null;
|
|
615
659
|
}
|
|
616
660
|
/**
|
|
@@ -625,7 +669,13 @@ class LanguageDescription {
|
|
|
625
669
|
Create a language description.
|
|
626
670
|
*/
|
|
627
671
|
static of(spec) {
|
|
628
|
-
|
|
672
|
+
let { load, support } = spec;
|
|
673
|
+
if (!load) {
|
|
674
|
+
if (!support)
|
|
675
|
+
throw new RangeError("Must pass either 'load' or 'support' to LanguageDescription.of");
|
|
676
|
+
load = () => Promise.resolve(support);
|
|
677
|
+
}
|
|
678
|
+
return new LanguageDescription(spec.name, (spec.alias || []).concat(spec.name).map(s => s.toLowerCase()), spec.extensions || [], spec.filename, load, support);
|
|
629
679
|
}
|
|
630
680
|
/**
|
|
631
681
|
Look for a language in the given array of descriptions that
|
|
@@ -835,24 +885,7 @@ definitive indentation can be determined.
|
|
|
835
885
|
const indentNodeProp = /*@__PURE__*/new NodeProp();
|
|
836
886
|
// Compute the indentation for a given position from the syntax tree.
|
|
837
887
|
function syntaxIndentation(cx, ast, pos) {
|
|
838
|
-
|
|
839
|
-
// Enter previous nodes that end in empty error terms, which means
|
|
840
|
-
// they were broken off by error recovery, so that indentation
|
|
841
|
-
// works even if the constructs haven't been finished.
|
|
842
|
-
for (let scan = tree, scanPos = pos;;) {
|
|
843
|
-
let last = scan.childBefore(scanPos);
|
|
844
|
-
if (!last)
|
|
845
|
-
break;
|
|
846
|
-
if (last.type.isError && last.from == last.to) {
|
|
847
|
-
tree = scan;
|
|
848
|
-
scanPos = last.from;
|
|
849
|
-
}
|
|
850
|
-
else {
|
|
851
|
-
scan = last;
|
|
852
|
-
scanPos = scan.to + 1;
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
return indentFrom(tree, pos, cx);
|
|
888
|
+
return indentFrom(ast.resolveInner(pos).enterUnfinishedNodesBefore(pos), pos, cx);
|
|
856
889
|
}
|
|
857
890
|
function ignoreClosed(cx) {
|
|
858
891
|
return cx.pos == cx.options.simulateBreak && cx.options.simulateDoubleBreak;
|
|
@@ -1108,4 +1141,4 @@ function foldable(state, lineStart, lineEnd) {
|
|
|
1108
1141
|
return syntaxFolding(state, lineStart, lineEnd);
|
|
1109
1142
|
}
|
|
1110
1143
|
|
|
1111
|
-
export { IndentContext, LRLanguage, Language, LanguageDescription, LanguageSupport, ParseContext, TreeIndentContext, continuedIndent, defineLanguageFacet, delimitedIndent, ensureSyntaxTree, flatIndent, foldInside, foldNodeProp, foldService, foldable, getIndentUnit, getIndentation, indentNodeProp, indentOnInput, indentService, indentString, indentUnit, language, languageDataProp, syntaxTree };
|
|
1144
|
+
export { IndentContext, LRLanguage, Language, LanguageDescription, LanguageSupport, ParseContext, TreeIndentContext, continuedIndent, defineLanguageFacet, delimitedIndent, ensureSyntaxTree, flatIndent, foldInside, foldNodeProp, foldService, foldable, getIndentUnit, getIndentation, indentNodeProp, indentOnInput, indentService, indentString, indentUnit, language, languageDataProp, syntaxParserRunning, syntaxTree, syntaxTreeAvailable };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codemirror/language",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.6",
|
|
4
4
|
"description": "Language support infrastructure for the CodeMirror code editor",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "cm-runtests",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"@codemirror/state": "^0.19.0",
|
|
30
30
|
"@codemirror/text": "^0.19.0",
|
|
31
31
|
"@codemirror/view": "^0.19.0",
|
|
32
|
-
"@lezer/common": "^0.15.
|
|
32
|
+
"@lezer/common": "^0.15.5",
|
|
33
33
|
"@lezer/lr": "^0.15.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|