@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 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.EditorParseContext.skipUntilInView)
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 == null ? this.treeLen == this.state.doc.length : this.treeLen >= 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) > this.treeLen) {
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
- return this.skipped.length < startLen;
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
- movedPast(pos) {
425
- return this.treeLen < pos && this.parse && this.parse.parsedPos >= pos;
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(25 /* Apply */, upto))
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 parseState = new ParseContext(state.facet(language).parser, state, [], common.Tree.empty, 0, { from: 0, to: state.doc.length }, [], null);
459
- if (!parseState.work(25 /* Apply */))
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 = typeof window != "undefined" && window.requestIdleCallback ||
476
- ((callback, { timeout }) => setTimeout(callback, timeout));
477
- let cancelIdle = typeof window != "undefined" && window.cancelIdleCallback || clearTimeout;
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 = -1;
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.viewportChanged) {
492
- if (cx.updateViewport(update.view.viewport))
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 > -1)
542
+ if (this.working)
506
543
  return;
507
- let { state } = this.view, field = state.field(Language.state), frags = field.context.fragments;
508
- if (field.tree == field.context.tree && field.context.treeLen >= state.doc.length &&
509
- frags.length && frags[0].from == 0 && frags[0].to >= state.doc.length)
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 = -1;
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 + 1000000 /* MaxParseAhead */)
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()) : 100 /* Slice */);
526
- let done = field.context.work(time, vpTo + 1000000 /* MaxParseAhead */);
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 || field.context.movedPast(vpTo)) {
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 (!done && this.chunkBudget > 0)
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
- cx.scheduleOn.then(() => this.scheduleWork());
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 >= 0)
544
- cancelIdle(this.working);
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
- return new LanguageDescription(spec.name, (spec.alias || []).concat(spec.name).map(s => s.toLowerCase()), spec.extensions || [], spec.filename, spec.load);
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
- let tree = ast.resolveInner(pos);
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.EditorParseContext.skipUntilInView)
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 language.
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: () => Promise<LanguageSupport>;
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 { Facet, EditorState, StateEffect, StateField } from '@codemirror/state';
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.EditorParseContext.skipUntilInView)
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 == null ? this.treeLen == this.state.doc.length : this.treeLen >= 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) > this.treeLen) {
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
- return this.skipped.length < startLen;
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
- movedPast(pos) {
421
- return this.treeLen < pos && this.parse && this.parse.parsedPos >= pos;
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(25 /* Apply */, upto))
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 parseState = new ParseContext(state.facet(language).parser, state, [], Tree.empty, 0, { from: 0, to: state.doc.length }, [], null);
455
- if (!parseState.work(25 /* Apply */))
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 = typeof window != "undefined" && window.requestIdleCallback ||
472
- ((callback, { timeout }) => setTimeout(callback, timeout));
473
- let cancelIdle = typeof window != "undefined" && window.cancelIdleCallback || clearTimeout;
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 = -1;
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.viewportChanged) {
488
- if (cx.updateViewport(update.view.viewport))
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 > -1)
538
+ if (this.working)
502
539
  return;
503
- let { state } = this.view, field = state.field(Language.state), frags = field.context.fragments;
504
- if (field.tree == field.context.tree && field.context.treeLen >= state.doc.length &&
505
- frags.length && frags[0].from == 0 && frags[0].to >= state.doc.length)
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 = -1;
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 + 1000000 /* MaxParseAhead */)
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()) : 100 /* Slice */);
522
- let done = field.context.work(time, vpTo + 1000000 /* MaxParseAhead */);
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 || field.context.movedPast(vpTo)) {
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 (!done && this.chunkBudget > 0)
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
- cx.scheduleOn.then(() => this.scheduleWork());
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 >= 0)
540
- cancelIdle(this.working);
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
- return new LanguageDescription(spec.name, (spec.alias || []).concat(spec.name).map(s => s.toLowerCase()), spec.extensions || [], spec.filename, spec.load);
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
- let tree = ast.resolveInner(pos);
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.2",
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.0",
32
+ "@lezer/common": "^0.15.5",
33
33
  "@lezer/lr": "^0.15.0"
34
34
  },
35
35
  "devDependencies": {