@codemirror/language 0.19.10 → 0.20.2
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 +34 -0
- package/dist/index.cjs +1208 -40
- package/dist/index.d.ts +497 -50
- package/dist/index.js +1192 -43
- package/package.json +8 -7
package/dist/index.cjs
CHANGED
|
@@ -5,12 +5,13 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
5
5
|
var common = require('@lezer/common');
|
|
6
6
|
var state = require('@codemirror/state');
|
|
7
7
|
var view = require('@codemirror/view');
|
|
8
|
-
var
|
|
8
|
+
var highlight = require('@lezer/highlight');
|
|
9
|
+
var styleMod = require('style-mod');
|
|
9
10
|
|
|
10
11
|
var _a;
|
|
11
12
|
/**
|
|
12
|
-
Node prop stored in a
|
|
13
|
-
facet that stores language data for that language.
|
|
13
|
+
Node prop stored in a parser's top syntax node to provide the
|
|
14
|
+
facet that stores language-specific data for that language.
|
|
14
15
|
*/
|
|
15
16
|
const languageDataProp = new common.NodeProp();
|
|
16
17
|
/**
|
|
@@ -29,31 +30,27 @@ function defineLanguageFacet(baseData) {
|
|
|
29
30
|
/**
|
|
30
31
|
A language object manages parsing and per-language
|
|
31
32
|
[metadata](https://codemirror.net/6/docs/ref/#state.EditorState.languageDataAt). Parse data is
|
|
32
|
-
managed as a [Lezer](https://lezer.codemirror.net) tree.
|
|
33
|
-
|
|
34
|
-
[
|
|
35
|
-
[`StreamLanguage`](https://codemirror.net/6/docs/ref/#
|
|
36
|
-
|
|
33
|
+
managed as a [Lezer](https://lezer.codemirror.net) tree. The class
|
|
34
|
+
can be used directly, via the [`LRLanguage`](https://codemirror.net/6/docs/ref/#language.LRLanguage)
|
|
35
|
+
subclass for [Lezer](https://lezer.codemirror.net/) LR parsers, or
|
|
36
|
+
via the [`StreamLanguage`](https://codemirror.net/6/docs/ref/#language.StreamLanguage) subclass
|
|
37
|
+
for stream parsers.
|
|
37
38
|
*/
|
|
38
39
|
class Language {
|
|
39
40
|
/**
|
|
40
|
-
Construct a language object.
|
|
41
|
-
|
|
42
|
-
[`defineLanguageFacet`](https://codemirror.net/6/docs/ref/#language.defineLanguageFacet)
|
|
43
|
-
|
|
41
|
+
Construct a language object. If you need to invoke this
|
|
42
|
+
directly, first define a data facet with
|
|
43
|
+
[`defineLanguageFacet`](https://codemirror.net/6/docs/ref/#language.defineLanguageFacet), and then
|
|
44
|
+
configure your parser to [attach](https://codemirror.net/6/docs/ref/#language.languageDataProp) it
|
|
45
|
+
to the language's outer syntax node.
|
|
44
46
|
*/
|
|
45
47
|
constructor(
|
|
46
48
|
/**
|
|
47
|
-
The [language data](https://codemirror.net/6/docs/ref/#state.EditorState.languageDataAt)
|
|
48
|
-
|
|
49
|
+
The [language data](https://codemirror.net/6/docs/ref/#state.EditorState.languageDataAt) facet
|
|
50
|
+
used for this language.
|
|
49
51
|
*/
|
|
50
|
-
data, parser,
|
|
51
|
-
/**
|
|
52
|
-
The node type of the top node of trees produced by this parser.
|
|
53
|
-
*/
|
|
54
|
-
topNode, extraExtensions = []) {
|
|
52
|
+
data, parser, extraExtensions = []) {
|
|
55
53
|
this.data = data;
|
|
56
|
-
this.topNode = topNode;
|
|
57
54
|
// Kludge to define EditorState.tree as a debugging helper,
|
|
58
55
|
// without the EditorState package actually knowing about
|
|
59
56
|
// languages and lezer trees.
|
|
@@ -130,7 +127,7 @@ function languageDataFacetAt(state, pos, side) {
|
|
|
130
127
|
return null;
|
|
131
128
|
let facet = topLang.data;
|
|
132
129
|
if (topLang.allowsNesting) {
|
|
133
|
-
for (let node = syntaxTree(state).topNode; node; node = node.enter(pos, side,
|
|
130
|
+
for (let node = syntaxTree(state).topNode; node; node = node.enter(pos, side, common.IterMode.ExcludeBuffers))
|
|
134
131
|
facet = node.type.prop(languageDataProp) || facet;
|
|
135
132
|
}
|
|
136
133
|
return facet;
|
|
@@ -142,7 +139,7 @@ parsers.
|
|
|
142
139
|
*/
|
|
143
140
|
class LRLanguage extends Language {
|
|
144
141
|
constructor(data, parser) {
|
|
145
|
-
super(data, parser
|
|
142
|
+
super(data, parser);
|
|
146
143
|
this.parser = parser;
|
|
147
144
|
}
|
|
148
145
|
/**
|
|
@@ -161,12 +158,13 @@ class LRLanguage extends Language {
|
|
|
161
158
|
configure(options) {
|
|
162
159
|
return new LRLanguage(this.data, this.parser.configure(options));
|
|
163
160
|
}
|
|
164
|
-
get allowsNesting() { return this.parser.
|
|
161
|
+
get allowsNesting() { return this.parser.hasWrappers(); }
|
|
165
162
|
}
|
|
166
163
|
/**
|
|
167
164
|
Get the syntax tree for a state, which is the current (possibly
|
|
168
|
-
incomplete) parse tree of active
|
|
169
|
-
or the empty tree if there is no
|
|
165
|
+
incomplete) parse tree of the active
|
|
166
|
+
[language](https://codemirror.net/6/docs/ref/#language.Language), or the empty tree if there is no
|
|
167
|
+
language available.
|
|
170
168
|
*/
|
|
171
169
|
function syntaxTree(state) {
|
|
172
170
|
let field = state.field(Language.state, false);
|
|
@@ -187,7 +185,7 @@ Queries whether there is a full syntax tree available up to the
|
|
|
187
185
|
given document position. If there isn't, the background parse
|
|
188
186
|
process _might_ still be working and update the tree further, but
|
|
189
187
|
there is no guarantee of that—the parser will [stop
|
|
190
|
-
working](https://codemirror.net/6/docs/ref/#language.
|
|
188
|
+
working](https://codemirror.net/6/docs/ref/#language.syntaxParserRunning) when it has spent a
|
|
191
189
|
certain amount of time or has moved beyond the visible viewport.
|
|
192
190
|
Always returns false if no language has been enabled.
|
|
193
191
|
*/
|
|
@@ -196,6 +194,18 @@ function syntaxTreeAvailable(state, upto = state.doc.length) {
|
|
|
196
194
|
return ((_a = state.field(Language.state, false)) === null || _a === void 0 ? void 0 : _a.context.isDone(upto)) || false;
|
|
197
195
|
}
|
|
198
196
|
/**
|
|
197
|
+
Move parsing forward, and update the editor state afterwards to
|
|
198
|
+
reflect the new tree. Will work for at most `timeout`
|
|
199
|
+
milliseconds. Returns true if the parser managed get to the given
|
|
200
|
+
position in that time.
|
|
201
|
+
*/
|
|
202
|
+
function forceParsing(view, upto = view.viewport.to, timeout = 100) {
|
|
203
|
+
let success = ensureSyntaxTree(view.state, upto, timeout);
|
|
204
|
+
if (success != syntaxTree(view.state))
|
|
205
|
+
view.dispatch({});
|
|
206
|
+
return !!success;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
199
209
|
Tells you whether the language parser is planning to do more
|
|
200
210
|
parsing work (in a `requestIdleCallback` pseudo-thread) or has
|
|
201
211
|
stopped running, either because it parsed the entire document,
|
|
@@ -238,9 +248,6 @@ let currentContext = null;
|
|
|
238
248
|
A parse context provided to parsers working on the editor content.
|
|
239
249
|
*/
|
|
240
250
|
class ParseContext {
|
|
241
|
-
/**
|
|
242
|
-
@internal
|
|
243
|
-
*/
|
|
244
251
|
constructor(parser,
|
|
245
252
|
/**
|
|
246
253
|
The current editor state.
|
|
@@ -253,7 +260,11 @@ class ParseContext {
|
|
|
253
260
|
/**
|
|
254
261
|
@internal
|
|
255
262
|
*/
|
|
256
|
-
tree,
|
|
263
|
+
tree,
|
|
264
|
+
/**
|
|
265
|
+
@internal
|
|
266
|
+
*/
|
|
267
|
+
treeLen,
|
|
257
268
|
/**
|
|
258
269
|
The current editor viewport (or some overapproximation
|
|
259
270
|
thereof). Intended to be used for opportunistically avoiding
|
|
@@ -287,6 +298,12 @@ class ParseContext {
|
|
|
287
298
|
*/
|
|
288
299
|
this.tempSkipped = [];
|
|
289
300
|
}
|
|
301
|
+
/**
|
|
302
|
+
@internal
|
|
303
|
+
*/
|
|
304
|
+
static create(parser, state, viewport) {
|
|
305
|
+
return new ParseContext(parser, state, [], common.Tree.empty, 0, viewport, [], null);
|
|
306
|
+
}
|
|
290
307
|
startParse() {
|
|
291
308
|
return this.parser.startParse(new DocInput(this.state.doc), this.fragments);
|
|
292
309
|
}
|
|
@@ -492,7 +509,7 @@ class LanguageState {
|
|
|
492
509
|
}
|
|
493
510
|
static init(state) {
|
|
494
511
|
let vpTo = Math.min(3000 /* InitViewport */, state.doc.length);
|
|
495
|
-
let parseState =
|
|
512
|
+
let parseState = ParseContext.create(state.facet(language).parser, state, { from: 0, to: vpTo });
|
|
496
513
|
if (!parseState.work(20 /* Apply */, vpTo))
|
|
497
514
|
parseState.takeTree();
|
|
498
515
|
return new LanguageState(parseState);
|
|
@@ -606,7 +623,7 @@ const language = state.Facet.define({
|
|
|
606
623
|
enables: [Language.state, parseWorker]
|
|
607
624
|
});
|
|
608
625
|
/**
|
|
609
|
-
This class bundles a [language
|
|
626
|
+
This class bundles a [language](https://codemirror.net/6/docs/ref/#language.Language) with an
|
|
610
627
|
optional set of supporting extensions. Language packages are
|
|
611
628
|
encouraged to export a function that optionally takes a
|
|
612
629
|
configuration object and returns a `LanguageSupport` instance, as
|
|
@@ -614,7 +631,7 @@ the main way for client code to use the package.
|
|
|
614
631
|
*/
|
|
615
632
|
class LanguageSupport {
|
|
616
633
|
/**
|
|
617
|
-
Create a support object.
|
|
634
|
+
Create a language support object.
|
|
618
635
|
*/
|
|
619
636
|
constructor(
|
|
620
637
|
/**
|
|
@@ -867,7 +884,7 @@ class IndentContext {
|
|
|
867
884
|
position in the given string.
|
|
868
885
|
*/
|
|
869
886
|
countColumn(line, pos = line.length) {
|
|
870
|
-
return
|
|
887
|
+
return state.countColumn(line, this.state.tabSize, pos);
|
|
871
888
|
}
|
|
872
889
|
/**
|
|
873
890
|
Find the indentation column of the line at the given point.
|
|
@@ -920,19 +937,16 @@ function indentFrom(node, pos, base) {
|
|
|
920
937
|
for (; node; node = node.parent) {
|
|
921
938
|
let strategy = indentStrategy(node);
|
|
922
939
|
if (strategy)
|
|
923
|
-
return strategy(
|
|
940
|
+
return strategy(TreeIndentContext.create(base, pos, node));
|
|
924
941
|
}
|
|
925
942
|
return null;
|
|
926
943
|
}
|
|
927
944
|
function topIndent() { return 0; }
|
|
928
945
|
/**
|
|
929
946
|
Objects of this type provide context information and helper
|
|
930
|
-
methods to indentation functions.
|
|
947
|
+
methods to indentation functions registered on syntax nodes.
|
|
931
948
|
*/
|
|
932
949
|
class TreeIndentContext extends IndentContext {
|
|
933
|
-
/**
|
|
934
|
-
@internal
|
|
935
|
-
*/
|
|
936
950
|
constructor(base,
|
|
937
951
|
/**
|
|
938
952
|
The position at which indentation is being computed.
|
|
@@ -949,6 +963,12 @@ class TreeIndentContext extends IndentContext {
|
|
|
949
963
|
this.node = node;
|
|
950
964
|
}
|
|
951
965
|
/**
|
|
966
|
+
@internal
|
|
967
|
+
*/
|
|
968
|
+
static create(base, pos, node) {
|
|
969
|
+
return new TreeIndentContext(base, pos, node);
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
952
972
|
Get the text directly after `this.pos`, either the entire line
|
|
953
973
|
or the next 100 characters, whichever is shorter.
|
|
954
974
|
*/
|
|
@@ -1121,7 +1141,7 @@ function foldInside(node) {
|
|
|
1121
1141
|
}
|
|
1122
1142
|
function syntaxFolding(state, start, end) {
|
|
1123
1143
|
let tree = syntaxTree(state);
|
|
1124
|
-
if (tree.length
|
|
1144
|
+
if (tree.length < end)
|
|
1125
1145
|
return null;
|
|
1126
1146
|
let inner = tree.resolveInner(end);
|
|
1127
1147
|
let found = null;
|
|
@@ -1159,25 +1179,1168 @@ function foldable(state, lineStart, lineEnd) {
|
|
|
1159
1179
|
}
|
|
1160
1180
|
return syntaxFolding(state, lineStart, lineEnd);
|
|
1161
1181
|
}
|
|
1182
|
+
function mapRange(range, mapping) {
|
|
1183
|
+
let from = mapping.mapPos(range.from, 1), to = mapping.mapPos(range.to, -1);
|
|
1184
|
+
return from >= to ? undefined : { from, to };
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
State effect that can be attached to a transaction to fold the
|
|
1188
|
+
given range. (You probably only need this in exceptional
|
|
1189
|
+
circumstances—usually you'll just want to let
|
|
1190
|
+
[`foldCode`](https://codemirror.net/6/docs/ref/#language.foldCode) and the [fold
|
|
1191
|
+
gutter](https://codemirror.net/6/docs/ref/#language.foldGutter) create the transactions.)
|
|
1192
|
+
*/
|
|
1193
|
+
const foldEffect = state.StateEffect.define({ map: mapRange });
|
|
1194
|
+
/**
|
|
1195
|
+
State effect that unfolds the given range (if it was folded).
|
|
1196
|
+
*/
|
|
1197
|
+
const unfoldEffect = state.StateEffect.define({ map: mapRange });
|
|
1198
|
+
function selectedLines(view) {
|
|
1199
|
+
let lines = [];
|
|
1200
|
+
for (let { head } of view.state.selection.ranges) {
|
|
1201
|
+
if (lines.some(l => l.from <= head && l.to >= head))
|
|
1202
|
+
continue;
|
|
1203
|
+
lines.push(view.lineBlockAt(head));
|
|
1204
|
+
}
|
|
1205
|
+
return lines;
|
|
1206
|
+
}
|
|
1207
|
+
const foldState = state.StateField.define({
|
|
1208
|
+
create() {
|
|
1209
|
+
return view.Decoration.none;
|
|
1210
|
+
},
|
|
1211
|
+
update(folded, tr) {
|
|
1212
|
+
folded = folded.map(tr.changes);
|
|
1213
|
+
for (let e of tr.effects) {
|
|
1214
|
+
if (e.is(foldEffect) && !foldExists(folded, e.value.from, e.value.to))
|
|
1215
|
+
folded = folded.update({ add: [foldWidget.range(e.value.from, e.value.to)] });
|
|
1216
|
+
else if (e.is(unfoldEffect))
|
|
1217
|
+
folded = folded.update({ filter: (from, to) => e.value.from != from || e.value.to != to,
|
|
1218
|
+
filterFrom: e.value.from, filterTo: e.value.to });
|
|
1219
|
+
}
|
|
1220
|
+
// Clear folded ranges that cover the selection head
|
|
1221
|
+
if (tr.selection) {
|
|
1222
|
+
let onSelection = false, { head } = tr.selection.main;
|
|
1223
|
+
folded.between(head, head, (a, b) => { if (a < head && b > head)
|
|
1224
|
+
onSelection = true; });
|
|
1225
|
+
if (onSelection)
|
|
1226
|
+
folded = folded.update({
|
|
1227
|
+
filterFrom: head,
|
|
1228
|
+
filterTo: head,
|
|
1229
|
+
filter: (a, b) => b <= head || a >= head
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
return folded;
|
|
1233
|
+
},
|
|
1234
|
+
provide: f => view.EditorView.decorations.from(f)
|
|
1235
|
+
});
|
|
1236
|
+
/**
|
|
1237
|
+
Get a [range set](https://codemirror.net/6/docs/ref/#state.RangeSet) containing the folded ranges
|
|
1238
|
+
in the given state.
|
|
1239
|
+
*/
|
|
1240
|
+
function foldedRanges(state$1) {
|
|
1241
|
+
return state$1.field(foldState, false) || state.RangeSet.empty;
|
|
1242
|
+
}
|
|
1243
|
+
function findFold(state, from, to) {
|
|
1244
|
+
var _a;
|
|
1245
|
+
let found = null;
|
|
1246
|
+
(_a = state.field(foldState, false)) === null || _a === void 0 ? void 0 : _a.between(from, to, (from, to) => {
|
|
1247
|
+
if (!found || found.from > from)
|
|
1248
|
+
found = { from, to };
|
|
1249
|
+
});
|
|
1250
|
+
return found;
|
|
1251
|
+
}
|
|
1252
|
+
function foldExists(folded, from, to) {
|
|
1253
|
+
let found = false;
|
|
1254
|
+
folded.between(from, from, (a, b) => { if (a == from && b == to)
|
|
1255
|
+
found = true; });
|
|
1256
|
+
return found;
|
|
1257
|
+
}
|
|
1258
|
+
function maybeEnable(state$1, other) {
|
|
1259
|
+
return state$1.field(foldState, false) ? other : other.concat(state.StateEffect.appendConfig.of(codeFolding()));
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
Fold the lines that are selected, if possible.
|
|
1263
|
+
*/
|
|
1264
|
+
const foldCode = view => {
|
|
1265
|
+
for (let line of selectedLines(view)) {
|
|
1266
|
+
let range = foldable(view.state, line.from, line.to);
|
|
1267
|
+
if (range) {
|
|
1268
|
+
view.dispatch({ effects: maybeEnable(view.state, [foldEffect.of(range), announceFold(view, range)]) });
|
|
1269
|
+
return true;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
return false;
|
|
1273
|
+
};
|
|
1274
|
+
/**
|
|
1275
|
+
Unfold folded ranges on selected lines.
|
|
1276
|
+
*/
|
|
1277
|
+
const unfoldCode = view => {
|
|
1278
|
+
if (!view.state.field(foldState, false))
|
|
1279
|
+
return false;
|
|
1280
|
+
let effects = [];
|
|
1281
|
+
for (let line of selectedLines(view)) {
|
|
1282
|
+
let folded = findFold(view.state, line.from, line.to);
|
|
1283
|
+
if (folded)
|
|
1284
|
+
effects.push(unfoldEffect.of(folded), announceFold(view, folded, false));
|
|
1285
|
+
}
|
|
1286
|
+
if (effects.length)
|
|
1287
|
+
view.dispatch({ effects });
|
|
1288
|
+
return effects.length > 0;
|
|
1289
|
+
};
|
|
1290
|
+
function announceFold(view$1, range, fold = true) {
|
|
1291
|
+
let lineFrom = view$1.state.doc.lineAt(range.from).number, lineTo = view$1.state.doc.lineAt(range.to).number;
|
|
1292
|
+
return view.EditorView.announce.of(`${view$1.state.phrase(fold ? "Folded lines" : "Unfolded lines")} ${lineFrom} ${view$1.state.phrase("to")} ${lineTo}.`);
|
|
1293
|
+
}
|
|
1294
|
+
/**
|
|
1295
|
+
Fold all top-level foldable ranges. Note that, in most cases,
|
|
1296
|
+
folding information will depend on the [syntax
|
|
1297
|
+
tree](https://codemirror.net/6/docs/ref/#language.syntaxTree), and folding everything may not work
|
|
1298
|
+
reliably when the document hasn't been fully parsed (either
|
|
1299
|
+
because the editor state was only just initialized, or because the
|
|
1300
|
+
document is so big that the parser decided not to parse it
|
|
1301
|
+
entirely).
|
|
1302
|
+
*/
|
|
1303
|
+
const foldAll = view => {
|
|
1304
|
+
let { state } = view, effects = [];
|
|
1305
|
+
for (let pos = 0; pos < state.doc.length;) {
|
|
1306
|
+
let line = view.lineBlockAt(pos), range = foldable(state, line.from, line.to);
|
|
1307
|
+
if (range)
|
|
1308
|
+
effects.push(foldEffect.of(range));
|
|
1309
|
+
pos = (range ? view.lineBlockAt(range.to) : line).to + 1;
|
|
1310
|
+
}
|
|
1311
|
+
if (effects.length)
|
|
1312
|
+
view.dispatch({ effects: maybeEnable(view.state, effects) });
|
|
1313
|
+
return !!effects.length;
|
|
1314
|
+
};
|
|
1315
|
+
/**
|
|
1316
|
+
Unfold all folded code.
|
|
1317
|
+
*/
|
|
1318
|
+
const unfoldAll = view => {
|
|
1319
|
+
let field = view.state.field(foldState, false);
|
|
1320
|
+
if (!field || !field.size)
|
|
1321
|
+
return false;
|
|
1322
|
+
let effects = [];
|
|
1323
|
+
field.between(0, view.state.doc.length, (from, to) => { effects.push(unfoldEffect.of({ from, to })); });
|
|
1324
|
+
view.dispatch({ effects });
|
|
1325
|
+
return true;
|
|
1326
|
+
};
|
|
1327
|
+
/**
|
|
1328
|
+
Default fold-related key bindings.
|
|
1329
|
+
|
|
1330
|
+
- Ctrl-Shift-[ (Cmd-Alt-[ on macOS): [`foldCode`](https://codemirror.net/6/docs/ref/#language.foldCode).
|
|
1331
|
+
- Ctrl-Shift-] (Cmd-Alt-] on macOS): [`unfoldCode`](https://codemirror.net/6/docs/ref/#language.unfoldCode).
|
|
1332
|
+
- Ctrl-Alt-[: [`foldAll`](https://codemirror.net/6/docs/ref/#language.foldAll).
|
|
1333
|
+
- Ctrl-Alt-]: [`unfoldAll`](https://codemirror.net/6/docs/ref/#language.unfoldAll).
|
|
1334
|
+
*/
|
|
1335
|
+
const foldKeymap = [
|
|
1336
|
+
{ key: "Ctrl-Shift-[", mac: "Cmd-Alt-[", run: foldCode },
|
|
1337
|
+
{ key: "Ctrl-Shift-]", mac: "Cmd-Alt-]", run: unfoldCode },
|
|
1338
|
+
{ key: "Ctrl-Alt-[", run: foldAll },
|
|
1339
|
+
{ key: "Ctrl-Alt-]", run: unfoldAll }
|
|
1340
|
+
];
|
|
1341
|
+
const defaultConfig = {
|
|
1342
|
+
placeholderDOM: null,
|
|
1343
|
+
placeholderText: "…"
|
|
1344
|
+
};
|
|
1345
|
+
const foldConfig = state.Facet.define({
|
|
1346
|
+
combine(values) { return state.combineConfig(values, defaultConfig); }
|
|
1347
|
+
});
|
|
1348
|
+
/**
|
|
1349
|
+
Create an extension that configures code folding.
|
|
1350
|
+
*/
|
|
1351
|
+
function codeFolding(config) {
|
|
1352
|
+
let result = [foldState, baseTheme$1];
|
|
1353
|
+
if (config)
|
|
1354
|
+
result.push(foldConfig.of(config));
|
|
1355
|
+
return result;
|
|
1356
|
+
}
|
|
1357
|
+
const foldWidget = view.Decoration.replace({ widget: new class extends view.WidgetType {
|
|
1358
|
+
toDOM(view) {
|
|
1359
|
+
let { state } = view, conf = state.facet(foldConfig);
|
|
1360
|
+
let onclick = (event) => {
|
|
1361
|
+
let line = view.lineBlockAt(view.posAtDOM(event.target));
|
|
1362
|
+
let folded = findFold(view.state, line.from, line.to);
|
|
1363
|
+
if (folded)
|
|
1364
|
+
view.dispatch({ effects: unfoldEffect.of(folded) });
|
|
1365
|
+
event.preventDefault();
|
|
1366
|
+
};
|
|
1367
|
+
if (conf.placeholderDOM)
|
|
1368
|
+
return conf.placeholderDOM(view, onclick);
|
|
1369
|
+
let element = document.createElement("span");
|
|
1370
|
+
element.textContent = conf.placeholderText;
|
|
1371
|
+
element.setAttribute("aria-label", state.phrase("folded code"));
|
|
1372
|
+
element.title = state.phrase("unfold");
|
|
1373
|
+
element.className = "cm-foldPlaceholder";
|
|
1374
|
+
element.onclick = onclick;
|
|
1375
|
+
return element;
|
|
1376
|
+
}
|
|
1377
|
+
} });
|
|
1378
|
+
const foldGutterDefaults = {
|
|
1379
|
+
openText: "⌄",
|
|
1380
|
+
closedText: "›",
|
|
1381
|
+
markerDOM: null,
|
|
1382
|
+
domEventHandlers: {},
|
|
1383
|
+
};
|
|
1384
|
+
class FoldMarker extends view.GutterMarker {
|
|
1385
|
+
constructor(config, open) {
|
|
1386
|
+
super();
|
|
1387
|
+
this.config = config;
|
|
1388
|
+
this.open = open;
|
|
1389
|
+
}
|
|
1390
|
+
eq(other) { return this.config == other.config && this.open == other.open; }
|
|
1391
|
+
toDOM(view) {
|
|
1392
|
+
if (this.config.markerDOM)
|
|
1393
|
+
return this.config.markerDOM(this.open);
|
|
1394
|
+
let span = document.createElement("span");
|
|
1395
|
+
span.textContent = this.open ? this.config.openText : this.config.closedText;
|
|
1396
|
+
span.title = view.state.phrase(this.open ? "Fold line" : "Unfold line");
|
|
1397
|
+
return span;
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
/**
|
|
1401
|
+
Create an extension that registers a fold gutter, which shows a
|
|
1402
|
+
fold status indicator before foldable lines (which can be clicked
|
|
1403
|
+
to fold or unfold the line).
|
|
1404
|
+
*/
|
|
1405
|
+
function foldGutter(config = {}) {
|
|
1406
|
+
let fullConfig = Object.assign(Object.assign({}, foldGutterDefaults), config);
|
|
1407
|
+
let canFold = new FoldMarker(fullConfig, true), canUnfold = new FoldMarker(fullConfig, false);
|
|
1408
|
+
let markers = view.ViewPlugin.fromClass(class {
|
|
1409
|
+
constructor(view) {
|
|
1410
|
+
this.from = view.viewport.from;
|
|
1411
|
+
this.markers = this.buildMarkers(view);
|
|
1412
|
+
}
|
|
1413
|
+
update(update) {
|
|
1414
|
+
if (update.docChanged || update.viewportChanged ||
|
|
1415
|
+
update.startState.facet(language) != update.state.facet(language) ||
|
|
1416
|
+
update.startState.field(foldState, false) != update.state.field(foldState, false) ||
|
|
1417
|
+
syntaxTree(update.startState) != syntaxTree(update.state))
|
|
1418
|
+
this.markers = this.buildMarkers(update.view);
|
|
1419
|
+
}
|
|
1420
|
+
buildMarkers(view) {
|
|
1421
|
+
let builder = new state.RangeSetBuilder();
|
|
1422
|
+
for (let line of view.viewportLineBlocks) {
|
|
1423
|
+
let mark = findFold(view.state, line.from, line.to) ? canUnfold
|
|
1424
|
+
: foldable(view.state, line.from, line.to) ? canFold : null;
|
|
1425
|
+
if (mark)
|
|
1426
|
+
builder.add(line.from, line.from, mark);
|
|
1427
|
+
}
|
|
1428
|
+
return builder.finish();
|
|
1429
|
+
}
|
|
1430
|
+
});
|
|
1431
|
+
let { domEventHandlers } = fullConfig;
|
|
1432
|
+
return [
|
|
1433
|
+
markers,
|
|
1434
|
+
view.gutter({
|
|
1435
|
+
class: "cm-foldGutter",
|
|
1436
|
+
markers(view) { var _a; return ((_a = view.plugin(markers)) === null || _a === void 0 ? void 0 : _a.markers) || state.RangeSet.empty; },
|
|
1437
|
+
initialSpacer() {
|
|
1438
|
+
return new FoldMarker(fullConfig, false);
|
|
1439
|
+
},
|
|
1440
|
+
domEventHandlers: Object.assign(Object.assign({}, domEventHandlers), { click: (view, line, event) => {
|
|
1441
|
+
if (domEventHandlers.click && domEventHandlers.click(view, line, event))
|
|
1442
|
+
return true;
|
|
1443
|
+
let folded = findFold(view.state, line.from, line.to);
|
|
1444
|
+
if (folded) {
|
|
1445
|
+
view.dispatch({ effects: unfoldEffect.of(folded) });
|
|
1446
|
+
return true;
|
|
1447
|
+
}
|
|
1448
|
+
let range = foldable(view.state, line.from, line.to);
|
|
1449
|
+
if (range) {
|
|
1450
|
+
view.dispatch({ effects: foldEffect.of(range) });
|
|
1451
|
+
return true;
|
|
1452
|
+
}
|
|
1453
|
+
return false;
|
|
1454
|
+
} })
|
|
1455
|
+
}),
|
|
1456
|
+
codeFolding()
|
|
1457
|
+
];
|
|
1458
|
+
}
|
|
1459
|
+
const baseTheme$1 = view.EditorView.baseTheme({
|
|
1460
|
+
".cm-foldPlaceholder": {
|
|
1461
|
+
backgroundColor: "#eee",
|
|
1462
|
+
border: "1px solid #ddd",
|
|
1463
|
+
color: "#888",
|
|
1464
|
+
borderRadius: ".2em",
|
|
1465
|
+
margin: "0 1px",
|
|
1466
|
+
padding: "0 1px",
|
|
1467
|
+
cursor: "pointer"
|
|
1468
|
+
},
|
|
1469
|
+
".cm-foldGutter span": {
|
|
1470
|
+
padding: "0 1px",
|
|
1471
|
+
cursor: "pointer"
|
|
1472
|
+
}
|
|
1473
|
+
});
|
|
1474
|
+
|
|
1475
|
+
/**
|
|
1476
|
+
A highlight style associates CSS styles with higlighting
|
|
1477
|
+
[tags](https://lezer.codemirror.net/docs/ref#highlight.Tag).
|
|
1478
|
+
*/
|
|
1479
|
+
class HighlightStyle {
|
|
1480
|
+
constructor(spec, options) {
|
|
1481
|
+
let modSpec;
|
|
1482
|
+
function def(spec) {
|
|
1483
|
+
let cls = styleMod.StyleModule.newName();
|
|
1484
|
+
(modSpec || (modSpec = Object.create(null)))["." + cls] = spec;
|
|
1485
|
+
return cls;
|
|
1486
|
+
}
|
|
1487
|
+
const all = typeof options.all == "string" ? options.all : options.all ? def(options.all) : undefined;
|
|
1488
|
+
const scopeOpt = options.scope;
|
|
1489
|
+
this.scope = scopeOpt instanceof Language ? (type) => type.prop(languageDataProp) == scopeOpt.data
|
|
1490
|
+
: scopeOpt ? (type) => type == scopeOpt : undefined;
|
|
1491
|
+
this.style = highlight.tagHighlighter(spec.map(style => ({
|
|
1492
|
+
tag: style.tag,
|
|
1493
|
+
class: style.class || def(Object.assign({}, style, { tag: null }))
|
|
1494
|
+
})), {
|
|
1495
|
+
all,
|
|
1496
|
+
}).style;
|
|
1497
|
+
this.module = modSpec ? new styleMod.StyleModule(modSpec) : null;
|
|
1498
|
+
this.themeType = options.themeType;
|
|
1499
|
+
}
|
|
1500
|
+
/**
|
|
1501
|
+
Create a highlighter style that associates the given styles to
|
|
1502
|
+
the given tags. The specs must be objects that hold a style tag
|
|
1503
|
+
or array of tags in their `tag` property, and either a single
|
|
1504
|
+
`class` property providing a static CSS class (for highlighter
|
|
1505
|
+
that rely on external styling), or a
|
|
1506
|
+
[`style-mod`](https://github.com/marijnh/style-mod#documentation)-style
|
|
1507
|
+
set of CSS properties (which define the styling for those tags).
|
|
1508
|
+
|
|
1509
|
+
The CSS rules created for a highlighter will be emitted in the
|
|
1510
|
+
order of the spec's properties. That means that for elements that
|
|
1511
|
+
have multiple tags associated with them, styles defined further
|
|
1512
|
+
down in the list will have a higher CSS precedence than styles
|
|
1513
|
+
defined earlier.
|
|
1514
|
+
*/
|
|
1515
|
+
static define(specs, options) {
|
|
1516
|
+
return new HighlightStyle(specs, options || {});
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
const highlighterFacet = state.Facet.define();
|
|
1520
|
+
const fallbackHighlighter = state.Facet.define({
|
|
1521
|
+
combine(values) { return values.length ? [values[0]] : null; }
|
|
1522
|
+
});
|
|
1523
|
+
function getHighlighters(state) {
|
|
1524
|
+
let main = state.facet(highlighterFacet);
|
|
1525
|
+
return main.length ? main : state.facet(fallbackHighlighter);
|
|
1526
|
+
}
|
|
1527
|
+
/**
|
|
1528
|
+
Wrap a highlighter in an editor extension that uses it to apply
|
|
1529
|
+
syntax highlighting to the editor content.
|
|
1530
|
+
|
|
1531
|
+
When multiple (non-fallback) styles are provided, the styling
|
|
1532
|
+
applied is the union of the classes they emit.
|
|
1533
|
+
*/
|
|
1534
|
+
function syntaxHighlighting(highlighter, options) {
|
|
1535
|
+
let ext = [treeHighlighter], themeType;
|
|
1536
|
+
if (highlighter instanceof HighlightStyle) {
|
|
1537
|
+
if (highlighter.module)
|
|
1538
|
+
ext.push(view.EditorView.styleModule.of(highlighter.module));
|
|
1539
|
+
themeType = highlighter.themeType;
|
|
1540
|
+
}
|
|
1541
|
+
if (options === null || options === void 0 ? void 0 : options.fallback)
|
|
1542
|
+
ext.push(fallbackHighlighter.of(highlighter));
|
|
1543
|
+
else if (themeType)
|
|
1544
|
+
ext.push(highlighterFacet.computeN([view.EditorView.darkTheme], state => {
|
|
1545
|
+
return state.facet(view.EditorView.darkTheme) == (themeType == "dark") ? [highlighter] : [];
|
|
1546
|
+
}));
|
|
1547
|
+
else
|
|
1548
|
+
ext.push(highlighterFacet.of(highlighter));
|
|
1549
|
+
return ext;
|
|
1550
|
+
}
|
|
1551
|
+
/**
|
|
1552
|
+
Returns the CSS classes (if any) that the highlighters active in
|
|
1553
|
+
the state would assign to the given style
|
|
1554
|
+
[tags](https://lezer.codemirror.net/docs/ref#highlight.Tag) and
|
|
1555
|
+
(optional) language
|
|
1556
|
+
[scope](https://codemirror.net/6/docs/ref/#language.HighlightStyle^define^options.scope).
|
|
1557
|
+
*/
|
|
1558
|
+
function highlightingFor(state, tags, scope) {
|
|
1559
|
+
let highlighters = getHighlighters(state);
|
|
1560
|
+
let result = null;
|
|
1561
|
+
if (highlighters)
|
|
1562
|
+
for (let highlighter of highlighters) {
|
|
1563
|
+
if (!highlighter.scope || scope && highlighter.scope(scope)) {
|
|
1564
|
+
let cls = highlighter.style(tags);
|
|
1565
|
+
if (cls)
|
|
1566
|
+
result = result ? result + " " + cls : cls;
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
return result;
|
|
1570
|
+
}
|
|
1571
|
+
class TreeHighlighter {
|
|
1572
|
+
constructor(view) {
|
|
1573
|
+
this.markCache = Object.create(null);
|
|
1574
|
+
this.tree = syntaxTree(view.state);
|
|
1575
|
+
this.decorations = this.buildDeco(view, getHighlighters(view.state));
|
|
1576
|
+
}
|
|
1577
|
+
update(update) {
|
|
1578
|
+
let tree = syntaxTree(update.state), highlighters = getHighlighters(update.state);
|
|
1579
|
+
let styleChange = highlighters != getHighlighters(update.startState);
|
|
1580
|
+
if (tree.length < update.view.viewport.to && !styleChange && tree.type == this.tree.type) {
|
|
1581
|
+
this.decorations = this.decorations.map(update.changes);
|
|
1582
|
+
}
|
|
1583
|
+
else if (tree != this.tree || update.viewportChanged || styleChange) {
|
|
1584
|
+
this.tree = tree;
|
|
1585
|
+
this.decorations = this.buildDeco(update.view, highlighters);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
buildDeco(view$1, highlighters) {
|
|
1589
|
+
if (!highlighters || !this.tree.length)
|
|
1590
|
+
return view.Decoration.none;
|
|
1591
|
+
let builder = new state.RangeSetBuilder();
|
|
1592
|
+
for (let { from, to } of view$1.visibleRanges) {
|
|
1593
|
+
highlight.highlightTree(this.tree, highlighters, (from, to, style) => {
|
|
1594
|
+
builder.add(from, to, this.markCache[style] || (this.markCache[style] = view.Decoration.mark({ class: style })));
|
|
1595
|
+
}, from, to);
|
|
1596
|
+
}
|
|
1597
|
+
return builder.finish();
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
const treeHighlighter = state.Prec.high(view.ViewPlugin.fromClass(TreeHighlighter, {
|
|
1601
|
+
decorations: v => v.decorations
|
|
1602
|
+
}));
|
|
1603
|
+
/**
|
|
1604
|
+
A default highlight style (works well with light themes).
|
|
1605
|
+
*/
|
|
1606
|
+
const defaultHighlightStyle = HighlightStyle.define([
|
|
1607
|
+
{ tag: highlight.tags.meta,
|
|
1608
|
+
color: "#7a757a" },
|
|
1609
|
+
{ tag: highlight.tags.link,
|
|
1610
|
+
textDecoration: "underline" },
|
|
1611
|
+
{ tag: highlight.tags.heading,
|
|
1612
|
+
textDecoration: "underline",
|
|
1613
|
+
fontWeight: "bold" },
|
|
1614
|
+
{ tag: highlight.tags.emphasis,
|
|
1615
|
+
fontStyle: "italic" },
|
|
1616
|
+
{ tag: highlight.tags.strong,
|
|
1617
|
+
fontWeight: "bold" },
|
|
1618
|
+
{ tag: highlight.tags.strikethrough,
|
|
1619
|
+
textDecoration: "line-through" },
|
|
1620
|
+
{ tag: highlight.tags.keyword,
|
|
1621
|
+
color: "#708" },
|
|
1622
|
+
{ tag: [highlight.tags.atom, highlight.tags.bool, highlight.tags.url, highlight.tags.contentSeparator, highlight.tags.labelName],
|
|
1623
|
+
color: "#219" },
|
|
1624
|
+
{ tag: [highlight.tags.literal, highlight.tags.inserted],
|
|
1625
|
+
color: "#164" },
|
|
1626
|
+
{ tag: [highlight.tags.string, highlight.tags.deleted],
|
|
1627
|
+
color: "#a11" },
|
|
1628
|
+
{ tag: [highlight.tags.regexp, highlight.tags.escape, highlight.tags.special(highlight.tags.string)],
|
|
1629
|
+
color: "#e40" },
|
|
1630
|
+
{ tag: highlight.tags.definition(highlight.tags.variableName),
|
|
1631
|
+
color: "#00f" },
|
|
1632
|
+
{ tag: highlight.tags.local(highlight.tags.variableName),
|
|
1633
|
+
color: "#30a" },
|
|
1634
|
+
{ tag: [highlight.tags.typeName, highlight.tags.namespace],
|
|
1635
|
+
color: "#085" },
|
|
1636
|
+
{ tag: highlight.tags.className,
|
|
1637
|
+
color: "#167" },
|
|
1638
|
+
{ tag: [highlight.tags.special(highlight.tags.variableName), highlight.tags.macroName],
|
|
1639
|
+
color: "#256" },
|
|
1640
|
+
{ tag: highlight.tags.definition(highlight.tags.propertyName),
|
|
1641
|
+
color: "#00c" },
|
|
1642
|
+
{ tag: highlight.tags.comment,
|
|
1643
|
+
color: "#940" },
|
|
1644
|
+
{ tag: highlight.tags.invalid,
|
|
1645
|
+
color: "#f00" }
|
|
1646
|
+
]);
|
|
1647
|
+
|
|
1648
|
+
const baseTheme = view.EditorView.baseTheme({
|
|
1649
|
+
"&.cm-focused .cm-matchingBracket": { backgroundColor: "#328c8252" },
|
|
1650
|
+
"&.cm-focused .cm-nonmatchingBracket": { backgroundColor: "#bb555544" }
|
|
1651
|
+
});
|
|
1652
|
+
const DefaultScanDist = 10000, DefaultBrackets = "()[]{}";
|
|
1653
|
+
const bracketMatchingConfig = state.Facet.define({
|
|
1654
|
+
combine(configs) {
|
|
1655
|
+
return state.combineConfig(configs, {
|
|
1656
|
+
afterCursor: true,
|
|
1657
|
+
brackets: DefaultBrackets,
|
|
1658
|
+
maxScanDistance: DefaultScanDist,
|
|
1659
|
+
renderMatch: defaultRenderMatch
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1662
|
+
});
|
|
1663
|
+
const matchingMark = view.Decoration.mark({ class: "cm-matchingBracket" }), nonmatchingMark = view.Decoration.mark({ class: "cm-nonmatchingBracket" });
|
|
1664
|
+
function defaultRenderMatch(match) {
|
|
1665
|
+
let decorations = [];
|
|
1666
|
+
let mark = match.matched ? matchingMark : nonmatchingMark;
|
|
1667
|
+
decorations.push(mark.range(match.start.from, match.start.to));
|
|
1668
|
+
if (match.end)
|
|
1669
|
+
decorations.push(mark.range(match.end.from, match.end.to));
|
|
1670
|
+
return decorations;
|
|
1671
|
+
}
|
|
1672
|
+
const bracketMatchingState = state.StateField.define({
|
|
1673
|
+
create() { return view.Decoration.none; },
|
|
1674
|
+
update(deco, tr) {
|
|
1675
|
+
if (!tr.docChanged && !tr.selection)
|
|
1676
|
+
return deco;
|
|
1677
|
+
let decorations = [];
|
|
1678
|
+
let config = tr.state.facet(bracketMatchingConfig);
|
|
1679
|
+
for (let range of tr.state.selection.ranges) {
|
|
1680
|
+
if (!range.empty)
|
|
1681
|
+
continue;
|
|
1682
|
+
let match = matchBrackets(tr.state, range.head, -1, config)
|
|
1683
|
+
|| (range.head > 0 && matchBrackets(tr.state, range.head - 1, 1, config))
|
|
1684
|
+
|| (config.afterCursor &&
|
|
1685
|
+
(matchBrackets(tr.state, range.head, 1, config) ||
|
|
1686
|
+
(range.head < tr.state.doc.length && matchBrackets(tr.state, range.head + 1, -1, config))));
|
|
1687
|
+
if (match)
|
|
1688
|
+
decorations = decorations.concat(config.renderMatch(match, tr.state));
|
|
1689
|
+
}
|
|
1690
|
+
return view.Decoration.set(decorations, true);
|
|
1691
|
+
},
|
|
1692
|
+
provide: f => view.EditorView.decorations.from(f)
|
|
1693
|
+
});
|
|
1694
|
+
const bracketMatchingUnique = [
|
|
1695
|
+
bracketMatchingState,
|
|
1696
|
+
baseTheme
|
|
1697
|
+
];
|
|
1698
|
+
/**
|
|
1699
|
+
Create an extension that enables bracket matching. Whenever the
|
|
1700
|
+
cursor is next to a bracket, that bracket and the one it matches
|
|
1701
|
+
are highlighted. Or, when no matching bracket is found, another
|
|
1702
|
+
highlighting style is used to indicate this.
|
|
1703
|
+
*/
|
|
1704
|
+
function bracketMatching(config = {}) {
|
|
1705
|
+
return [bracketMatchingConfig.of(config), bracketMatchingUnique];
|
|
1706
|
+
}
|
|
1707
|
+
function matchingNodes(node, dir, brackets) {
|
|
1708
|
+
let byProp = node.prop(dir < 0 ? common.NodeProp.openedBy : common.NodeProp.closedBy);
|
|
1709
|
+
if (byProp)
|
|
1710
|
+
return byProp;
|
|
1711
|
+
if (node.name.length == 1) {
|
|
1712
|
+
let index = brackets.indexOf(node.name);
|
|
1713
|
+
if (index > -1 && index % 2 == (dir < 0 ? 1 : 0))
|
|
1714
|
+
return [brackets[index + dir]];
|
|
1715
|
+
}
|
|
1716
|
+
return null;
|
|
1717
|
+
}
|
|
1718
|
+
/**
|
|
1719
|
+
Find the matching bracket for the token at `pos`, scanning
|
|
1720
|
+
direction `dir`. Only the `brackets` and `maxScanDistance`
|
|
1721
|
+
properties are used from `config`, if given. Returns null if no
|
|
1722
|
+
bracket was found at `pos`, or a match result otherwise.
|
|
1723
|
+
*/
|
|
1724
|
+
function matchBrackets(state, pos, dir, config = {}) {
|
|
1725
|
+
let maxScanDistance = config.maxScanDistance || DefaultScanDist, brackets = config.brackets || DefaultBrackets;
|
|
1726
|
+
let tree = syntaxTree(state), node = tree.resolveInner(pos, dir);
|
|
1727
|
+
for (let cur = node; cur; cur = cur.parent) {
|
|
1728
|
+
let matches = matchingNodes(cur.type, dir, brackets);
|
|
1729
|
+
if (matches && cur.from < cur.to)
|
|
1730
|
+
return matchMarkedBrackets(state, pos, dir, cur, matches, brackets);
|
|
1731
|
+
}
|
|
1732
|
+
return matchPlainBrackets(state, pos, dir, tree, node.type, maxScanDistance, brackets);
|
|
1733
|
+
}
|
|
1734
|
+
function matchMarkedBrackets(_state, _pos, dir, token, matching, brackets) {
|
|
1735
|
+
let parent = token.parent, firstToken = { from: token.from, to: token.to };
|
|
1736
|
+
let depth = 0, cursor = parent === null || parent === void 0 ? void 0 : parent.cursor();
|
|
1737
|
+
if (cursor && (dir < 0 ? cursor.childBefore(token.from) : cursor.childAfter(token.to)))
|
|
1738
|
+
do {
|
|
1739
|
+
if (dir < 0 ? cursor.to <= token.from : cursor.from >= token.to) {
|
|
1740
|
+
if (depth == 0 && matching.indexOf(cursor.type.name) > -1 && cursor.from < cursor.to) {
|
|
1741
|
+
return { start: firstToken, end: { from: cursor.from, to: cursor.to }, matched: true };
|
|
1742
|
+
}
|
|
1743
|
+
else if (matchingNodes(cursor.type, dir, brackets)) {
|
|
1744
|
+
depth++;
|
|
1745
|
+
}
|
|
1746
|
+
else if (matchingNodes(cursor.type, -dir, brackets)) {
|
|
1747
|
+
depth--;
|
|
1748
|
+
if (depth == 0)
|
|
1749
|
+
return {
|
|
1750
|
+
start: firstToken,
|
|
1751
|
+
end: cursor.from == cursor.to ? undefined : { from: cursor.from, to: cursor.to },
|
|
1752
|
+
matched: false
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
} while (dir < 0 ? cursor.prevSibling() : cursor.nextSibling());
|
|
1757
|
+
return { start: firstToken, matched: false };
|
|
1758
|
+
}
|
|
1759
|
+
function matchPlainBrackets(state, pos, dir, tree, tokenType, maxScanDistance, brackets) {
|
|
1760
|
+
let startCh = dir < 0 ? state.sliceDoc(pos - 1, pos) : state.sliceDoc(pos, pos + 1);
|
|
1761
|
+
let bracket = brackets.indexOf(startCh);
|
|
1762
|
+
if (bracket < 0 || (bracket % 2 == 0) != (dir > 0))
|
|
1763
|
+
return null;
|
|
1764
|
+
let startToken = { from: dir < 0 ? pos - 1 : pos, to: dir > 0 ? pos + 1 : pos };
|
|
1765
|
+
let iter = state.doc.iterRange(pos, dir > 0 ? state.doc.length : 0), depth = 0;
|
|
1766
|
+
for (let distance = 0; !(iter.next()).done && distance <= maxScanDistance;) {
|
|
1767
|
+
let text = iter.value;
|
|
1768
|
+
if (dir < 0)
|
|
1769
|
+
distance += text.length;
|
|
1770
|
+
let basePos = pos + distance * dir;
|
|
1771
|
+
for (let pos = dir > 0 ? 0 : text.length - 1, end = dir > 0 ? text.length : -1; pos != end; pos += dir) {
|
|
1772
|
+
let found = brackets.indexOf(text[pos]);
|
|
1773
|
+
if (found < 0 || tree.resolve(basePos + pos, 1).type != tokenType)
|
|
1774
|
+
continue;
|
|
1775
|
+
if ((found % 2 == 0) == (dir > 0)) {
|
|
1776
|
+
depth++;
|
|
1777
|
+
}
|
|
1778
|
+
else if (depth == 1) { // Closing
|
|
1779
|
+
return { start: startToken, end: { from: basePos + pos, to: basePos + pos + 1 }, matched: (found >> 1) == (bracket >> 1) };
|
|
1780
|
+
}
|
|
1781
|
+
else {
|
|
1782
|
+
depth--;
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
if (dir > 0)
|
|
1786
|
+
distance += text.length;
|
|
1787
|
+
}
|
|
1788
|
+
return iter.done ? { start: startToken, matched: false } : null;
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
// Counts the column offset in a string, taking tabs into account.
|
|
1792
|
+
// Used mostly to find indentation.
|
|
1793
|
+
function countCol(string, end, tabSize, startIndex = 0, startValue = 0) {
|
|
1794
|
+
if (end == null) {
|
|
1795
|
+
end = string.search(/[^\s\u00a0]/);
|
|
1796
|
+
if (end == -1)
|
|
1797
|
+
end = string.length;
|
|
1798
|
+
}
|
|
1799
|
+
let n = startValue;
|
|
1800
|
+
for (let i = startIndex; i < end; i++) {
|
|
1801
|
+
if (string.charCodeAt(i) == 9)
|
|
1802
|
+
n += tabSize - (n % tabSize);
|
|
1803
|
+
else
|
|
1804
|
+
n++;
|
|
1805
|
+
}
|
|
1806
|
+
return n;
|
|
1807
|
+
}
|
|
1808
|
+
/**
|
|
1809
|
+
Encapsulates a single line of input. Given to stream syntax code,
|
|
1810
|
+
which uses it to tokenize the content.
|
|
1811
|
+
*/
|
|
1812
|
+
class StringStream {
|
|
1813
|
+
/**
|
|
1814
|
+
Create a stream.
|
|
1815
|
+
*/
|
|
1816
|
+
constructor(
|
|
1817
|
+
/**
|
|
1818
|
+
The line.
|
|
1819
|
+
*/
|
|
1820
|
+
string, tabSize,
|
|
1821
|
+
/**
|
|
1822
|
+
The current indent unit size.
|
|
1823
|
+
*/
|
|
1824
|
+
indentUnit) {
|
|
1825
|
+
this.string = string;
|
|
1826
|
+
this.tabSize = tabSize;
|
|
1827
|
+
this.indentUnit = indentUnit;
|
|
1828
|
+
/**
|
|
1829
|
+
The current position on the line.
|
|
1830
|
+
*/
|
|
1831
|
+
this.pos = 0;
|
|
1832
|
+
/**
|
|
1833
|
+
The start position of the current token.
|
|
1834
|
+
*/
|
|
1835
|
+
this.start = 0;
|
|
1836
|
+
this.lastColumnPos = 0;
|
|
1837
|
+
this.lastColumnValue = 0;
|
|
1838
|
+
}
|
|
1839
|
+
/**
|
|
1840
|
+
True if we are at the end of the line.
|
|
1841
|
+
*/
|
|
1842
|
+
eol() { return this.pos >= this.string.length; }
|
|
1843
|
+
/**
|
|
1844
|
+
True if we are at the start of the line.
|
|
1845
|
+
*/
|
|
1846
|
+
sol() { return this.pos == 0; }
|
|
1847
|
+
/**
|
|
1848
|
+
Get the next code unit after the current position, or undefined
|
|
1849
|
+
if we're at the end of the line.
|
|
1850
|
+
*/
|
|
1851
|
+
peek() { return this.string.charAt(this.pos) || undefined; }
|
|
1852
|
+
/**
|
|
1853
|
+
Read the next code unit and advance `this.pos`.
|
|
1854
|
+
*/
|
|
1855
|
+
next() {
|
|
1856
|
+
if (this.pos < this.string.length)
|
|
1857
|
+
return this.string.charAt(this.pos++);
|
|
1858
|
+
}
|
|
1859
|
+
/**
|
|
1860
|
+
Match the next character against the given string, regular
|
|
1861
|
+
expression, or predicate. Consume and return it if it matches.
|
|
1862
|
+
*/
|
|
1863
|
+
eat(match) {
|
|
1864
|
+
let ch = this.string.charAt(this.pos);
|
|
1865
|
+
let ok;
|
|
1866
|
+
if (typeof match == "string")
|
|
1867
|
+
ok = ch == match;
|
|
1868
|
+
else
|
|
1869
|
+
ok = ch && (match instanceof RegExp ? match.test(ch) : match(ch));
|
|
1870
|
+
if (ok) {
|
|
1871
|
+
++this.pos;
|
|
1872
|
+
return ch;
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
/**
|
|
1876
|
+
Continue matching characters that match the given string,
|
|
1877
|
+
regular expression, or predicate function. Return true if any
|
|
1878
|
+
characters were consumed.
|
|
1879
|
+
*/
|
|
1880
|
+
eatWhile(match) {
|
|
1881
|
+
let start = this.pos;
|
|
1882
|
+
while (this.eat(match)) { }
|
|
1883
|
+
return this.pos > start;
|
|
1884
|
+
}
|
|
1885
|
+
/**
|
|
1886
|
+
Consume whitespace ahead of `this.pos`. Return true if any was
|
|
1887
|
+
found.
|
|
1888
|
+
*/
|
|
1889
|
+
eatSpace() {
|
|
1890
|
+
let start = this.pos;
|
|
1891
|
+
while (/[\s\u00a0]/.test(this.string.charAt(this.pos)))
|
|
1892
|
+
++this.pos;
|
|
1893
|
+
return this.pos > start;
|
|
1894
|
+
}
|
|
1895
|
+
/**
|
|
1896
|
+
Move to the end of the line.
|
|
1897
|
+
*/
|
|
1898
|
+
skipToEnd() { this.pos = this.string.length; }
|
|
1899
|
+
/**
|
|
1900
|
+
Move to directly before the given character, if found on the
|
|
1901
|
+
current line.
|
|
1902
|
+
*/
|
|
1903
|
+
skipTo(ch) {
|
|
1904
|
+
let found = this.string.indexOf(ch, this.pos);
|
|
1905
|
+
if (found > -1) {
|
|
1906
|
+
this.pos = found;
|
|
1907
|
+
return true;
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
/**
|
|
1911
|
+
Move back `n` characters.
|
|
1912
|
+
*/
|
|
1913
|
+
backUp(n) { this.pos -= n; }
|
|
1914
|
+
/**
|
|
1915
|
+
Get the column position at `this.pos`.
|
|
1916
|
+
*/
|
|
1917
|
+
column() {
|
|
1918
|
+
if (this.lastColumnPos < this.start) {
|
|
1919
|
+
this.lastColumnValue = countCol(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue);
|
|
1920
|
+
this.lastColumnPos = this.start;
|
|
1921
|
+
}
|
|
1922
|
+
return this.lastColumnValue;
|
|
1923
|
+
}
|
|
1924
|
+
/**
|
|
1925
|
+
Get the indentation column of the current line.
|
|
1926
|
+
*/
|
|
1927
|
+
indentation() {
|
|
1928
|
+
return countCol(this.string, null, this.tabSize);
|
|
1929
|
+
}
|
|
1930
|
+
/**
|
|
1931
|
+
Match the input against the given string or regular expression
|
|
1932
|
+
(which should start with a `^`). Return true or the regexp match
|
|
1933
|
+
if it matches.
|
|
1934
|
+
|
|
1935
|
+
Unless `consume` is set to `false`, this will move `this.pos`
|
|
1936
|
+
past the matched text.
|
|
1937
|
+
|
|
1938
|
+
When matching a string `caseInsensitive` can be set to true to
|
|
1939
|
+
make the match case-insensitive.
|
|
1940
|
+
*/
|
|
1941
|
+
match(pattern, consume, caseInsensitive) {
|
|
1942
|
+
if (typeof pattern == "string") {
|
|
1943
|
+
let cased = (str) => caseInsensitive ? str.toLowerCase() : str;
|
|
1944
|
+
let substr = this.string.substr(this.pos, pattern.length);
|
|
1945
|
+
if (cased(substr) == cased(pattern)) {
|
|
1946
|
+
if (consume !== false)
|
|
1947
|
+
this.pos += pattern.length;
|
|
1948
|
+
return true;
|
|
1949
|
+
}
|
|
1950
|
+
else
|
|
1951
|
+
return null;
|
|
1952
|
+
}
|
|
1953
|
+
else {
|
|
1954
|
+
let match = this.string.slice(this.pos).match(pattern);
|
|
1955
|
+
if (match && match.index > 0)
|
|
1956
|
+
return null;
|
|
1957
|
+
if (match && consume !== false)
|
|
1958
|
+
this.pos += match[0].length;
|
|
1959
|
+
return match;
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
/**
|
|
1963
|
+
Get the current token.
|
|
1964
|
+
*/
|
|
1965
|
+
current() { return this.string.slice(this.start, this.pos); }
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
function fullParser(spec) {
|
|
1969
|
+
return {
|
|
1970
|
+
token: spec.token,
|
|
1971
|
+
blankLine: spec.blankLine || (() => { }),
|
|
1972
|
+
startState: spec.startState || (() => true),
|
|
1973
|
+
copyState: spec.copyState || defaultCopyState,
|
|
1974
|
+
indent: spec.indent || (() => null),
|
|
1975
|
+
languageData: spec.languageData || {},
|
|
1976
|
+
tokenTable: spec.tokenTable || noTokens
|
|
1977
|
+
};
|
|
1978
|
+
}
|
|
1979
|
+
function defaultCopyState(state) {
|
|
1980
|
+
if (typeof state != "object")
|
|
1981
|
+
return state;
|
|
1982
|
+
let newState = {};
|
|
1983
|
+
for (let prop in state) {
|
|
1984
|
+
let val = state[prop];
|
|
1985
|
+
newState[prop] = (val instanceof Array ? val.slice() : val);
|
|
1986
|
+
}
|
|
1987
|
+
return newState;
|
|
1988
|
+
}
|
|
1989
|
+
/**
|
|
1990
|
+
A [language](https://codemirror.net/6/docs/ref/#language.Language) class based on a CodeMirror
|
|
1991
|
+
5-style [streaming parser](https://codemirror.net/6/docs/ref/#language.StreamParser).
|
|
1992
|
+
*/
|
|
1993
|
+
class StreamLanguage extends Language {
|
|
1994
|
+
constructor(parser) {
|
|
1995
|
+
let data = defineLanguageFacet(parser.languageData);
|
|
1996
|
+
let p = fullParser(parser), self;
|
|
1997
|
+
let impl = new class extends common.Parser {
|
|
1998
|
+
createParse(input, fragments, ranges) {
|
|
1999
|
+
return new Parse(self, input, fragments, ranges);
|
|
2000
|
+
}
|
|
2001
|
+
};
|
|
2002
|
+
super(data, impl, [indentService.of((cx, pos) => this.getIndent(cx, pos))]);
|
|
2003
|
+
this.topNode = docID(data);
|
|
2004
|
+
self = this;
|
|
2005
|
+
this.streamParser = p;
|
|
2006
|
+
this.stateAfter = new common.NodeProp({ perNode: true });
|
|
2007
|
+
this.tokenTable = parser.tokenTable ? new TokenTable(p.tokenTable) : defaultTokenTable;
|
|
2008
|
+
}
|
|
2009
|
+
/**
|
|
2010
|
+
Define a stream language.
|
|
2011
|
+
*/
|
|
2012
|
+
static define(spec) { return new StreamLanguage(spec); }
|
|
2013
|
+
getIndent(cx, pos) {
|
|
2014
|
+
let tree = syntaxTree(cx.state), at = tree.resolve(pos);
|
|
2015
|
+
while (at && at.type != this.topNode)
|
|
2016
|
+
at = at.parent;
|
|
2017
|
+
if (!at)
|
|
2018
|
+
return null;
|
|
2019
|
+
let start = findState(this, tree, 0, at.from, pos), statePos, state;
|
|
2020
|
+
if (start) {
|
|
2021
|
+
state = start.state;
|
|
2022
|
+
statePos = start.pos + 1;
|
|
2023
|
+
}
|
|
2024
|
+
else {
|
|
2025
|
+
state = this.streamParser.startState(cx.unit);
|
|
2026
|
+
statePos = 0;
|
|
2027
|
+
}
|
|
2028
|
+
if (pos - statePos > 10000 /* MaxIndentScanDist */)
|
|
2029
|
+
return null;
|
|
2030
|
+
while (statePos < pos) {
|
|
2031
|
+
let line = cx.state.doc.lineAt(statePos), end = Math.min(pos, line.to);
|
|
2032
|
+
if (line.length) {
|
|
2033
|
+
let stream = new StringStream(line.text, cx.state.tabSize, cx.unit);
|
|
2034
|
+
while (stream.pos < end - line.from)
|
|
2035
|
+
readToken(this.streamParser.token, stream, state);
|
|
2036
|
+
}
|
|
2037
|
+
else {
|
|
2038
|
+
this.streamParser.blankLine(state, cx.unit);
|
|
2039
|
+
}
|
|
2040
|
+
if (end == pos)
|
|
2041
|
+
break;
|
|
2042
|
+
statePos = line.to + 1;
|
|
2043
|
+
}
|
|
2044
|
+
let { text } = cx.lineAt(pos);
|
|
2045
|
+
return this.streamParser.indent(state, /^\s*(.*)/.exec(text)[1], cx);
|
|
2046
|
+
}
|
|
2047
|
+
get allowsNesting() { return false; }
|
|
2048
|
+
}
|
|
2049
|
+
function findState(lang, tree, off, startPos, before) {
|
|
2050
|
+
let state = off >= startPos && off + tree.length <= before && tree.prop(lang.stateAfter);
|
|
2051
|
+
if (state)
|
|
2052
|
+
return { state: lang.streamParser.copyState(state), pos: off + tree.length };
|
|
2053
|
+
for (let i = tree.children.length - 1; i >= 0; i--) {
|
|
2054
|
+
let child = tree.children[i], pos = off + tree.positions[i];
|
|
2055
|
+
let found = child instanceof common.Tree && pos < before && findState(lang, child, pos, startPos, before);
|
|
2056
|
+
if (found)
|
|
2057
|
+
return found;
|
|
2058
|
+
}
|
|
2059
|
+
return null;
|
|
2060
|
+
}
|
|
2061
|
+
function cutTree(lang, tree, from, to, inside) {
|
|
2062
|
+
if (inside && from <= 0 && to >= tree.length)
|
|
2063
|
+
return tree;
|
|
2064
|
+
if (!inside && tree.type == lang.topNode)
|
|
2065
|
+
inside = true;
|
|
2066
|
+
for (let i = tree.children.length - 1; i >= 0; i--) {
|
|
2067
|
+
let pos = tree.positions[i], child = tree.children[i], inner;
|
|
2068
|
+
if (pos < to && child instanceof common.Tree) {
|
|
2069
|
+
if (!(inner = cutTree(lang, child, from - pos, to - pos, inside)))
|
|
2070
|
+
break;
|
|
2071
|
+
return !inside ? inner
|
|
2072
|
+
: new common.Tree(tree.type, tree.children.slice(0, i).concat(inner), tree.positions.slice(0, i + 1), pos + inner.length);
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
return null;
|
|
2076
|
+
}
|
|
2077
|
+
function findStartInFragments(lang, fragments, startPos, editorState) {
|
|
2078
|
+
for (let f of fragments) {
|
|
2079
|
+
let from = f.from + (f.openStart ? 25 : 0), to = f.to - (f.openEnd ? 25 : 0);
|
|
2080
|
+
let found = from <= startPos && to > startPos && findState(lang, f.tree, 0 - f.offset, startPos, to), tree;
|
|
2081
|
+
if (found && (tree = cutTree(lang, f.tree, startPos + f.offset, found.pos + f.offset, false)))
|
|
2082
|
+
return { state: found.state, tree };
|
|
2083
|
+
}
|
|
2084
|
+
return { state: lang.streamParser.startState(editorState ? getIndentUnit(editorState) : 4), tree: common.Tree.empty };
|
|
2085
|
+
}
|
|
2086
|
+
class Parse {
|
|
2087
|
+
constructor(lang, input, fragments, ranges) {
|
|
2088
|
+
this.lang = lang;
|
|
2089
|
+
this.input = input;
|
|
2090
|
+
this.fragments = fragments;
|
|
2091
|
+
this.ranges = ranges;
|
|
2092
|
+
this.stoppedAt = null;
|
|
2093
|
+
this.chunks = [];
|
|
2094
|
+
this.chunkPos = [];
|
|
2095
|
+
this.chunk = [];
|
|
2096
|
+
this.chunkReused = undefined;
|
|
2097
|
+
this.rangeIndex = 0;
|
|
2098
|
+
this.to = ranges[ranges.length - 1].to;
|
|
2099
|
+
let context = ParseContext.get(), from = ranges[0].from;
|
|
2100
|
+
let { state, tree } = findStartInFragments(lang, fragments, from, context === null || context === void 0 ? void 0 : context.state);
|
|
2101
|
+
this.state = state;
|
|
2102
|
+
this.parsedPos = this.chunkStart = from + tree.length;
|
|
2103
|
+
for (let i = 0; i < tree.children.length; i++) {
|
|
2104
|
+
this.chunks.push(tree.children[i]);
|
|
2105
|
+
this.chunkPos.push(tree.positions[i]);
|
|
2106
|
+
}
|
|
2107
|
+
if (context && this.parsedPos < context.viewport.from - 100000 /* MaxDistanceBeforeViewport */) {
|
|
2108
|
+
this.state = this.lang.streamParser.startState(getIndentUnit(context.state));
|
|
2109
|
+
context.skipUntilInView(this.parsedPos, context.viewport.from);
|
|
2110
|
+
this.parsedPos = context.viewport.from;
|
|
2111
|
+
}
|
|
2112
|
+
this.moveRangeIndex();
|
|
2113
|
+
}
|
|
2114
|
+
advance() {
|
|
2115
|
+
let context = ParseContext.get();
|
|
2116
|
+
let parseEnd = this.stoppedAt == null ? this.to : Math.min(this.to, this.stoppedAt);
|
|
2117
|
+
let end = Math.min(parseEnd, this.chunkStart + 2048 /* ChunkSize */);
|
|
2118
|
+
if (context)
|
|
2119
|
+
end = Math.min(end, context.viewport.to);
|
|
2120
|
+
while (this.parsedPos < end)
|
|
2121
|
+
this.parseLine(context);
|
|
2122
|
+
if (this.chunkStart < this.parsedPos)
|
|
2123
|
+
this.finishChunk();
|
|
2124
|
+
if (this.parsedPos >= parseEnd)
|
|
2125
|
+
return this.finish();
|
|
2126
|
+
if (context && this.parsedPos >= context.viewport.to) {
|
|
2127
|
+
context.skipUntilInView(this.parsedPos, parseEnd);
|
|
2128
|
+
return this.finish();
|
|
2129
|
+
}
|
|
2130
|
+
return null;
|
|
2131
|
+
}
|
|
2132
|
+
stopAt(pos) {
|
|
2133
|
+
this.stoppedAt = pos;
|
|
2134
|
+
}
|
|
2135
|
+
lineAfter(pos) {
|
|
2136
|
+
let chunk = this.input.chunk(pos);
|
|
2137
|
+
if (!this.input.lineChunks) {
|
|
2138
|
+
let eol = chunk.indexOf("\n");
|
|
2139
|
+
if (eol > -1)
|
|
2140
|
+
chunk = chunk.slice(0, eol);
|
|
2141
|
+
}
|
|
2142
|
+
else if (chunk == "\n") {
|
|
2143
|
+
chunk = "";
|
|
2144
|
+
}
|
|
2145
|
+
return pos + chunk.length <= this.to ? chunk : chunk.slice(0, this.to - pos);
|
|
2146
|
+
}
|
|
2147
|
+
nextLine() {
|
|
2148
|
+
let from = this.parsedPos, line = this.lineAfter(from), end = from + line.length;
|
|
2149
|
+
for (let index = this.rangeIndex;;) {
|
|
2150
|
+
let rangeEnd = this.ranges[index].to;
|
|
2151
|
+
if (rangeEnd >= end)
|
|
2152
|
+
break;
|
|
2153
|
+
line = line.slice(0, rangeEnd - (end - line.length));
|
|
2154
|
+
index++;
|
|
2155
|
+
if (index == this.ranges.length)
|
|
2156
|
+
break;
|
|
2157
|
+
let rangeStart = this.ranges[index].from;
|
|
2158
|
+
let after = this.lineAfter(rangeStart);
|
|
2159
|
+
line += after;
|
|
2160
|
+
end = rangeStart + after.length;
|
|
2161
|
+
}
|
|
2162
|
+
return { line, end };
|
|
2163
|
+
}
|
|
2164
|
+
skipGapsTo(pos, offset, side) {
|
|
2165
|
+
for (;;) {
|
|
2166
|
+
let end = this.ranges[this.rangeIndex].to, offPos = pos + offset;
|
|
2167
|
+
if (side > 0 ? end > offPos : end >= offPos)
|
|
2168
|
+
break;
|
|
2169
|
+
let start = this.ranges[++this.rangeIndex].from;
|
|
2170
|
+
offset += start - end;
|
|
2171
|
+
}
|
|
2172
|
+
return offset;
|
|
2173
|
+
}
|
|
2174
|
+
moveRangeIndex() {
|
|
2175
|
+
while (this.ranges[this.rangeIndex].to < this.parsedPos)
|
|
2176
|
+
this.rangeIndex++;
|
|
2177
|
+
}
|
|
2178
|
+
emitToken(id, from, to, size, offset) {
|
|
2179
|
+
if (this.ranges.length > 1) {
|
|
2180
|
+
offset = this.skipGapsTo(from, offset, 1);
|
|
2181
|
+
from += offset;
|
|
2182
|
+
let len0 = this.chunk.length;
|
|
2183
|
+
offset = this.skipGapsTo(to, offset, -1);
|
|
2184
|
+
to += offset;
|
|
2185
|
+
size += this.chunk.length - len0;
|
|
2186
|
+
}
|
|
2187
|
+
this.chunk.push(id, from, to, size);
|
|
2188
|
+
return offset;
|
|
2189
|
+
}
|
|
2190
|
+
parseLine(context) {
|
|
2191
|
+
let { line, end } = this.nextLine(), offset = 0, { streamParser } = this.lang;
|
|
2192
|
+
let stream = new StringStream(line, context ? context.state.tabSize : 4, context ? getIndentUnit(context.state) : 2);
|
|
2193
|
+
if (stream.eol()) {
|
|
2194
|
+
streamParser.blankLine(this.state, stream.indentUnit);
|
|
2195
|
+
}
|
|
2196
|
+
else {
|
|
2197
|
+
while (!stream.eol()) {
|
|
2198
|
+
let token = readToken(streamParser.token, stream, this.state);
|
|
2199
|
+
if (token)
|
|
2200
|
+
offset = this.emitToken(this.lang.tokenTable.resolve(token), this.parsedPos + stream.start, this.parsedPos + stream.pos, 4, offset);
|
|
2201
|
+
if (stream.start > 10000 /* MaxLineLength */)
|
|
2202
|
+
break;
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
this.parsedPos = end;
|
|
2206
|
+
this.moveRangeIndex();
|
|
2207
|
+
if (this.parsedPos < this.to)
|
|
2208
|
+
this.parsedPos++;
|
|
2209
|
+
}
|
|
2210
|
+
finishChunk() {
|
|
2211
|
+
let tree = common.Tree.build({
|
|
2212
|
+
buffer: this.chunk,
|
|
2213
|
+
start: this.chunkStart,
|
|
2214
|
+
length: this.parsedPos - this.chunkStart,
|
|
2215
|
+
nodeSet,
|
|
2216
|
+
topID: 0,
|
|
2217
|
+
maxBufferLength: 2048 /* ChunkSize */,
|
|
2218
|
+
reused: this.chunkReused
|
|
2219
|
+
});
|
|
2220
|
+
tree = new common.Tree(tree.type, tree.children, tree.positions, tree.length, [[this.lang.stateAfter, this.lang.streamParser.copyState(this.state)]]);
|
|
2221
|
+
this.chunks.push(tree);
|
|
2222
|
+
this.chunkPos.push(this.chunkStart - this.ranges[0].from);
|
|
2223
|
+
this.chunk = [];
|
|
2224
|
+
this.chunkReused = undefined;
|
|
2225
|
+
this.chunkStart = this.parsedPos;
|
|
2226
|
+
}
|
|
2227
|
+
finish() {
|
|
2228
|
+
return new common.Tree(this.lang.topNode, this.chunks, this.chunkPos, this.parsedPos - this.ranges[0].from).balance();
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
function readToken(token, stream, state) {
|
|
2232
|
+
stream.start = stream.pos;
|
|
2233
|
+
for (let i = 0; i < 10; i++) {
|
|
2234
|
+
let result = token(stream, state);
|
|
2235
|
+
if (stream.pos > stream.start)
|
|
2236
|
+
return result;
|
|
2237
|
+
}
|
|
2238
|
+
throw new Error("Stream parser failed to advance stream.");
|
|
2239
|
+
}
|
|
2240
|
+
const noTokens = Object.create(null);
|
|
2241
|
+
const typeArray = [common.NodeType.none];
|
|
2242
|
+
const nodeSet = new common.NodeSet(typeArray);
|
|
2243
|
+
const warned = [];
|
|
2244
|
+
const defaultTable = Object.create(null);
|
|
2245
|
+
for (let [legacyName, name] of [
|
|
2246
|
+
["variable", "variableName"],
|
|
2247
|
+
["variable-2", "variableName.special"],
|
|
2248
|
+
["string-2", "string.special"],
|
|
2249
|
+
["def", "variableName.definition"],
|
|
2250
|
+
["tag", "typeName"],
|
|
2251
|
+
["attribute", "propertyName"],
|
|
2252
|
+
["type", "typeName"],
|
|
2253
|
+
["builtin", "variableName.standard"],
|
|
2254
|
+
["qualifier", "modifier"],
|
|
2255
|
+
["error", "invalid"],
|
|
2256
|
+
["header", "heading"],
|
|
2257
|
+
["property", "propertyName"]
|
|
2258
|
+
])
|
|
2259
|
+
defaultTable[legacyName] = createTokenType(noTokens, name);
|
|
2260
|
+
class TokenTable {
|
|
2261
|
+
constructor(extra) {
|
|
2262
|
+
this.extra = extra;
|
|
2263
|
+
this.table = Object.assign(Object.create(null), defaultTable);
|
|
2264
|
+
}
|
|
2265
|
+
resolve(tag) {
|
|
2266
|
+
return !tag ? 0 : this.table[tag] || (this.table[tag] = createTokenType(this.extra, tag));
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
const defaultTokenTable = new TokenTable(noTokens);
|
|
2270
|
+
function warnForPart(part, msg) {
|
|
2271
|
+
if (warned.indexOf(part) > -1)
|
|
2272
|
+
return;
|
|
2273
|
+
warned.push(part);
|
|
2274
|
+
console.warn(msg);
|
|
2275
|
+
}
|
|
2276
|
+
function createTokenType(extra, tagStr) {
|
|
2277
|
+
let tag = null;
|
|
2278
|
+
for (let part of tagStr.split(".")) {
|
|
2279
|
+
let value = (extra[part] || highlight.tags[part]);
|
|
2280
|
+
if (!value) {
|
|
2281
|
+
warnForPart(part, `Unknown highlighting tag ${part}`);
|
|
2282
|
+
}
|
|
2283
|
+
else if (typeof value == "function") {
|
|
2284
|
+
if (!tag)
|
|
2285
|
+
warnForPart(part, `Modifier ${part} used at start of tag`);
|
|
2286
|
+
else
|
|
2287
|
+
tag = value(tag);
|
|
2288
|
+
}
|
|
2289
|
+
else {
|
|
2290
|
+
if (tag)
|
|
2291
|
+
warnForPart(part, `Tag ${part} used as modifier`);
|
|
2292
|
+
else
|
|
2293
|
+
tag = value;
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
if (!tag)
|
|
2297
|
+
return 0;
|
|
2298
|
+
let name = tagStr.replace(/ /g, "_"), type = common.NodeType.define({
|
|
2299
|
+
id: typeArray.length,
|
|
2300
|
+
name,
|
|
2301
|
+
props: [highlight.styleTags({ [name]: tag })]
|
|
2302
|
+
});
|
|
2303
|
+
typeArray.push(type);
|
|
2304
|
+
return type.id;
|
|
2305
|
+
}
|
|
2306
|
+
function docID(data) {
|
|
2307
|
+
let type = common.NodeType.define({ id: typeArray.length, name: "Document", props: [languageDataProp.add(() => data)] });
|
|
2308
|
+
typeArray.push(type);
|
|
2309
|
+
return type;
|
|
2310
|
+
}
|
|
1162
2311
|
|
|
2312
|
+
exports.HighlightStyle = HighlightStyle;
|
|
1163
2313
|
exports.IndentContext = IndentContext;
|
|
1164
2314
|
exports.LRLanguage = LRLanguage;
|
|
1165
2315
|
exports.Language = Language;
|
|
1166
2316
|
exports.LanguageDescription = LanguageDescription;
|
|
1167
2317
|
exports.LanguageSupport = LanguageSupport;
|
|
1168
2318
|
exports.ParseContext = ParseContext;
|
|
2319
|
+
exports.StreamLanguage = StreamLanguage;
|
|
2320
|
+
exports.StringStream = StringStream;
|
|
1169
2321
|
exports.TreeIndentContext = TreeIndentContext;
|
|
2322
|
+
exports.bracketMatching = bracketMatching;
|
|
2323
|
+
exports.codeFolding = codeFolding;
|
|
1170
2324
|
exports.continuedIndent = continuedIndent;
|
|
2325
|
+
exports.defaultHighlightStyle = defaultHighlightStyle;
|
|
1171
2326
|
exports.defineLanguageFacet = defineLanguageFacet;
|
|
1172
2327
|
exports.delimitedIndent = delimitedIndent;
|
|
1173
2328
|
exports.ensureSyntaxTree = ensureSyntaxTree;
|
|
1174
2329
|
exports.flatIndent = flatIndent;
|
|
2330
|
+
exports.foldAll = foldAll;
|
|
2331
|
+
exports.foldCode = foldCode;
|
|
2332
|
+
exports.foldEffect = foldEffect;
|
|
2333
|
+
exports.foldGutter = foldGutter;
|
|
1175
2334
|
exports.foldInside = foldInside;
|
|
2335
|
+
exports.foldKeymap = foldKeymap;
|
|
1176
2336
|
exports.foldNodeProp = foldNodeProp;
|
|
1177
2337
|
exports.foldService = foldService;
|
|
1178
2338
|
exports.foldable = foldable;
|
|
2339
|
+
exports.foldedRanges = foldedRanges;
|
|
2340
|
+
exports.forceParsing = forceParsing;
|
|
1179
2341
|
exports.getIndentUnit = getIndentUnit;
|
|
1180
2342
|
exports.getIndentation = getIndentation;
|
|
2343
|
+
exports.highlightingFor = highlightingFor;
|
|
1181
2344
|
exports.indentNodeProp = indentNodeProp;
|
|
1182
2345
|
exports.indentOnInput = indentOnInput;
|
|
1183
2346
|
exports.indentService = indentService;
|
|
@@ -1185,6 +2348,11 @@ exports.indentString = indentString;
|
|
|
1185
2348
|
exports.indentUnit = indentUnit;
|
|
1186
2349
|
exports.language = language;
|
|
1187
2350
|
exports.languageDataProp = languageDataProp;
|
|
2351
|
+
exports.matchBrackets = matchBrackets;
|
|
2352
|
+
exports.syntaxHighlighting = syntaxHighlighting;
|
|
1188
2353
|
exports.syntaxParserRunning = syntaxParserRunning;
|
|
1189
2354
|
exports.syntaxTree = syntaxTree;
|
|
1190
2355
|
exports.syntaxTreeAvailable = syntaxTreeAvailable;
|
|
2356
|
+
exports.unfoldAll = unfoldAll;
|
|
2357
|
+
exports.unfoldCode = unfoldCode;
|
|
2358
|
+
exports.unfoldEffect = unfoldEffect;
|