@codemirror/language 6.8.0 → 6.9.1

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,21 @@
1
+ ## 6.9.1 (2023-09-20)
2
+
3
+ ### Bug fixes
4
+
5
+ Indentation now works a lot better in mixed-language documents that interleave the languages in a complex way.
6
+
7
+ Code folding is now able to pick the right foldable syntax node when the line end falls in a mixed-parsing language that doesn't match the target node.
8
+
9
+ ## 6.9.0 (2023-08-16)
10
+
11
+ ### Bug fixes
12
+
13
+ Make `getIndentation` return null, rather than 0, when there is no syntax tree available.
14
+
15
+ ### New features
16
+
17
+ The new `preparePlaceholder` option to `codeFolding` makes it possible to display contextual information in a folded range placeholder widget.
18
+
1
19
  ## 6.8.0 (2023-06-12)
2
20
 
3
21
  ### New features
package/dist/index.cjs CHANGED
@@ -1,7 +1,5 @@
1
1
  'use strict';
2
2
 
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
3
  var common = require('@lezer/common');
6
4
  var state = require('@codemirror/state');
7
5
  var view = require('@codemirror/view');
@@ -538,14 +536,14 @@ class LanguageState {
538
536
  // state updates with parse work beyond the viewport.
539
537
  let upto = this.context.treeLen == tr.startState.doc.length ? undefined
540
538
  : Math.max(tr.changes.mapPos(this.context.treeLen), newCx.viewport.to);
541
- if (!newCx.work(20 /* Apply */, upto))
539
+ if (!newCx.work(20 /* Work.Apply */, upto))
542
540
  newCx.takeTree();
543
541
  return new LanguageState(newCx);
544
542
  }
545
543
  static init(state) {
546
- let vpTo = Math.min(3000 /* InitViewport */, state.doc.length);
544
+ let vpTo = Math.min(3000 /* Work.InitViewport */, state.doc.length);
547
545
  let parseState = ParseContext.create(state.facet(language).parser, state, { from: 0, to: vpTo });
548
- if (!parseState.work(20 /* Apply */, vpTo))
546
+ if (!parseState.work(20 /* Work.Apply */, vpTo))
549
547
  parseState.takeTree();
550
548
  return new LanguageState(parseState);
551
549
  }
@@ -562,14 +560,14 @@ Language.state = state.StateField.define({
562
560
  }
563
561
  });
564
562
  let requestIdle = (callback) => {
565
- let timeout = setTimeout(() => callback(), 500 /* MaxPause */);
563
+ let timeout = setTimeout(() => callback(), 500 /* Work.MaxPause */);
566
564
  return () => clearTimeout(timeout);
567
565
  };
568
566
  if (typeof requestIdleCallback != "undefined")
569
567
  requestIdle = (callback) => {
570
568
  let idle = -1, timeout = setTimeout(() => {
571
- idle = requestIdleCallback(callback, { timeout: 500 /* MaxPause */ - 100 /* MinPause */ });
572
- }, 100 /* MinPause */);
569
+ idle = requestIdleCallback(callback, { timeout: 500 /* Work.MaxPause */ - 100 /* Work.MinPause */ });
570
+ }, 100 /* Work.MinPause */);
573
571
  return () => idle < 0 ? clearTimeout(timeout) : cancelIdleCallback(idle);
574
572
  };
575
573
  const isInputPending = typeof navigator != "undefined" && ((_a = navigator.scheduling) === null || _a === void 0 ? void 0 : _a.isInputPending)
@@ -590,9 +588,9 @@ const parseWorker = view.ViewPlugin.fromClass(class ParseWorker {
590
588
  let cx = this.view.state.field(Language.state).context;
591
589
  if (cx.updateViewport(update.view.viewport) || this.view.viewport.to > cx.treeLen)
592
590
  this.scheduleWork();
593
- if (update.docChanged) {
591
+ if (update.docChanged || update.selectionSet) {
594
592
  if (this.view.hasFocus)
595
- this.chunkBudget += 50 /* ChangeBonus */;
593
+ this.chunkBudget += 50 /* Work.ChangeBonus */;
596
594
  this.scheduleWork();
597
595
  }
598
596
  this.checkAsyncSchedule(cx);
@@ -608,19 +606,19 @@ const parseWorker = view.ViewPlugin.fromClass(class ParseWorker {
608
606
  this.working = null;
609
607
  let now = Date.now();
610
608
  if (this.chunkEnd < now && (this.chunkEnd < 0 || this.view.hasFocus)) { // Start a new chunk
611
- this.chunkEnd = now + 30000 /* ChunkTime */;
612
- this.chunkBudget = 3000 /* ChunkBudget */;
609
+ this.chunkEnd = now + 30000 /* Work.ChunkTime */;
610
+ this.chunkBudget = 3000 /* Work.ChunkBudget */;
613
611
  }
614
612
  if (this.chunkBudget <= 0)
615
613
  return; // No more budget
616
614
  let { state, viewport: { to: vpTo } } = this.view, field = state.field(Language.state);
617
- if (field.tree == field.context.tree && field.context.isDone(vpTo + 100000 /* MaxParseAhead */))
615
+ if (field.tree == field.context.tree && field.context.isDone(vpTo + 100000 /* Work.MaxParseAhead */))
618
616
  return;
619
- let endTime = Date.now() + Math.min(this.chunkBudget, 100 /* Slice */, deadline && !isInputPending ? Math.max(25 /* MinSlice */, deadline.timeRemaining() - 5) : 1e9);
617
+ let endTime = Date.now() + Math.min(this.chunkBudget, 100 /* Work.Slice */, deadline && !isInputPending ? Math.max(25 /* Work.MinSlice */, deadline.timeRemaining() - 5) : 1e9);
620
618
  let viewportFirst = field.context.treeLen < vpTo && state.doc.length > vpTo + 1000;
621
619
  let done = field.context.work(() => {
622
620
  return isInputPending && isInputPending() || Date.now() > endTime;
623
- }, vpTo + (viewportFirst ? 0 : 100000 /* MaxParseAhead */));
621
+ }, vpTo + (viewportFirst ? 0 : 100000 /* Work.MaxParseAhead */));
624
622
  this.chunkBudget -= Date.now() - now;
625
623
  if (done || this.chunkBudget <= 0) {
626
624
  field.context.takeTree();
@@ -865,7 +863,7 @@ function getIndentation(context, pos) {
865
863
  return result;
866
864
  }
867
865
  let tree = syntaxTree(context.state);
868
- return tree ? syntaxIndentation(context, tree, pos) : null;
866
+ return tree.length >= pos ? syntaxIndentation(context, tree, pos) : null;
869
867
  }
870
868
  /**
871
869
  Create a change set that auto-indents all lines touched by the
@@ -996,7 +994,24 @@ indicates that no definitive indentation can be determined.
996
994
  const indentNodeProp = new common.NodeProp();
997
995
  // Compute the indentation for a given position from the syntax tree.
998
996
  function syntaxIndentation(cx, ast, pos) {
999
- return indentFrom(ast.resolveInner(pos).enterUnfinishedNodesBefore(pos), pos, cx);
997
+ let stack = ast.resolveStack(pos);
998
+ let inner = stack.node.enterUnfinishedNodesBefore(pos);
999
+ if (inner != stack.node) {
1000
+ let add = [];
1001
+ for (let cur = inner; cur != stack.node; cur = cur.parent)
1002
+ add.push(cur);
1003
+ for (let i = add.length - 1; i >= 0; i--)
1004
+ stack = { node: add[i], next: stack };
1005
+ }
1006
+ return indentFor(stack, cx, pos);
1007
+ }
1008
+ function indentFor(stack, cx, pos) {
1009
+ for (let cur = stack; cur; cur = cur.next) {
1010
+ let strategy = indentStrategy(cur.node);
1011
+ if (strategy)
1012
+ return strategy(TreeIndentContext.create(cx, pos, cur));
1013
+ }
1014
+ return 0;
1000
1015
  }
1001
1016
  function ignoreClosed(cx) {
1002
1017
  return cx.pos == cx.options.simulateBreak && cx.options.simulateDoubleBreak;
@@ -1012,14 +1027,6 @@ function indentStrategy(tree) {
1012
1027
  }
1013
1028
  return tree.parent == null ? topIndent : null;
1014
1029
  }
1015
- function indentFrom(node, pos, base) {
1016
- for (; node; node = node.parent) {
1017
- let strategy = indentStrategy(node);
1018
- if (strategy)
1019
- return strategy(TreeIndentContext.create(base, pos, node));
1020
- }
1021
- return null;
1022
- }
1023
1030
  function topIndent() { return 0; }
1024
1031
  /**
1025
1032
  Objects of this type provide context information and helper
@@ -1032,20 +1039,24 @@ class TreeIndentContext extends IndentContext {
1032
1039
  */
1033
1040
  pos,
1034
1041
  /**
1035
- The syntax tree node to which the indentation strategy
1036
- applies.
1042
+ @internal
1037
1043
  */
1038
- node) {
1044
+ context) {
1039
1045
  super(base.state, base.options);
1040
1046
  this.base = base;
1041
1047
  this.pos = pos;
1042
- this.node = node;
1048
+ this.context = context;
1043
1049
  }
1044
1050
  /**
1051
+ The syntax tree node to which the indentation strategy
1052
+ applies.
1053
+ */
1054
+ get node() { return this.context.node; }
1055
+ /**
1045
1056
  @internal
1046
1057
  */
1047
- static create(base, pos, node) {
1048
- return new TreeIndentContext(base, pos, node);
1058
+ static create(base, pos, context) {
1059
+ return new TreeIndentContext(base, pos, context);
1049
1060
  }
1050
1061
  /**
1051
1062
  Get the text directly after `this.pos`, either the entire line
@@ -1086,8 +1097,7 @@ class TreeIndentContext extends IndentContext {
1086
1097
  and return the result of that.
1087
1098
  */
1088
1099
  continue() {
1089
- let parent = this.node.parent;
1090
- return parent ? indentFrom(parent, this.pos, this.base) : 0;
1100
+ return indentFor(this.context.next, this.base, this.pos);
1091
1101
  }
1092
1102
  }
1093
1103
  function isParent(parent, of) {
@@ -1229,9 +1239,10 @@ function syntaxFolding(state, start, end) {
1229
1239
  let tree = syntaxTree(state);
1230
1240
  if (tree.length < end)
1231
1241
  return null;
1232
- let inner = tree.resolveInner(end, 1);
1242
+ let stack = tree.resolveStack(end, 1);
1233
1243
  let found = null;
1234
- for (let cur = inner; cur; cur = cur.parent) {
1244
+ for (let iter = stack; iter; iter = iter.next) {
1245
+ let cur = iter.node;
1235
1246
  if (cur.to <= end || cur.from > end)
1236
1247
  continue;
1237
1248
  if (found && cur.from < start)
@@ -1304,11 +1315,16 @@ const foldState = state.StateField.define({
1304
1315
  update(folded, tr) {
1305
1316
  folded = folded.map(tr.changes);
1306
1317
  for (let e of tr.effects) {
1307
- if (e.is(foldEffect) && !foldExists(folded, e.value.from, e.value.to))
1308
- folded = folded.update({ add: [foldWidget.range(e.value.from, e.value.to)] });
1309
- else if (e.is(unfoldEffect))
1318
+ if (e.is(foldEffect) && !foldExists(folded, e.value.from, e.value.to)) {
1319
+ let { preparePlaceholder } = tr.state.facet(foldConfig);
1320
+ let widget = !preparePlaceholder ? foldWidget :
1321
+ view.Decoration.replace({ widget: new PreparedFoldWidget(preparePlaceholder(tr.state, e.value)) });
1322
+ folded = folded.update({ add: [widget.range(e.value.from, e.value.to)] });
1323
+ }
1324
+ else if (e.is(unfoldEffect)) {
1310
1325
  folded = folded.update({ filter: (from, to) => e.value.from != from || e.value.to != to,
1311
1326
  filterFrom: e.value.from, filterTo: e.value.to });
1327
+ }
1312
1328
  }
1313
1329
  // Clear folded ranges that cover the selection head
1314
1330
  if (tr.selection) {
@@ -1485,6 +1501,7 @@ const foldKeymap = [
1485
1501
  ];
1486
1502
  const defaultConfig = {
1487
1503
  placeholderDOM: null,
1504
+ preparePlaceholder: null,
1488
1505
  placeholderText: "…"
1489
1506
  };
1490
1507
  const foldConfig = state.Facet.define({
@@ -1499,27 +1516,36 @@ function codeFolding(config) {
1499
1516
  result.push(foldConfig.of(config));
1500
1517
  return result;
1501
1518
  }
1519
+ function widgetToDOM(view, prepared) {
1520
+ let { state } = view, conf = state.facet(foldConfig);
1521
+ let onclick = (event) => {
1522
+ let line = view.lineBlockAt(view.posAtDOM(event.target));
1523
+ let folded = findFold(view.state, line.from, line.to);
1524
+ if (folded)
1525
+ view.dispatch({ effects: unfoldEffect.of(folded) });
1526
+ event.preventDefault();
1527
+ };
1528
+ if (conf.placeholderDOM)
1529
+ return conf.placeholderDOM(view, onclick, prepared);
1530
+ let element = document.createElement("span");
1531
+ element.textContent = conf.placeholderText;
1532
+ element.setAttribute("aria-label", state.phrase("folded code"));
1533
+ element.title = state.phrase("unfold");
1534
+ element.className = "cm-foldPlaceholder";
1535
+ element.onclick = onclick;
1536
+ return element;
1537
+ }
1502
1538
  const foldWidget = view.Decoration.replace({ widget: new class extends view.WidgetType {
1503
- toDOM(view) {
1504
- let { state } = view, conf = state.facet(foldConfig);
1505
- let onclick = (event) => {
1506
- let line = view.lineBlockAt(view.posAtDOM(event.target));
1507
- let folded = findFold(view.state, line.from, line.to);
1508
- if (folded)
1509
- view.dispatch({ effects: unfoldEffect.of(folded) });
1510
- event.preventDefault();
1511
- };
1512
- if (conf.placeholderDOM)
1513
- return conf.placeholderDOM(view, onclick);
1514
- let element = document.createElement("span");
1515
- element.textContent = conf.placeholderText;
1516
- element.setAttribute("aria-label", state.phrase("folded code"));
1517
- element.title = state.phrase("unfold");
1518
- element.className = "cm-foldPlaceholder";
1519
- element.onclick = onclick;
1520
- return element;
1521
- }
1539
+ toDOM(view) { return widgetToDOM(view, null); }
1522
1540
  } });
1541
+ class PreparedFoldWidget extends view.WidgetType {
1542
+ constructor(value) {
1543
+ super();
1544
+ this.value = value;
1545
+ }
1546
+ eq(other) { return this.value == other.value; }
1547
+ toDOM(view) { return widgetToDOM(view, this.value); }
1548
+ }
1523
1549
  const foldGutterDefaults = {
1524
1550
  openText: "⌄",
1525
1551
  closedText: "›",
@@ -2207,7 +2233,7 @@ class StreamLanguage extends Language {
2207
2233
  state = this.streamParser.startState(cx.unit);
2208
2234
  statePos = 0;
2209
2235
  }
2210
- if (pos - statePos > 10000 /* MaxIndentScanDist */)
2236
+ if (pos - statePos > 10000 /* C.MaxIndentScanDist */)
2211
2237
  return null;
2212
2238
  while (statePos < pos) {
2213
2239
  let line = cx.state.doc.lineAt(statePos), end = Math.min(pos, line.to);
@@ -2289,7 +2315,7 @@ class Parse {
2289
2315
  this.chunks.push(tree.children[i]);
2290
2316
  this.chunkPos.push(tree.positions[i]);
2291
2317
  }
2292
- if (context && this.parsedPos < context.viewport.from - 100000 /* MaxDistanceBeforeViewport */) {
2318
+ if (context && this.parsedPos < context.viewport.from - 100000 /* C.MaxDistanceBeforeViewport */) {
2293
2319
  this.state = this.lang.streamParser.startState(getIndentUnit(context.state));
2294
2320
  context.skipUntilInView(this.parsedPos, context.viewport.from);
2295
2321
  this.parsedPos = context.viewport.from;
@@ -2299,7 +2325,7 @@ class Parse {
2299
2325
  advance() {
2300
2326
  let context = ParseContext.get();
2301
2327
  let parseEnd = this.stoppedAt == null ? this.to : Math.min(this.to, this.stoppedAt);
2302
- let end = Math.min(parseEnd, this.chunkStart + 2048 /* ChunkSize */);
2328
+ let end = Math.min(parseEnd, this.chunkStart + 2048 /* C.ChunkSize */);
2303
2329
  if (context)
2304
2330
  end = Math.min(end, context.viewport.to);
2305
2331
  while (this.parsedPos < end)
@@ -2383,7 +2409,7 @@ class Parse {
2383
2409
  let token = readToken(streamParser.token, stream, this.state);
2384
2410
  if (token)
2385
2411
  offset = this.emitToken(this.lang.tokenTable.resolve(token), this.parsedPos + stream.start, this.parsedPos + stream.pos, 4, offset);
2386
- if (stream.start > 10000 /* MaxLineLength */)
2412
+ if (stream.start > 10000 /* C.MaxLineLength */)
2387
2413
  break;
2388
2414
  }
2389
2415
  }
@@ -2399,7 +2425,7 @@ class Parse {
2399
2425
  length: this.parsedPos - this.chunkStart,
2400
2426
  nodeSet,
2401
2427
  topID: 0,
2402
- maxBufferLength: 2048 /* ChunkSize */,
2428
+ maxBufferLength: 2048 /* C.ChunkSize */,
2403
2429
  reused: this.chunkReused
2404
2430
  });
2405
2431
  tree = new common.Tree(tree.type, tree.children, tree.positions, tree.length, [[this.lang.stateAfter, this.lang.streamParser.copyState(this.state)]]);
@@ -2489,7 +2515,7 @@ function createTokenType(extra, tagStr) {
2489
2515
  return type.id;
2490
2516
  }
2491
2517
  function docID(data) {
2492
- let type = common.NodeType.define({ id: typeArray.length, name: "Document", props: [languageDataProp.add(() => data)] });
2518
+ let type = common.NodeType.define({ id: typeArray.length, name: "Document", props: [languageDataProp.add(() => data)], top: true });
2493
2519
  typeArray.push(type);
2494
2520
  return type;
2495
2521
  }
package/dist/index.d.cts CHANGED
@@ -567,12 +567,12 @@ declare class TreeIndentContext extends IndentContext {
567
567
  The position at which indentation is being computed.
568
568
  */
569
569
  readonly pos: number;
570
+ private constructor();
570
571
  /**
571
572
  The syntax tree node to which the indentation strategy
572
573
  applies.
573
574
  */
574
- readonly node: SyntaxNode;
575
- private constructor();
575
+ get node(): SyntaxNode;
576
576
  /**
577
577
  Get the text directly after `this.pos`, either the entire line
578
578
  or the next 100 characters, whichever is shorter.
@@ -689,7 +689,7 @@ declare function foldable(state: EditorState, lineStart: number, lineEnd: number
689
689
  from: number;
690
690
  to: number;
691
691
  } | null;
692
- declare type DocRange = {
692
+ type DocRange = {
693
693
  from: number;
694
694
  to: number;
695
695
  };
@@ -761,23 +761,36 @@ interface FoldConfig {
761
761
  position of folded code. The `onclick` argument is the default
762
762
  click event handler, which toggles folding on the line that
763
763
  holds the element, and should probably be added as an event
764
- handler to the returned element.
764
+ handler to the returned element. If
765
+ [`preparePlaceholder`](https://codemirror.net/6/docs/ref/#language.FoldConfig.preparePlaceholder)
766
+ is given, its result will be passed as 3rd argument. Otherwise,
767
+ this will be null.
765
768
 
766
769
  When this option isn't given, the `placeholderText` option will
767
770
  be used to create the placeholder element.
768
771
  */
769
- placeholderDOM?: ((view: EditorView, onclick: (event: Event) => void) => HTMLElement) | null;
772
+ placeholderDOM?: ((view: EditorView, onclick: (event: Event) => void, prepared: any) => HTMLElement) | null;
770
773
  /**
771
774
  Text to use as placeholder for folded text. Defaults to `"…"`.
772
775
  Will be styled with the `"cm-foldPlaceholder"` class.
773
776
  */
774
777
  placeholderText?: string;
778
+ /**
779
+ Given a range that is being folded, create a value that
780
+ describes it, to be used by `placeholderDOM` to render a custom
781
+ widget that, for example, indicates something about the folded
782
+ range's size or type.
783
+ */
784
+ preparePlaceholder?: (state: EditorState, range: {
785
+ from: number;
786
+ to: number;
787
+ }) => any;
775
788
  }
776
789
  /**
777
790
  Create an extension that configures code folding.
778
791
  */
779
792
  declare function codeFolding(config?: FoldConfig): Extension;
780
- declare type Handlers = {
793
+ type Handlers = {
781
794
  [event: string]: (view: EditorView, line: BlockInfo, event: Event) => boolean;
782
795
  };
783
796
  interface FoldGutterConfig {
package/dist/index.d.ts CHANGED
@@ -567,12 +567,12 @@ declare class TreeIndentContext extends IndentContext {
567
567
  The position at which indentation is being computed.
568
568
  */
569
569
  readonly pos: number;
570
+ private constructor();
570
571
  /**
571
572
  The syntax tree node to which the indentation strategy
572
573
  applies.
573
574
  */
574
- readonly node: SyntaxNode;
575
- private constructor();
575
+ get node(): SyntaxNode;
576
576
  /**
577
577
  Get the text directly after `this.pos`, either the entire line
578
578
  or the next 100 characters, whichever is shorter.
@@ -689,7 +689,7 @@ declare function foldable(state: EditorState, lineStart: number, lineEnd: number
689
689
  from: number;
690
690
  to: number;
691
691
  } | null;
692
- declare type DocRange = {
692
+ type DocRange = {
693
693
  from: number;
694
694
  to: number;
695
695
  };
@@ -761,23 +761,36 @@ interface FoldConfig {
761
761
  position of folded code. The `onclick` argument is the default
762
762
  click event handler, which toggles folding on the line that
763
763
  holds the element, and should probably be added as an event
764
- handler to the returned element.
764
+ handler to the returned element. If
765
+ [`preparePlaceholder`](https://codemirror.net/6/docs/ref/#language.FoldConfig.preparePlaceholder)
766
+ is given, its result will be passed as 3rd argument. Otherwise,
767
+ this will be null.
765
768
 
766
769
  When this option isn't given, the `placeholderText` option will
767
770
  be used to create the placeholder element.
768
771
  */
769
- placeholderDOM?: ((view: EditorView, onclick: (event: Event) => void) => HTMLElement) | null;
772
+ placeholderDOM?: ((view: EditorView, onclick: (event: Event) => void, prepared: any) => HTMLElement) | null;
770
773
  /**
771
774
  Text to use as placeholder for folded text. Defaults to `"…"`.
772
775
  Will be styled with the `"cm-foldPlaceholder"` class.
773
776
  */
774
777
  placeholderText?: string;
778
+ /**
779
+ Given a range that is being folded, create a value that
780
+ describes it, to be used by `placeholderDOM` to render a custom
781
+ widget that, for example, indicates something about the folded
782
+ range's size or type.
783
+ */
784
+ preparePlaceholder?: (state: EditorState, range: {
785
+ from: number;
786
+ to: number;
787
+ }) => any;
775
788
  }
776
789
  /**
777
790
  Create an extension that configures code folding.
778
791
  */
779
792
  declare function codeFolding(config?: FoldConfig): Extension;
780
- declare type Handlers = {
793
+ type Handlers = {
781
794
  [event: string]: (view: EditorView, line: BlockInfo, event: Event) => boolean;
782
795
  };
783
796
  interface FoldGutterConfig {
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { NodeProp, Tree, IterMode, TreeFragment, Parser, NodeType, NodeSet } from '@lezer/common';
1
+ import { NodeProp, IterMode, Tree, TreeFragment, Parser, NodeType, NodeSet } from '@lezer/common';
2
2
  import { StateEffect, StateField, Facet, EditorState, countColumn, combineConfig, RangeSet, RangeSetBuilder, Prec } from '@codemirror/state';
3
3
  import { ViewPlugin, logException, EditorView, Decoration, WidgetType, gutter, GutterMarker } from '@codemirror/view';
4
4
  import { tags, tagHighlighter, highlightTree, styleTags } from '@lezer/highlight';
@@ -534,14 +534,14 @@ class LanguageState {
534
534
  // state updates with parse work beyond the viewport.
535
535
  let upto = this.context.treeLen == tr.startState.doc.length ? undefined
536
536
  : Math.max(tr.changes.mapPos(this.context.treeLen), newCx.viewport.to);
537
- if (!newCx.work(20 /* Apply */, upto))
537
+ if (!newCx.work(20 /* Work.Apply */, upto))
538
538
  newCx.takeTree();
539
539
  return new LanguageState(newCx);
540
540
  }
541
541
  static init(state) {
542
- let vpTo = Math.min(3000 /* InitViewport */, state.doc.length);
542
+ let vpTo = Math.min(3000 /* Work.InitViewport */, state.doc.length);
543
543
  let parseState = ParseContext.create(state.facet(language).parser, state, { from: 0, to: vpTo });
544
- if (!parseState.work(20 /* Apply */, vpTo))
544
+ if (!parseState.work(20 /* Work.Apply */, vpTo))
545
545
  parseState.takeTree();
546
546
  return new LanguageState(parseState);
547
547
  }
@@ -558,14 +558,14 @@ Language.state = /*@__PURE__*/StateField.define({
558
558
  }
559
559
  });
560
560
  let requestIdle = (callback) => {
561
- let timeout = setTimeout(() => callback(), 500 /* MaxPause */);
561
+ let timeout = setTimeout(() => callback(), 500 /* Work.MaxPause */);
562
562
  return () => clearTimeout(timeout);
563
563
  };
564
564
  if (typeof requestIdleCallback != "undefined")
565
565
  requestIdle = (callback) => {
566
566
  let idle = -1, timeout = setTimeout(() => {
567
- idle = requestIdleCallback(callback, { timeout: 500 /* MaxPause */ - 100 /* MinPause */ });
568
- }, 100 /* MinPause */);
567
+ idle = requestIdleCallback(callback, { timeout: 500 /* Work.MaxPause */ - 100 /* Work.MinPause */ });
568
+ }, 100 /* Work.MinPause */);
569
569
  return () => idle < 0 ? clearTimeout(timeout) : cancelIdleCallback(idle);
570
570
  };
571
571
  const isInputPending = typeof navigator != "undefined" && ((_a = navigator.scheduling) === null || _a === void 0 ? void 0 : _a.isInputPending)
@@ -586,9 +586,9 @@ const parseWorker = /*@__PURE__*/ViewPlugin.fromClass(class ParseWorker {
586
586
  let cx = this.view.state.field(Language.state).context;
587
587
  if (cx.updateViewport(update.view.viewport) || this.view.viewport.to > cx.treeLen)
588
588
  this.scheduleWork();
589
- if (update.docChanged) {
589
+ if (update.docChanged || update.selectionSet) {
590
590
  if (this.view.hasFocus)
591
- this.chunkBudget += 50 /* ChangeBonus */;
591
+ this.chunkBudget += 50 /* Work.ChangeBonus */;
592
592
  this.scheduleWork();
593
593
  }
594
594
  this.checkAsyncSchedule(cx);
@@ -604,19 +604,19 @@ const parseWorker = /*@__PURE__*/ViewPlugin.fromClass(class ParseWorker {
604
604
  this.working = null;
605
605
  let now = Date.now();
606
606
  if (this.chunkEnd < now && (this.chunkEnd < 0 || this.view.hasFocus)) { // Start a new chunk
607
- this.chunkEnd = now + 30000 /* ChunkTime */;
608
- this.chunkBudget = 3000 /* ChunkBudget */;
607
+ this.chunkEnd = now + 30000 /* Work.ChunkTime */;
608
+ this.chunkBudget = 3000 /* Work.ChunkBudget */;
609
609
  }
610
610
  if (this.chunkBudget <= 0)
611
611
  return; // No more budget
612
612
  let { state, viewport: { to: vpTo } } = this.view, field = state.field(Language.state);
613
- if (field.tree == field.context.tree && field.context.isDone(vpTo + 100000 /* MaxParseAhead */))
613
+ if (field.tree == field.context.tree && field.context.isDone(vpTo + 100000 /* Work.MaxParseAhead */))
614
614
  return;
615
- let endTime = Date.now() + Math.min(this.chunkBudget, 100 /* Slice */, deadline && !isInputPending ? Math.max(25 /* MinSlice */, deadline.timeRemaining() - 5) : 1e9);
615
+ let endTime = Date.now() + Math.min(this.chunkBudget, 100 /* Work.Slice */, deadline && !isInputPending ? Math.max(25 /* Work.MinSlice */, deadline.timeRemaining() - 5) : 1e9);
616
616
  let viewportFirst = field.context.treeLen < vpTo && state.doc.length > vpTo + 1000;
617
617
  let done = field.context.work(() => {
618
618
  return isInputPending && isInputPending() || Date.now() > endTime;
619
- }, vpTo + (viewportFirst ? 0 : 100000 /* MaxParseAhead */));
619
+ }, vpTo + (viewportFirst ? 0 : 100000 /* Work.MaxParseAhead */));
620
620
  this.chunkBudget -= Date.now() - now;
621
621
  if (done || this.chunkBudget <= 0) {
622
622
  field.context.takeTree();
@@ -861,7 +861,7 @@ function getIndentation(context, pos) {
861
861
  return result;
862
862
  }
863
863
  let tree = syntaxTree(context.state);
864
- return tree ? syntaxIndentation(context, tree, pos) : null;
864
+ return tree.length >= pos ? syntaxIndentation(context, tree, pos) : null;
865
865
  }
866
866
  /**
867
867
  Create a change set that auto-indents all lines touched by the
@@ -992,7 +992,24 @@ indicates that no definitive indentation can be determined.
992
992
  const indentNodeProp = /*@__PURE__*/new NodeProp();
993
993
  // Compute the indentation for a given position from the syntax tree.
994
994
  function syntaxIndentation(cx, ast, pos) {
995
- return indentFrom(ast.resolveInner(pos).enterUnfinishedNodesBefore(pos), pos, cx);
995
+ let stack = ast.resolveStack(pos);
996
+ let inner = stack.node.enterUnfinishedNodesBefore(pos);
997
+ if (inner != stack.node) {
998
+ let add = [];
999
+ for (let cur = inner; cur != stack.node; cur = cur.parent)
1000
+ add.push(cur);
1001
+ for (let i = add.length - 1; i >= 0; i--)
1002
+ stack = { node: add[i], next: stack };
1003
+ }
1004
+ return indentFor(stack, cx, pos);
1005
+ }
1006
+ function indentFor(stack, cx, pos) {
1007
+ for (let cur = stack; cur; cur = cur.next) {
1008
+ let strategy = indentStrategy(cur.node);
1009
+ if (strategy)
1010
+ return strategy(TreeIndentContext.create(cx, pos, cur));
1011
+ }
1012
+ return 0;
996
1013
  }
997
1014
  function ignoreClosed(cx) {
998
1015
  return cx.pos == cx.options.simulateBreak && cx.options.simulateDoubleBreak;
@@ -1008,14 +1025,6 @@ function indentStrategy(tree) {
1008
1025
  }
1009
1026
  return tree.parent == null ? topIndent : null;
1010
1027
  }
1011
- function indentFrom(node, pos, base) {
1012
- for (; node; node = node.parent) {
1013
- let strategy = indentStrategy(node);
1014
- if (strategy)
1015
- return strategy(TreeIndentContext.create(base, pos, node));
1016
- }
1017
- return null;
1018
- }
1019
1028
  function topIndent() { return 0; }
1020
1029
  /**
1021
1030
  Objects of this type provide context information and helper
@@ -1028,20 +1037,24 @@ class TreeIndentContext extends IndentContext {
1028
1037
  */
1029
1038
  pos,
1030
1039
  /**
1031
- The syntax tree node to which the indentation strategy
1032
- applies.
1040
+ @internal
1033
1041
  */
1034
- node) {
1042
+ context) {
1035
1043
  super(base.state, base.options);
1036
1044
  this.base = base;
1037
1045
  this.pos = pos;
1038
- this.node = node;
1046
+ this.context = context;
1039
1047
  }
1040
1048
  /**
1049
+ The syntax tree node to which the indentation strategy
1050
+ applies.
1051
+ */
1052
+ get node() { return this.context.node; }
1053
+ /**
1041
1054
  @internal
1042
1055
  */
1043
- static create(base, pos, node) {
1044
- return new TreeIndentContext(base, pos, node);
1056
+ static create(base, pos, context) {
1057
+ return new TreeIndentContext(base, pos, context);
1045
1058
  }
1046
1059
  /**
1047
1060
  Get the text directly after `this.pos`, either the entire line
@@ -1082,8 +1095,7 @@ class TreeIndentContext extends IndentContext {
1082
1095
  and return the result of that.
1083
1096
  */
1084
1097
  continue() {
1085
- let parent = this.node.parent;
1086
- return parent ? indentFrom(parent, this.pos, this.base) : 0;
1098
+ return indentFor(this.context.next, this.base, this.pos);
1087
1099
  }
1088
1100
  }
1089
1101
  function isParent(parent, of) {
@@ -1225,9 +1237,10 @@ function syntaxFolding(state, start, end) {
1225
1237
  let tree = syntaxTree(state);
1226
1238
  if (tree.length < end)
1227
1239
  return null;
1228
- let inner = tree.resolveInner(end, 1);
1240
+ let stack = tree.resolveStack(end, 1);
1229
1241
  let found = null;
1230
- for (let cur = inner; cur; cur = cur.parent) {
1242
+ for (let iter = stack; iter; iter = iter.next) {
1243
+ let cur = iter.node;
1231
1244
  if (cur.to <= end || cur.from > end)
1232
1245
  continue;
1233
1246
  if (found && cur.from < start)
@@ -1300,11 +1313,16 @@ const foldState = /*@__PURE__*/StateField.define({
1300
1313
  update(folded, tr) {
1301
1314
  folded = folded.map(tr.changes);
1302
1315
  for (let e of tr.effects) {
1303
- if (e.is(foldEffect) && !foldExists(folded, e.value.from, e.value.to))
1304
- folded = folded.update({ add: [foldWidget.range(e.value.from, e.value.to)] });
1305
- else if (e.is(unfoldEffect))
1316
+ if (e.is(foldEffect) && !foldExists(folded, e.value.from, e.value.to)) {
1317
+ let { preparePlaceholder } = tr.state.facet(foldConfig);
1318
+ let widget = !preparePlaceholder ? foldWidget :
1319
+ Decoration.replace({ widget: new PreparedFoldWidget(preparePlaceholder(tr.state, e.value)) });
1320
+ folded = folded.update({ add: [widget.range(e.value.from, e.value.to)] });
1321
+ }
1322
+ else if (e.is(unfoldEffect)) {
1306
1323
  folded = folded.update({ filter: (from, to) => e.value.from != from || e.value.to != to,
1307
1324
  filterFrom: e.value.from, filterTo: e.value.to });
1325
+ }
1308
1326
  }
1309
1327
  // Clear folded ranges that cover the selection head
1310
1328
  if (tr.selection) {
@@ -1481,6 +1499,7 @@ const foldKeymap = [
1481
1499
  ];
1482
1500
  const defaultConfig = {
1483
1501
  placeholderDOM: null,
1502
+ preparePlaceholder: null,
1484
1503
  placeholderText: "…"
1485
1504
  };
1486
1505
  const foldConfig = /*@__PURE__*/Facet.define({
@@ -1495,27 +1514,36 @@ function codeFolding(config) {
1495
1514
  result.push(foldConfig.of(config));
1496
1515
  return result;
1497
1516
  }
1517
+ function widgetToDOM(view, prepared) {
1518
+ let { state } = view, conf = state.facet(foldConfig);
1519
+ let onclick = (event) => {
1520
+ let line = view.lineBlockAt(view.posAtDOM(event.target));
1521
+ let folded = findFold(view.state, line.from, line.to);
1522
+ if (folded)
1523
+ view.dispatch({ effects: unfoldEffect.of(folded) });
1524
+ event.preventDefault();
1525
+ };
1526
+ if (conf.placeholderDOM)
1527
+ return conf.placeholderDOM(view, onclick, prepared);
1528
+ let element = document.createElement("span");
1529
+ element.textContent = conf.placeholderText;
1530
+ element.setAttribute("aria-label", state.phrase("folded code"));
1531
+ element.title = state.phrase("unfold");
1532
+ element.className = "cm-foldPlaceholder";
1533
+ element.onclick = onclick;
1534
+ return element;
1535
+ }
1498
1536
  const foldWidget = /*@__PURE__*/Decoration.replace({ widget: /*@__PURE__*/new class extends WidgetType {
1499
- toDOM(view) {
1500
- let { state } = view, conf = state.facet(foldConfig);
1501
- let onclick = (event) => {
1502
- let line = view.lineBlockAt(view.posAtDOM(event.target));
1503
- let folded = findFold(view.state, line.from, line.to);
1504
- if (folded)
1505
- view.dispatch({ effects: unfoldEffect.of(folded) });
1506
- event.preventDefault();
1507
- };
1508
- if (conf.placeholderDOM)
1509
- return conf.placeholderDOM(view, onclick);
1510
- let element = document.createElement("span");
1511
- element.textContent = conf.placeholderText;
1512
- element.setAttribute("aria-label", state.phrase("folded code"));
1513
- element.title = state.phrase("unfold");
1514
- element.className = "cm-foldPlaceholder";
1515
- element.onclick = onclick;
1516
- return element;
1517
- }
1537
+ toDOM(view) { return widgetToDOM(view, null); }
1518
1538
  } });
1539
+ class PreparedFoldWidget extends WidgetType {
1540
+ constructor(value) {
1541
+ super();
1542
+ this.value = value;
1543
+ }
1544
+ eq(other) { return this.value == other.value; }
1545
+ toDOM(view) { return widgetToDOM(view, this.value); }
1546
+ }
1519
1547
  const foldGutterDefaults = {
1520
1548
  openText: "⌄",
1521
1549
  closedText: "›",
@@ -2203,7 +2231,7 @@ class StreamLanguage extends Language {
2203
2231
  state = this.streamParser.startState(cx.unit);
2204
2232
  statePos = 0;
2205
2233
  }
2206
- if (pos - statePos > 10000 /* MaxIndentScanDist */)
2234
+ if (pos - statePos > 10000 /* C.MaxIndentScanDist */)
2207
2235
  return null;
2208
2236
  while (statePos < pos) {
2209
2237
  let line = cx.state.doc.lineAt(statePos), end = Math.min(pos, line.to);
@@ -2285,7 +2313,7 @@ class Parse {
2285
2313
  this.chunks.push(tree.children[i]);
2286
2314
  this.chunkPos.push(tree.positions[i]);
2287
2315
  }
2288
- if (context && this.parsedPos < context.viewport.from - 100000 /* MaxDistanceBeforeViewport */) {
2316
+ if (context && this.parsedPos < context.viewport.from - 100000 /* C.MaxDistanceBeforeViewport */) {
2289
2317
  this.state = this.lang.streamParser.startState(getIndentUnit(context.state));
2290
2318
  context.skipUntilInView(this.parsedPos, context.viewport.from);
2291
2319
  this.parsedPos = context.viewport.from;
@@ -2295,7 +2323,7 @@ class Parse {
2295
2323
  advance() {
2296
2324
  let context = ParseContext.get();
2297
2325
  let parseEnd = this.stoppedAt == null ? this.to : Math.min(this.to, this.stoppedAt);
2298
- let end = Math.min(parseEnd, this.chunkStart + 2048 /* ChunkSize */);
2326
+ let end = Math.min(parseEnd, this.chunkStart + 2048 /* C.ChunkSize */);
2299
2327
  if (context)
2300
2328
  end = Math.min(end, context.viewport.to);
2301
2329
  while (this.parsedPos < end)
@@ -2379,7 +2407,7 @@ class Parse {
2379
2407
  let token = readToken(streamParser.token, stream, this.state);
2380
2408
  if (token)
2381
2409
  offset = this.emitToken(this.lang.tokenTable.resolve(token), this.parsedPos + stream.start, this.parsedPos + stream.pos, 4, offset);
2382
- if (stream.start > 10000 /* MaxLineLength */)
2410
+ if (stream.start > 10000 /* C.MaxLineLength */)
2383
2411
  break;
2384
2412
  }
2385
2413
  }
@@ -2395,7 +2423,7 @@ class Parse {
2395
2423
  length: this.parsedPos - this.chunkStart,
2396
2424
  nodeSet,
2397
2425
  topID: 0,
2398
- maxBufferLength: 2048 /* ChunkSize */,
2426
+ maxBufferLength: 2048 /* C.ChunkSize */,
2399
2427
  reused: this.chunkReused
2400
2428
  });
2401
2429
  tree = new Tree(tree.type, tree.children, tree.positions, tree.length, [[this.lang.stateAfter, this.lang.streamParser.copyState(this.state)]]);
@@ -2485,7 +2513,7 @@ function createTokenType(extra, tagStr) {
2485
2513
  return type.id;
2486
2514
  }
2487
2515
  function docID(data) {
2488
- let type = NodeType.define({ id: typeArray.length, name: "Document", props: [languageDataProp.add(() => data)] });
2516
+ let type = NodeType.define({ id: typeArray.length, name: "Document", props: [languageDataProp.add(() => data)], top: true });
2489
2517
  typeArray.push(type);
2490
2518
  return type;
2491
2519
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/language",
3
- "version": "6.8.0",
3
+ "version": "6.9.1",
4
4
  "description": "Language support infrastructure for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",
@@ -28,7 +28,7 @@
28
28
  "dependencies": {
29
29
  "@codemirror/state": "^6.0.0",
30
30
  "@codemirror/view": "^6.0.0",
31
- "@lezer/common": "^1.0.0",
31
+ "@lezer/common": "^1.1.0",
32
32
  "@lezer/highlight": "^1.0.0",
33
33
  "@lezer/lr": "^1.0.0",
34
34
  "style-mod": "^4.0.0"