@codemirror/language 0.19.5 → 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,11 @@
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
+
1
9
  ## 0.19.5 (2021-11-17)
2
10
 
3
11
  ### New features
package/dist/index.cjs CHANGED
@@ -301,12 +301,12 @@ class ParseContext {
301
301
  }
302
302
  return this.withContext(() => {
303
303
  var _a;
304
+ let endTime = Date.now() + time;
304
305
  if (!this.parse)
305
306
  this.parse = this.startParse();
306
307
  if (upto != null && (this.parse.stoppedAt == null || this.parse.stoppedAt > upto) &&
307
308
  upto < this.state.doc.length)
308
309
  this.parse.stopAt(upto);
309
- let endTime = Date.now() + time;
310
310
  for (;;) {
311
311
  let done = this.parse.advance();
312
312
  if (done) {
@@ -329,10 +329,11 @@ class ParseContext {
329
329
  */
330
330
  takeTree() {
331
331
  let pos, tree;
332
- if (this.parse && (pos = this.parse.parsedPos) > this.treeLen) {
332
+ if (this.parse && (pos = this.parse.parsedPos) >= this.treeLen) {
333
333
  if (this.parse.stoppedAt == null || this.parse.stoppedAt > pos)
334
334
  this.parse.stopAt(pos);
335
335
  this.withContext(() => { while (!(tree = this.parse.advance())) { } });
336
+ this.treeLen = pos;
336
337
  this.tree = tree;
337
338
  this.fragments = this.withoutTempSkipped(common.TreeFragment.addTree(this.tree, this.fragments, true));
338
339
  this.parse = null;
@@ -450,12 +451,6 @@ class ParseContext {
450
451
  /**
451
452
  @internal
452
453
  */
453
- movedPast(pos) {
454
- return this.treeLen < pos && this.parse && this.parse.parsedPos >= pos;
455
- }
456
- /**
457
- @internal
458
- */
459
454
  isDone(upto) {
460
455
  let frags = this.fragments;
461
456
  return this.treeLen >= upto && frags.length && frags[0].from == 0 && frags[0].to >= upto;
@@ -486,13 +481,14 @@ class LanguageState {
486
481
  // state updates with parse work beyond the viewport.
487
482
  let upto = this.context.treeLen == tr.startState.doc.length ? undefined
488
483
  : Math.max(tr.changes.mapPos(this.context.treeLen), newCx.viewport.to);
489
- if (!newCx.work(25 /* Apply */, upto))
484
+ if (!newCx.work(20 /* Apply */, upto))
490
485
  newCx.takeTree();
491
486
  return new LanguageState(newCx);
492
487
  }
493
488
  static init(state) {
494
- let parseState = new ParseContext(state.facet(language).parser, state, [], common.Tree.empty, 0, { from: 0, to: state.doc.length }, [], null);
495
- 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))
496
492
  parseState.takeTree();
497
493
  return new LanguageState(parseState);
498
494
  }
@@ -508,13 +504,21 @@ Language.state = state.StateField.define({
508
504
  return value.apply(tr);
509
505
  }
510
506
  });
511
- let requestIdle = typeof window != "undefined" && window.requestIdleCallback ||
512
- ((callback, { timeout }) => setTimeout(callback, timeout));
513
- 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
+ };
514
518
  const parseWorker = view.ViewPlugin.fromClass(class ParseWorker {
515
519
  constructor(view) {
516
520
  this.view = view;
517
- this.working = -1;
521
+ this.working = null;
518
522
  this.workScheduled = 0;
519
523
  // End of the current time chunk
520
524
  this.chunkEnd = -1;
@@ -535,14 +539,14 @@ const parseWorker = view.ViewPlugin.fromClass(class ParseWorker {
535
539
  this.checkAsyncSchedule(cx);
536
540
  }
537
541
  scheduleWork() {
538
- if (this.working > -1)
542
+ if (this.working)
539
543
  return;
540
544
  let { state } = this.view, field = state.field(Language.state);
541
545
  if (field.tree != field.context.tree || !field.context.isDone(state.doc.length))
542
- this.working = requestIdle(this.work, { timeout: 500 /* Pause */ });
546
+ this.working = requestIdle(this.work);
543
547
  }
544
548
  work(deadline) {
545
- this.working = -1;
549
+ this.working = null;
546
550
  let now = Date.now();
547
551
  if (this.chunkEnd < now && (this.chunkEnd < 0 || this.view.hasFocus)) { // Start a new chunk
548
552
  this.chunkEnd = now + 30000 /* ChunkTime */;
@@ -551,16 +555,17 @@ const parseWorker = view.ViewPlugin.fromClass(class ParseWorker {
551
555
  if (this.chunkBudget <= 0)
552
556
  return; // No more budget
553
557
  let { state, viewport: { to: vpTo } } = this.view, field = state.field(Language.state);
554
- 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 */)
555
559
  return;
556
- let time = Math.min(this.chunkBudget, deadline ? Math.max(25 /* MinSlice */, deadline.timeRemaining()) : 100 /* Slice */);
557
- 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 */));
558
563
  this.chunkBudget -= Date.now() - now;
559
- if (done || this.chunkBudget <= 0 || field.context.movedPast(vpTo)) {
564
+ if (done || this.chunkBudget <= 0) {
560
565
  field.context.takeTree();
561
566
  this.view.dispatch({ effects: Language.setState.of(new LanguageState(field.context)) });
562
567
  }
563
- if (!done && this.chunkBudget > 0)
568
+ if (this.chunkBudget > 0 && !(done && !viewportFirst))
564
569
  this.scheduleWork();
565
570
  this.checkAsyncSchedule(field.context);
566
571
  }
@@ -575,11 +580,11 @@ const parseWorker = view.ViewPlugin.fromClass(class ParseWorker {
575
580
  }
576
581
  }
577
582
  destroy() {
578
- if (this.working >= 0)
579
- cancelIdle(this.working);
583
+ if (this.working)
584
+ this.working();
580
585
  }
581
586
  isWorking() {
582
- return this.working >= 0 || this.workScheduled > 0;
587
+ return this.working || this.workScheduled > 0;
583
588
  }
584
589
  }, {
585
590
  eventHandlers: { focus() { this.scheduleWork(); } }
package/dist/index.d.ts CHANGED
@@ -156,7 +156,7 @@ stopped running, either because it parsed the entire document,
156
156
  because it spent too much time and was cut off, or because there
157
157
  is no language parser enabled.
158
158
  */
159
- declare function syntaxParserRunning(view: EditorView): boolean;
159
+ declare function syntaxParserRunning(view: EditorView): boolean | (() => void);
160
160
  /**
161
161
  A parse context provided to parsers working on the editor content.
162
162
  */
package/dist/index.js CHANGED
@@ -297,12 +297,12 @@ class ParseContext {
297
297
  }
298
298
  return this.withContext(() => {
299
299
  var _a;
300
+ let endTime = Date.now() + time;
300
301
  if (!this.parse)
301
302
  this.parse = this.startParse();
302
303
  if (upto != null && (this.parse.stoppedAt == null || this.parse.stoppedAt > upto) &&
303
304
  upto < this.state.doc.length)
304
305
  this.parse.stopAt(upto);
305
- let endTime = Date.now() + time;
306
306
  for (;;) {
307
307
  let done = this.parse.advance();
308
308
  if (done) {
@@ -325,10 +325,11 @@ class ParseContext {
325
325
  */
326
326
  takeTree() {
327
327
  let pos, tree;
328
- if (this.parse && (pos = this.parse.parsedPos) > this.treeLen) {
328
+ if (this.parse && (pos = this.parse.parsedPos) >= this.treeLen) {
329
329
  if (this.parse.stoppedAt == null || this.parse.stoppedAt > pos)
330
330
  this.parse.stopAt(pos);
331
331
  this.withContext(() => { while (!(tree = this.parse.advance())) { } });
332
+ this.treeLen = pos;
332
333
  this.tree = tree;
333
334
  this.fragments = this.withoutTempSkipped(TreeFragment.addTree(this.tree, this.fragments, true));
334
335
  this.parse = null;
@@ -446,12 +447,6 @@ class ParseContext {
446
447
  /**
447
448
  @internal
448
449
  */
449
- movedPast(pos) {
450
- return this.treeLen < pos && this.parse && this.parse.parsedPos >= pos;
451
- }
452
- /**
453
- @internal
454
- */
455
450
  isDone(upto) {
456
451
  let frags = this.fragments;
457
452
  return this.treeLen >= upto && frags.length && frags[0].from == 0 && frags[0].to >= upto;
@@ -482,13 +477,14 @@ class LanguageState {
482
477
  // state updates with parse work beyond the viewport.
483
478
  let upto = this.context.treeLen == tr.startState.doc.length ? undefined
484
479
  : Math.max(tr.changes.mapPos(this.context.treeLen), newCx.viewport.to);
485
- if (!newCx.work(25 /* Apply */, upto))
480
+ if (!newCx.work(20 /* Apply */, upto))
486
481
  newCx.takeTree();
487
482
  return new LanguageState(newCx);
488
483
  }
489
484
  static init(state) {
490
- let parseState = new ParseContext(state.facet(language).parser, state, [], Tree.empty, 0, { from: 0, to: state.doc.length }, [], null);
491
- 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))
492
488
  parseState.takeTree();
493
489
  return new LanguageState(parseState);
494
490
  }
@@ -504,13 +500,21 @@ Language.state = /*@__PURE__*/StateField.define({
504
500
  return value.apply(tr);
505
501
  }
506
502
  });
507
- let requestIdle = typeof window != "undefined" && window.requestIdleCallback ||
508
- ((callback, { timeout }) => setTimeout(callback, timeout));
509
- 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
+ };
510
514
  const parseWorker = /*@__PURE__*/ViewPlugin.fromClass(class ParseWorker {
511
515
  constructor(view) {
512
516
  this.view = view;
513
- this.working = -1;
517
+ this.working = null;
514
518
  this.workScheduled = 0;
515
519
  // End of the current time chunk
516
520
  this.chunkEnd = -1;
@@ -531,14 +535,14 @@ const parseWorker = /*@__PURE__*/ViewPlugin.fromClass(class ParseWorker {
531
535
  this.checkAsyncSchedule(cx);
532
536
  }
533
537
  scheduleWork() {
534
- if (this.working > -1)
538
+ if (this.working)
535
539
  return;
536
540
  let { state } = this.view, field = state.field(Language.state);
537
541
  if (field.tree != field.context.tree || !field.context.isDone(state.doc.length))
538
- this.working = requestIdle(this.work, { timeout: 500 /* Pause */ });
542
+ this.working = requestIdle(this.work);
539
543
  }
540
544
  work(deadline) {
541
- this.working = -1;
545
+ this.working = null;
542
546
  let now = Date.now();
543
547
  if (this.chunkEnd < now && (this.chunkEnd < 0 || this.view.hasFocus)) { // Start a new chunk
544
548
  this.chunkEnd = now + 30000 /* ChunkTime */;
@@ -547,16 +551,17 @@ const parseWorker = /*@__PURE__*/ViewPlugin.fromClass(class ParseWorker {
547
551
  if (this.chunkBudget <= 0)
548
552
  return; // No more budget
549
553
  let { state, viewport: { to: vpTo } } = this.view, field = state.field(Language.state);
550
- 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 */)
551
555
  return;
552
- let time = Math.min(this.chunkBudget, deadline ? Math.max(25 /* MinSlice */, deadline.timeRemaining()) : 100 /* Slice */);
553
- 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 */));
554
559
  this.chunkBudget -= Date.now() - now;
555
- if (done || this.chunkBudget <= 0 || field.context.movedPast(vpTo)) {
560
+ if (done || this.chunkBudget <= 0) {
556
561
  field.context.takeTree();
557
562
  this.view.dispatch({ effects: Language.setState.of(new LanguageState(field.context)) });
558
563
  }
559
- if (!done && this.chunkBudget > 0)
564
+ if (this.chunkBudget > 0 && !(done && !viewportFirst))
560
565
  this.scheduleWork();
561
566
  this.checkAsyncSchedule(field.context);
562
567
  }
@@ -571,11 +576,11 @@ const parseWorker = /*@__PURE__*/ViewPlugin.fromClass(class ParseWorker {
571
576
  }
572
577
  }
573
578
  destroy() {
574
- if (this.working >= 0)
575
- cancelIdle(this.working);
579
+ if (this.working)
580
+ this.working();
576
581
  }
577
582
  isWorking() {
578
- return this.working >= 0 || this.workScheduled > 0;
583
+ return this.working || this.workScheduled > 0;
579
584
  }
580
585
  }, {
581
586
  eventHandlers: { focus() { this.scheduleWork(); } }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/language",
3
- "version": "0.19.5",
3
+ "version": "0.19.6",
4
4
  "description": "Language support infrastructure for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",