@codemirror/language 0.19.3 → 0.19.7

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.7 (2021-12-02)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix an issue where the parse worker could incorrectly stop working when the parse tree has skipped gaps in it.
6
+
7
+ ## 0.19.6 (2021-11-26)
8
+
9
+ ### Bug fixes
10
+
11
+ Fixes an issue where the background parse work would be scheduled too aggressively, degrading responsiveness on a newly-created editor with a large document.
12
+
13
+ Improve initial highlight for mixed-language editors and limit the amount of parsing done on state creation for faster startup.
14
+
15
+ ## 0.19.5 (2021-11-17)
16
+
17
+ ### New features
18
+
19
+ The new function `syntaxTreeAvailable` can be used to check if a fully-parsed syntax tree is available up to a given document position.
20
+
21
+ The module now exports `syntaxParserRunning`, which tells you whether the background parser is still planning to do more work for a given editor view.
22
+
23
+ ## 0.19.4 (2021-11-13)
24
+
25
+ ### New features
26
+
27
+ `LanguageDescription.of` now takes an optional already-loaded extension.
28
+
1
29
  ## 0.19.3 (2021-09-13)
2
30
 
3
31
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -179,7 +179,31 @@ up to that point if the tree isn't already available.
179
179
  function ensureSyntaxTree(state, upto, timeout = 50) {
180
180
  var _a;
181
181
  let parse = (_a = state.field(Language.state, false)) === null || _a === void 0 ? void 0 : _a.context;
182
- return !parse ? null : parse.treeLen >= upto || parse.work(timeout, upto) ? parse.tree : null;
182
+ return !parse ? null : parse.isDone(upto) || parse.work(timeout, upto) ? parse.tree : null;
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;
183
207
  }
184
208
  // Lezer-style Input object for a Text document.
185
209
  class DocInput {
@@ -277,12 +301,12 @@ class ParseContext {
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;
@@ -426,13 +451,8 @@ class ParseContext {
426
451
  /**
427
452
  @internal
428
453
  */
429
- movedPast(pos) {
430
- return this.treeLen < pos && this.parse && this.parse.parsedPos >= pos;
431
- }
432
- /**
433
- @internal
434
- */
435
454
  isDone(upto) {
455
+ upto = Math.min(upto, this.state.doc.length);
436
456
  let frags = this.fragments;
437
457
  return this.treeLen >= upto && frags.length && frags[0].from == 0 && frags[0].to >= upto;
438
458
  }
@@ -462,13 +482,14 @@ class LanguageState {
462
482
  // state updates with parse work beyond the viewport.
463
483
  let upto = this.context.treeLen == tr.startState.doc.length ? undefined
464
484
  : Math.max(tr.changes.mapPos(this.context.treeLen), newCx.viewport.to);
465
- if (!newCx.work(25 /* Apply */, upto))
485
+ if (!newCx.work(20 /* Apply */, upto))
466
486
  newCx.takeTree();
467
487
  return new LanguageState(newCx);
468
488
  }
469
489
  static init(state) {
470
- let parseState = new ParseContext(state.facet(language).parser, state, [], common.Tree.empty, 0, { from: 0, to: state.doc.length }, [], null);
471
- if (!parseState.work(25 /* Apply */))
490
+ let vpTo = Math.min(3000 /* InitViewport */, state.doc.length);
491
+ let parseState = new ParseContext(state.facet(language).parser, state, [], common.Tree.empty, 0, { from: 0, to: vpTo }, [], null);
492
+ if (!parseState.work(20 /* Apply */, vpTo))
472
493
  parseState.takeTree();
473
494
  return new LanguageState(parseState);
474
495
  }
@@ -484,13 +505,22 @@ Language.state = state.StateField.define({
484
505
  return value.apply(tr);
485
506
  }
486
507
  });
487
- let requestIdle = typeof window != "undefined" && window.requestIdleCallback ||
488
- ((callback, { timeout }) => setTimeout(callback, timeout));
489
- let cancelIdle = typeof window != "undefined" && window.cancelIdleCallback || clearTimeout;
508
+ let requestIdle = (callback) => {
509
+ let timeout = setTimeout(() => callback(), 500 /* MaxPause */);
510
+ return () => clearTimeout(timeout);
511
+ };
512
+ if (typeof requestIdleCallback != "undefined")
513
+ requestIdle = (callback) => {
514
+ let idle = -1, timeout = setTimeout(() => {
515
+ idle = requestIdleCallback(callback, { timeout: 500 /* MaxPause */ - 100 /* MinPause */ });
516
+ }, 100 /* MinPause */);
517
+ return () => idle < 0 ? clearTimeout(timeout) : cancelIdleCallback(idle);
518
+ };
490
519
  const parseWorker = view.ViewPlugin.fromClass(class ParseWorker {
491
520
  constructor(view) {
492
521
  this.view = view;
493
- this.working = -1;
522
+ this.working = null;
523
+ this.workScheduled = 0;
494
524
  // End of the current time chunk
495
525
  this.chunkEnd = -1;
496
526
  // Milliseconds of budget left for this chunk
@@ -510,14 +540,14 @@ const parseWorker = view.ViewPlugin.fromClass(class ParseWorker {
510
540
  this.checkAsyncSchedule(cx);
511
541
  }
512
542
  scheduleWork() {
513
- if (this.working > -1)
543
+ if (this.working)
514
544
  return;
515
545
  let { state } = this.view, field = state.field(Language.state);
516
546
  if (field.tree != field.context.tree || !field.context.isDone(state.doc.length))
517
- this.working = requestIdle(this.work, { timeout: 500 /* Pause */ });
547
+ this.working = requestIdle(this.work);
518
548
  }
519
549
  work(deadline) {
520
- this.working = -1;
550
+ this.working = null;
521
551
  let now = Date.now();
522
552
  if (this.chunkEnd < now && (this.chunkEnd < 0 || this.view.hasFocus)) { // Start a new chunk
523
553
  this.chunkEnd = now + 30000 /* ChunkTime */;
@@ -526,28 +556,36 @@ const parseWorker = view.ViewPlugin.fromClass(class ParseWorker {
526
556
  if (this.chunkBudget <= 0)
527
557
  return; // No more budget
528
558
  let { state, viewport: { to: vpTo } } = this.view, field = state.field(Language.state);
529
- if (field.tree == field.context.tree && field.context.treeLen >= vpTo + 1000000 /* MaxParseAhead */)
559
+ if (field.tree == field.context.tree && field.context.isDone(vpTo + 100000 /* MaxParseAhead */))
530
560
  return;
531
- let time = Math.min(this.chunkBudget, deadline ? Math.max(25 /* MinSlice */, deadline.timeRemaining()) : 100 /* Slice */);
532
- let done = field.context.work(time, vpTo + 1000000 /* MaxParseAhead */);
561
+ let time = Math.min(this.chunkBudget, 100 /* Slice */, deadline ? Math.max(25 /* MinSlice */, deadline.timeRemaining() - 5) : 1e9);
562
+ let viewportFirst = field.context.treeLen < vpTo && state.doc.length > vpTo + 1000;
563
+ let done = field.context.work(time, vpTo + (viewportFirst ? 0 : 100000 /* MaxParseAhead */));
533
564
  this.chunkBudget -= Date.now() - now;
534
- if (done || this.chunkBudget <= 0 || field.context.movedPast(vpTo)) {
565
+ if (done || this.chunkBudget <= 0) {
535
566
  field.context.takeTree();
536
567
  this.view.dispatch({ effects: Language.setState.of(new LanguageState(field.context)) });
537
568
  }
538
- if (!done && this.chunkBudget > 0)
569
+ if (this.chunkBudget > 0 && !(done && !viewportFirst))
539
570
  this.scheduleWork();
540
571
  this.checkAsyncSchedule(field.context);
541
572
  }
542
573
  checkAsyncSchedule(cx) {
543
574
  if (cx.scheduleOn) {
544
- cx.scheduleOn.then(() => this.scheduleWork());
575
+ this.workScheduled++;
576
+ cx.scheduleOn
577
+ .then(() => this.scheduleWork())
578
+ .catch(err => view.logException(this.view.state, err))
579
+ .then(() => this.workScheduled--);
545
580
  cx.scheduleOn = null;
546
581
  }
547
582
  }
548
583
  destroy() {
549
- if (this.working >= 0)
550
- cancelIdle(this.working);
584
+ if (this.working)
585
+ this.working();
586
+ }
587
+ isWorking() {
588
+ return this.working || this.workScheduled > 0;
551
589
  }
552
590
  }, {
553
591
  eventHandlers: { focus() { this.scheduleWork(); } }
@@ -611,16 +649,17 @@ class LanguageDescription {
611
649
  Optional filename pattern that should be associated with this
612
650
  language.
613
651
  */
614
- filename, loadFunc) {
652
+ filename, loadFunc,
653
+ /**
654
+ If the language has been loaded, this will hold its value.
655
+ */
656
+ support = undefined) {
615
657
  this.name = name;
616
658
  this.alias = alias;
617
659
  this.extensions = extensions;
618
660
  this.filename = filename;
619
661
  this.loadFunc = loadFunc;
620
- /**
621
- If the language has been loaded, this will hold its value.
622
- */
623
- this.support = undefined;
662
+ this.support = support;
624
663
  this.loading = null;
625
664
  }
626
665
  /**
@@ -635,7 +674,13 @@ class LanguageDescription {
635
674
  Create a language description.
636
675
  */
637
676
  static of(spec) {
638
- return new LanguageDescription(spec.name, (spec.alias || []).concat(spec.name).map(s => s.toLowerCase()), spec.extensions || [], spec.filename, spec.load);
677
+ let { load, support } = spec;
678
+ if (!load) {
679
+ if (!support)
680
+ throw new RangeError("Must pass either 'load' or 'support' to LanguageDescription.of");
681
+ load = () => Promise.resolve(support);
682
+ }
683
+ return new LanguageDescription(spec.name, (spec.alias || []).concat(spec.name).map(s => s.toLowerCase()), spec.extensions || [], spec.filename, load, support);
639
684
  }
640
685
  /**
641
686
  Look for a language in the given array of descriptions that
@@ -1126,4 +1171,6 @@ exports.indentString = indentString;
1126
1171
  exports.indentUnit = indentUnit;
1127
1172
  exports.language = language;
1128
1173
  exports.languageDataProp = languageDataProp;
1174
+ exports.syntaxParserRunning = syntaxParserRunning;
1129
1175
  exports.syntaxTree = syntaxTree;
1176
+ 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 {
@@ -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
  /**
@@ -175,7 +175,31 @@ up to that point if the tree isn't already available.
175
175
  function ensureSyntaxTree(state, upto, timeout = 50) {
176
176
  var _a;
177
177
  let parse = (_a = state.field(Language.state, false)) === null || _a === void 0 ? void 0 : _a.context;
178
- return !parse ? null : parse.treeLen >= upto || parse.work(timeout, upto) ? parse.tree : null;
178
+ return !parse ? null : parse.isDone(upto) || parse.work(timeout, upto) ? parse.tree : null;
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;
179
203
  }
180
204
  // Lezer-style Input object for a Text document.
181
205
  class DocInput {
@@ -273,12 +297,12 @@ class ParseContext {
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;
@@ -422,13 +447,8 @@ class ParseContext {
422
447
  /**
423
448
  @internal
424
449
  */
425
- movedPast(pos) {
426
- return this.treeLen < pos && this.parse && this.parse.parsedPos >= pos;
427
- }
428
- /**
429
- @internal
430
- */
431
450
  isDone(upto) {
451
+ upto = Math.min(upto, this.state.doc.length);
432
452
  let frags = this.fragments;
433
453
  return this.treeLen >= upto && frags.length && frags[0].from == 0 && frags[0].to >= upto;
434
454
  }
@@ -458,13 +478,14 @@ class LanguageState {
458
478
  // state updates with parse work beyond the viewport.
459
479
  let upto = this.context.treeLen == tr.startState.doc.length ? undefined
460
480
  : Math.max(tr.changes.mapPos(this.context.treeLen), newCx.viewport.to);
461
- if (!newCx.work(25 /* Apply */, upto))
481
+ if (!newCx.work(20 /* Apply */, upto))
462
482
  newCx.takeTree();
463
483
  return new LanguageState(newCx);
464
484
  }
465
485
  static init(state) {
466
- let parseState = new ParseContext(state.facet(language).parser, state, [], Tree.empty, 0, { from: 0, to: state.doc.length }, [], null);
467
- if (!parseState.work(25 /* Apply */))
486
+ let vpTo = Math.min(3000 /* InitViewport */, state.doc.length);
487
+ let parseState = new ParseContext(state.facet(language).parser, state, [], Tree.empty, 0, { from: 0, to: vpTo }, [], null);
488
+ if (!parseState.work(20 /* Apply */, vpTo))
468
489
  parseState.takeTree();
469
490
  return new LanguageState(parseState);
470
491
  }
@@ -480,13 +501,22 @@ Language.state = /*@__PURE__*/StateField.define({
480
501
  return value.apply(tr);
481
502
  }
482
503
  });
483
- let requestIdle = typeof window != "undefined" && window.requestIdleCallback ||
484
- ((callback, { timeout }) => setTimeout(callback, timeout));
485
- let cancelIdle = typeof window != "undefined" && window.cancelIdleCallback || clearTimeout;
504
+ let requestIdle = (callback) => {
505
+ let timeout = setTimeout(() => callback(), 500 /* MaxPause */);
506
+ return () => clearTimeout(timeout);
507
+ };
508
+ if (typeof requestIdleCallback != "undefined")
509
+ requestIdle = (callback) => {
510
+ let idle = -1, timeout = setTimeout(() => {
511
+ idle = requestIdleCallback(callback, { timeout: 500 /* MaxPause */ - 100 /* MinPause */ });
512
+ }, 100 /* MinPause */);
513
+ return () => idle < 0 ? clearTimeout(timeout) : cancelIdleCallback(idle);
514
+ };
486
515
  const parseWorker = /*@__PURE__*/ViewPlugin.fromClass(class ParseWorker {
487
516
  constructor(view) {
488
517
  this.view = view;
489
- this.working = -1;
518
+ this.working = null;
519
+ this.workScheduled = 0;
490
520
  // End of the current time chunk
491
521
  this.chunkEnd = -1;
492
522
  // Milliseconds of budget left for this chunk
@@ -506,14 +536,14 @@ const parseWorker = /*@__PURE__*/ViewPlugin.fromClass(class ParseWorker {
506
536
  this.checkAsyncSchedule(cx);
507
537
  }
508
538
  scheduleWork() {
509
- if (this.working > -1)
539
+ if (this.working)
510
540
  return;
511
541
  let { state } = this.view, field = state.field(Language.state);
512
542
  if (field.tree != field.context.tree || !field.context.isDone(state.doc.length))
513
- this.working = requestIdle(this.work, { timeout: 500 /* Pause */ });
543
+ this.working = requestIdle(this.work);
514
544
  }
515
545
  work(deadline) {
516
- this.working = -1;
546
+ this.working = null;
517
547
  let now = Date.now();
518
548
  if (this.chunkEnd < now && (this.chunkEnd < 0 || this.view.hasFocus)) { // Start a new chunk
519
549
  this.chunkEnd = now + 30000 /* ChunkTime */;
@@ -522,28 +552,36 @@ const parseWorker = /*@__PURE__*/ViewPlugin.fromClass(class ParseWorker {
522
552
  if (this.chunkBudget <= 0)
523
553
  return; // No more budget
524
554
  let { state, viewport: { to: vpTo } } = this.view, field = state.field(Language.state);
525
- if (field.tree == field.context.tree && field.context.treeLen >= vpTo + 1000000 /* MaxParseAhead */)
555
+ if (field.tree == field.context.tree && field.context.isDone(vpTo + 100000 /* MaxParseAhead */))
526
556
  return;
527
- let time = Math.min(this.chunkBudget, deadline ? Math.max(25 /* MinSlice */, deadline.timeRemaining()) : 100 /* Slice */);
528
- let done = field.context.work(time, vpTo + 1000000 /* MaxParseAhead */);
557
+ let time = Math.min(this.chunkBudget, 100 /* Slice */, deadline ? Math.max(25 /* MinSlice */, deadline.timeRemaining() - 5) : 1e9);
558
+ let viewportFirst = field.context.treeLen < vpTo && state.doc.length > vpTo + 1000;
559
+ let done = field.context.work(time, vpTo + (viewportFirst ? 0 : 100000 /* MaxParseAhead */));
529
560
  this.chunkBudget -= Date.now() - now;
530
- if (done || this.chunkBudget <= 0 || field.context.movedPast(vpTo)) {
561
+ if (done || this.chunkBudget <= 0) {
531
562
  field.context.takeTree();
532
563
  this.view.dispatch({ effects: Language.setState.of(new LanguageState(field.context)) });
533
564
  }
534
- if (!done && this.chunkBudget > 0)
565
+ if (this.chunkBudget > 0 && !(done && !viewportFirst))
535
566
  this.scheduleWork();
536
567
  this.checkAsyncSchedule(field.context);
537
568
  }
538
569
  checkAsyncSchedule(cx) {
539
570
  if (cx.scheduleOn) {
540
- cx.scheduleOn.then(() => this.scheduleWork());
571
+ this.workScheduled++;
572
+ cx.scheduleOn
573
+ .then(() => this.scheduleWork())
574
+ .catch(err => logException(this.view.state, err))
575
+ .then(() => this.workScheduled--);
541
576
  cx.scheduleOn = null;
542
577
  }
543
578
  }
544
579
  destroy() {
545
- if (this.working >= 0)
546
- cancelIdle(this.working);
580
+ if (this.working)
581
+ this.working();
582
+ }
583
+ isWorking() {
584
+ return this.working || this.workScheduled > 0;
547
585
  }
548
586
  }, {
549
587
  eventHandlers: { focus() { this.scheduleWork(); } }
@@ -607,16 +645,17 @@ class LanguageDescription {
607
645
  Optional filename pattern that should be associated with this
608
646
  language.
609
647
  */
610
- filename, loadFunc) {
648
+ filename, loadFunc,
649
+ /**
650
+ If the language has been loaded, this will hold its value.
651
+ */
652
+ support = undefined) {
611
653
  this.name = name;
612
654
  this.alias = alias;
613
655
  this.extensions = extensions;
614
656
  this.filename = filename;
615
657
  this.loadFunc = loadFunc;
616
- /**
617
- If the language has been loaded, this will hold its value.
618
- */
619
- this.support = undefined;
658
+ this.support = support;
620
659
  this.loading = null;
621
660
  }
622
661
  /**
@@ -631,7 +670,13 @@ class LanguageDescription {
631
670
  Create a language description.
632
671
  */
633
672
  static of(spec) {
634
- return new LanguageDescription(spec.name, (spec.alias || []).concat(spec.name).map(s => s.toLowerCase()), spec.extensions || [], spec.filename, spec.load);
673
+ let { load, support } = spec;
674
+ if (!load) {
675
+ if (!support)
676
+ throw new RangeError("Must pass either 'load' or 'support' to LanguageDescription.of");
677
+ load = () => Promise.resolve(support);
678
+ }
679
+ return new LanguageDescription(spec.name, (spec.alias || []).concat(spec.name).map(s => s.toLowerCase()), spec.extensions || [], spec.filename, load, support);
635
680
  }
636
681
  /**
637
682
  Look for a language in the given array of descriptions that
@@ -1097,4 +1142,4 @@ function foldable(state, lineStart, lineEnd) {
1097
1142
  return syntaxFolding(state, lineStart, lineEnd);
1098
1143
  }
1099
1144
 
1100
- 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 };
1145
+ 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",
3
+ "version": "0.19.7",
4
4
  "description": "Language support infrastructure for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",