|
|
(self["webpackChunk_JUPYTERLAB_CORE_OUTPUT"] = self["webpackChunk_JUPYTERLAB_CORE_OUTPUT"] || []).push([[9799,7061],{
|
|
|
|
|
|
/***/ 56318:
|
|
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
|
|
|
|
"use strict";
|
|
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
|
/* harmony export */ GA: () => (/* binding */ closeBracketsKeymap),
|
|
|
/* harmony export */ Gn: () => (/* binding */ snippetCompletion),
|
|
|
/* harmony export */ Mb: () => (/* binding */ completeFromList),
|
|
|
/* harmony export */ TK: () => (/* binding */ CompletionContext),
|
|
|
/* harmony export */ eC: () => (/* binding */ ifNotIn),
|
|
|
/* harmony export */ vQ: () => (/* binding */ closeBrackets)
|
|
|
/* harmony export */ });
|
|
|
/* unused harmony exports acceptCompletion, autocompletion, clearSnippet, closeCompletion, completeAnyWord, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, hasNextSnippetField, hasPrevSnippetField, ifIn, insertBracket, insertCompletionText, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetKeymap, startCompletion */
|
|
|
/* harmony import */ var _codemirror_state__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(82990);
|
|
|
/* harmony import */ var _codemirror_state__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_codemirror_state__WEBPACK_IMPORTED_MODULE_0__);
|
|
|
/* harmony import */ var _codemirror_view__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(21486);
|
|
|
/* harmony import */ var _codemirror_view__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_codemirror_view__WEBPACK_IMPORTED_MODULE_1__);
|
|
|
/* harmony import */ var _codemirror_language__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(27914);
|
|
|
/* harmony import */ var _codemirror_language__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_codemirror_language__WEBPACK_IMPORTED_MODULE_2__);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
An instance of this is passed to completion source functions.
|
|
|
*/
|
|
|
class CompletionContext {
|
|
|
/**
|
|
|
Create a new completion context. (Mostly useful for testing
|
|
|
completion sources—in the editor, the extension will create
|
|
|
these for you.)
|
|
|
*/
|
|
|
constructor(
|
|
|
/**
|
|
|
The editor state that the completion happens in.
|
|
|
*/
|
|
|
state,
|
|
|
/**
|
|
|
The position at which the completion is happening.
|
|
|
*/
|
|
|
pos,
|
|
|
/**
|
|
|
Indicates whether completion was activated explicitly, or
|
|
|
implicitly by typing. The usual way to respond to this is to
|
|
|
only return completions when either there is part of a
|
|
|
completable entity before the cursor, or `explicit` is true.
|
|
|
*/
|
|
|
explicit,
|
|
|
/**
|
|
|
The editor view. May be undefined if the context was created
|
|
|
in a situation where there is no such view available, such as
|
|
|
in synchronous updates via
|
|
|
[`CompletionResult.update`](https://codemirror.net/6/docs/ref/#autocomplete.CompletionResult.update)
|
|
|
or when called by test code.
|
|
|
*/
|
|
|
view) {
|
|
|
this.state = state;
|
|
|
this.pos = pos;
|
|
|
this.explicit = explicit;
|
|
|
this.view = view;
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
this.abortListeners = [];
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
this.abortOnDocChange = false;
|
|
|
}
|
|
|
/**
|
|
|
Get the extent, content, and (if there is a token) type of the
|
|
|
token before `this.pos`.
|
|
|
*/
|
|
|
tokenBefore(types) {
|
|
|
let token = (0,_codemirror_language__WEBPACK_IMPORTED_MODULE_2__.syntaxTree)(this.state).resolveInner(this.pos, -1);
|
|
|
while (token && types.indexOf(token.name) < 0)
|
|
|
token = token.parent;
|
|
|
return token ? { from: token.from, to: this.pos,
|
|
|
text: this.state.sliceDoc(token.from, this.pos),
|
|
|
type: token.type } : null;
|
|
|
}
|
|
|
/**
|
|
|
Get the match of the given expression directly before the
|
|
|
cursor.
|
|
|
*/
|
|
|
matchBefore(expr) {
|
|
|
let line = this.state.doc.lineAt(this.pos);
|
|
|
let start = Math.max(line.from, this.pos - 250);
|
|
|
let str = line.text.slice(start - line.from, this.pos - line.from);
|
|
|
let found = str.search(ensureAnchor(expr, false));
|
|
|
return found < 0 ? null : { from: start + found, to: this.pos, text: str.slice(found) };
|
|
|
}
|
|
|
/**
|
|
|
Yields true when the query has been aborted. Can be useful in
|
|
|
asynchronous queries to avoid doing work that will be ignored.
|
|
|
*/
|
|
|
get aborted() { return this.abortListeners == null; }
|
|
|
/**
|
|
|
Allows you to register abort handlers, which will be called when
|
|
|
the query is
|
|
|
[aborted](https://codemirror.net/6/docs/ref/#autocomplete.CompletionContext.aborted).
|
|
|
|
|
|
By default, running queries will not be aborted for regular
|
|
|
typing or backspacing, on the assumption that they are likely to
|
|
|
return a result with a
|
|
|
[`validFor`](https://codemirror.net/6/docs/ref/#autocomplete.CompletionResult.validFor) field that
|
|
|
allows the result to be used after all. Passing `onDocChange:
|
|
|
true` will cause this query to be aborted for any document
|
|
|
change.
|
|
|
*/
|
|
|
addEventListener(type, listener, options) {
|
|
|
if (type == "abort" && this.abortListeners) {
|
|
|
this.abortListeners.push(listener);
|
|
|
if (options && options.onDocChange)
|
|
|
this.abortOnDocChange = true;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
function toSet(chars) {
|
|
|
let flat = Object.keys(chars).join("");
|
|
|
let words = /\w/.test(flat);
|
|
|
if (words)
|
|
|
flat = flat.replace(/\w/g, "");
|
|
|
return `[${words ? "\\w" : ""}${flat.replace(/[^\w\s]/g, "\\$&")}]`;
|
|
|
}
|
|
|
function prefixMatch(options) {
|
|
|
let first = Object.create(null), rest = Object.create(null);
|
|
|
for (let { label } of options) {
|
|
|
first[label[0]] = true;
|
|
|
for (let i = 1; i < label.length; i++)
|
|
|
rest[label[i]] = true;
|
|
|
}
|
|
|
let source = toSet(first) + toSet(rest) + "*$";
|
|
|
return [new RegExp("^" + source), new RegExp(source)];
|
|
|
}
|
|
|
/**
|
|
|
Given a a fixed array of options, return an autocompleter that
|
|
|
completes them.
|
|
|
*/
|
|
|
function completeFromList(list) {
|
|
|
let options = list.map(o => typeof o == "string" ? { label: o } : o);
|
|
|
let [validFor, match] = options.every(o => /^\w+$/.test(o.label)) ? [/\w*$/, /\w+$/] : prefixMatch(options);
|
|
|
return (context) => {
|
|
|
let token = context.matchBefore(match);
|
|
|
return token || context.explicit ? { from: token ? token.from : context.pos, options, validFor } : null;
|
|
|
};
|
|
|
}
|
|
|
/**
|
|
|
Wrap the given completion source so that it will only fire when the
|
|
|
cursor is in a syntax node with one of the given names.
|
|
|
*/
|
|
|
function ifIn(nodes, source) {
|
|
|
return (context) => {
|
|
|
for (let pos = syntaxTree(context.state).resolveInner(context.pos, -1); pos; pos = pos.parent) {
|
|
|
if (nodes.indexOf(pos.name) > -1)
|
|
|
return source(context);
|
|
|
if (pos.type.isTop)
|
|
|
break;
|
|
|
}
|
|
|
return null;
|
|
|
};
|
|
|
}
|
|
|
/**
|
|
|
Wrap the given completion source so that it will not fire when the
|
|
|
cursor is in a syntax node with one of the given names.
|
|
|
*/
|
|
|
function ifNotIn(nodes, source) {
|
|
|
return (context) => {
|
|
|
for (let pos = (0,_codemirror_language__WEBPACK_IMPORTED_MODULE_2__.syntaxTree)(context.state).resolveInner(context.pos, -1); pos; pos = pos.parent) {
|
|
|
if (nodes.indexOf(pos.name) > -1)
|
|
|
return null;
|
|
|
if (pos.type.isTop)
|
|
|
break;
|
|
|
}
|
|
|
return source(context);
|
|
|
};
|
|
|
}
|
|
|
class Option {
|
|
|
constructor(completion, source, match, score) {
|
|
|
this.completion = completion;
|
|
|
this.source = source;
|
|
|
this.match = match;
|
|
|
this.score = score;
|
|
|
}
|
|
|
}
|
|
|
function cur(state) { return state.selection.main.from; }
|
|
|
// Make sure the given regexp has a $ at its end and, if `start` is
|
|
|
// true, a ^ at its start.
|
|
|
function ensureAnchor(expr, start) {
|
|
|
var _a;
|
|
|
let { source } = expr;
|
|
|
let addStart = start && source[0] != "^", addEnd = source[source.length - 1] != "$";
|
|
|
if (!addStart && !addEnd)
|
|
|
return expr;
|
|
|
return new RegExp(`${addStart ? "^" : ""}(?:${source})${addEnd ? "$" : ""}`, (_a = expr.flags) !== null && _a !== void 0 ? _a : (expr.ignoreCase ? "i" : ""));
|
|
|
}
|
|
|
/**
|
|
|
This annotation is added to transactions that are produced by
|
|
|
picking a completion.
|
|
|
*/
|
|
|
const pickedCompletion = /*@__PURE__*/_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.Annotation.define();
|
|
|
/**
|
|
|
Helper function that returns a transaction spec which inserts a
|
|
|
completion's text in the main selection range, and any other
|
|
|
selection range that has the same text in front of it.
|
|
|
*/
|
|
|
function insertCompletionText(state, text, from, to) {
|
|
|
let { main } = state.selection, fromOff = from - main.from, toOff = to - main.from;
|
|
|
return Object.assign(Object.assign({}, state.changeByRange(range => {
|
|
|
if (range != main && from != to &&
|
|
|
state.sliceDoc(range.from + fromOff, range.from + toOff) != state.sliceDoc(from, to))
|
|
|
return { range };
|
|
|
let lines = state.toText(text);
|
|
|
return {
|
|
|
changes: { from: range.from + fromOff, to: to == main.from ? range.to : range.from + toOff, insert: lines },
|
|
|
range: _codemirror_state__WEBPACK_IMPORTED_MODULE_0__.EditorSelection.cursor(range.from + fromOff + lines.length)
|
|
|
};
|
|
|
})), { scrollIntoView: true, userEvent: "input.complete" });
|
|
|
}
|
|
|
const SourceCache = /*@__PURE__*/new WeakMap();
|
|
|
function asSource(source) {
|
|
|
if (!Array.isArray(source))
|
|
|
return source;
|
|
|
let known = SourceCache.get(source);
|
|
|
if (!known)
|
|
|
SourceCache.set(source, known = completeFromList(source));
|
|
|
return known;
|
|
|
}
|
|
|
const startCompletionEffect = /*@__PURE__*/_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.StateEffect.define();
|
|
|
const closeCompletionEffect = /*@__PURE__*/_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.StateEffect.define();
|
|
|
|
|
|
// A pattern matcher for fuzzy completion matching. Create an instance
|
|
|
// once for a pattern, and then use that to match any number of
|
|
|
// completions.
|
|
|
class FuzzyMatcher {
|
|
|
constructor(pattern) {
|
|
|
this.pattern = pattern;
|
|
|
this.chars = [];
|
|
|
this.folded = [];
|
|
|
// Buffers reused by calls to `match` to track matched character
|
|
|
// positions.
|
|
|
this.any = [];
|
|
|
this.precise = [];
|
|
|
this.byWord = [];
|
|
|
this.score = 0;
|
|
|
this.matched = [];
|
|
|
for (let p = 0; p < pattern.length;) {
|
|
|
let char = (0,_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.codePointAt)(pattern, p), size = (0,_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.codePointSize)(char);
|
|
|
this.chars.push(char);
|
|
|
let part = pattern.slice(p, p + size), upper = part.toUpperCase();
|
|
|
this.folded.push((0,_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.codePointAt)(upper == part ? part.toLowerCase() : upper, 0));
|
|
|
p += size;
|
|
|
}
|
|
|
this.astral = pattern.length != this.chars.length;
|
|
|
}
|
|
|
ret(score, matched) {
|
|
|
this.score = score;
|
|
|
this.matched = matched;
|
|
|
return this;
|
|
|
}
|
|
|
// Matches a given word (completion) against the pattern (input).
|
|
|
// Will return a boolean indicating whether there was a match and,
|
|
|
// on success, set `this.score` to the score, `this.matched` to an
|
|
|
// array of `from, to` pairs indicating the matched parts of `word`.
|
|
|
//
|
|
|
// The score is a number that is more negative the worse the match
|
|
|
// is. See `Penalty` above.
|
|
|
match(word) {
|
|
|
if (this.pattern.length == 0)
|
|
|
return this.ret(-100 /* Penalty.NotFull */, []);
|
|
|
if (word.length < this.pattern.length)
|
|
|
return null;
|
|
|
let { chars, folded, any, precise, byWord } = this;
|
|
|
// For single-character queries, only match when they occur right
|
|
|
// at the start
|
|
|
if (chars.length == 1) {
|
|
|
let first = (0,_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.codePointAt)(word, 0), firstSize = (0,_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.codePointSize)(first);
|
|
|
let score = firstSize == word.length ? 0 : -100 /* Penalty.NotFull */;
|
|
|
if (first == chars[0]) ;
|
|
|
else if (first == folded[0])
|
|
|
score += -200 /* Penalty.CaseFold */;
|
|
|
else
|
|
|
return null;
|
|
|
return this.ret(score, [0, firstSize]);
|
|
|
}
|
|
|
let direct = word.indexOf(this.pattern);
|
|
|
if (direct == 0)
|
|
|
return this.ret(word.length == this.pattern.length ? 0 : -100 /* Penalty.NotFull */, [0, this.pattern.length]);
|
|
|
let len = chars.length, anyTo = 0;
|
|
|
if (direct < 0) {
|
|
|
for (let i = 0, e = Math.min(word.length, 200); i < e && anyTo < len;) {
|
|
|
let next = (0,_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.codePointAt)(word, i);
|
|
|
if (next == chars[anyTo] || next == folded[anyTo])
|
|
|
any[anyTo++] = i;
|
|
|
i += (0,_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.codePointSize)(next);
|
|
|
}
|
|
|
// No match, exit immediately
|
|
|
if (anyTo < len)
|
|
|
return null;
|
|
|
}
|
|
|
// This tracks the extent of the precise (non-folded, not
|
|
|
// necessarily adjacent) match
|
|
|
let preciseTo = 0;
|
|
|
// Tracks whether there is a match that hits only characters that
|
|
|
// appear to be starting words. `byWordFolded` is set to true when
|
|
|
// a case folded character is encountered in such a match
|
|
|
let byWordTo = 0, byWordFolded = false;
|
|
|
// If we've found a partial adjacent match, these track its state
|
|
|
let adjacentTo = 0, adjacentStart = -1, adjacentEnd = -1;
|
|
|
let hasLower = /[a-z]/.test(word), wordAdjacent = true;
|
|
|
// Go over the option's text, scanning for the various kinds of matches
|
|
|
for (let i = 0, e = Math.min(word.length, 200), prevType = 0 /* Tp.NonWord */; i < e && byWordTo < len;) {
|
|
|
let next = (0,_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.codePointAt)(word, i);
|
|
|
if (direct < 0) {
|
|
|
if (preciseTo < len && next == chars[preciseTo])
|
|
|
precise[preciseTo++] = i;
|
|
|
if (adjacentTo < len) {
|
|
|
if (next == chars[adjacentTo] || next == folded[adjacentTo]) {
|
|
|
if (adjacentTo == 0)
|
|
|
adjacentStart = i;
|
|
|
adjacentEnd = i + 1;
|
|
|
adjacentTo++;
|
|
|
}
|
|
|
else {
|
|
|
adjacentTo = 0;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
let ch, type = next < 0xff
|
|
|
? (next >= 48 && next <= 57 || next >= 97 && next <= 122 ? 2 /* Tp.Lower */ : next >= 65 && next <= 90 ? 1 /* Tp.Upper */ : 0 /* Tp.NonWord */)
|
|
|
: ((ch = (0,_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.fromCodePoint)(next)) != ch.toLowerCase() ? 1 /* Tp.Upper */ : ch != ch.toUpperCase() ? 2 /* Tp.Lower */ : 0 /* Tp.NonWord */);
|
|
|
if (!i || type == 1 /* Tp.Upper */ && hasLower || prevType == 0 /* Tp.NonWord */ && type != 0 /* Tp.NonWord */) {
|
|
|
if (chars[byWordTo] == next || (folded[byWordTo] == next && (byWordFolded = true)))
|
|
|
byWord[byWordTo++] = i;
|
|
|
else if (byWord.length)
|
|
|
wordAdjacent = false;
|
|
|
}
|
|
|
prevType = type;
|
|
|
i += (0,_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.codePointSize)(next);
|
|
|
}
|
|
|
if (byWordTo == len && byWord[0] == 0 && wordAdjacent)
|
|
|
return this.result(-100 /* Penalty.ByWord */ + (byWordFolded ? -200 /* Penalty.CaseFold */ : 0), byWord, word);
|
|
|
if (adjacentTo == len && adjacentStart == 0)
|
|
|
return this.ret(-200 /* Penalty.CaseFold */ - word.length + (adjacentEnd == word.length ? 0 : -100 /* Penalty.NotFull */), [0, adjacentEnd]);
|
|
|
if (direct > -1)
|
|
|
return this.ret(-700 /* Penalty.NotStart */ - word.length, [direct, direct + this.pattern.length]);
|
|
|
if (adjacentTo == len)
|
|
|
return this.ret(-200 /* Penalty.CaseFold */ + -700 /* Penalty.NotStart */ - word.length, [adjacentStart, adjacentEnd]);
|
|
|
if (byWordTo == len)
|
|
|
return this.result(-100 /* Penalty.ByWord */ + (byWordFolded ? -200 /* Penalty.CaseFold */ : 0) + -700 /* Penalty.NotStart */ +
|
|
|
(wordAdjacent ? 0 : -1100 /* Penalty.Gap */), byWord, word);
|
|
|
return chars.length == 2 ? null
|
|
|
: this.result((any[0] ? -700 /* Penalty.NotStart */ : 0) + -200 /* Penalty.CaseFold */ + -1100 /* Penalty.Gap */, any, word);
|
|
|
}
|
|
|
result(score, positions, word) {
|
|
|
let result = [], i = 0;
|
|
|
for (let pos of positions) {
|
|
|
let to = pos + (this.astral ? (0,_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.codePointSize)((0,_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.codePointAt)(word, pos)) : 1);
|
|
|
if (i && result[i - 1] == pos)
|
|
|
result[i - 1] = to;
|
|
|
else {
|
|
|
result[i++] = pos;
|
|
|
result[i++] = to;
|
|
|
}
|
|
|
}
|
|
|
return this.ret(score - word.length, result);
|
|
|
}
|
|
|
}
|
|
|
class StrictMatcher {
|
|
|
constructor(pattern) {
|
|
|
this.pattern = pattern;
|
|
|
this.matched = [];
|
|
|
this.score = 0;
|
|
|
this.folded = pattern.toLowerCase();
|
|
|
}
|
|
|
match(word) {
|
|
|
if (word.length < this.pattern.length)
|
|
|
return null;
|
|
|
let start = word.slice(0, this.pattern.length);
|
|
|
let match = start == this.pattern ? 0 : start.toLowerCase() == this.folded ? -200 /* Penalty.CaseFold */ : null;
|
|
|
if (match == null)
|
|
|
return null;
|
|
|
this.matched = [0, start.length];
|
|
|
this.score = match + (word.length == this.pattern.length ? 0 : -100 /* Penalty.NotFull */);
|
|
|
return this;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const completionConfig = /*@__PURE__*/_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.Facet.define({
|
|
|
combine(configs) {
|
|
|
return (0,_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.combineConfig)(configs, {
|
|
|
activateOnTyping: true,
|
|
|
activateOnCompletion: () => false,
|
|
|
activateOnTypingDelay: 100,
|
|
|
selectOnOpen: true,
|
|
|
override: null,
|
|
|
closeOnBlur: true,
|
|
|
maxRenderedOptions: 100,
|
|
|
defaultKeymap: true,
|
|
|
tooltipClass: () => "",
|
|
|
optionClass: () => "",
|
|
|
aboveCursor: false,
|
|
|
icons: true,
|
|
|
addToOptions: [],
|
|
|
positionInfo: defaultPositionInfo,
|
|
|
filterStrict: false,
|
|
|
compareCompletions: (a, b) => a.label.localeCompare(b.label),
|
|
|
interactionDelay: 75,
|
|
|
updateSyncTime: 100
|
|
|
}, {
|
|
|
defaultKeymap: (a, b) => a && b,
|
|
|
closeOnBlur: (a, b) => a && b,
|
|
|
icons: (a, b) => a && b,
|
|
|
tooltipClass: (a, b) => c => joinClass(a(c), b(c)),
|
|
|
optionClass: (a, b) => c => joinClass(a(c), b(c)),
|
|
|
addToOptions: (a, b) => a.concat(b),
|
|
|
filterStrict: (a, b) => a || b,
|
|
|
});
|
|
|
}
|
|
|
});
|
|
|
function joinClass(a, b) {
|
|
|
return a ? b ? a + " " + b : a : b;
|
|
|
}
|
|
|
function defaultPositionInfo(view, list, option, info, space, tooltip) {
|
|
|
let rtl = view.textDirection == _codemirror_view__WEBPACK_IMPORTED_MODULE_1__.Direction.RTL, left = rtl, narrow = false;
|
|
|
let side = "top", offset, maxWidth;
|
|
|
let spaceLeft = list.left - space.left, spaceRight = space.right - list.right;
|
|
|
let infoWidth = info.right - info.left, infoHeight = info.bottom - info.top;
|
|
|
if (left && spaceLeft < Math.min(infoWidth, spaceRight))
|
|
|
left = false;
|
|
|
else if (!left && spaceRight < Math.min(infoWidth, spaceLeft))
|
|
|
left = true;
|
|
|
if (infoWidth <= (left ? spaceLeft : spaceRight)) {
|
|
|
offset = Math.max(space.top, Math.min(option.top, space.bottom - infoHeight)) - list.top;
|
|
|
maxWidth = Math.min(400 /* Info.Width */, left ? spaceLeft : spaceRight);
|
|
|
}
|
|
|
else {
|
|
|
narrow = true;
|
|
|
maxWidth = Math.min(400 /* Info.Width */, (rtl ? list.right : space.right - list.left) - 30 /* Info.Margin */);
|
|
|
let spaceBelow = space.bottom - list.bottom;
|
|
|
if (spaceBelow >= infoHeight || spaceBelow > list.top) { // Below the completion
|
|
|
offset = option.bottom - list.top;
|
|
|
}
|
|
|
else { // Above it
|
|
|
side = "bottom";
|
|
|
offset = list.bottom - option.top;
|
|
|
}
|
|
|
}
|
|
|
let scaleY = (list.bottom - list.top) / tooltip.offsetHeight;
|
|
|
let scaleX = (list.right - list.left) / tooltip.offsetWidth;
|
|
|
return {
|
|
|
style: `${side}: ${offset / scaleY}px; max-width: ${maxWidth / scaleX}px`,
|
|
|
class: "cm-completionInfo-" + (narrow ? (rtl ? "left-narrow" : "right-narrow") : left ? "left" : "right")
|
|
|
};
|
|
|
}
|
|
|
|
|
|
function optionContent(config) {
|
|
|
let content = config.addToOptions.slice();
|
|
|
if (config.icons)
|
|
|
content.push({
|
|
|
render(completion) {
|
|
|
let icon = document.createElement("div");
|
|
|
icon.classList.add("cm-completionIcon");
|
|
|
if (completion.type)
|
|
|
icon.classList.add(...completion.type.split(/\s+/g).map(cls => "cm-completionIcon-" + cls));
|
|
|
icon.setAttribute("aria-hidden", "true");
|
|
|
return icon;
|
|
|
},
|
|
|
position: 20
|
|
|
});
|
|
|
content.push({
|
|
|
render(completion, _s, _v, match) {
|
|
|
let labelElt = document.createElement("span");
|
|
|
labelElt.className = "cm-completionLabel";
|
|
|
let label = completion.displayLabel || completion.label, off = 0;
|
|
|
for (let j = 0; j < match.length;) {
|
|
|
let from = match[j++], to = match[j++];
|
|
|
if (from > off)
|
|
|
labelElt.appendChild(document.createTextNode(label.slice(off, from)));
|
|
|
let span = labelElt.appendChild(document.createElement("span"));
|
|
|
span.appendChild(document.createTextNode(label.slice(from, to)));
|
|
|
span.className = "cm-completionMatchedText";
|
|
|
off = to;
|
|
|
}
|
|
|
if (off < label.length)
|
|
|
labelElt.appendChild(document.createTextNode(label.slice(off)));
|
|
|
return labelElt;
|
|
|
},
|
|
|
position: 50
|
|
|
}, {
|
|
|
render(completion) {
|
|
|
if (!completion.detail)
|
|
|
return null;
|
|
|
let detailElt = document.createElement("span");
|
|
|
detailElt.className = "cm-completionDetail";
|
|
|
detailElt.textContent = completion.detail;
|
|
|
return detailElt;
|
|
|
},
|
|
|
position: 80
|
|
|
});
|
|
|
return content.sort((a, b) => a.position - b.position).map(a => a.render);
|
|
|
}
|
|
|
function rangeAroundSelected(total, selected, max) {
|
|
|
if (total <= max)
|
|
|
return { from: 0, to: total };
|
|
|
if (selected < 0)
|
|
|
selected = 0;
|
|
|
if (selected <= (total >> 1)) {
|
|
|
let off = Math.floor(selected / max);
|
|
|
return { from: off * max, to: (off + 1) * max };
|
|
|
}
|
|
|
let off = Math.floor((total - selected) / max);
|
|
|
return { from: total - (off + 1) * max, to: total - off * max };
|
|
|
}
|
|
|
class CompletionTooltip {
|
|
|
constructor(view, stateField, applyCompletion) {
|
|
|
this.view = view;
|
|
|
this.stateField = stateField;
|
|
|
this.applyCompletion = applyCompletion;
|
|
|
this.info = null;
|
|
|
this.infoDestroy = null;
|
|
|
this.placeInfoReq = {
|
|
|
read: () => this.measureInfo(),
|
|
|
write: (pos) => this.placeInfo(pos),
|
|
|
key: this
|
|
|
};
|
|
|
this.space = null;
|
|
|
this.currentClass = "";
|
|
|
let cState = view.state.field(stateField);
|
|
|
let { options, selected } = cState.open;
|
|
|
let config = view.state.facet(completionConfig);
|
|
|
this.optionContent = optionContent(config);
|
|
|
this.optionClass = config.optionClass;
|
|
|
this.tooltipClass = config.tooltipClass;
|
|
|
this.range = rangeAroundSelected(options.length, selected, config.maxRenderedOptions);
|
|
|
this.dom = document.createElement("div");
|
|
|
this.dom.className = "cm-tooltip-autocomplete";
|
|
|
this.updateTooltipClass(view.state);
|
|
|
this.dom.addEventListener("mousedown", (e) => {
|
|
|
let { options } = view.state.field(stateField).open;
|
|
|
for (let dom = e.target, match; dom && dom != this.dom; dom = dom.parentNode) {
|
|
|
if (dom.nodeName == "LI" && (match = /-(\d+)$/.exec(dom.id)) && +match[1] < options.length) {
|
|
|
this.applyCompletion(view, options[+match[1]]);
|
|
|
e.preventDefault();
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
this.dom.addEventListener("focusout", (e) => {
|
|
|
let state = view.state.field(this.stateField, false);
|
|
|
if (state && state.tooltip && view.state.facet(completionConfig).closeOnBlur &&
|
|
|
e.relatedTarget != view.contentDOM)
|
|
|
view.dispatch({ effects: closeCompletionEffect.of(null) });
|
|
|
});
|
|
|
this.showOptions(options, cState.id);
|
|
|
}
|
|
|
mount() { this.updateSel(); }
|
|
|
showOptions(options, id) {
|
|
|
if (this.list)
|
|
|
this.list.remove();
|
|
|
this.list = this.dom.appendChild(this.createListBox(options, id, this.range));
|
|
|
this.list.addEventListener("scroll", () => {
|
|
|
if (this.info)
|
|
|
this.view.requestMeasure(this.placeInfoReq);
|
|
|
});
|
|
|
}
|
|
|
update(update) {
|
|
|
var _a;
|
|
|
let cState = update.state.field(this.stateField);
|
|
|
let prevState = update.startState.field(this.stateField);
|
|
|
this.updateTooltipClass(update.state);
|
|
|
if (cState != prevState) {
|
|
|
let { options, selected, disabled } = cState.open;
|
|
|
if (!prevState.open || prevState.open.options != options) {
|
|
|
this.range = rangeAroundSelected(options.length, selected, update.state.facet(completionConfig).maxRenderedOptions);
|
|
|
this.showOptions(options, cState.id);
|
|
|
}
|
|
|
this.updateSel();
|
|
|
if (disabled != ((_a = prevState.open) === null || _a === void 0 ? void 0 : _a.disabled))
|
|
|
this.dom.classList.toggle("cm-tooltip-autocomplete-disabled", !!disabled);
|
|
|
}
|
|
|
}
|
|
|
updateTooltipClass(state) {
|
|
|
let cls = this.tooltipClass(state);
|
|
|
if (cls != this.currentClass) {
|
|
|
for (let c of this.currentClass.split(" "))
|
|
|
if (c)
|
|
|
this.dom.classList.remove(c);
|
|
|
for (let c of cls.split(" "))
|
|
|
if (c)
|
|
|
this.dom.classList.add(c);
|
|
|
this.currentClass = cls;
|
|
|
}
|
|
|
}
|
|
|
positioned(space) {
|
|
|
this.space = space;
|
|
|
if (this.info)
|
|
|
this.view.requestMeasure(this.placeInfoReq);
|
|
|
}
|
|
|
updateSel() {
|
|
|
let cState = this.view.state.field(this.stateField), open = cState.open;
|
|
|
if (open.selected > -1 && open.selected < this.range.from || open.selected >= this.range.to) {
|
|
|
this.range = rangeAroundSelected(open.options.length, open.selected, this.view.state.facet(completionConfig).maxRenderedOptions);
|
|
|
this.showOptions(open.options, cState.id);
|
|
|
}
|
|
|
if (this.updateSelectedOption(open.selected)) {
|
|
|
this.destroyInfo();
|
|
|
let { completion } = open.options[open.selected];
|
|
|
let { info } = completion;
|
|
|
if (!info)
|
|
|
return;
|
|
|
let infoResult = typeof info === "string" ? document.createTextNode(info) : info(completion);
|
|
|
if (!infoResult)
|
|
|
return;
|
|
|
if ("then" in infoResult) {
|
|
|
infoResult.then(obj => {
|
|
|
if (obj && this.view.state.field(this.stateField, false) == cState)
|
|
|
this.addInfoPane(obj, completion);
|
|
|
}).catch(e => (0,_codemirror_view__WEBPACK_IMPORTED_MODULE_1__.logException)(this.view.state, e, "completion info"));
|
|
|
}
|
|
|
else {
|
|
|
this.addInfoPane(infoResult, completion);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
addInfoPane(content, completion) {
|
|
|
this.destroyInfo();
|
|
|
let wrap = this.info = document.createElement("div");
|
|
|
wrap.className = "cm-tooltip cm-completionInfo";
|
|
|
if (content.nodeType != null) {
|
|
|
wrap.appendChild(content);
|
|
|
this.infoDestroy = null;
|
|
|
}
|
|
|
else {
|
|
|
let { dom, destroy } = content;
|
|
|
wrap.appendChild(dom);
|
|
|
this.infoDestroy = destroy || null;
|
|
|
}
|
|
|
this.dom.appendChild(wrap);
|
|
|
this.view.requestMeasure(this.placeInfoReq);
|
|
|
}
|
|
|
updateSelectedOption(selected) {
|
|
|
let set = null;
|
|
|
for (let opt = this.list.firstChild, i = this.range.from; opt; opt = opt.nextSibling, i++) {
|
|
|
if (opt.nodeName != "LI" || !opt.id) {
|
|
|
i--; // A section header
|
|
|
}
|
|
|
else if (i == selected) {
|
|
|
if (!opt.hasAttribute("aria-selected")) {
|
|
|
opt.setAttribute("aria-selected", "true");
|
|
|
set = opt;
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
if (opt.hasAttribute("aria-selected"))
|
|
|
opt.removeAttribute("aria-selected");
|
|
|
}
|
|
|
}
|
|
|
if (set)
|
|
|
scrollIntoView(this.list, set);
|
|
|
return set;
|
|
|
}
|
|
|
measureInfo() {
|
|
|
let sel = this.dom.querySelector("[aria-selected]");
|
|
|
if (!sel || !this.info)
|
|
|
return null;
|
|
|
let listRect = this.dom.getBoundingClientRect();
|
|
|
let infoRect = this.info.getBoundingClientRect();
|
|
|
let selRect = sel.getBoundingClientRect();
|
|
|
let space = this.space;
|
|
|
if (!space) {
|
|
|
let docElt = this.dom.ownerDocument.documentElement;
|
|
|
space = { left: 0, top: 0, right: docElt.clientWidth, bottom: docElt.clientHeight };
|
|
|
}
|
|
|
if (selRect.top > Math.min(space.bottom, listRect.bottom) - 10 ||
|
|
|
selRect.bottom < Math.max(space.top, listRect.top) + 10)
|
|
|
return null;
|
|
|
return this.view.state.facet(completionConfig).positionInfo(this.view, listRect, selRect, infoRect, space, this.dom);
|
|
|
}
|
|
|
placeInfo(pos) {
|
|
|
if (this.info) {
|
|
|
if (pos) {
|
|
|
if (pos.style)
|
|
|
this.info.style.cssText = pos.style;
|
|
|
this.info.className = "cm-tooltip cm-completionInfo " + (pos.class || "");
|
|
|
}
|
|
|
else {
|
|
|
this.info.style.cssText = "top: -1e6px";
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
createListBox(options, id, range) {
|
|
|
const ul = document.createElement("ul");
|
|
|
ul.id = id;
|
|
|
ul.setAttribute("role", "listbox");
|
|
|
ul.setAttribute("aria-expanded", "true");
|
|
|
ul.setAttribute("aria-label", this.view.state.phrase("Completions"));
|
|
|
ul.addEventListener("mousedown", e => {
|
|
|
// Prevent focus change when clicking the scrollbar
|
|
|
if (e.target == ul)
|
|
|
e.preventDefault();
|
|
|
});
|
|
|
let curSection = null;
|
|
|
for (let i = range.from; i < range.to; i++) {
|
|
|
let { completion, match } = options[i], { section } = completion;
|
|
|
if (section) {
|
|
|
let name = typeof section == "string" ? section : section.name;
|
|
|
if (name != curSection && (i > range.from || range.from == 0)) {
|
|
|
curSection = name;
|
|
|
if (typeof section != "string" && section.header) {
|
|
|
ul.appendChild(section.header(section));
|
|
|
}
|
|
|
else {
|
|
|
let header = ul.appendChild(document.createElement("completion-section"));
|
|
|
header.textContent = name;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
const li = ul.appendChild(document.createElement("li"));
|
|
|
li.id = id + "-" + i;
|
|
|
li.setAttribute("role", "option");
|
|
|
let cls = this.optionClass(completion);
|
|
|
if (cls)
|
|
|
li.className = cls;
|
|
|
for (let source of this.optionContent) {
|
|
|
let node = source(completion, this.view.state, this.view, match);
|
|
|
if (node)
|
|
|
li.appendChild(node);
|
|
|
}
|
|
|
}
|
|
|
if (range.from)
|
|
|
ul.classList.add("cm-completionListIncompleteTop");
|
|
|
if (range.to < options.length)
|
|
|
ul.classList.add("cm-completionListIncompleteBottom");
|
|
|
return ul;
|
|
|
}
|
|
|
destroyInfo() {
|
|
|
if (this.info) {
|
|
|
if (this.infoDestroy)
|
|
|
this.infoDestroy();
|
|
|
this.info.remove();
|
|
|
this.info = null;
|
|
|
}
|
|
|
}
|
|
|
destroy() {
|
|
|
this.destroyInfo();
|
|
|
}
|
|
|
}
|
|
|
function completionTooltip(stateField, applyCompletion) {
|
|
|
return (view) => new CompletionTooltip(view, stateField, applyCompletion);
|
|
|
}
|
|
|
function scrollIntoView(container, element) {
|
|
|
let parent = container.getBoundingClientRect();
|
|
|
let self = element.getBoundingClientRect();
|
|
|
let scaleY = parent.height / container.offsetHeight;
|
|
|
if (self.top < parent.top)
|
|
|
container.scrollTop -= (parent.top - self.top) / scaleY;
|
|
|
else if (self.bottom > parent.bottom)
|
|
|
container.scrollTop += (self.bottom - parent.bottom) / scaleY;
|
|
|
}
|
|
|
|
|
|
// Used to pick a preferred option when two options with the same
|
|
|
// label occur in the result.
|
|
|
function score(option) {
|
|
|
return (option.boost || 0) * 100 + (option.apply ? 10 : 0) + (option.info ? 5 : 0) +
|
|
|
(option.type ? 1 : 0);
|
|
|
}
|
|
|
function sortOptions(active, state) {
|
|
|
let options = [];
|
|
|
let sections = null;
|
|
|
let addOption = (option) => {
|
|
|
options.push(option);
|
|
|
let { section } = option.completion;
|
|
|
if (section) {
|
|
|
if (!sections)
|
|
|
sections = [];
|
|
|
let name = typeof section == "string" ? section : section.name;
|
|
|
if (!sections.some(s => s.name == name))
|
|
|
sections.push(typeof section == "string" ? { name } : section);
|
|
|
}
|
|
|
};
|
|
|
let conf = state.facet(completionConfig);
|
|
|
for (let a of active)
|
|
|
if (a.hasResult()) {
|
|
|
let getMatch = a.result.getMatch;
|
|
|
if (a.result.filter === false) {
|
|
|
for (let option of a.result.options) {
|
|
|
addOption(new Option(option, a.source, getMatch ? getMatch(option) : [], 1e9 - options.length));
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
let pattern = state.sliceDoc(a.from, a.to), match;
|
|
|
let matcher = conf.filterStrict ? new StrictMatcher(pattern) : new FuzzyMatcher(pattern);
|
|
|
for (let option of a.result.options)
|
|
|
if (match = matcher.match(option.label)) {
|
|
|
let matched = !option.displayLabel ? match.matched : getMatch ? getMatch(option, match.matched) : [];
|
|
|
addOption(new Option(option, a.source, matched, match.score + (option.boost || 0)));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
if (sections) {
|
|
|
let sectionOrder = Object.create(null), pos = 0;
|
|
|
let cmp = (a, b) => { var _a, _b; return ((_a = a.rank) !== null && _a !== void 0 ? _a : 1e9) - ((_b = b.rank) !== null && _b !== void 0 ? _b : 1e9) || (a.name < b.name ? -1 : 1); };
|
|
|
for (let s of sections.sort(cmp)) {
|
|
|
pos -= 1e5;
|
|
|
sectionOrder[s.name] = pos;
|
|
|
}
|
|
|
for (let option of options) {
|
|
|
let { section } = option.completion;
|
|
|
if (section)
|
|
|
option.score += sectionOrder[typeof section == "string" ? section : section.name];
|
|
|
}
|
|
|
}
|
|
|
let result = [], prev = null;
|
|
|
let compare = conf.compareCompletions;
|
|
|
for (let opt of options.sort((a, b) => (b.score - a.score) || compare(a.completion, b.completion))) {
|
|
|
let cur = opt.completion;
|
|
|
if (!prev || prev.label != cur.label || prev.detail != cur.detail ||
|
|
|
(prev.type != null && cur.type != null && prev.type != cur.type) ||
|
|
|
prev.apply != cur.apply || prev.boost != cur.boost)
|
|
|
result.push(opt);
|
|
|
else if (score(opt.completion) > score(prev))
|
|
|
result[result.length - 1] = opt;
|
|
|
prev = opt.completion;
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
class CompletionDialog {
|
|
|
constructor(options, attrs, tooltip, timestamp, selected, disabled) {
|
|
|
this.options = options;
|
|
|
this.attrs = attrs;
|
|
|
this.tooltip = tooltip;
|
|
|
this.timestamp = timestamp;
|
|
|
this.selected = selected;
|
|
|
this.disabled = disabled;
|
|
|
}
|
|
|
setSelected(selected, id) {
|
|
|
return selected == this.selected || selected >= this.options.length ? this
|
|
|
: new CompletionDialog(this.options, makeAttrs(id, selected), this.tooltip, this.timestamp, selected, this.disabled);
|
|
|
}
|
|
|
static build(active, state, id, prev, conf, didSetActive) {
|
|
|
if (prev && !didSetActive && active.some(s => s.isPending))
|
|
|
return prev.setDisabled();
|
|
|
let options = sortOptions(active, state);
|
|
|
if (!options.length)
|
|
|
return prev && active.some(a => a.isPending) ? prev.setDisabled() : null;
|
|
|
let selected = state.facet(completionConfig).selectOnOpen ? 0 : -1;
|
|
|
if (prev && prev.selected != selected && prev.selected != -1) {
|
|
|
let selectedValue = prev.options[prev.selected].completion;
|
|
|
for (let i = 0; i < options.length; i++)
|
|
|
if (options[i].completion == selectedValue) {
|
|
|
selected = i;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
return new CompletionDialog(options, makeAttrs(id, selected), {
|
|
|
pos: active.reduce((a, b) => b.hasResult() ? Math.min(a, b.from) : a, 1e8),
|
|
|
create: createTooltip,
|
|
|
above: conf.aboveCursor,
|
|
|
}, prev ? prev.timestamp : Date.now(), selected, false);
|
|
|
}
|
|
|
map(changes) {
|
|
|
return new CompletionDialog(this.options, this.attrs, Object.assign(Object.assign({}, this.tooltip), { pos: changes.mapPos(this.tooltip.pos) }), this.timestamp, this.selected, this.disabled);
|
|
|
}
|
|
|
setDisabled() {
|
|
|
return new CompletionDialog(this.options, this.attrs, this.tooltip, this.timestamp, this.selected, true);
|
|
|
}
|
|
|
}
|
|
|
class CompletionState {
|
|
|
constructor(active, id, open) {
|
|
|
this.active = active;
|
|
|
this.id = id;
|
|
|
this.open = open;
|
|
|
}
|
|
|
static start() {
|
|
|
return new CompletionState(none, "cm-ac-" + Math.floor(Math.random() * 2e6).toString(36), null);
|
|
|
}
|
|
|
update(tr) {
|
|
|
let { state } = tr, conf = state.facet(completionConfig);
|
|
|
let sources = conf.override ||
|
|
|
state.languageDataAt("autocomplete", cur(state)).map(asSource);
|
|
|
let active = sources.map(source => {
|
|
|
let value = this.active.find(s => s.source == source) ||
|
|
|
new ActiveSource(source, this.active.some(a => a.state != 0 /* State.Inactive */) ? 1 /* State.Pending */ : 0 /* State.Inactive */);
|
|
|
return value.update(tr, conf);
|
|
|
});
|
|
|
if (active.length == this.active.length && active.every((a, i) => a == this.active[i]))
|
|
|
active = this.active;
|
|
|
let open = this.open, didSet = tr.effects.some(e => e.is(setActiveEffect));
|
|
|
if (open && tr.docChanged)
|
|
|
open = open.map(tr.changes);
|
|
|
if (tr.selection || active.some(a => a.hasResult() && tr.changes.touchesRange(a.from, a.to)) ||
|
|
|
!sameResults(active, this.active) || didSet)
|
|
|
open = CompletionDialog.build(active, state, this.id, open, conf, didSet);
|
|
|
else if (open && open.disabled && !active.some(a => a.isPending))
|
|
|
open = null;
|
|
|
if (!open && active.every(a => !a.isPending) && active.some(a => a.hasResult()))
|
|
|
active = active.map(a => a.hasResult() ? new ActiveSource(a.source, 0 /* State.Inactive */) : a);
|
|
|
for (let effect of tr.effects)
|
|
|
if (effect.is(setSelectedEffect))
|
|
|
open = open && open.setSelected(effect.value, this.id);
|
|
|
return active == this.active && open == this.open ? this : new CompletionState(active, this.id, open);
|
|
|
}
|
|
|
get tooltip() { return this.open ? this.open.tooltip : null; }
|
|
|
get attrs() { return this.open ? this.open.attrs : this.active.length ? baseAttrs : noAttrs; }
|
|
|
}
|
|
|
function sameResults(a, b) {
|
|
|
if (a == b)
|
|
|
return true;
|
|
|
for (let iA = 0, iB = 0;;) {
|
|
|
while (iA < a.length && !a[iA].hasResult())
|
|
|
iA++;
|
|
|
while (iB < b.length && !b[iB].hasResult())
|
|
|
iB++;
|
|
|
let endA = iA == a.length, endB = iB == b.length;
|
|
|
if (endA || endB)
|
|
|
return endA == endB;
|
|
|
if (a[iA++].result != b[iB++].result)
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
const baseAttrs = {
|
|
|
"aria-autocomplete": "list"
|
|
|
};
|
|
|
const noAttrs = {};
|
|
|
function makeAttrs(id, selected) {
|
|
|
let result = {
|
|
|
"aria-autocomplete": "list",
|
|
|
"aria-haspopup": "listbox",
|
|
|
"aria-controls": id
|
|
|
};
|
|
|
if (selected > -1)
|
|
|
result["aria-activedescendant"] = id + "-" + selected;
|
|
|
return result;
|
|
|
}
|
|
|
const none = [];
|
|
|
function getUpdateType(tr, conf) {
|
|
|
if (tr.isUserEvent("input.complete")) {
|
|
|
let completion = tr.annotation(pickedCompletion);
|
|
|
if (completion && conf.activateOnCompletion(completion))
|
|
|
return 4 /* UpdateType.Activate */ | 8 /* UpdateType.Reset */;
|
|
|
}
|
|
|
let typing = tr.isUserEvent("input.type");
|
|
|
return typing && conf.activateOnTyping ? 4 /* UpdateType.Activate */ | 1 /* UpdateType.Typing */
|
|
|
: typing ? 1 /* UpdateType.Typing */
|
|
|
: tr.isUserEvent("delete.backward") ? 2 /* UpdateType.Backspacing */
|
|
|
: tr.selection ? 8 /* UpdateType.Reset */
|
|
|
: tr.docChanged ? 16 /* UpdateType.ResetIfTouching */ : 0 /* UpdateType.None */;
|
|
|
}
|
|
|
class ActiveSource {
|
|
|
constructor(source, state, explicit = false) {
|
|
|
this.source = source;
|
|
|
this.state = state;
|
|
|
this.explicit = explicit;
|
|
|
}
|
|
|
hasResult() { return false; }
|
|
|
get isPending() { return this.state == 1 /* State.Pending */; }
|
|
|
update(tr, conf) {
|
|
|
let type = getUpdateType(tr, conf), value = this;
|
|
|
if ((type & 8 /* UpdateType.Reset */) || (type & 16 /* UpdateType.ResetIfTouching */) && this.touches(tr))
|
|
|
value = new ActiveSource(value.source, 0 /* State.Inactive */);
|
|
|
if ((type & 4 /* UpdateType.Activate */) && value.state == 0 /* State.Inactive */)
|
|
|
value = new ActiveSource(this.source, 1 /* State.Pending */);
|
|
|
value = value.updateFor(tr, type);
|
|
|
for (let effect of tr.effects) {
|
|
|
if (effect.is(startCompletionEffect))
|
|
|
value = new ActiveSource(value.source, 1 /* State.Pending */, effect.value);
|
|
|
else if (effect.is(closeCompletionEffect))
|
|
|
value = new ActiveSource(value.source, 0 /* State.Inactive */);
|
|
|
else if (effect.is(setActiveEffect))
|
|
|
for (let active of effect.value)
|
|
|
if (active.source == value.source)
|
|
|
value = active;
|
|
|
}
|
|
|
return value;
|
|
|
}
|
|
|
updateFor(tr, type) { return this.map(tr.changes); }
|
|
|
map(changes) { return this; }
|
|
|
touches(tr) {
|
|
|
return tr.changes.touchesRange(cur(tr.state));
|
|
|
}
|
|
|
}
|
|
|
class ActiveResult extends ActiveSource {
|
|
|
constructor(source, explicit, limit, result, from, to) {
|
|
|
super(source, 3 /* State.Result */, explicit);
|
|
|
this.limit = limit;
|
|
|
this.result = result;
|
|
|
this.from = from;
|
|
|
this.to = to;
|
|
|
}
|
|
|
hasResult() { return true; }
|
|
|
updateFor(tr, type) {
|
|
|
var _a;
|
|
|
if (!(type & 3 /* UpdateType.SimpleInteraction */))
|
|
|
return this.map(tr.changes);
|
|
|
let result = this.result;
|
|
|
if (result.map && !tr.changes.empty)
|
|
|
result = result.map(result, tr.changes);
|
|
|
let from = tr.changes.mapPos(this.from), to = tr.changes.mapPos(this.to, 1);
|
|
|
let pos = cur(tr.state);
|
|
|
if (pos > to || !result ||
|
|
|
(type & 2 /* UpdateType.Backspacing */) && (cur(tr.startState) == this.from || pos < this.limit))
|
|
|
return new ActiveSource(this.source, type & 4 /* UpdateType.Activate */ ? 1 /* State.Pending */ : 0 /* State.Inactive */);
|
|
|
let limit = tr.changes.mapPos(this.limit);
|
|
|
if (checkValid(result.validFor, tr.state, from, to))
|
|
|
return new ActiveResult(this.source, this.explicit, limit, result, from, to);
|
|
|
if (result.update &&
|
|
|
(result = result.update(result, from, to, new CompletionContext(tr.state, pos, false))))
|
|
|
return new ActiveResult(this.source, this.explicit, limit, result, result.from, (_a = result.to) !== null && _a !== void 0 ? _a : cur(tr.state));
|
|
|
return new ActiveSource(this.source, 1 /* State.Pending */, this.explicit);
|
|
|
}
|
|
|
map(mapping) {
|
|
|
if (mapping.empty)
|
|
|
return this;
|
|
|
let result = this.result.map ? this.result.map(this.result, mapping) : this.result;
|
|
|
if (!result)
|
|
|
return new ActiveSource(this.source, 0 /* State.Inactive */);
|
|
|
return new ActiveResult(this.source, this.explicit, mapping.mapPos(this.limit), this.result, mapping.mapPos(this.from), mapping.mapPos(this.to, 1));
|
|
|
}
|
|
|
touches(tr) {
|
|
|
return tr.changes.touchesRange(this.from, this.to);
|
|
|
}
|
|
|
}
|
|
|
function checkValid(validFor, state, from, to) {
|
|
|
if (!validFor)
|
|
|
return false;
|
|
|
let text = state.sliceDoc(from, to);
|
|
|
return typeof validFor == "function" ? validFor(text, from, to, state) : ensureAnchor(validFor, true).test(text);
|
|
|
}
|
|
|
const setActiveEffect = /*@__PURE__*/_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.StateEffect.define({
|
|
|
map(sources, mapping) { return sources.map(s => s.map(mapping)); }
|
|
|
});
|
|
|
const setSelectedEffect = /*@__PURE__*/_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.StateEffect.define();
|
|
|
const completionState = /*@__PURE__*/_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.StateField.define({
|
|
|
create() { return CompletionState.start(); },
|
|
|
update(value, tr) { return value.update(tr); },
|
|
|
provide: f => [
|
|
|
_codemirror_view__WEBPACK_IMPORTED_MODULE_1__.showTooltip.from(f, val => val.tooltip),
|
|
|
_codemirror_view__WEBPACK_IMPORTED_MODULE_1__.EditorView.contentAttributes.from(f, state => state.attrs)
|
|
|
]
|
|
|
});
|
|
|
function applyCompletion(view, option) {
|
|
|
const apply = option.completion.apply || option.completion.label;
|
|
|
let result = view.state.field(completionState).active.find(a => a.source == option.source);
|
|
|
if (!(result instanceof ActiveResult))
|
|
|
return false;
|
|
|
if (typeof apply == "string")
|
|
|
view.dispatch(Object.assign(Object.assign({}, insertCompletionText(view.state, apply, result.from, result.to)), { annotations: pickedCompletion.of(option.completion) }));
|
|
|
else
|
|
|
apply(view, option.completion, result.from, result.to);
|
|
|
return true;
|
|
|
}
|
|
|
const createTooltip = /*@__PURE__*/completionTooltip(completionState, applyCompletion);
|
|
|
|
|
|
/**
|
|
|
Returns a command that moves the completion selection forward or
|
|
|
backward by the given amount.
|
|
|
*/
|
|
|
function moveCompletionSelection(forward, by = "option") {
|
|
|
return (view) => {
|
|
|
let cState = view.state.field(completionState, false);
|
|
|
if (!cState || !cState.open || cState.open.disabled ||
|
|
|
Date.now() - cState.open.timestamp < view.state.facet(completionConfig).interactionDelay)
|
|
|
return false;
|
|
|
let step = 1, tooltip;
|
|
|
if (by == "page" && (tooltip = (0,_codemirror_view__WEBPACK_IMPORTED_MODULE_1__.getTooltip)(view, cState.open.tooltip)))
|
|
|
step = Math.max(2, Math.floor(tooltip.dom.offsetHeight /
|
|
|
tooltip.dom.querySelector("li").offsetHeight) - 1);
|
|
|
let { length } = cState.open.options;
|
|
|
let selected = cState.open.selected > -1 ? cState.open.selected + step * (forward ? 1 : -1) : forward ? 0 : length - 1;
|
|
|
if (selected < 0)
|
|
|
selected = by == "page" ? 0 : length - 1;
|
|
|
else if (selected >= length)
|
|
|
selected = by == "page" ? length - 1 : 0;
|
|
|
view.dispatch({ effects: setSelectedEffect.of(selected) });
|
|
|
return true;
|
|
|
};
|
|
|
}
|
|
|
/**
|
|
|
Accept the current completion.
|
|
|
*/
|
|
|
const acceptCompletion = (view) => {
|
|
|
let cState = view.state.field(completionState, false);
|
|
|
if (view.state.readOnly || !cState || !cState.open || cState.open.selected < 0 || cState.open.disabled ||
|
|
|
Date.now() - cState.open.timestamp < view.state.facet(completionConfig).interactionDelay)
|
|
|
return false;
|
|
|
return applyCompletion(view, cState.open.options[cState.open.selected]);
|
|
|
};
|
|
|
/**
|
|
|
Explicitly start autocompletion.
|
|
|
*/
|
|
|
const startCompletion = (view) => {
|
|
|
let cState = view.state.field(completionState, false);
|
|
|
if (!cState)
|
|
|
return false;
|
|
|
view.dispatch({ effects: startCompletionEffect.of(true) });
|
|
|
return true;
|
|
|
};
|
|
|
/**
|
|
|
Close the currently active completion.
|
|
|
*/
|
|
|
const closeCompletion = (view) => {
|
|
|
let cState = view.state.field(completionState, false);
|
|
|
if (!cState || !cState.active.some(a => a.state != 0 /* State.Inactive */))
|
|
|
return false;
|
|
|
view.dispatch({ effects: closeCompletionEffect.of(null) });
|
|
|
return true;
|
|
|
};
|
|
|
class RunningQuery {
|
|
|
constructor(active, context) {
|
|
|
this.active = active;
|
|
|
this.context = context;
|
|
|
this.time = Date.now();
|
|
|
this.updates = [];
|
|
|
// Note that 'undefined' means 'not done yet', whereas 'null' means
|
|
|
// 'query returned null'.
|
|
|
this.done = undefined;
|
|
|
}
|
|
|
}
|
|
|
const MaxUpdateCount = 50, MinAbortTime = 1000;
|
|
|
const completionPlugin = /*@__PURE__*/_codemirror_view__WEBPACK_IMPORTED_MODULE_1__.ViewPlugin.fromClass(class {
|
|
|
constructor(view) {
|
|
|
this.view = view;
|
|
|
this.debounceUpdate = -1;
|
|
|
this.running = [];
|
|
|
this.debounceAccept = -1;
|
|
|
this.pendingStart = false;
|
|
|
this.composing = 0 /* CompositionState.None */;
|
|
|
for (let active of view.state.field(completionState).active)
|
|
|
if (active.isPending)
|
|
|
this.startQuery(active);
|
|
|
}
|
|
|
update(update) {
|
|
|
let cState = update.state.field(completionState);
|
|
|
let conf = update.state.facet(completionConfig);
|
|
|
if (!update.selectionSet && !update.docChanged && update.startState.field(completionState) == cState)
|
|
|
return;
|
|
|
let doesReset = update.transactions.some(tr => {
|
|
|
let type = getUpdateType(tr, conf);
|
|
|
return (type & 8 /* UpdateType.Reset */) || (tr.selection || tr.docChanged) && !(type & 3 /* UpdateType.SimpleInteraction */);
|
|
|
});
|
|
|
for (let i = 0; i < this.running.length; i++) {
|
|
|
let query = this.running[i];
|
|
|
if (doesReset ||
|
|
|
query.context.abortOnDocChange && update.docChanged ||
|
|
|
query.updates.length + update.transactions.length > MaxUpdateCount && Date.now() - query.time > MinAbortTime) {
|
|
|
for (let handler of query.context.abortListeners) {
|
|
|
try {
|
|
|
handler();
|
|
|
}
|
|
|
catch (e) {
|
|
|
(0,_codemirror_view__WEBPACK_IMPORTED_MODULE_1__.logException)(this.view.state, e);
|
|
|
}
|
|
|
}
|
|
|
query.context.abortListeners = null;
|
|
|
this.running.splice(i--, 1);
|
|
|
}
|
|
|
else {
|
|
|
query.updates.push(...update.transactions);
|
|
|
}
|
|
|
}
|
|
|
if (this.debounceUpdate > -1)
|
|
|
clearTimeout(this.debounceUpdate);
|
|
|
if (update.transactions.some(tr => tr.effects.some(e => e.is(startCompletionEffect))))
|
|
|
this.pendingStart = true;
|
|
|
let delay = this.pendingStart ? 50 : conf.activateOnTypingDelay;
|
|
|
this.debounceUpdate = cState.active.some(a => a.isPending && !this.running.some(q => q.active.source == a.source))
|
|
|
? setTimeout(() => this.startUpdate(), delay) : -1;
|
|
|
if (this.composing != 0 /* CompositionState.None */)
|
|
|
for (let tr of update.transactions) {
|
|
|
if (tr.isUserEvent("input.type"))
|
|
|
this.composing = 2 /* CompositionState.Changed */;
|
|
|
else if (this.composing == 2 /* CompositionState.Changed */ && tr.selection)
|
|
|
this.composing = 3 /* CompositionState.ChangedAndMoved */;
|
|
|
}
|
|
|
}
|
|
|
startUpdate() {
|
|
|
this.debounceUpdate = -1;
|
|
|
this.pendingStart = false;
|
|
|
let { state } = this.view, cState = state.field(completionState);
|
|
|
for (let active of cState.active) {
|
|
|
if (active.isPending && !this.running.some(r => r.active.source == active.source))
|
|
|
this.startQuery(active);
|
|
|
}
|
|
|
if (this.running.length && cState.open && cState.open.disabled)
|
|
|
this.debounceAccept = setTimeout(() => this.accept(), this.view.state.facet(completionConfig).updateSyncTime);
|
|
|
}
|
|
|
startQuery(active) {
|
|
|
let { state } = this.view, pos = cur(state);
|
|
|
let context = new CompletionContext(state, pos, active.explicit, this.view);
|
|
|
let pending = new RunningQuery(active, context);
|
|
|
this.running.push(pending);
|
|
|
Promise.resolve(active.source(context)).then(result => {
|
|
|
if (!pending.context.aborted) {
|
|
|
pending.done = result || null;
|
|
|
this.scheduleAccept();
|
|
|
}
|
|
|
}, err => {
|
|
|
this.view.dispatch({ effects: closeCompletionEffect.of(null) });
|
|
|
(0,_codemirror_view__WEBPACK_IMPORTED_MODULE_1__.logException)(this.view.state, err);
|
|
|
});
|
|
|
}
|
|
|
scheduleAccept() {
|
|
|
if (this.running.every(q => q.done !== undefined))
|
|
|
this.accept();
|
|
|
else if (this.debounceAccept < 0)
|
|
|
this.debounceAccept = setTimeout(() => this.accept(), this.view.state.facet(completionConfig).updateSyncTime);
|
|
|
}
|
|
|
// For each finished query in this.running, try to create a result
|
|
|
// or, if appropriate, restart the query.
|
|
|
accept() {
|
|
|
var _a;
|
|
|
if (this.debounceAccept > -1)
|
|
|
clearTimeout(this.debounceAccept);
|
|
|
this.debounceAccept = -1;
|
|
|
let updated = [];
|
|
|
let conf = this.view.state.facet(completionConfig), cState = this.view.state.field(completionState);
|
|
|
for (let i = 0; i < this.running.length; i++) {
|
|
|
let query = this.running[i];
|
|
|
if (query.done === undefined)
|
|
|
continue;
|
|
|
this.running.splice(i--, 1);
|
|
|
if (query.done) {
|
|
|
let pos = cur(query.updates.length ? query.updates[0].startState : this.view.state);
|
|
|
let limit = Math.min(pos, query.done.from + (query.active.explicit ? 0 : 1));
|
|
|
let active = new ActiveResult(query.active.source, query.active.explicit, limit, query.done, query.done.from, (_a = query.done.to) !== null && _a !== void 0 ? _a : pos);
|
|
|
// Replay the transactions that happened since the start of
|
|
|
// the request and see if that preserves the result
|
|
|
for (let tr of query.updates)
|
|
|
active = active.update(tr, conf);
|
|
|
if (active.hasResult()) {
|
|
|
updated.push(active);
|
|
|
continue;
|
|
|
}
|
|
|
}
|
|
|
let current = cState.active.find(a => a.source == query.active.source);
|
|
|
if (current && current.isPending) {
|
|
|
if (query.done == null) {
|
|
|
// Explicitly failed. Should clear the pending status if it
|
|
|
// hasn't been re-set in the meantime.
|
|
|
let active = new ActiveSource(query.active.source, 0 /* State.Inactive */);
|
|
|
for (let tr of query.updates)
|
|
|
active = active.update(tr, conf);
|
|
|
if (!active.isPending)
|
|
|
updated.push(active);
|
|
|
}
|
|
|
else {
|
|
|
// Cleared by subsequent transactions. Restart.
|
|
|
this.startQuery(current);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
if (updated.length || cState.open && cState.open.disabled)
|
|
|
this.view.dispatch({ effects: setActiveEffect.of(updated) });
|
|
|
}
|
|
|
}, {
|
|
|
eventHandlers: {
|
|
|
blur(event) {
|
|
|
let state = this.view.state.field(completionState, false);
|
|
|
if (state && state.tooltip && this.view.state.facet(completionConfig).closeOnBlur) {
|
|
|
let dialog = state.open && (0,_codemirror_view__WEBPACK_IMPORTED_MODULE_1__.getTooltip)(this.view, state.open.tooltip);
|
|
|
if (!dialog || !dialog.dom.contains(event.relatedTarget))
|
|
|
setTimeout(() => this.view.dispatch({ effects: closeCompletionEffect.of(null) }), 10);
|
|
|
}
|
|
|
},
|
|
|
compositionstart() {
|
|
|
this.composing = 1 /* CompositionState.Started */;
|
|
|
},
|
|
|
compositionend() {
|
|
|
if (this.composing == 3 /* CompositionState.ChangedAndMoved */) {
|
|
|
// Safari fires compositionend events synchronously, possibly
|
|
|
// from inside an update, so dispatch asynchronously to avoid reentrancy
|
|
|
setTimeout(() => this.view.dispatch({ effects: startCompletionEffect.of(false) }), 20);
|
|
|
}
|
|
|
this.composing = 0 /* CompositionState.None */;
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
const windows = typeof navigator == "object" && /*@__PURE__*//Win/.test(navigator.platform);
|
|
|
const commitCharacters = /*@__PURE__*/_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.Prec.highest(/*@__PURE__*/_codemirror_view__WEBPACK_IMPORTED_MODULE_1__.EditorView.domEventHandlers({
|
|
|
keydown(event, view) {
|
|
|
let field = view.state.field(completionState, false);
|
|
|
if (!field || !field.open || field.open.disabled || field.open.selected < 0 ||
|
|
|
event.key.length > 1 || event.ctrlKey && !(windows && event.altKey) || event.metaKey)
|
|
|
return false;
|
|
|
let option = field.open.options[field.open.selected];
|
|
|
let result = field.active.find(a => a.source == option.source);
|
|
|
let commitChars = option.completion.commitCharacters || result.result.commitCharacters;
|
|
|
if (commitChars && commitChars.indexOf(event.key) > -1)
|
|
|
applyCompletion(view, option);
|
|
|
return false;
|
|
|
}
|
|
|
}));
|
|
|
|
|
|
const baseTheme = /*@__PURE__*/_codemirror_view__WEBPACK_IMPORTED_MODULE_1__.EditorView.baseTheme({
|
|
|
".cm-tooltip.cm-tooltip-autocomplete": {
|
|
|
"& > ul": {
|
|
|
fontFamily: "monospace",
|
|
|
whiteSpace: "nowrap",
|
|
|
overflow: "hidden auto",
|
|
|
maxWidth_fallback: "700px",
|
|
|
maxWidth: "min(700px, 95vw)",
|
|
|
minWidth: "250px",
|
|
|
maxHeight: "10em",
|
|
|
height: "100%",
|
|
|
listStyle: "none",
|
|
|
margin: 0,
|
|
|
padding: 0,
|
|
|
"& > li, & > completion-section": {
|
|
|
padding: "1px 3px",
|
|
|
lineHeight: 1.2
|
|
|
},
|
|
|
"& > li": {
|
|
|
overflowX: "hidden",
|
|
|
textOverflow: "ellipsis",
|
|
|
cursor: "pointer"
|
|
|
},
|
|
|
"& > completion-section": {
|
|
|
display: "list-item",
|
|
|
borderBottom: "1px solid silver",
|
|
|
paddingLeft: "0.5em",
|
|
|
opacity: 0.7
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
"&light .cm-tooltip-autocomplete ul li[aria-selected]": {
|
|
|
background: "#17c",
|
|
|
color: "white",
|
|
|
},
|
|
|
"&light .cm-tooltip-autocomplete-disabled ul li[aria-selected]": {
|
|
|
background: "#777",
|
|
|
},
|
|
|
"&dark .cm-tooltip-autocomplete ul li[aria-selected]": {
|
|
|
background: "#347",
|
|
|
color: "white",
|
|
|
},
|
|
|
"&dark .cm-tooltip-autocomplete-disabled ul li[aria-selected]": {
|
|
|
background: "#444",
|
|
|
},
|
|
|
".cm-completionListIncompleteTop:before, .cm-completionListIncompleteBottom:after": {
|
|
|
content: '"···"',
|
|
|
opacity: 0.5,
|
|
|
display: "block",
|
|
|
textAlign: "center"
|
|
|
},
|
|
|
".cm-tooltip.cm-completionInfo": {
|
|
|
position: "absolute",
|
|
|
padding: "3px 9px",
|
|
|
width: "max-content",
|
|
|
maxWidth: `${400 /* Info.Width */}px`,
|
|
|
boxSizing: "border-box",
|
|
|
whiteSpace: "pre-line"
|
|
|
},
|
|
|
".cm-completionInfo.cm-completionInfo-left": { right: "100%" },
|
|
|
".cm-completionInfo.cm-completionInfo-right": { left: "100%" },
|
|
|
".cm-completionInfo.cm-completionInfo-left-narrow": { right: `${30 /* Info.Margin */}px` },
|
|
|
".cm-completionInfo.cm-completionInfo-right-narrow": { left: `${30 /* Info.Margin */}px` },
|
|
|
"&light .cm-snippetField": { backgroundColor: "#00000022" },
|
|
|
"&dark .cm-snippetField": { backgroundColor: "#ffffff22" },
|
|
|
".cm-snippetFieldPosition": {
|
|
|
verticalAlign: "text-top",
|
|
|
width: 0,
|
|
|
height: "1.15em",
|
|
|
display: "inline-block",
|
|
|
margin: "0 -0.7px -.7em",
|
|
|
borderLeft: "1.4px dotted #888"
|
|
|
},
|
|
|
".cm-completionMatchedText": {
|
|
|
textDecoration: "underline"
|
|
|
},
|
|
|
".cm-completionDetail": {
|
|
|
marginLeft: "0.5em",
|
|
|
fontStyle: "italic"
|
|
|
},
|
|
|
".cm-completionIcon": {
|
|
|
fontSize: "90%",
|
|
|
width: ".8em",
|
|
|
display: "inline-block",
|
|
|
textAlign: "center",
|
|
|
paddingRight: ".6em",
|
|
|
opacity: "0.6",
|
|
|
boxSizing: "content-box"
|
|
|
},
|
|
|
".cm-completionIcon-function, .cm-completionIcon-method": {
|
|
|
"&:after": { content: "'ƒ'" }
|
|
|
},
|
|
|
".cm-completionIcon-class": {
|
|
|
"&:after": { content: "'○'" }
|
|
|
},
|
|
|
".cm-completionIcon-interface": {
|
|
|
"&:after": { content: "'◌'" }
|
|
|
},
|
|
|
".cm-completionIcon-variable": {
|
|
|
"&:after": { content: "'𝑥'" }
|
|
|
},
|
|
|
".cm-completionIcon-constant": {
|
|
|
"&:after": { content: "'𝐶'" }
|
|
|
},
|
|
|
".cm-completionIcon-type": {
|
|
|
"&:after": { content: "'𝑡'" }
|
|
|
},
|
|
|
".cm-completionIcon-enum": {
|
|
|
"&:after": { content: "'∪'" }
|
|
|
},
|
|
|
".cm-completionIcon-property": {
|
|
|
"&:after": { content: "'□'" }
|
|
|
},
|
|
|
".cm-completionIcon-keyword": {
|
|
|
"&:after": { content: "'🔑\uFE0E'" } // Disable emoji rendering
|
|
|
},
|
|
|
".cm-completionIcon-namespace": {
|
|
|
"&:after": { content: "'▢'" }
|
|
|
},
|
|
|
".cm-completionIcon-text": {
|
|
|
"&:after": { content: "'abc'", fontSize: "50%", verticalAlign: "middle" }
|
|
|
}
|
|
|
});
|
|
|
|
|
|
class FieldPos {
|
|
|
constructor(field, line, from, to) {
|
|
|
this.field = field;
|
|
|
this.line = line;
|
|
|
this.from = from;
|
|
|
this.to = to;
|
|
|
}
|
|
|
}
|
|
|
class FieldRange {
|
|
|
constructor(field, from, to) {
|
|
|
this.field = field;
|
|
|
this.from = from;
|
|
|
this.to = to;
|
|
|
}
|
|
|
map(changes) {
|
|
|
let from = changes.mapPos(this.from, -1, _codemirror_state__WEBPACK_IMPORTED_MODULE_0__.MapMode.TrackDel);
|
|
|
let to = changes.mapPos(this.to, 1, _codemirror_state__WEBPACK_IMPORTED_MODULE_0__.MapMode.TrackDel);
|
|
|
return from == null || to == null ? null : new FieldRange(this.field, from, to);
|
|
|
}
|
|
|
}
|
|
|
class Snippet {
|
|
|
constructor(lines, fieldPositions) {
|
|
|
this.lines = lines;
|
|
|
this.fieldPositions = fieldPositions;
|
|
|
}
|
|
|
instantiate(state, pos) {
|
|
|
let text = [], lineStart = [pos];
|
|
|
let lineObj = state.doc.lineAt(pos), baseIndent = /^\s*/.exec(lineObj.text)[0];
|
|
|
for (let line of this.lines) {
|
|
|
if (text.length) {
|
|
|
let indent = baseIndent, tabs = /^\t*/.exec(line)[0].length;
|
|
|
for (let i = 0; i < tabs; i++)
|
|
|
indent += state.facet(_codemirror_language__WEBPACK_IMPORTED_MODULE_2__.indentUnit);
|
|
|
lineStart.push(pos + indent.length - tabs);
|
|
|
line = indent + line.slice(tabs);
|
|
|
}
|
|
|
text.push(line);
|
|
|
pos += line.length + 1;
|
|
|
}
|
|
|
let ranges = this.fieldPositions.map(pos => new FieldRange(pos.field, lineStart[pos.line] + pos.from, lineStart[pos.line] + pos.to));
|
|
|
return { text, ranges };
|
|
|
}
|
|
|
static parse(template) {
|
|
|
let fields = [];
|
|
|
let lines = [], positions = [], m;
|
|
|
for (let line of template.split(/\r\n?|\n/)) {
|
|
|
while (m = /[#$]\{(?:(\d+)(?::([^}]*))?|((?:\\[{}]|[^}])*))\}/.exec(line)) {
|
|
|
let seq = m[1] ? +m[1] : null, rawName = m[2] || m[3] || "", found = -1;
|
|
|
let name = rawName.replace(/\\[{}]/g, m => m[1]);
|
|
|
for (let i = 0; i < fields.length; i++) {
|
|
|
if (seq != null ? fields[i].seq == seq : name ? fields[i].name == name : false)
|
|
|
found = i;
|
|
|
}
|
|
|
if (found < 0) {
|
|
|
let i = 0;
|
|
|
while (i < fields.length && (seq == null || (fields[i].seq != null && fields[i].seq < seq)))
|
|
|
i++;
|
|
|
fields.splice(i, 0, { seq, name });
|
|
|
found = i;
|
|
|
for (let pos of positions)
|
|
|
if (pos.field >= found)
|
|
|
pos.field++;
|
|
|
}
|
|
|
positions.push(new FieldPos(found, lines.length, m.index, m.index + name.length));
|
|
|
line = line.slice(0, m.index) + rawName + line.slice(m.index + m[0].length);
|
|
|
}
|
|
|
line = line.replace(/\\([{}])/g, (_, brace, index) => {
|
|
|
for (let pos of positions)
|
|
|
if (pos.line == lines.length && pos.from > index) {
|
|
|
pos.from--;
|
|
|
pos.to--;
|
|
|
}
|
|
|
return brace;
|
|
|
});
|
|
|
lines.push(line);
|
|
|
}
|
|
|
return new Snippet(lines, positions);
|
|
|
}
|
|
|
}
|
|
|
let fieldMarker = /*@__PURE__*/_codemirror_view__WEBPACK_IMPORTED_MODULE_1__.Decoration.widget({ widget: /*@__PURE__*/new class extends _codemirror_view__WEBPACK_IMPORTED_MODULE_1__.WidgetType {
|
|
|
toDOM() {
|
|
|
let span = document.createElement("span");
|
|
|
span.className = "cm-snippetFieldPosition";
|
|
|
return span;
|
|
|
}
|
|
|
ignoreEvent() { return false; }
|
|
|
} });
|
|
|
let fieldRange = /*@__PURE__*/_codemirror_view__WEBPACK_IMPORTED_MODULE_1__.Decoration.mark({ class: "cm-snippetField" });
|
|
|
class ActiveSnippet {
|
|
|
constructor(ranges, active) {
|
|
|
this.ranges = ranges;
|
|
|
this.active = active;
|
|
|
this.deco = _codemirror_view__WEBPACK_IMPORTED_MODULE_1__.Decoration.set(ranges.map(r => (r.from == r.to ? fieldMarker : fieldRange).range(r.from, r.to)));
|
|
|
}
|
|
|
map(changes) {
|
|
|
let ranges = [];
|
|
|
for (let r of this.ranges) {
|
|
|
let mapped = r.map(changes);
|
|
|
if (!mapped)
|
|
|
return null;
|
|
|
ranges.push(mapped);
|
|
|
}
|
|
|
return new ActiveSnippet(ranges, this.active);
|
|
|
}
|
|
|
selectionInsideField(sel) {
|
|
|
return sel.ranges.every(range => this.ranges.some(r => r.field == this.active && r.from <= range.from && r.to >= range.to));
|
|
|
}
|
|
|
}
|
|
|
const setActive = /*@__PURE__*/_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.StateEffect.define({
|
|
|
map(value, changes) { return value && value.map(changes); }
|
|
|
});
|
|
|
const moveToField = /*@__PURE__*/_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.StateEffect.define();
|
|
|
const snippetState = /*@__PURE__*/_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.StateField.define({
|
|
|
create() { return null; },
|
|
|
update(value, tr) {
|
|
|
for (let effect of tr.effects) {
|
|
|
if (effect.is(setActive))
|
|
|
return effect.value;
|
|
|
if (effect.is(moveToField) && value)
|
|
|
return new ActiveSnippet(value.ranges, effect.value);
|
|
|
}
|
|
|
if (value && tr.docChanged)
|
|
|
value = value.map(tr.changes);
|
|
|
if (value && tr.selection && !value.selectionInsideField(tr.selection))
|
|
|
value = null;
|
|
|
return value;
|
|
|
},
|
|
|
provide: f => _codemirror_view__WEBPACK_IMPORTED_MODULE_1__.EditorView.decorations.from(f, val => val ? val.deco : _codemirror_view__WEBPACK_IMPORTED_MODULE_1__.Decoration.none)
|
|
|
});
|
|
|
function fieldSelection(ranges, field) {
|
|
|
return _codemirror_state__WEBPACK_IMPORTED_MODULE_0__.EditorSelection.create(ranges.filter(r => r.field == field).map(r => _codemirror_state__WEBPACK_IMPORTED_MODULE_0__.EditorSelection.range(r.from, r.to)));
|
|
|
}
|
|
|
/**
|
|
|
Convert a snippet template to a function that can
|
|
|
[apply](https://codemirror.net/6/docs/ref/#autocomplete.Completion.apply) it. Snippets are written
|
|
|
using syntax like this:
|
|
|
|
|
|
"for (let ${index} = 0; ${index} < ${end}; ${index}++) {\n\t${}\n}"
|
|
|
|
|
|
Each `${}` placeholder (you may also use `#{}`) indicates a field
|
|
|
that the user can fill in. Its name, if any, will be the default
|
|
|
content for the field.
|
|
|
|
|
|
When the snippet is activated by calling the returned function,
|
|
|
the code is inserted at the given position. Newlines in the
|
|
|
template are indented by the indentation of the start line, plus
|
|
|
one [indent unit](https://codemirror.net/6/docs/ref/#language.indentUnit) per tab character after
|
|
|
the newline.
|
|
|
|
|
|
On activation, (all instances of) the first field are selected.
|
|
|
The user can move between fields with Tab and Shift-Tab as long as
|
|
|
the fields are active. Moving to the last field or moving the
|
|
|
cursor out of the current field deactivates the fields.
|
|
|
|
|
|
The order of fields defaults to textual order, but you can add
|
|
|
numbers to placeholders (`${1}` or `${1:defaultText}`) to provide
|
|
|
a custom order.
|
|
|
|
|
|
To include a literal `{` or `}` in your template, put a backslash
|
|
|
in front of it. This will be removed and the brace will not be
|
|
|
interpreted as indicating a placeholder.
|
|
|
*/
|
|
|
function snippet(template) {
|
|
|
let snippet = Snippet.parse(template);
|
|
|
return (editor, completion, from, to) => {
|
|
|
let { text, ranges } = snippet.instantiate(editor.state, from);
|
|
|
let { main } = editor.state.selection;
|
|
|
let spec = {
|
|
|
changes: { from, to: to == main.from ? main.to : to, insert: _codemirror_state__WEBPACK_IMPORTED_MODULE_0__.Text.of(text) },
|
|
|
scrollIntoView: true,
|
|
|
annotations: completion ? [pickedCompletion.of(completion), _codemirror_state__WEBPACK_IMPORTED_MODULE_0__.Transaction.userEvent.of("input.complete")] : undefined
|
|
|
};
|
|
|
if (ranges.length)
|
|
|
spec.selection = fieldSelection(ranges, 0);
|
|
|
if (ranges.some(r => r.field > 0)) {
|
|
|
let active = new ActiveSnippet(ranges, 0);
|
|
|
let effects = spec.effects = [setActive.of(active)];
|
|
|
if (editor.state.field(snippetState, false) === undefined)
|
|
|
effects.push(_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.StateEffect.appendConfig.of([snippetState, addSnippetKeymap, snippetPointerHandler, baseTheme]));
|
|
|
}
|
|
|
editor.dispatch(editor.state.update(spec));
|
|
|
};
|
|
|
}
|
|
|
function moveField(dir) {
|
|
|
return ({ state, dispatch }) => {
|
|
|
let active = state.field(snippetState, false);
|
|
|
if (!active || dir < 0 && active.active == 0)
|
|
|
return false;
|
|
|
let next = active.active + dir, last = dir > 0 && !active.ranges.some(r => r.field == next + dir);
|
|
|
dispatch(state.update({
|
|
|
selection: fieldSelection(active.ranges, next),
|
|
|
effects: setActive.of(last ? null : new ActiveSnippet(active.ranges, next)),
|
|
|
scrollIntoView: true
|
|
|
}));
|
|
|
return true;
|
|
|
};
|
|
|
}
|
|
|
/**
|
|
|
A command that clears the active snippet, if any.
|
|
|
*/
|
|
|
const clearSnippet = ({ state, dispatch }) => {
|
|
|
let active = state.field(snippetState, false);
|
|
|
if (!active)
|
|
|
return false;
|
|
|
dispatch(state.update({ effects: setActive.of(null) }));
|
|
|
return true;
|
|
|
};
|
|
|
/**
|
|
|
Move to the next snippet field, if available.
|
|
|
*/
|
|
|
const nextSnippetField = /*@__PURE__*/moveField(1);
|
|
|
/**
|
|
|
Move to the previous snippet field, if available.
|
|
|
*/
|
|
|
const prevSnippetField = /*@__PURE__*/moveField(-1);
|
|
|
/**
|
|
|
Check if there is an active snippet with a next field for
|
|
|
`nextSnippetField` to move to.
|
|
|
*/
|
|
|
function hasNextSnippetField(state) {
|
|
|
let active = state.field(snippetState, false);
|
|
|
return !!(active && active.ranges.some(r => r.field == active.active + 1));
|
|
|
}
|
|
|
/**
|
|
|
Returns true if there is an active snippet and a previous field
|
|
|
for `prevSnippetField` to move to.
|
|
|
*/
|
|
|
function hasPrevSnippetField(state) {
|
|
|
let active = state.field(snippetState, false);
|
|
|
return !!(active && active.active > 0);
|
|
|
}
|
|
|
const defaultSnippetKeymap = [
|
|
|
{ key: "Tab", run: nextSnippetField, shift: prevSnippetField },
|
|
|
{ key: "Escape", run: clearSnippet }
|
|
|
];
|
|
|
/**
|
|
|
A facet that can be used to configure the key bindings used by
|
|
|
snippets. The default binds Tab to
|
|
|
[`nextSnippetField`](https://codemirror.net/6/docs/ref/#autocomplete.nextSnippetField), Shift-Tab to
|
|
|
[`prevSnippetField`](https://codemirror.net/6/docs/ref/#autocomplete.prevSnippetField), and Escape
|
|
|
to [`clearSnippet`](https://codemirror.net/6/docs/ref/#autocomplete.clearSnippet).
|
|
|
*/
|
|
|
const snippetKeymap = /*@__PURE__*/_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.Facet.define({
|
|
|
combine(maps) { return maps.length ? maps[0] : defaultSnippetKeymap; }
|
|
|
});
|
|
|
const addSnippetKeymap = /*@__PURE__*/_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.Prec.highest(/*@__PURE__*/_codemirror_view__WEBPACK_IMPORTED_MODULE_1__.keymap.compute([snippetKeymap], state => state.facet(snippetKeymap)));
|
|
|
/**
|
|
|
Create a completion from a snippet. Returns an object with the
|
|
|
properties from `completion`, plus an `apply` function that
|
|
|
applies the snippet.
|
|
|
*/
|
|
|
function snippetCompletion(template, completion) {
|
|
|
return Object.assign(Object.assign({}, completion), { apply: snippet(template) });
|
|
|
}
|
|
|
const snippetPointerHandler = /*@__PURE__*/_codemirror_view__WEBPACK_IMPORTED_MODULE_1__.EditorView.domEventHandlers({
|
|
|
mousedown(event, view) {
|
|
|
let active = view.state.field(snippetState, false), pos;
|
|
|
if (!active || (pos = view.posAtCoords({ x: event.clientX, y: event.clientY })) == null)
|
|
|
return false;
|
|
|
let match = active.ranges.find(r => r.from <= pos && r.to >= pos);
|
|
|
if (!match || match.field == active.active)
|
|
|
return false;
|
|
|
view.dispatch({
|
|
|
selection: fieldSelection(active.ranges, match.field),
|
|
|
effects: setActive.of(active.ranges.some(r => r.field > match.field)
|
|
|
? new ActiveSnippet(active.ranges, match.field) : null),
|
|
|
scrollIntoView: true
|
|
|
});
|
|
|
return true;
|
|
|
}
|
|
|
});
|
|
|
|
|
|
function wordRE(wordChars) {
|
|
|
let escaped = wordChars.replace(/[\]\-\\]/g, "\\$&");
|
|
|
try {
|
|
|
return new RegExp(`[\\p{Alphabetic}\\p{Number}_${escaped}]+`, "ug");
|
|
|
}
|
|
|
catch (_a) {
|
|
|
return new RegExp(`[\w${escaped}]`, "g");
|
|
|
}
|
|
|
}
|
|
|
function mapRE(re, f) {
|
|
|
return new RegExp(f(re.source), re.unicode ? "u" : "");
|
|
|
}
|
|
|
const wordCaches = /*@__PURE__*/(/* unused pure expression or super */ null && (Object.create(null)));
|
|
|
function wordCache(wordChars) {
|
|
|
return wordCaches[wordChars] || (wordCaches[wordChars] = new WeakMap);
|
|
|
}
|
|
|
function storeWords(doc, wordRE, result, seen, ignoreAt) {
|
|
|
for (let lines = doc.iterLines(), pos = 0; !lines.next().done;) {
|
|
|
let { value } = lines, m;
|
|
|
wordRE.lastIndex = 0;
|
|
|
while (m = wordRE.exec(value)) {
|
|
|
if (!seen[m[0]] && pos + m.index != ignoreAt) {
|
|
|
result.push({ type: "text", label: m[0] });
|
|
|
seen[m[0]] = true;
|
|
|
if (result.length >= 2000 /* C.MaxList */)
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
pos += value.length + 1;
|
|
|
}
|
|
|
}
|
|
|
function collectWords(doc, cache, wordRE, to, ignoreAt) {
|
|
|
let big = doc.length >= 1000 /* C.MinCacheLen */;
|
|
|
let cached = big && cache.get(doc);
|
|
|
if (cached)
|
|
|
return cached;
|
|
|
let result = [], seen = Object.create(null);
|
|
|
if (doc.children) {
|
|
|
let pos = 0;
|
|
|
for (let ch of doc.children) {
|
|
|
if (ch.length >= 1000 /* C.MinCacheLen */) {
|
|
|
for (let c of collectWords(ch, cache, wordRE, to - pos, ignoreAt - pos)) {
|
|
|
if (!seen[c.label]) {
|
|
|
seen[c.label] = true;
|
|
|
result.push(c);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
storeWords(ch, wordRE, result, seen, ignoreAt - pos);
|
|
|
}
|
|
|
pos += ch.length + 1;
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
storeWords(doc, wordRE, result, seen, ignoreAt);
|
|
|
}
|
|
|
if (big && result.length < 2000 /* C.MaxList */)
|
|
|
cache.set(doc, result);
|
|
|
return result;
|
|
|
}
|
|
|
/**
|
|
|
A completion source that will scan the document for words (using a
|
|
|
[character categorizer](https://codemirror.net/6/docs/ref/#state.EditorState.charCategorizer)), and
|
|
|
return those as completions.
|
|
|
*/
|
|
|
const completeAnyWord = context => {
|
|
|
let wordChars = context.state.languageDataAt("wordChars", context.pos).join("");
|
|
|
let re = wordRE(wordChars);
|
|
|
let token = context.matchBefore(mapRE(re, s => s + "$"));
|
|
|
if (!token && !context.explicit)
|
|
|
return null;
|
|
|
let from = token ? token.from : context.pos;
|
|
|
let options = collectWords(context.state.doc, wordCache(wordChars), re, 50000 /* C.Range */, from);
|
|
|
return { from, options, validFor: mapRE(re, s => "^" + s) };
|
|
|
};
|
|
|
|
|
|
const defaults = {
|
|
|
brackets: ["(", "[", "{", "'", '"'],
|
|
|
before: ")]}:;>",
|
|
|
stringPrefixes: []
|
|
|
};
|
|
|
const closeBracketEffect = /*@__PURE__*/_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.StateEffect.define({
|
|
|
map(value, mapping) {
|
|
|
let mapped = mapping.mapPos(value, -1, _codemirror_state__WEBPACK_IMPORTED_MODULE_0__.MapMode.TrackAfter);
|
|
|
return mapped == null ? undefined : mapped;
|
|
|
}
|
|
|
});
|
|
|
const closedBracket = /*@__PURE__*/new class extends _codemirror_state__WEBPACK_IMPORTED_MODULE_0__.RangeValue {
|
|
|
};
|
|
|
closedBracket.startSide = 1;
|
|
|
closedBracket.endSide = -1;
|
|
|
const bracketState = /*@__PURE__*/_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.StateField.define({
|
|
|
create() { return _codemirror_state__WEBPACK_IMPORTED_MODULE_0__.RangeSet.empty; },
|
|
|
update(value, tr) {
|
|
|
value = value.map(tr.changes);
|
|
|
if (tr.selection) {
|
|
|
let line = tr.state.doc.lineAt(tr.selection.main.head);
|
|
|
value = value.update({ filter: from => from >= line.from && from <= line.to });
|
|
|
}
|
|
|
for (let effect of tr.effects)
|
|
|
if (effect.is(closeBracketEffect))
|
|
|
value = value.update({ add: [closedBracket.range(effect.value, effect.value + 1)] });
|
|
|
return value;
|
|
|
}
|
|
|
});
|
|
|
/**
|
|
|
Extension to enable bracket-closing behavior. When a closeable
|
|
|
bracket is typed, its closing bracket is immediately inserted
|
|
|
after the cursor. When closing a bracket directly in front of a
|
|
|
closing bracket inserted by the extension, the cursor moves over
|
|
|
that bracket.
|
|
|
*/
|
|
|
function closeBrackets() {
|
|
|
return [inputHandler, bracketState];
|
|
|
}
|
|
|
const definedClosing = "()[]{}<>«»»«[]{}";
|
|
|
function closing(ch) {
|
|
|
for (let i = 0; i < definedClosing.length; i += 2)
|
|
|
if (definedClosing.charCodeAt(i) == ch)
|
|
|
return definedClosing.charAt(i + 1);
|
|
|
return (0,_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.fromCodePoint)(ch < 128 ? ch : ch + 1);
|
|
|
}
|
|
|
function config(state, pos) {
|
|
|
return state.languageDataAt("closeBrackets", pos)[0] || defaults;
|
|
|
}
|
|
|
const android = typeof navigator == "object" && /*@__PURE__*//Android\b/.test(navigator.userAgent);
|
|
|
const inputHandler = /*@__PURE__*/_codemirror_view__WEBPACK_IMPORTED_MODULE_1__.EditorView.inputHandler.of((view, from, to, insert) => {
|
|
|
if ((android ? view.composing : view.compositionStarted) || view.state.readOnly)
|
|
|
return false;
|
|
|
let sel = view.state.selection.main;
|
|
|
if (insert.length > 2 || insert.length == 2 && (0,_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.codePointSize)((0,_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.codePointAt)(insert, 0)) == 1 ||
|
|
|
from != sel.from || to != sel.to)
|
|
|
return false;
|
|
|
let tr = insertBracket(view.state, insert);
|
|
|
if (!tr)
|
|
|
return false;
|
|
|
view.dispatch(tr);
|
|
|
return true;
|
|
|
});
|
|
|
/**
|
|
|
Command that implements deleting a pair of matching brackets when
|
|
|
the cursor is between them.
|
|
|
*/
|
|
|
const deleteBracketPair = ({ state, dispatch }) => {
|
|
|
if (state.readOnly)
|
|
|
return false;
|
|
|
let conf = config(state, state.selection.main.head);
|
|
|
let tokens = conf.brackets || defaults.brackets;
|
|
|
let dont = null, changes = state.changeByRange(range => {
|
|
|
if (range.empty) {
|
|
|
let before = prevChar(state.doc, range.head);
|
|
|
for (let token of tokens) {
|
|
|
if (token == before && nextChar(state.doc, range.head) == closing((0,_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.codePointAt)(token, 0)))
|
|
|
return { changes: { from: range.head - token.length, to: range.head + token.length },
|
|
|
range: _codemirror_state__WEBPACK_IMPORTED_MODULE_0__.EditorSelection.cursor(range.head - token.length) };
|
|
|
}
|
|
|
}
|
|
|
return { range: dont = range };
|
|
|
});
|
|
|
if (!dont)
|
|
|
dispatch(state.update(changes, { scrollIntoView: true, userEvent: "delete.backward" }));
|
|
|
return !dont;
|
|
|
};
|
|
|
/**
|
|
|
Close-brackets related key bindings. Binds Backspace to
|
|
|
[`deleteBracketPair`](https://codemirror.net/6/docs/ref/#autocomplete.deleteBracketPair).
|
|
|
*/
|
|
|
const closeBracketsKeymap = [
|
|
|
{ key: "Backspace", run: deleteBracketPair }
|
|
|
];
|
|
|
/**
|
|
|
Implements the extension's behavior on text insertion. If the
|
|
|
given string counts as a bracket in the language around the
|
|
|
selection, and replacing the selection with it requires custom
|
|
|
behavior (inserting a closing version or skipping past a
|
|
|
previously-closed bracket), this function returns a transaction
|
|
|
representing that custom behavior. (You only need this if you want
|
|
|
to programmatically insert brackets—the
|
|
|
[`closeBrackets`](https://codemirror.net/6/docs/ref/#autocomplete.closeBrackets) extension will
|
|
|
take care of running this for user input.)
|
|
|
*/
|
|
|
function insertBracket(state, bracket) {
|
|
|
let conf = config(state, state.selection.main.head);
|
|
|
let tokens = conf.brackets || defaults.brackets;
|
|
|
for (let tok of tokens) {
|
|
|
let closed = closing((0,_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.codePointAt)(tok, 0));
|
|
|
if (bracket == tok)
|
|
|
return closed == tok ? handleSame(state, tok, tokens.indexOf(tok + tok + tok) > -1, conf)
|
|
|
: handleOpen(state, tok, closed, conf.before || defaults.before);
|
|
|
if (bracket == closed && closedBracketAt(state, state.selection.main.from))
|
|
|
return handleClose(state, tok, closed);
|
|
|
}
|
|
|
return null;
|
|
|
}
|
|
|
function closedBracketAt(state, pos) {
|
|
|
let found = false;
|
|
|
state.field(bracketState).between(0, state.doc.length, from => {
|
|
|
if (from == pos)
|
|
|
found = true;
|
|
|
});
|
|
|
return found;
|
|
|
}
|
|
|
function nextChar(doc, pos) {
|
|
|
let next = doc.sliceString(pos, pos + 2);
|
|
|
return next.slice(0, (0,_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.codePointSize)((0,_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.codePointAt)(next, 0)));
|
|
|
}
|
|
|
function prevChar(doc, pos) {
|
|
|
let prev = doc.sliceString(pos - 2, pos);
|
|
|
return (0,_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.codePointSize)((0,_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.codePointAt)(prev, 0)) == prev.length ? prev : prev.slice(1);
|
|
|
}
|
|
|
function handleOpen(state, open, close, closeBefore) {
|
|
|
let dont = null, changes = state.changeByRange(range => {
|
|
|
if (!range.empty)
|
|
|
return { changes: [{ insert: open, from: range.from }, { insert: close, from: range.to }],
|
|
|
effects: closeBracketEffect.of(range.to + open.length),
|
|
|
range: _codemirror_state__WEBPACK_IMPORTED_MODULE_0__.EditorSelection.range(range.anchor + open.length, range.head + open.length) };
|
|
|
let next = nextChar(state.doc, range.head);
|
|
|
if (!next || /\s/.test(next) || closeBefore.indexOf(next) > -1)
|
|
|
return { changes: { insert: open + close, from: range.head },
|
|
|
effects: closeBracketEffect.of(range.head + open.length),
|
|
|
range: _codemirror_state__WEBPACK_IMPORTED_MODULE_0__.EditorSelection.cursor(range.head + open.length) };
|
|
|
return { range: dont = range };
|
|
|
});
|
|
|
return dont ? null : state.update(changes, {
|
|
|
scrollIntoView: true,
|
|
|
userEvent: "input.type"
|
|
|
});
|
|
|
}
|
|
|
function handleClose(state, _open, close) {
|
|
|
let dont = null, changes = state.changeByRange(range => {
|
|
|
if (range.empty && nextChar(state.doc, range.head) == close)
|
|
|
return { changes: { from: range.head, to: range.head + close.length, insert: close },
|
|
|
range: _codemirror_state__WEBPACK_IMPORTED_MODULE_0__.EditorSelection.cursor(range.head + close.length) };
|
|
|
return dont = { range };
|
|
|
});
|
|
|
return dont ? null : state.update(changes, {
|
|
|
scrollIntoView: true,
|
|
|
userEvent: "input.type"
|
|
|
});
|
|
|
}
|
|
|
// Handles cases where the open and close token are the same, and
|
|
|
// possibly triple quotes (as in `"""abc"""`-style quoting).
|
|
|
function handleSame(state, token, allowTriple, config) {
|
|
|
let stringPrefixes = config.stringPrefixes || defaults.stringPrefixes;
|
|
|
let dont = null, changes = state.changeByRange(range => {
|
|
|
if (!range.empty)
|
|
|
return { changes: [{ insert: token, from: range.from }, { insert: token, from: range.to }],
|
|
|
effects: closeBracketEffect.of(range.to + token.length),
|
|
|
range: _codemirror_state__WEBPACK_IMPORTED_MODULE_0__.EditorSelection.range(range.anchor + token.length, range.head + token.length) };
|
|
|
let pos = range.head, next = nextChar(state.doc, pos), start;
|
|
|
if (next == token) {
|
|
|
if (nodeStart(state, pos)) {
|
|
|
return { changes: { insert: token + token, from: pos },
|
|
|
effects: closeBracketEffect.of(pos + token.length),
|
|
|
range: _codemirror_state__WEBPACK_IMPORTED_MODULE_0__.EditorSelection.cursor(pos + token.length) };
|
|
|
}
|
|
|
else if (closedBracketAt(state, pos)) {
|
|
|
let isTriple = allowTriple && state.sliceDoc(pos, pos + token.length * 3) == token + token + token;
|
|
|
let content = isTriple ? token + token + token : token;
|
|
|
return { changes: { from: pos, to: pos + content.length, insert: content },
|
|
|
range: _codemirror_state__WEBPACK_IMPORTED_MODULE_0__.EditorSelection.cursor(pos + content.length) };
|
|
|
}
|
|
|
}
|
|
|
else if (allowTriple && state.sliceDoc(pos - 2 * token.length, pos) == token + token &&
|
|
|
(start = canStartStringAt(state, pos - 2 * token.length, stringPrefixes)) > -1 &&
|
|
|
nodeStart(state, start)) {
|
|
|
return { changes: { insert: token + token + token + token, from: pos },
|
|
|
effects: closeBracketEffect.of(pos + token.length),
|
|
|
range: _codemirror_state__WEBPACK_IMPORTED_MODULE_0__.EditorSelection.cursor(pos + token.length) };
|
|
|
}
|
|
|
else if (state.charCategorizer(pos)(next) != _codemirror_state__WEBPACK_IMPORTED_MODULE_0__.CharCategory.Word) {
|
|
|
if (canStartStringAt(state, pos, stringPrefixes) > -1 && !probablyInString(state, pos, token, stringPrefixes))
|
|
|
return { changes: { insert: token + token, from: pos },
|
|
|
effects: closeBracketEffect.of(pos + token.length),
|
|
|
range: _codemirror_state__WEBPACK_IMPORTED_MODULE_0__.EditorSelection.cursor(pos + token.length) };
|
|
|
}
|
|
|
return { range: dont = range };
|
|
|
});
|
|
|
return dont ? null : state.update(changes, {
|
|
|
scrollIntoView: true,
|
|
|
userEvent: "input.type"
|
|
|
});
|
|
|
}
|
|
|
function nodeStart(state, pos) {
|
|
|
let tree = (0,_codemirror_language__WEBPACK_IMPORTED_MODULE_2__.syntaxTree)(state).resolveInner(pos + 1);
|
|
|
return tree.parent && tree.from == pos;
|
|
|
}
|
|
|
function probablyInString(state, pos, quoteToken, prefixes) {
|
|
|
let node = (0,_codemirror_language__WEBPACK_IMPORTED_MODULE_2__.syntaxTree)(state).resolveInner(pos, -1);
|
|
|
let maxPrefix = prefixes.reduce((m, p) => Math.max(m, p.length), 0);
|
|
|
for (let i = 0; i < 5; i++) {
|
|
|
let start = state.sliceDoc(node.from, Math.min(node.to, node.from + quoteToken.length + maxPrefix));
|
|
|
let quotePos = start.indexOf(quoteToken);
|
|
|
if (!quotePos || quotePos > -1 && prefixes.indexOf(start.slice(0, quotePos)) > -1) {
|
|
|
let first = node.firstChild;
|
|
|
while (first && first.from == node.from && first.to - first.from > quoteToken.length + quotePos) {
|
|
|
if (state.sliceDoc(first.to - quoteToken.length, first.to) == quoteToken)
|
|
|
return false;
|
|
|
first = first.firstChild;
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
let parent = node.to == pos && node.parent;
|
|
|
if (!parent)
|
|
|
break;
|
|
|
node = parent;
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
function canStartStringAt(state, pos, prefixes) {
|
|
|
let charCat = state.charCategorizer(pos);
|
|
|
if (charCat(state.sliceDoc(pos - 1, pos)) != _codemirror_state__WEBPACK_IMPORTED_MODULE_0__.CharCategory.Word)
|
|
|
return pos;
|
|
|
for (let prefix of prefixes) {
|
|
|
let start = pos - prefix.length;
|
|
|
if (state.sliceDoc(start, pos) == prefix && charCat(state.sliceDoc(start - 1, start)) != _codemirror_state__WEBPACK_IMPORTED_MODULE_0__.CharCategory.Word)
|
|
|
return start;
|
|
|
}
|
|
|
return -1;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
Returns an extension that enables autocompletion.
|
|
|
*/
|
|
|
function autocompletion(config = {}) {
|
|
|
return [
|
|
|
commitCharacters,
|
|
|
completionState,
|
|
|
completionConfig.of(config),
|
|
|
completionPlugin,
|
|
|
completionKeymapExt,
|
|
|
baseTheme
|
|
|
];
|
|
|
}
|
|
|
/**
|
|
|
Basic keybindings for autocompletion.
|
|
|
|
|
|
- Ctrl-Space (and Alt-\` on macOS): [`startCompletion`](https://codemirror.net/6/docs/ref/#autocomplete.startCompletion)
|
|
|
- Escape: [`closeCompletion`](https://codemirror.net/6/docs/ref/#autocomplete.closeCompletion)
|
|
|
- ArrowDown: [`moveCompletionSelection`](https://codemirror.net/6/docs/ref/#autocomplete.moveCompletionSelection)`(true)`
|
|
|
- ArrowUp: [`moveCompletionSelection`](https://codemirror.net/6/docs/ref/#autocomplete.moveCompletionSelection)`(false)`
|
|
|
- PageDown: [`moveCompletionSelection`](https://codemirror.net/6/docs/ref/#autocomplete.moveCompletionSelection)`(true, "page")`
|
|
|
- PageDown: [`moveCompletionSelection`](https://codemirror.net/6/docs/ref/#autocomplete.moveCompletionSelection)`(true, "page")`
|
|
|
- Enter: [`acceptCompletion`](https://codemirror.net/6/docs/ref/#autocomplete.acceptCompletion)
|
|
|
*/
|
|
|
const completionKeymap = [
|
|
|
{ key: "Ctrl-Space", run: startCompletion },
|
|
|
{ mac: "Alt-`", run: startCompletion },
|
|
|
{ key: "Escape", run: closeCompletion },
|
|
|
{ key: "ArrowDown", run: /*@__PURE__*/moveCompletionSelection(true) },
|
|
|
{ key: "ArrowUp", run: /*@__PURE__*/moveCompletionSelection(false) },
|
|
|
{ key: "PageDown", run: /*@__PURE__*/moveCompletionSelection(true, "page") },
|
|
|
{ key: "PageUp", run: /*@__PURE__*/moveCompletionSelection(false, "page") },
|
|
|
{ key: "Enter", run: acceptCompletion }
|
|
|
];
|
|
|
const completionKeymapExt = /*@__PURE__*/_codemirror_state__WEBPACK_IMPORTED_MODULE_0__.Prec.highest(/*@__PURE__*/_codemirror_view__WEBPACK_IMPORTED_MODULE_1__.keymap.computeN([completionConfig], state => state.facet(completionConfig).defaultKeymap ? [completionKeymap] : []));
|
|
|
/**
|
|
|
Get the current completion status. When completions are available,
|
|
|
this will return `"active"`. When completions are pending (in the
|
|
|
process of being queried), this returns `"pending"`. Otherwise, it
|
|
|
returns `null`.
|
|
|
*/
|
|
|
function completionStatus(state) {
|
|
|
let cState = state.field(completionState, false);
|
|
|
return cState && cState.active.some(a => a.isPending) ? "pending"
|
|
|
: cState && cState.active.some(a => a.state != 0 /* State.Inactive */) ? "active" : null;
|
|
|
}
|
|
|
const completionArrayCache = /*@__PURE__*/new WeakMap;
|
|
|
/**
|
|
|
Returns the available completions as an array.
|
|
|
*/
|
|
|
function currentCompletions(state) {
|
|
|
var _a;
|
|
|
let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
|
|
|
if (!open || open.disabled)
|
|
|
return [];
|
|
|
let completions = completionArrayCache.get(open.options);
|
|
|
if (!completions)
|
|
|
completionArrayCache.set(open.options, completions = open.options.map(o => o.completion));
|
|
|
return completions;
|
|
|
}
|
|
|
/**
|
|
|
Return the currently selected completion, if any.
|
|
|
*/
|
|
|
function selectedCompletion(state) {
|
|
|
var _a;
|
|
|
let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
|
|
|
return open && !open.disabled && open.selected >= 0 ? open.options[open.selected].completion : null;
|
|
|
}
|
|
|
/**
|
|
|
Returns the currently selected position in the active completion
|
|
|
list, or null if no completions are active.
|
|
|
*/
|
|
|
function selectedCompletionIndex(state) {
|
|
|
var _a;
|
|
|
let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
|
|
|
return open && !open.disabled && open.selected >= 0 ? open.selected : null;
|
|
|
}
|
|
|
/**
|
|
|
Create an effect that can be attached to a transaction to change
|
|
|
the currently selected completion.
|
|
|
*/
|
|
|
function setSelectedCompletion(index) {
|
|
|
return setSelectedEffect.of(index);
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/***/ }),
|
|
|
|
|
|
/***/ 49906:
|
|
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
|
|
|
|
"use strict";
|
|
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
|
/* harmony export */ IK: () => (/* binding */ ContextTracker),
|
|
|
/* harmony export */ Jq: () => (/* binding */ ExternalTokenizer),
|
|
|
/* harmony export */ RA: () => (/* binding */ LocalTokenGroup),
|
|
|
/* harmony export */ WQ: () => (/* binding */ LRParser)
|
|
|
/* harmony export */ });
|
|
|
/* unused harmony exports InputStream, Stack */
|
|
|
/* harmony import */ var _lezer_common__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(79352);
|
|
|
/* harmony import */ var _lezer_common__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_lezer_common__WEBPACK_IMPORTED_MODULE_0__);
|
|
|
/* provided dependency */ var process = __webpack_require__(27061);
|
|
|
|
|
|
|
|
|
/**
|
|
|
A parse stack. These are used internally by the parser to track
|
|
|
parsing progress. They also provide some properties and methods
|
|
|
that external code such as a tokenizer can use to get information
|
|
|
about the parse state.
|
|
|
*/
|
|
|
class Stack {
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
constructor(
|
|
|
/**
|
|
|
The parse that this stack is part of @internal
|
|
|
*/
|
|
|
p,
|
|
|
/**
|
|
|
Holds state, input pos, buffer index triplets for all but the
|
|
|
top state @internal
|
|
|
*/
|
|
|
stack,
|
|
|
/**
|
|
|
The current parse state @internal
|
|
|
*/
|
|
|
state,
|
|
|
// The position at which the next reduce should take place. This
|
|
|
// can be less than `this.pos` when skipped expressions have been
|
|
|
// added to the stack (which should be moved outside of the next
|
|
|
// reduction)
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
reducePos,
|
|
|
/**
|
|
|
The input position up to which this stack has parsed.
|
|
|
*/
|
|
|
pos,
|
|
|
/**
|
|
|
The dynamic score of the stack, including dynamic precedence
|
|
|
and error-recovery penalties
|
|
|
@internal
|
|
|
*/
|
|
|
score,
|
|
|
// The output buffer. Holds (type, start, end, size) quads
|
|
|
// representing nodes created by the parser, where `size` is
|
|
|
// amount of buffer array entries covered by this node.
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
buffer,
|
|
|
// The base offset of the buffer. When stacks are split, the split
|
|
|
// instance shared the buffer history with its parent up to
|
|
|
// `bufferBase`, which is the absolute offset (including the
|
|
|
// offset of previous splits) into the buffer at which this stack
|
|
|
// starts writing.
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
bufferBase,
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
curContext,
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
lookAhead = 0,
|
|
|
// A parent stack from which this was split off, if any. This is
|
|
|
// set up so that it always points to a stack that has some
|
|
|
// additional buffer content, never to a stack with an equal
|
|
|
// `bufferBase`.
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
parent) {
|
|
|
this.p = p;
|
|
|
this.stack = stack;
|
|
|
this.state = state;
|
|
|
this.reducePos = reducePos;
|
|
|
this.pos = pos;
|
|
|
this.score = score;
|
|
|
this.buffer = buffer;
|
|
|
this.bufferBase = bufferBase;
|
|
|
this.curContext = curContext;
|
|
|
this.lookAhead = lookAhead;
|
|
|
this.parent = parent;
|
|
|
}
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
toString() {
|
|
|
return `[${this.stack.filter((_, i) => i % 3 == 0).concat(this.state)}]@${this.pos}${this.score ? "!" + this.score : ""}`;
|
|
|
}
|
|
|
// Start an empty stack
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
static start(p, state, pos = 0) {
|
|
|
let cx = p.parser.context;
|
|
|
return new Stack(p, [], state, pos, pos, 0, [], 0, cx ? new StackContext(cx, cx.start) : null, 0, null);
|
|
|
}
|
|
|
/**
|
|
|
The stack's current [context](#lr.ContextTracker) value, if
|
|
|
any. Its type will depend on the context tracker's type
|
|
|
parameter, or it will be `null` if there is no context
|
|
|
tracker.
|
|
|
*/
|
|
|
get context() { return this.curContext ? this.curContext.context : null; }
|
|
|
// Push a state onto the stack, tracking its start position as well
|
|
|
// as the buffer base at that point.
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
pushState(state, start) {
|
|
|
this.stack.push(this.state, start, this.bufferBase + this.buffer.length);
|
|
|
this.state = state;
|
|
|
}
|
|
|
// Apply a reduce action
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
reduce(action) {
|
|
|
var _a;
|
|
|
let depth = action >> 19 /* Action.ReduceDepthShift */, type = action & 65535 /* Action.ValueMask */;
|
|
|
let { parser } = this.p;
|
|
|
let dPrec = parser.dynamicPrecedence(type);
|
|
|
if (dPrec)
|
|
|
this.score += dPrec;
|
|
|
if (depth == 0) {
|
|
|
this.pushState(parser.getGoto(this.state, type, true), this.reducePos);
|
|
|
// Zero-depth reductions are a special case—they add stuff to
|
|
|
// the stack without popping anything off.
|
|
|
if (type < parser.minRepeatTerm)
|
|
|
this.storeNode(type, this.reducePos, this.reducePos, 4, true);
|
|
|
this.reduceContext(type, this.reducePos);
|
|
|
return;
|
|
|
}
|
|
|
// Find the base index into `this.stack`, content after which will
|
|
|
// be dropped. Note that with `StayFlag` reductions we need to
|
|
|
// consume two extra frames (the dummy parent node for the skipped
|
|
|
// expression and the state that we'll be staying in, which should
|
|
|
// be moved to `this.state`).
|
|
|
let base = this.stack.length - ((depth - 1) * 3) - (action & 262144 /* Action.StayFlag */ ? 6 : 0);
|
|
|
let start = base ? this.stack[base - 2] : this.p.ranges[0].from, size = this.reducePos - start;
|
|
|
// This is a kludge to try and detect overly deep left-associative
|
|
|
// trees, which will not increase the parse stack depth and thus
|
|
|
// won't be caught by the regular stack-depth limit check.
|
|
|
if (size >= 2000 /* Recover.MinBigReduction */ && !((_a = this.p.parser.nodeSet.types[type]) === null || _a === void 0 ? void 0 : _a.isAnonymous)) {
|
|
|
if (start == this.p.lastBigReductionStart) {
|
|
|
this.p.bigReductionCount++;
|
|
|
this.p.lastBigReductionSize = size;
|
|
|
}
|
|
|
else if (this.p.lastBigReductionSize < size) {
|
|
|
this.p.bigReductionCount = 1;
|
|
|
this.p.lastBigReductionStart = start;
|
|
|
this.p.lastBigReductionSize = size;
|
|
|
}
|
|
|
}
|
|
|
let bufferBase = base ? this.stack[base - 1] : 0, count = this.bufferBase + this.buffer.length - bufferBase;
|
|
|
// Store normal terms or `R -> R R` repeat reductions
|
|
|
if (type < parser.minRepeatTerm || (action & 131072 /* Action.RepeatFlag */)) {
|
|
|
let pos = parser.stateFlag(this.state, 1 /* StateFlag.Skipped */) ? this.pos : this.reducePos;
|
|
|
this.storeNode(type, start, pos, count + 4, true);
|
|
|
}
|
|
|
if (action & 262144 /* Action.StayFlag */) {
|
|
|
this.state = this.stack[base];
|
|
|
}
|
|
|
else {
|
|
|
let baseStateID = this.stack[base - 3];
|
|
|
this.state = parser.getGoto(baseStateID, type, true);
|
|
|
}
|
|
|
while (this.stack.length > base)
|
|
|
this.stack.pop();
|
|
|
this.reduceContext(type, start);
|
|
|
}
|
|
|
// Shift a value into the buffer
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
storeNode(term, start, end, size = 4, isReduce = false) {
|
|
|
if (term == 0 /* Term.Err */ &&
|
|
|
(!this.stack.length || this.stack[this.stack.length - 1] < this.buffer.length + this.bufferBase)) {
|
|
|
// Try to omit/merge adjacent error nodes
|
|
|
let cur = this, top = this.buffer.length;
|
|
|
if (top == 0 && cur.parent) {
|
|
|
top = cur.bufferBase - cur.parent.bufferBase;
|
|
|
cur = cur.parent;
|
|
|
}
|
|
|
if (top > 0 && cur.buffer[top - 4] == 0 /* Term.Err */ && cur.buffer[top - 1] > -1) {
|
|
|
if (start == end)
|
|
|
return;
|
|
|
if (cur.buffer[top - 2] >= start) {
|
|
|
cur.buffer[top - 2] = end;
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
if (!isReduce || this.pos == end) { // Simple case, just append
|
|
|
this.buffer.push(term, start, end, size);
|
|
|
}
|
|
|
else { // There may be skipped nodes that have to be moved forward
|
|
|
let index = this.buffer.length;
|
|
|
if (index > 0 && this.buffer[index - 4] != 0 /* Term.Err */)
|
|
|
while (index > 0 && this.buffer[index - 2] > end) {
|
|
|
// Move this record forward
|
|
|
this.buffer[index] = this.buffer[index - 4];
|
|
|
this.buffer[index + 1] = this.buffer[index - 3];
|
|
|
this.buffer[index + 2] = this.buffer[index - 2];
|
|
|
this.buffer[index + 3] = this.buffer[index - 1];
|
|
|
index -= 4;
|
|
|
if (size > 4)
|
|
|
size -= 4;
|
|
|
}
|
|
|
this.buffer[index] = term;
|
|
|
this.buffer[index + 1] = start;
|
|
|
this.buffer[index + 2] = end;
|
|
|
this.buffer[index + 3] = size;
|
|
|
}
|
|
|
}
|
|
|
// Apply a shift action
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
shift(action, type, start, end) {
|
|
|
if (action & 131072 /* Action.GotoFlag */) {
|
|
|
this.pushState(action & 65535 /* Action.ValueMask */, this.pos);
|
|
|
}
|
|
|
else if ((action & 262144 /* Action.StayFlag */) == 0) { // Regular shift
|
|
|
let nextState = action, { parser } = this.p;
|
|
|
if (end > this.pos || type <= parser.maxNode) {
|
|
|
this.pos = end;
|
|
|
if (!parser.stateFlag(nextState, 1 /* StateFlag.Skipped */))
|
|
|
this.reducePos = end;
|
|
|
}
|
|
|
this.pushState(nextState, start);
|
|
|
this.shiftContext(type, start);
|
|
|
if (type <= parser.maxNode)
|
|
|
this.buffer.push(type, start, end, 4);
|
|
|
}
|
|
|
else { // Shift-and-stay, which means this is a skipped token
|
|
|
this.pos = end;
|
|
|
this.shiftContext(type, start);
|
|
|
if (type <= this.p.parser.maxNode)
|
|
|
this.buffer.push(type, start, end, 4);
|
|
|
}
|
|
|
}
|
|
|
// Apply an action
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
apply(action, next, nextStart, nextEnd) {
|
|
|
if (action & 65536 /* Action.ReduceFlag */)
|
|
|
this.reduce(action);
|
|
|
else
|
|
|
this.shift(action, next, nextStart, nextEnd);
|
|
|
}
|
|
|
// Add a prebuilt (reused) node into the buffer.
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
useNode(value, next) {
|
|
|
let index = this.p.reused.length - 1;
|
|
|
if (index < 0 || this.p.reused[index] != value) {
|
|
|
this.p.reused.push(value);
|
|
|
index++;
|
|
|
}
|
|
|
let start = this.pos;
|
|
|
this.reducePos = this.pos = start + value.length;
|
|
|
this.pushState(next, start);
|
|
|
this.buffer.push(index, start, this.reducePos, -1 /* size == -1 means this is a reused value */);
|
|
|
if (this.curContext)
|
|
|
this.updateContext(this.curContext.tracker.reuse(this.curContext.context, value, this, this.p.stream.reset(this.pos - value.length)));
|
|
|
}
|
|
|
// Split the stack. Due to the buffer sharing and the fact
|
|
|
// that `this.stack` tends to stay quite shallow, this isn't very
|
|
|
// expensive.
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
split() {
|
|
|
let parent = this;
|
|
|
let off = parent.buffer.length;
|
|
|
// Because the top of the buffer (after this.pos) may be mutated
|
|
|
// to reorder reductions and skipped tokens, and shared buffers
|
|
|
// should be immutable, this copies any outstanding skipped tokens
|
|
|
// to the new buffer, and puts the base pointer before them.
|
|
|
while (off > 0 && parent.buffer[off - 2] > parent.reducePos)
|
|
|
off -= 4;
|
|
|
let buffer = parent.buffer.slice(off), base = parent.bufferBase + off;
|
|
|
// Make sure parent points to an actual parent with content, if there is such a parent.
|
|
|
while (parent && base == parent.bufferBase)
|
|
|
parent = parent.parent;
|
|
|
return new Stack(this.p, this.stack.slice(), this.state, this.reducePos, this.pos, this.score, buffer, base, this.curContext, this.lookAhead, parent);
|
|
|
}
|
|
|
// Try to recover from an error by 'deleting' (ignoring) one token.
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
recoverByDelete(next, nextEnd) {
|
|
|
let isNode = next <= this.p.parser.maxNode;
|
|
|
if (isNode)
|
|
|
this.storeNode(next, this.pos, nextEnd, 4);
|
|
|
this.storeNode(0 /* Term.Err */, this.pos, nextEnd, isNode ? 8 : 4);
|
|
|
this.pos = this.reducePos = nextEnd;
|
|
|
this.score -= 190 /* Recover.Delete */;
|
|
|
}
|
|
|
/**
|
|
|
Check if the given term would be able to be shifted (optionally
|
|
|
after some reductions) on this stack. This can be useful for
|
|
|
external tokenizers that want to make sure they only provide a
|
|
|
given token when it applies.
|
|
|
*/
|
|
|
canShift(term) {
|
|
|
for (let sim = new SimulatedStack(this);;) {
|
|
|
let action = this.p.parser.stateSlot(sim.state, 4 /* ParseState.DefaultReduce */) || this.p.parser.hasAction(sim.state, term);
|
|
|
if (action == 0)
|
|
|
return false;
|
|
|
if ((action & 65536 /* Action.ReduceFlag */) == 0)
|
|
|
return true;
|
|
|
sim.reduce(action);
|
|
|
}
|
|
|
}
|
|
|
// Apply up to Recover.MaxNext recovery actions that conceptually
|
|
|
// inserts some missing token or rule.
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
recoverByInsert(next) {
|
|
|
if (this.stack.length >= 300 /* Recover.MaxInsertStackDepth */)
|
|
|
return [];
|
|
|
let nextStates = this.p.parser.nextStates(this.state);
|
|
|
if (nextStates.length > 4 /* Recover.MaxNext */ << 1 || this.stack.length >= 120 /* Recover.DampenInsertStackDepth */) {
|
|
|
let best = [];
|
|
|
for (let i = 0, s; i < nextStates.length; i += 2) {
|
|
|
if ((s = nextStates[i + 1]) != this.state && this.p.parser.hasAction(s, next))
|
|
|
best.push(nextStates[i], s);
|
|
|
}
|
|
|
if (this.stack.length < 120 /* Recover.DampenInsertStackDepth */)
|
|
|
for (let i = 0; best.length < 4 /* Recover.MaxNext */ << 1 && i < nextStates.length; i += 2) {
|
|
|
let s = nextStates[i + 1];
|
|
|
if (!best.some((v, i) => (i & 1) && v == s))
|
|
|
best.push(nextStates[i], s);
|
|
|
}
|
|
|
nextStates = best;
|
|
|
}
|
|
|
let result = [];
|
|
|
for (let i = 0; i < nextStates.length && result.length < 4 /* Recover.MaxNext */; i += 2) {
|
|
|
let s = nextStates[i + 1];
|
|
|
if (s == this.state)
|
|
|
continue;
|
|
|
let stack = this.split();
|
|
|
stack.pushState(s, this.pos);
|
|
|
stack.storeNode(0 /* Term.Err */, stack.pos, stack.pos, 4, true);
|
|
|
stack.shiftContext(nextStates[i], this.pos);
|
|
|
stack.reducePos = this.pos;
|
|
|
stack.score -= 200 /* Recover.Insert */;
|
|
|
result.push(stack);
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
// Force a reduce, if possible. Return false if that can't
|
|
|
// be done.
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
forceReduce() {
|
|
|
let { parser } = this.p;
|
|
|
let reduce = parser.stateSlot(this.state, 5 /* ParseState.ForcedReduce */);
|
|
|
if ((reduce & 65536 /* Action.ReduceFlag */) == 0)
|
|
|
return false;
|
|
|
if (!parser.validAction(this.state, reduce)) {
|
|
|
let depth = reduce >> 19 /* Action.ReduceDepthShift */, term = reduce & 65535 /* Action.ValueMask */;
|
|
|
let target = this.stack.length - depth * 3;
|
|
|
if (target < 0 || parser.getGoto(this.stack[target], term, false) < 0) {
|
|
|
let backup = this.findForcedReduction();
|
|
|
if (backup == null)
|
|
|
return false;
|
|
|
reduce = backup;
|
|
|
}
|
|
|
this.storeNode(0 /* Term.Err */, this.pos, this.pos, 4, true);
|
|
|
this.score -= 100 /* Recover.Reduce */;
|
|
|
}
|
|
|
this.reducePos = this.pos;
|
|
|
this.reduce(reduce);
|
|
|
return true;
|
|
|
}
|
|
|
/**
|
|
|
Try to scan through the automaton to find some kind of reduction
|
|
|
that can be applied. Used when the regular ForcedReduce field
|
|
|
isn't a valid action. @internal
|
|
|
*/
|
|
|
findForcedReduction() {
|
|
|
let { parser } = this.p, seen = [];
|
|
|
let explore = (state, depth) => {
|
|
|
if (seen.includes(state))
|
|
|
return;
|
|
|
seen.push(state);
|
|
|
return parser.allActions(state, (action) => {
|
|
|
if (action & (262144 /* Action.StayFlag */ | 131072 /* Action.GotoFlag */)) ;
|
|
|
else if (action & 65536 /* Action.ReduceFlag */) {
|
|
|
let rDepth = (action >> 19 /* Action.ReduceDepthShift */) - depth;
|
|
|
if (rDepth > 1) {
|
|
|
let term = action & 65535 /* Action.ValueMask */, target = this.stack.length - rDepth * 3;
|
|
|
if (target >= 0 && parser.getGoto(this.stack[target], term, false) >= 0)
|
|
|
return (rDepth << 19 /* Action.ReduceDepthShift */) | 65536 /* Action.ReduceFlag */ | term;
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
let found = explore(action, depth + 1);
|
|
|
if (found != null)
|
|
|
return found;
|
|
|
}
|
|
|
});
|
|
|
};
|
|
|
return explore(this.state, 0);
|
|
|
}
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
forceAll() {
|
|
|
while (!this.p.parser.stateFlag(this.state, 2 /* StateFlag.Accepting */)) {
|
|
|
if (!this.forceReduce()) {
|
|
|
this.storeNode(0 /* Term.Err */, this.pos, this.pos, 4, true);
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
return this;
|
|
|
}
|
|
|
/**
|
|
|
Check whether this state has no further actions (assumed to be a direct descendant of the
|
|
|
top state, since any other states must be able to continue
|
|
|
somehow). @internal
|
|
|
*/
|
|
|
get deadEnd() {
|
|
|
if (this.stack.length != 3)
|
|
|
return false;
|
|
|
let { parser } = this.p;
|
|
|
return parser.data[parser.stateSlot(this.state, 1 /* ParseState.Actions */)] == 65535 /* Seq.End */ &&
|
|
|
!parser.stateSlot(this.state, 4 /* ParseState.DefaultReduce */);
|
|
|
}
|
|
|
/**
|
|
|
Restart the stack (put it back in its start state). Only safe
|
|
|
when this.stack.length == 3 (state is directly below the top
|
|
|
state). @internal
|
|
|
*/
|
|
|
restart() {
|
|
|
this.storeNode(0 /* Term.Err */, this.pos, this.pos, 4, true);
|
|
|
this.state = this.stack[0];
|
|
|
this.stack.length = 0;
|
|
|
}
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
sameState(other) {
|
|
|
if (this.state != other.state || this.stack.length != other.stack.length)
|
|
|
return false;
|
|
|
for (let i = 0; i < this.stack.length; i += 3)
|
|
|
if (this.stack[i] != other.stack[i])
|
|
|
return false;
|
|
|
return true;
|
|
|
}
|
|
|
/**
|
|
|
Get the parser used by this stack.
|
|
|
*/
|
|
|
get parser() { return this.p.parser; }
|
|
|
/**
|
|
|
Test whether a given dialect (by numeric ID, as exported from
|
|
|
the terms file) is enabled.
|
|
|
*/
|
|
|
dialectEnabled(dialectID) { return this.p.parser.dialect.flags[dialectID]; }
|
|
|
shiftContext(term, start) {
|
|
|
if (this.curContext)
|
|
|
this.updateContext(this.curContext.tracker.shift(this.curContext.context, term, this, this.p.stream.reset(start)));
|
|
|
}
|
|
|
reduceContext(term, start) {
|
|
|
if (this.curContext)
|
|
|
this.updateContext(this.curContext.tracker.reduce(this.curContext.context, term, this, this.p.stream.reset(start)));
|
|
|
}
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
emitContext() {
|
|
|
let last = this.buffer.length - 1;
|
|
|
if (last < 0 || this.buffer[last] != -3)
|
|
|
this.buffer.push(this.curContext.hash, this.pos, this.pos, -3);
|
|
|
}
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
emitLookAhead() {
|
|
|
let last = this.buffer.length - 1;
|
|
|
if (last < 0 || this.buffer[last] != -4)
|
|
|
this.buffer.push(this.lookAhead, this.pos, this.pos, -4);
|
|
|
}
|
|
|
updateContext(context) {
|
|
|
if (context != this.curContext.context) {
|
|
|
let newCx = new StackContext(this.curContext.tracker, context);
|
|
|
if (newCx.hash != this.curContext.hash)
|
|
|
this.emitContext();
|
|
|
this.curContext = newCx;
|
|
|
}
|
|
|
}
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
setLookAhead(lookAhead) {
|
|
|
if (lookAhead > this.lookAhead) {
|
|
|
this.emitLookAhead();
|
|
|
this.lookAhead = lookAhead;
|
|
|
}
|
|
|
}
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
close() {
|
|
|
if (this.curContext && this.curContext.tracker.strict)
|
|
|
this.emitContext();
|
|
|
if (this.lookAhead > 0)
|
|
|
this.emitLookAhead();
|
|
|
}
|
|
|
}
|
|
|
class StackContext {
|
|
|
constructor(tracker, context) {
|
|
|
this.tracker = tracker;
|
|
|
this.context = context;
|
|
|
this.hash = tracker.strict ? tracker.hash(context) : 0;
|
|
|
}
|
|
|
}
|
|
|
// Used to cheaply run some reductions to scan ahead without mutating
|
|
|
// an entire stack
|
|
|
class SimulatedStack {
|
|
|
constructor(start) {
|
|
|
this.start = start;
|
|
|
this.state = start.state;
|
|
|
this.stack = start.stack;
|
|
|
this.base = this.stack.length;
|
|
|
}
|
|
|
reduce(action) {
|
|
|
let term = action & 65535 /* Action.ValueMask */, depth = action >> 19 /* Action.ReduceDepthShift */;
|
|
|
if (depth == 0) {
|
|
|
if (this.stack == this.start.stack)
|
|
|
this.stack = this.stack.slice();
|
|
|
this.stack.push(this.state, 0, 0);
|
|
|
this.base += 3;
|
|
|
}
|
|
|
else {
|
|
|
this.base -= (depth - 1) * 3;
|
|
|
}
|
|
|
let goto = this.start.p.parser.getGoto(this.stack[this.base - 3], term, true);
|
|
|
this.state = goto;
|
|
|
}
|
|
|
}
|
|
|
// This is given to `Tree.build` to build a buffer, and encapsulates
|
|
|
// the parent-stack-walking necessary to read the nodes.
|
|
|
class StackBufferCursor {
|
|
|
constructor(stack, pos, index) {
|
|
|
this.stack = stack;
|
|
|
this.pos = pos;
|
|
|
this.index = index;
|
|
|
this.buffer = stack.buffer;
|
|
|
if (this.index == 0)
|
|
|
this.maybeNext();
|
|
|
}
|
|
|
static create(stack, pos = stack.bufferBase + stack.buffer.length) {
|
|
|
return new StackBufferCursor(stack, pos, pos - stack.bufferBase);
|
|
|
}
|
|
|
maybeNext() {
|
|
|
let next = this.stack.parent;
|
|
|
if (next != null) {
|
|
|
this.index = this.stack.bufferBase - next.bufferBase;
|
|
|
this.stack = next;
|
|
|
this.buffer = next.buffer;
|
|
|
}
|
|
|
}
|
|
|
get id() { return this.buffer[this.index - 4]; }
|
|
|
get start() { return this.buffer[this.index - 3]; }
|
|
|
get end() { return this.buffer[this.index - 2]; }
|
|
|
get size() { return this.buffer[this.index - 1]; }
|
|
|
next() {
|
|
|
this.index -= 4;
|
|
|
this.pos -= 4;
|
|
|
if (this.index == 0)
|
|
|
this.maybeNext();
|
|
|
}
|
|
|
fork() {
|
|
|
return new StackBufferCursor(this.stack, this.pos, this.index);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// See lezer-generator/src/encode.ts for comments about the encoding
|
|
|
// used here
|
|
|
function decodeArray(input, Type = Uint16Array) {
|
|
|
if (typeof input != "string")
|
|
|
return input;
|
|
|
let array = null;
|
|
|
for (let pos = 0, out = 0; pos < input.length;) {
|
|
|
let value = 0;
|
|
|
for (;;) {
|
|
|
let next = input.charCodeAt(pos++), stop = false;
|
|
|
if (next == 126 /* Encode.BigValCode */) {
|
|
|
value = 65535 /* Encode.BigVal */;
|
|
|
break;
|
|
|
}
|
|
|
if (next >= 92 /* Encode.Gap2 */)
|
|
|
next--;
|
|
|
if (next >= 34 /* Encode.Gap1 */)
|
|
|
next--;
|
|
|
let digit = next - 32 /* Encode.Start */;
|
|
|
if (digit >= 46 /* Encode.Base */) {
|
|
|
digit -= 46 /* Encode.Base */;
|
|
|
stop = true;
|
|
|
}
|
|
|
value += digit;
|
|
|
if (stop)
|
|
|
break;
|
|
|
value *= 46 /* Encode.Base */;
|
|
|
}
|
|
|
if (array)
|
|
|
array[out++] = value;
|
|
|
else
|
|
|
array = new Type(value);
|
|
|
}
|
|
|
return array;
|
|
|
}
|
|
|
|
|
|
class CachedToken {
|
|
|
constructor() {
|
|
|
this.start = -1;
|
|
|
this.value = -1;
|
|
|
this.end = -1;
|
|
|
this.extended = -1;
|
|
|
this.lookAhead = 0;
|
|
|
this.mask = 0;
|
|
|
this.context = 0;
|
|
|
}
|
|
|
}
|
|
|
const nullToken = new CachedToken;
|
|
|
/**
|
|
|
[Tokenizers](#lr.ExternalTokenizer) interact with the input
|
|
|
through this interface. It presents the input as a stream of
|
|
|
characters, tracking lookahead and hiding the complexity of
|
|
|
[ranges](#common.Parser.parse^ranges) from tokenizer code.
|
|
|
*/
|
|
|
class InputStream {
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
constructor(
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
input,
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
ranges) {
|
|
|
this.input = input;
|
|
|
this.ranges = ranges;
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
this.chunk = "";
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
this.chunkOff = 0;
|
|
|
/**
|
|
|
Backup chunk
|
|
|
*/
|
|
|
this.chunk2 = "";
|
|
|
this.chunk2Pos = 0;
|
|
|
/**
|
|
|
The character code of the next code unit in the input, or -1
|
|
|
when the stream is at the end of the input.
|
|
|
*/
|
|
|
this.next = -1;
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
this.token = nullToken;
|
|
|
this.rangeIndex = 0;
|
|
|
this.pos = this.chunkPos = ranges[0].from;
|
|
|
this.range = ranges[0];
|
|
|
this.end = ranges[ranges.length - 1].to;
|
|
|
this.readNext();
|
|
|
}
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
resolveOffset(offset, assoc) {
|
|
|
let range = this.range, index = this.rangeIndex;
|
|
|
let pos = this.pos + offset;
|
|
|
while (pos < range.from) {
|
|
|
if (!index)
|
|
|
return null;
|
|
|
let next = this.ranges[--index];
|
|
|
pos -= range.from - next.to;
|
|
|
range = next;
|
|
|
}
|
|
|
while (assoc < 0 ? pos > range.to : pos >= range.to) {
|
|
|
if (index == this.ranges.length - 1)
|
|
|
return null;
|
|
|
let next = this.ranges[++index];
|
|
|
pos += next.from - range.to;
|
|
|
range = next;
|
|
|
}
|
|
|
return pos;
|
|
|
}
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
clipPos(pos) {
|
|
|
if (pos >= this.range.from && pos < this.range.to)
|
|
|
return pos;
|
|
|
for (let range of this.ranges)
|
|
|
if (range.to > pos)
|
|
|
return Math.max(pos, range.from);
|
|
|
return this.end;
|
|
|
}
|
|
|
/**
|
|
|
Look at a code unit near the stream position. `.peek(0)` equals
|
|
|
`.next`, `.peek(-1)` gives you the previous character, and so
|
|
|
on.
|
|
|
|
|
|
Note that looking around during tokenizing creates dependencies
|
|
|
on potentially far-away content, which may reduce the
|
|
|
effectiveness incremental parsing—when looking forward—or even
|
|
|
cause invalid reparses when looking backward more than 25 code
|
|
|
units, since the library does not track lookbehind.
|
|
|
*/
|
|
|
peek(offset) {
|
|
|
let idx = this.chunkOff + offset, pos, result;
|
|
|
if (idx >= 0 && idx < this.chunk.length) {
|
|
|
pos = this.pos + offset;
|
|
|
result = this.chunk.charCodeAt(idx);
|
|
|
}
|
|
|
else {
|
|
|
let resolved = this.resolveOffset(offset, 1);
|
|
|
if (resolved == null)
|
|
|
return -1;
|
|
|
pos = resolved;
|
|
|
if (pos >= this.chunk2Pos && pos < this.chunk2Pos + this.chunk2.length) {
|
|
|
result = this.chunk2.charCodeAt(pos - this.chunk2Pos);
|
|
|
}
|
|
|
else {
|
|
|
let i = this.rangeIndex, range = this.range;
|
|
|
while (range.to <= pos)
|
|
|
range = this.ranges[++i];
|
|
|
this.chunk2 = this.input.chunk(this.chunk2Pos = pos);
|
|
|
if (pos + this.chunk2.length > range.to)
|
|
|
this.chunk2 = this.chunk2.slice(0, range.to - pos);
|
|
|
result = this.chunk2.charCodeAt(0);
|
|
|
}
|
|
|
}
|
|
|
if (pos >= this.token.lookAhead)
|
|
|
this.token.lookAhead = pos + 1;
|
|
|
return result;
|
|
|
}
|
|
|
/**
|
|
|
Accept a token. By default, the end of the token is set to the
|
|
|
current stream position, but you can pass an offset (relative to
|
|
|
the stream position) to change that.
|
|
|
*/
|
|
|
acceptToken(token, endOffset = 0) {
|
|
|
let end = endOffset ? this.resolveOffset(endOffset, -1) : this.pos;
|
|
|
if (end == null || end < this.token.start)
|
|
|
throw new RangeError("Token end out of bounds");
|
|
|
this.token.value = token;
|
|
|
this.token.end = end;
|
|
|
}
|
|
|
getChunk() {
|
|
|
if (this.pos >= this.chunk2Pos && this.pos < this.chunk2Pos + this.chunk2.length) {
|
|
|
let { chunk, chunkPos } = this;
|
|
|
this.chunk = this.chunk2;
|
|
|
this.chunkPos = this.chunk2Pos;
|
|
|
this.chunk2 = chunk;
|
|
|
this.chunk2Pos = chunkPos;
|
|
|
this.chunkOff = this.pos - this.chunkPos;
|
|
|
}
|
|
|
else {
|
|
|
this.chunk2 = this.chunk;
|
|
|
this.chunk2Pos = this.chunkPos;
|
|
|
let nextChunk = this.input.chunk(this.pos);
|
|
|
let end = this.pos + nextChunk.length;
|
|
|
this.chunk = end > this.range.to ? nextChunk.slice(0, this.range.to - this.pos) : nextChunk;
|
|
|
this.chunkPos = this.pos;
|
|
|
this.chunkOff = 0;
|
|
|
}
|
|
|
}
|
|
|
readNext() {
|
|
|
if (this.chunkOff >= this.chunk.length) {
|
|
|
this.getChunk();
|
|
|
if (this.chunkOff == this.chunk.length)
|
|
|
return this.next = -1;
|
|
|
}
|
|
|
return this.next = this.chunk.charCodeAt(this.chunkOff);
|
|
|
}
|
|
|
/**
|
|
|
Move the stream forward N (defaults to 1) code units. Returns
|
|
|
the new value of [`next`](#lr.InputStream.next).
|
|
|
*/
|
|
|
advance(n = 1) {
|
|
|
this.chunkOff += n;
|
|
|
while (this.pos + n >= this.range.to) {
|
|
|
if (this.rangeIndex == this.ranges.length - 1)
|
|
|
return this.setDone();
|
|
|
n -= this.range.to - this.pos;
|
|
|
this.range = this.ranges[++this.rangeIndex];
|
|
|
this.pos = this.range.from;
|
|
|
}
|
|
|
this.pos += n;
|
|
|
if (this.pos >= this.token.lookAhead)
|
|
|
this.token.lookAhead = this.pos + 1;
|
|
|
return this.readNext();
|
|
|
}
|
|
|
setDone() {
|
|
|
this.pos = this.chunkPos = this.end;
|
|
|
this.range = this.ranges[this.rangeIndex = this.ranges.length - 1];
|
|
|
this.chunk = "";
|
|
|
return this.next = -1;
|
|
|
}
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
reset(pos, token) {
|
|
|
if (token) {
|
|
|
this.token = token;
|
|
|
token.start = pos;
|
|
|
token.lookAhead = pos + 1;
|
|
|
token.value = token.extended = -1;
|
|
|
}
|
|
|
else {
|
|
|
this.token = nullToken;
|
|
|
}
|
|
|
if (this.pos != pos) {
|
|
|
this.pos = pos;
|
|
|
if (pos == this.end) {
|
|
|
this.setDone();
|
|
|
return this;
|
|
|
}
|
|
|
while (pos < this.range.from)
|
|
|
this.range = this.ranges[--this.rangeIndex];
|
|
|
while (pos >= this.range.to)
|
|
|
this.range = this.ranges[++this.rangeIndex];
|
|
|
if (pos >= this.chunkPos && pos < this.chunkPos + this.chunk.length) {
|
|
|
this.chunkOff = pos - this.chunkPos;
|
|
|
}
|
|
|
else {
|
|
|
this.chunk = "";
|
|
|
this.chunkOff = 0;
|
|
|
}
|
|
|
this.readNext();
|
|
|
}
|
|
|
return this;
|
|
|
}
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
read(from, to) {
|
|
|
if (from >= this.chunkPos && to <= this.chunkPos + this.chunk.length)
|
|
|
return this.chunk.slice(from - this.chunkPos, to - this.chunkPos);
|
|
|
if (from >= this.chunk2Pos && to <= this.chunk2Pos + this.chunk2.length)
|
|
|
return this.chunk2.slice(from - this.chunk2Pos, to - this.chunk2Pos);
|
|
|
if (from >= this.range.from && to <= this.range.to)
|
|
|
return this.input.read(from, to);
|
|
|
let result = "";
|
|
|
for (let r of this.ranges) {
|
|
|
if (r.from >= to)
|
|
|
break;
|
|
|
if (r.to > from)
|
|
|
result += this.input.read(Math.max(r.from, from), Math.min(r.to, to));
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
}
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
class TokenGroup {
|
|
|
constructor(data, id) {
|
|
|
this.data = data;
|
|
|
this.id = id;
|
|
|
}
|
|
|
token(input, stack) {
|
|
|
let { parser } = stack.p;
|
|
|
readToken(this.data, input, stack, this.id, parser.data, parser.tokenPrecTable);
|
|
|
}
|
|
|
}
|
|
|
TokenGroup.prototype.contextual = TokenGroup.prototype.fallback = TokenGroup.prototype.extend = false;
|
|
|
/**
|
|
|
@hide
|
|
|
*/
|
|
|
class LocalTokenGroup {
|
|
|
constructor(data, precTable, elseToken) {
|
|
|
this.precTable = precTable;
|
|
|
this.elseToken = elseToken;
|
|
|
this.data = typeof data == "string" ? decodeArray(data) : data;
|
|
|
}
|
|
|
token(input, stack) {
|
|
|
let start = input.pos, skipped = 0;
|
|
|
for (;;) {
|
|
|
let atEof = input.next < 0, nextPos = input.resolveOffset(1, 1);
|
|
|
readToken(this.data, input, stack, 0, this.data, this.precTable);
|
|
|
if (input.token.value > -1)
|
|
|
break;
|
|
|
if (this.elseToken == null)
|
|
|
return;
|
|
|
if (!atEof)
|
|
|
skipped++;
|
|
|
if (nextPos == null)
|
|
|
break;
|
|
|
input.reset(nextPos, input.token);
|
|
|
}
|
|
|
if (skipped) {
|
|
|
input.reset(start, input.token);
|
|
|
input.acceptToken(this.elseToken, skipped);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
LocalTokenGroup.prototype.contextual = TokenGroup.prototype.fallback = TokenGroup.prototype.extend = false;
|
|
|
/**
|
|
|
`@external tokens` declarations in the grammar should resolve to
|
|
|
an instance of this class.
|
|
|
*/
|
|
|
class ExternalTokenizer {
|
|
|
/**
|
|
|
Create a tokenizer. The first argument is the function that,
|
|
|
given an input stream, scans for the types of tokens it
|
|
|
recognizes at the stream's position, and calls
|
|
|
[`acceptToken`](#lr.InputStream.acceptToken) when it finds
|
|
|
one.
|
|
|
*/
|
|
|
constructor(
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
token, options = {}) {
|
|
|
this.token = token;
|
|
|
this.contextual = !!options.contextual;
|
|
|
this.fallback = !!options.fallback;
|
|
|
this.extend = !!options.extend;
|
|
|
}
|
|
|
}
|
|
|
// Tokenizer data is stored a big uint16 array containing, for each
|
|
|
// state:
|
|
|
//
|
|
|
// - A group bitmask, indicating what token groups are reachable from
|
|
|
// this state, so that paths that can only lead to tokens not in
|
|
|
// any of the current groups can be cut off early.
|
|
|
//
|
|
|
// - The position of the end of the state's sequence of accepting
|
|
|
// tokens
|
|
|
//
|
|
|
// - The number of outgoing edges for the state
|
|
|
//
|
|
|
// - The accepting tokens, as (token id, group mask) pairs
|
|
|
//
|
|
|
// - The outgoing edges, as (start character, end character, state
|
|
|
// index) triples, with end character being exclusive
|
|
|
//
|
|
|
// This function interprets that data, running through a stream as
|
|
|
// long as new states with the a matching group mask can be reached,
|
|
|
// and updating `input.token` when it matches a token.
|
|
|
function readToken(data, input, stack, group, precTable, precOffset) {
|
|
|
let state = 0, groupMask = 1 << group, { dialect } = stack.p.parser;
|
|
|
scan: for (;;) {
|
|
|
if ((groupMask & data[state]) == 0)
|
|
|
break;
|
|
|
let accEnd = data[state + 1];
|
|
|
// Check whether this state can lead to a token in the current group
|
|
|
// Accept tokens in this state, possibly overwriting
|
|
|
// lower-precedence / shorter tokens
|
|
|
for (let i = state + 3; i < accEnd; i += 2)
|
|
|
if ((data[i + 1] & groupMask) > 0) {
|
|
|
let term = data[i];
|
|
|
if (dialect.allows(term) &&
|
|
|
(input.token.value == -1 || input.token.value == term ||
|
|
|
overrides(term, input.token.value, precTable, precOffset))) {
|
|
|
input.acceptToken(term);
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
let next = input.next, low = 0, high = data[state + 2];
|
|
|
// Special case for EOF
|
|
|
if (input.next < 0 && high > low && data[accEnd + high * 3 - 3] == 65535 /* Seq.End */ && data[accEnd + high * 3 - 3] == 65535 /* Seq.End */) {
|
|
|
state = data[accEnd + high * 3 - 1];
|
|
|
continue scan;
|
|
|
}
|
|
|
// Do a binary search on the state's edges
|
|
|
for (; low < high;) {
|
|
|
let mid = (low + high) >> 1;
|
|
|
let index = accEnd + mid + (mid << 1);
|
|
|
let from = data[index], to = data[index + 1] || 0x10000;
|
|
|
if (next < from)
|
|
|
high = mid;
|
|
|
else if (next >= to)
|
|
|
low = mid + 1;
|
|
|
else {
|
|
|
state = data[index + 2];
|
|
|
input.advance();
|
|
|
continue scan;
|
|
|
}
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
function findOffset(data, start, term) {
|
|
|
for (let i = start, next; (next = data[i]) != 65535 /* Seq.End */; i++)
|
|
|
if (next == term)
|
|
|
return i - start;
|
|
|
return -1;
|
|
|
}
|
|
|
function overrides(token, prev, tableData, tableOffset) {
|
|
|
let iPrev = findOffset(tableData, tableOffset, prev);
|
|
|
return iPrev < 0 || findOffset(tableData, tableOffset, token) < iPrev;
|
|
|
}
|
|
|
|
|
|
// Environment variable used to control console output
|
|
|
const verbose = typeof process != "undefined" && process.env && /\bparse\b/.test(process.env.LOG);
|
|
|
let stackIDs = null;
|
|
|
function cutAt(tree, pos, side) {
|
|
|
let cursor = tree.cursor(_lezer_common__WEBPACK_IMPORTED_MODULE_0__.IterMode.IncludeAnonymous);
|
|
|
cursor.moveTo(pos);
|
|
|
for (;;) {
|
|
|
if (!(side < 0 ? cursor.childBefore(pos) : cursor.childAfter(pos)))
|
|
|
for (;;) {
|
|
|
if ((side < 0 ? cursor.to < pos : cursor.from > pos) && !cursor.type.isError)
|
|
|
return side < 0 ? Math.max(0, Math.min(cursor.to - 1, pos - 25 /* Safety.Margin */))
|
|
|
: Math.min(tree.length, Math.max(cursor.from + 1, pos + 25 /* Safety.Margin */));
|
|
|
if (side < 0 ? cursor.prevSibling() : cursor.nextSibling())
|
|
|
break;
|
|
|
if (!cursor.parent())
|
|
|
return side < 0 ? 0 : tree.length;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
class FragmentCursor {
|
|
|
constructor(fragments, nodeSet) {
|
|
|
this.fragments = fragments;
|
|
|
this.nodeSet = nodeSet;
|
|
|
this.i = 0;
|
|
|
this.fragment = null;
|
|
|
this.safeFrom = -1;
|
|
|
this.safeTo = -1;
|
|
|
this.trees = [];
|
|
|
this.start = [];
|
|
|
this.index = [];
|
|
|
this.nextFragment();
|
|
|
}
|
|
|
nextFragment() {
|
|
|
let fr = this.fragment = this.i == this.fragments.length ? null : this.fragments[this.i++];
|
|
|
if (fr) {
|
|
|
this.safeFrom = fr.openStart ? cutAt(fr.tree, fr.from + fr.offset, 1) - fr.offset : fr.from;
|
|
|
this.safeTo = fr.openEnd ? cutAt(fr.tree, fr.to + fr.offset, -1) - fr.offset : fr.to;
|
|
|
while (this.trees.length) {
|
|
|
this.trees.pop();
|
|
|
this.start.pop();
|
|
|
this.index.pop();
|
|
|
}
|
|
|
this.trees.push(fr.tree);
|
|
|
this.start.push(-fr.offset);
|
|
|
this.index.push(0);
|
|
|
this.nextStart = this.safeFrom;
|
|
|
}
|
|
|
else {
|
|
|
this.nextStart = 1e9;
|
|
|
}
|
|
|
}
|
|
|
// `pos` must be >= any previously given `pos` for this cursor
|
|
|
nodeAt(pos) {
|
|
|
if (pos < this.nextStart)
|
|
|
return null;
|
|
|
while (this.fragment && this.safeTo <= pos)
|
|
|
this.nextFragment();
|
|
|
if (!this.fragment)
|
|
|
return null;
|
|
|
for (;;) {
|
|
|
let last = this.trees.length - 1;
|
|
|
if (last < 0) { // End of tree
|
|
|
this.nextFragment();
|
|
|
return null;
|
|
|
}
|
|
|
let top = this.trees[last], index = this.index[last];
|
|
|
if (index == top.children.length) {
|
|
|
this.trees.pop();
|
|
|
this.start.pop();
|
|
|
this.index.pop();
|
|
|
continue;
|
|
|
}
|
|
|
let next = top.children[index];
|
|
|
let start = this.start[last] + top.positions[index];
|
|
|
if (start > pos) {
|
|
|
this.nextStart = start;
|
|
|
return null;
|
|
|
}
|
|
|
if (next instanceof _lezer_common__WEBPACK_IMPORTED_MODULE_0__.Tree) {
|
|
|
if (start == pos) {
|
|
|
if (start < this.safeFrom)
|
|
|
return null;
|
|
|
let end = start + next.length;
|
|
|
if (end <= this.safeTo) {
|
|
|
let lookAhead = next.prop(_lezer_common__WEBPACK_IMPORTED_MODULE_0__.NodeProp.lookAhead);
|
|
|
if (!lookAhead || end + lookAhead < this.fragment.to)
|
|
|
return next;
|
|
|
}
|
|
|
}
|
|
|
this.index[last]++;
|
|
|
if (start + next.length >= Math.max(this.safeFrom, pos)) { // Enter this node
|
|
|
this.trees.push(next);
|
|
|
this.start.push(start);
|
|
|
this.index.push(0);
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
this.index[last]++;
|
|
|
this.nextStart = start + next.length;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
class TokenCache {
|
|
|
constructor(parser, stream) {
|
|
|
this.stream = stream;
|
|
|
this.tokens = [];
|
|
|
this.mainToken = null;
|
|
|
this.actions = [];
|
|
|
this.tokens = parser.tokenizers.map(_ => new CachedToken);
|
|
|
}
|
|
|
getActions(stack) {
|
|
|
let actionIndex = 0;
|
|
|
let main = null;
|
|
|
let { parser } = stack.p, { tokenizers } = parser;
|
|
|
let mask = parser.stateSlot(stack.state, 3 /* ParseState.TokenizerMask */);
|
|
|
let context = stack.curContext ? stack.curContext.hash : 0;
|
|
|
let lookAhead = 0;
|
|
|
for (let i = 0; i < tokenizers.length; i++) {
|
|
|
if (((1 << i) & mask) == 0)
|
|
|
continue;
|
|
|
let tokenizer = tokenizers[i], token = this.tokens[i];
|
|
|
if (main && !tokenizer.fallback)
|
|
|
continue;
|
|
|
if (tokenizer.contextual || token.start != stack.pos || token.mask != mask || token.context != context) {
|
|
|
this.updateCachedToken(token, tokenizer, stack);
|
|
|
token.mask = mask;
|
|
|
token.context = context;
|
|
|
}
|
|
|
if (token.lookAhead > token.end + 25 /* Safety.Margin */)
|
|
|
lookAhead = Math.max(token.lookAhead, lookAhead);
|
|
|
if (token.value != 0 /* Term.Err */) {
|
|
|
let startIndex = actionIndex;
|
|
|
if (token.extended > -1)
|
|
|
actionIndex = this.addActions(stack, token.extended, token.end, actionIndex);
|
|
|
actionIndex = this.addActions(stack, token.value, token.end, actionIndex);
|
|
|
if (!tokenizer.extend) {
|
|
|
main = token;
|
|
|
if (actionIndex > startIndex)
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
while (this.actions.length > actionIndex)
|
|
|
this.actions.pop();
|
|
|
if (lookAhead)
|
|
|
stack.setLookAhead(lookAhead);
|
|
|
if (!main && stack.pos == this.stream.end) {
|
|
|
main = new CachedToken;
|
|
|
main.value = stack.p.parser.eofTerm;
|
|
|
main.start = main.end = stack.pos;
|
|
|
actionIndex = this.addActions(stack, main.value, main.end, actionIndex);
|
|
|
}
|
|
|
this.mainToken = main;
|
|
|
return this.actions;
|
|
|
}
|
|
|
getMainToken(stack) {
|
|
|
if (this.mainToken)
|
|
|
return this.mainToken;
|
|
|
let main = new CachedToken, { pos, p } = stack;
|
|
|
main.start = pos;
|
|
|
main.end = Math.min(pos + 1, p.stream.end);
|
|
|
main.value = pos == p.stream.end ? p.parser.eofTerm : 0 /* Term.Err */;
|
|
|
return main;
|
|
|
}
|
|
|
updateCachedToken(token, tokenizer, stack) {
|
|
|
let start = this.stream.clipPos(stack.pos);
|
|
|
tokenizer.token(this.stream.reset(start, token), stack);
|
|
|
if (token.value > -1) {
|
|
|
let { parser } = stack.p;
|
|
|
for (let i = 0; i < parser.specialized.length; i++)
|
|
|
if (parser.specialized[i] == token.value) {
|
|
|
let result = parser.specializers[i](this.stream.read(token.start, token.end), stack);
|
|
|
if (result >= 0 && stack.p.parser.dialect.allows(result >> 1)) {
|
|
|
if ((result & 1) == 0 /* Specialize.Specialize */)
|
|
|
token.value = result >> 1;
|
|
|
else
|
|
|
token.extended = result >> 1;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
token.value = 0 /* Term.Err */;
|
|
|
token.end = this.stream.clipPos(start + 1);
|
|
|
}
|
|
|
}
|
|
|
putAction(action, token, end, index) {
|
|
|
// Don't add duplicate actions
|
|
|
for (let i = 0; i < index; i += 3)
|
|
|
if (this.actions[i] == action)
|
|
|
return index;
|
|
|
this.actions[index++] = action;
|
|
|
this.actions[index++] = token;
|
|
|
this.actions[index++] = end;
|
|
|
return index;
|
|
|
}
|
|
|
addActions(stack, token, end, index) {
|
|
|
let { state } = stack, { parser } = stack.p, { data } = parser;
|
|
|
for (let set = 0; set < 2; set++) {
|
|
|
for (let i = parser.stateSlot(state, set ? 2 /* ParseState.Skip */ : 1 /* ParseState.Actions */);; i += 3) {
|
|
|
if (data[i] == 65535 /* Seq.End */) {
|
|
|
if (data[i + 1] == 1 /* Seq.Next */) {
|
|
|
i = pair(data, i + 2);
|
|
|
}
|
|
|
else {
|
|
|
if (index == 0 && data[i + 1] == 2 /* Seq.Other */)
|
|
|
index = this.putAction(pair(data, i + 2), token, end, index);
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
if (data[i] == token)
|
|
|
index = this.putAction(pair(data, i + 1), token, end, index);
|
|
|
}
|
|
|
}
|
|
|
return index;
|
|
|
}
|
|
|
}
|
|
|
class Parse {
|
|
|
constructor(parser, input, fragments, ranges) {
|
|
|
this.parser = parser;
|
|
|
this.input = input;
|
|
|
this.ranges = ranges;
|
|
|
this.recovering = 0;
|
|
|
this.nextStackID = 0x2654; // ♔, ♕, ♖, ♗, ♘, ♙, ♠, ♡, ♢, ♣, ♤, ♥, ♦, ♧
|
|
|
this.minStackPos = 0;
|
|
|
this.reused = [];
|
|
|
this.stoppedAt = null;
|
|
|
this.lastBigReductionStart = -1;
|
|
|
this.lastBigReductionSize = 0;
|
|
|
this.bigReductionCount = 0;
|
|
|
this.stream = new InputStream(input, ranges);
|
|
|
this.tokens = new TokenCache(parser, this.stream);
|
|
|
this.topTerm = parser.top[1];
|
|
|
let { from } = ranges[0];
|
|
|
this.stacks = [Stack.start(this, parser.top[0], from)];
|
|
|
this.fragments = fragments.length && this.stream.end - from > parser.bufferLength * 4
|
|
|
? new FragmentCursor(fragments, parser.nodeSet) : null;
|
|
|
}
|
|
|
get parsedPos() {
|
|
|
return this.minStackPos;
|
|
|
}
|
|
|
// Move the parser forward. This will process all parse stacks at
|
|
|
// `this.pos` and try to advance them to a further position. If no
|
|
|
// stack for such a position is found, it'll start error-recovery.
|
|
|
//
|
|
|
// When the parse is finished, this will return a syntax tree. When
|
|
|
// not, it returns `null`.
|
|
|
advance() {
|
|
|
let stacks = this.stacks, pos = this.minStackPos;
|
|
|
// This will hold stacks beyond `pos`.
|
|
|
let newStacks = this.stacks = [];
|
|
|
let stopped, stoppedTokens;
|
|
|
// If a large amount of reductions happened with the same start
|
|
|
// position, force the stack out of that production in order to
|
|
|
// avoid creating a tree too deep to recurse through.
|
|
|
// (This is an ugly kludge, because unfortunately there is no
|
|
|
// straightforward, cheap way to check for this happening, due to
|
|
|
// the history of reductions only being available in an
|
|
|
// expensive-to-access format in the stack buffers.)
|
|
|
if (this.bigReductionCount > 300 /* Rec.MaxLeftAssociativeReductionCount */ && stacks.length == 1) {
|
|
|
let [s] = stacks;
|
|
|
while (s.forceReduce() && s.stack.length && s.stack[s.stack.length - 2] >= this.lastBigReductionStart) { }
|
|
|
this.bigReductionCount = this.lastBigReductionSize = 0;
|
|
|
}
|
|
|
// Keep advancing any stacks at `pos` until they either move
|
|
|
// forward or can't be advanced. Gather stacks that can't be
|
|
|
// advanced further in `stopped`.
|
|
|
for (let i = 0; i < stacks.length; i++) {
|
|
|
let stack = stacks[i];
|
|
|
for (;;) {
|
|
|
this.tokens.mainToken = null;
|
|
|
if (stack.pos > pos) {
|
|
|
newStacks.push(stack);
|
|
|
}
|
|
|
else if (this.advanceStack(stack, newStacks, stacks)) {
|
|
|
continue;
|
|
|
}
|
|
|
else {
|
|
|
if (!stopped) {
|
|
|
stopped = [];
|
|
|
stoppedTokens = [];
|
|
|
}
|
|
|
stopped.push(stack);
|
|
|
let tok = this.tokens.getMainToken(stack);
|
|
|
stoppedTokens.push(tok.value, tok.end);
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
if (!newStacks.length) {
|
|
|
let finished = stopped && findFinished(stopped);
|
|
|
if (finished) {
|
|
|
if (verbose)
|
|
|
console.log("Finish with " + this.stackID(finished));
|
|
|
return this.stackToTree(finished);
|
|
|
}
|
|
|
if (this.parser.strict) {
|
|
|
if (verbose && stopped)
|
|
|
console.log("Stuck with token " + (this.tokens.mainToken ? this.parser.getName(this.tokens.mainToken.value) : "none"));
|
|
|
throw new SyntaxError("No parse at " + pos);
|
|
|
}
|
|
|
if (!this.recovering)
|
|
|
this.recovering = 5 /* Rec.Distance */;
|
|
|
}
|
|
|
if (this.recovering && stopped) {
|
|
|
let finished = this.stoppedAt != null && stopped[0].pos > this.stoppedAt ? stopped[0]
|
|
|
: this.runRecovery(stopped, stoppedTokens, newStacks);
|
|
|
if (finished) {
|
|
|
if (verbose)
|
|
|
console.log("Force-finish " + this.stackID(finished));
|
|
|
return this.stackToTree(finished.forceAll());
|
|
|
}
|
|
|
}
|
|
|
if (this.recovering) {
|
|
|
let maxRemaining = this.recovering == 1 ? 1 : this.recovering * 3 /* Rec.MaxRemainingPerStep */;
|
|
|
if (newStacks.length > maxRemaining) {
|
|
|
newStacks.sort((a, b) => b.score - a.score);
|
|
|
while (newStacks.length > maxRemaining)
|
|
|
newStacks.pop();
|
|
|
}
|
|
|
if (newStacks.some(s => s.reducePos > pos))
|
|
|
this.recovering--;
|
|
|
}
|
|
|
else if (newStacks.length > 1) {
|
|
|
// Prune stacks that are in the same state, or that have been
|
|
|
// running without splitting for a while, to avoid getting stuck
|
|
|
// with multiple successful stacks running endlessly on.
|
|
|
outer: for (let i = 0; i < newStacks.length - 1; i++) {
|
|
|
let stack = newStacks[i];
|
|
|
for (let j = i + 1; j < newStacks.length; j++) {
|
|
|
let other = newStacks[j];
|
|
|
if (stack.sameState(other) ||
|
|
|
stack.buffer.length > 500 /* Rec.MinBufferLengthPrune */ && other.buffer.length > 500 /* Rec.MinBufferLengthPrune */) {
|
|
|
if (((stack.score - other.score) || (stack.buffer.length - other.buffer.length)) > 0) {
|
|
|
newStacks.splice(j--, 1);
|
|
|
}
|
|
|
else {
|
|
|
newStacks.splice(i--, 1);
|
|
|
continue outer;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
if (newStacks.length > 12 /* Rec.MaxStackCount */)
|
|
|
newStacks.splice(12 /* Rec.MaxStackCount */, newStacks.length - 12 /* Rec.MaxStackCount */);
|
|
|
}
|
|
|
this.minStackPos = newStacks[0].pos;
|
|
|
for (let i = 1; i < newStacks.length; i++)
|
|
|
if (newStacks[i].pos < this.minStackPos)
|
|
|
this.minStackPos = newStacks[i].pos;
|
|
|
return null;
|
|
|
}
|
|
|
stopAt(pos) {
|
|
|
if (this.stoppedAt != null && this.stoppedAt < pos)
|
|
|
throw new RangeError("Can't move stoppedAt forward");
|
|
|
this.stoppedAt = pos;
|
|
|
}
|
|
|
// Returns an updated version of the given stack, or null if the
|
|
|
// stack can't advance normally. When `split` and `stacks` are
|
|
|
// given, stacks split off by ambiguous operations will be pushed to
|
|
|
// `split`, or added to `stacks` if they move `pos` forward.
|
|
|
advanceStack(stack, stacks, split) {
|
|
|
let start = stack.pos, { parser } = this;
|
|
|
let base = verbose ? this.stackID(stack) + " -> " : "";
|
|
|
if (this.stoppedAt != null && start > this.stoppedAt)
|
|
|
return stack.forceReduce() ? stack : null;
|
|
|
if (this.fragments) {
|
|
|
let strictCx = stack.curContext && stack.curContext.tracker.strict, cxHash = strictCx ? stack.curContext.hash : 0;
|
|
|
for (let cached = this.fragments.nodeAt(start); cached;) {
|
|
|
let match = this.parser.nodeSet.types[cached.type.id] == cached.type ? parser.getGoto(stack.state, cached.type.id) : -1;
|
|
|
if (match > -1 && cached.length && (!strictCx || (cached.prop(_lezer_common__WEBPACK_IMPORTED_MODULE_0__.NodeProp.contextHash) || 0) == cxHash)) {
|
|
|
stack.useNode(cached, match);
|
|
|
if (verbose)
|
|
|
console.log(base + this.stackID(stack) + ` (via reuse of ${parser.getName(cached.type.id)})`);
|
|
|
return true;
|
|
|
}
|
|
|
if (!(cached instanceof _lezer_common__WEBPACK_IMPORTED_MODULE_0__.Tree) || cached.children.length == 0 || cached.positions[0] > 0)
|
|
|
break;
|
|
|
let inner = cached.children[0];
|
|
|
if (inner instanceof _lezer_common__WEBPACK_IMPORTED_MODULE_0__.Tree && cached.positions[0] == 0)
|
|
|
cached = inner;
|
|
|
else
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
let defaultReduce = parser.stateSlot(stack.state, 4 /* ParseState.DefaultReduce */);
|
|
|
if (defaultReduce > 0) {
|
|
|
stack.reduce(defaultReduce);
|
|
|
if (verbose)
|
|
|
console.log(base + this.stackID(stack) + ` (via always-reduce ${parser.getName(defaultReduce & 65535 /* Action.ValueMask */)})`);
|
|
|
return true;
|
|
|
}
|
|
|
if (stack.stack.length >= 9000 /* Rec.CutDepth */) {
|
|
|
while (stack.stack.length > 6000 /* Rec.CutTo */ && stack.forceReduce()) { }
|
|
|
}
|
|
|
let actions = this.tokens.getActions(stack);
|
|
|
for (let i = 0; i < actions.length;) {
|
|
|
let action = actions[i++], term = actions[i++], end = actions[i++];
|
|
|
let last = i == actions.length || !split;
|
|
|
let localStack = last ? stack : stack.split();
|
|
|
let main = this.tokens.mainToken;
|
|
|
localStack.apply(action, term, main ? main.start : localStack.pos, end);
|
|
|
if (verbose)
|
|
|
console.log(base + this.stackID(localStack) + ` (via ${(action & 65536 /* Action.ReduceFlag */) == 0 ? "shift"
|
|
|
: `reduce of ${parser.getName(action & 65535 /* Action.ValueMask */)}`} for ${parser.getName(term)} @ ${start}${localStack == stack ? "" : ", split"})`);
|
|
|
if (last)
|
|
|
return true;
|
|
|
else if (localStack.pos > start)
|
|
|
stacks.push(localStack);
|
|
|
else
|
|
|
split.push(localStack);
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
// Advance a given stack forward as far as it will go. Returns the
|
|
|
// (possibly updated) stack if it got stuck, or null if it moved
|
|
|
// forward and was given to `pushStackDedup`.
|
|
|
advanceFully(stack, newStacks) {
|
|
|
let pos = stack.pos;
|
|
|
for (;;) {
|
|
|
if (!this.advanceStack(stack, null, null))
|
|
|
return false;
|
|
|
if (stack.pos > pos) {
|
|
|
pushStackDedup(stack, newStacks);
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
runRecovery(stacks, tokens, newStacks) {
|
|
|
let finished = null, restarted = false;
|
|
|
for (let i = 0; i < stacks.length; i++) {
|
|
|
let stack = stacks[i], token = tokens[i << 1], tokenEnd = tokens[(i << 1) + 1];
|
|
|
let base = verbose ? this.stackID(stack) + " -> " : "";
|
|
|
if (stack.deadEnd) {
|
|
|
if (restarted)
|
|
|
continue;
|
|
|
restarted = true;
|
|
|
stack.restart();
|
|
|
if (verbose)
|
|
|
console.log(base + this.stackID(stack) + " (restarted)");
|
|
|
let done = this.advanceFully(stack, newStacks);
|
|
|
if (done)
|
|
|
continue;
|
|
|
}
|
|
|
let force = stack.split(), forceBase = base;
|
|
|
for (let j = 0; force.forceReduce() && j < 10 /* Rec.ForceReduceLimit */; j++) {
|
|
|
if (verbose)
|
|
|
console.log(forceBase + this.stackID(force) + " (via force-reduce)");
|
|
|
let done = this.advanceFully(force, newStacks);
|
|
|
if (done)
|
|
|
break;
|
|
|
if (verbose)
|
|
|
forceBase = this.stackID(force) + " -> ";
|
|
|
}
|
|
|
for (let insert of stack.recoverByInsert(token)) {
|
|
|
if (verbose)
|
|
|
console.log(base + this.stackID(insert) + " (via recover-insert)");
|
|
|
this.advanceFully(insert, newStacks);
|
|
|
}
|
|
|
if (this.stream.end > stack.pos) {
|
|
|
if (tokenEnd == stack.pos) {
|
|
|
tokenEnd++;
|
|
|
token = 0 /* Term.Err */;
|
|
|
}
|
|
|
stack.recoverByDelete(token, tokenEnd);
|
|
|
if (verbose)
|
|
|
console.log(base + this.stackID(stack) + ` (via recover-delete ${this.parser.getName(token)})`);
|
|
|
pushStackDedup(stack, newStacks);
|
|
|
}
|
|
|
else if (!finished || finished.score < stack.score) {
|
|
|
finished = stack;
|
|
|
}
|
|
|
}
|
|
|
return finished;
|
|
|
}
|
|
|
// Convert the stack's buffer to a syntax tree.
|
|
|
stackToTree(stack) {
|
|
|
stack.close();
|
|
|
return _lezer_common__WEBPACK_IMPORTED_MODULE_0__.Tree.build({ buffer: StackBufferCursor.create(stack),
|
|
|
nodeSet: this.parser.nodeSet,
|
|
|
topID: this.topTerm,
|
|
|
maxBufferLength: this.parser.bufferLength,
|
|
|
reused: this.reused,
|
|
|
start: this.ranges[0].from,
|
|
|
length: stack.pos - this.ranges[0].from,
|
|
|
minRepeatType: this.parser.minRepeatTerm });
|
|
|
}
|
|
|
stackID(stack) {
|
|
|
let id = (stackIDs || (stackIDs = new WeakMap)).get(stack);
|
|
|
if (!id)
|
|
|
stackIDs.set(stack, id = String.fromCodePoint(this.nextStackID++));
|
|
|
return id + stack;
|
|
|
}
|
|
|
}
|
|
|
function pushStackDedup(stack, newStacks) {
|
|
|
for (let i = 0; i < newStacks.length; i++) {
|
|
|
let other = newStacks[i];
|
|
|
if (other.pos == stack.pos && other.sameState(stack)) {
|
|
|
if (newStacks[i].score < stack.score)
|
|
|
newStacks[i] = stack;
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
newStacks.push(stack);
|
|
|
}
|
|
|
class Dialect {
|
|
|
constructor(source, flags, disabled) {
|
|
|
this.source = source;
|
|
|
this.flags = flags;
|
|
|
this.disabled = disabled;
|
|
|
}
|
|
|
allows(term) { return !this.disabled || this.disabled[term] == 0; }
|
|
|
}
|
|
|
const id = x => x;
|
|
|
/**
|
|
|
Context trackers are used to track stateful context (such as
|
|
|
indentation in the Python grammar, or parent elements in the XML
|
|
|
grammar) needed by external tokenizers. You declare them in a
|
|
|
grammar file as `@context exportName from "module"`.
|
|
|
|
|
|
Context values should be immutable, and can be updated (replaced)
|
|
|
on shift or reduce actions.
|
|
|
|
|
|
The export used in a `@context` declaration should be of this
|
|
|
type.
|
|
|
*/
|
|
|
class ContextTracker {
|
|
|
/**
|
|
|
Define a context tracker.
|
|
|
*/
|
|
|
constructor(spec) {
|
|
|
this.start = spec.start;
|
|
|
this.shift = spec.shift || id;
|
|
|
this.reduce = spec.reduce || id;
|
|
|
this.reuse = spec.reuse || id;
|
|
|
this.hash = spec.hash || (() => 0);
|
|
|
this.strict = spec.strict !== false;
|
|
|
}
|
|
|
}
|
|
|
/**
|
|
|
Holds the parse tables for a given grammar, as generated by
|
|
|
`lezer-generator`, and provides [methods](#common.Parser) to parse
|
|
|
content with.
|
|
|
*/
|
|
|
class LRParser extends _lezer_common__WEBPACK_IMPORTED_MODULE_0__.Parser {
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
constructor(spec) {
|
|
|
super();
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
this.wrappers = [];
|
|
|
if (spec.version != 14 /* File.Version */)
|
|
|
throw new RangeError(`Parser version (${spec.version}) doesn't match runtime version (${14 /* File.Version */})`);
|
|
|
let nodeNames = spec.nodeNames.split(" ");
|
|
|
this.minRepeatTerm = nodeNames.length;
|
|
|
for (let i = 0; i < spec.repeatNodeCount; i++)
|
|
|
nodeNames.push("");
|
|
|
let topTerms = Object.keys(spec.topRules).map(r => spec.topRules[r][1]);
|
|
|
let nodeProps = [];
|
|
|
for (let i = 0; i < nodeNames.length; i++)
|
|
|
nodeProps.push([]);
|
|
|
function setProp(nodeID, prop, value) {
|
|
|
nodeProps[nodeID].push([prop, prop.deserialize(String(value))]);
|
|
|
}
|
|
|
if (spec.nodeProps)
|
|
|
for (let propSpec of spec.nodeProps) {
|
|
|
let prop = propSpec[0];
|
|
|
if (typeof prop == "string")
|
|
|
prop = _lezer_common__WEBPACK_IMPORTED_MODULE_0__.NodeProp[prop];
|
|
|
for (let i = 1; i < propSpec.length;) {
|
|
|
let next = propSpec[i++];
|
|
|
if (next >= 0) {
|
|
|
setProp(next, prop, propSpec[i++]);
|
|
|
}
|
|
|
else {
|
|
|
let value = propSpec[i + -next];
|
|
|
for (let j = -next; j > 0; j--)
|
|
|
setProp(propSpec[i++], prop, value);
|
|
|
i++;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
this.nodeSet = new _lezer_common__WEBPACK_IMPORTED_MODULE_0__.NodeSet(nodeNames.map((name, i) => _lezer_common__WEBPACK_IMPORTED_MODULE_0__.NodeType.define({
|
|
|
name: i >= this.minRepeatTerm ? undefined : name,
|
|
|
id: i,
|
|
|
props: nodeProps[i],
|
|
|
top: topTerms.indexOf(i) > -1,
|
|
|
error: i == 0,
|
|
|
skipped: spec.skippedNodes && spec.skippedNodes.indexOf(i) > -1
|
|
|
})));
|
|
|
if (spec.propSources)
|
|
|
this.nodeSet = this.nodeSet.extend(...spec.propSources);
|
|
|
this.strict = false;
|
|
|
this.bufferLength = _lezer_common__WEBPACK_IMPORTED_MODULE_0__.DefaultBufferLength;
|
|
|
let tokenArray = decodeArray(spec.tokenData);
|
|
|
this.context = spec.context;
|
|
|
this.specializerSpecs = spec.specialized || [];
|
|
|
this.specialized = new Uint16Array(this.specializerSpecs.length);
|
|
|
for (let i = 0; i < this.specializerSpecs.length; i++)
|
|
|
this.specialized[i] = this.specializerSpecs[i].term;
|
|
|
this.specializers = this.specializerSpecs.map(getSpecializer);
|
|
|
this.states = decodeArray(spec.states, Uint32Array);
|
|
|
this.data = decodeArray(spec.stateData);
|
|
|
this.goto = decodeArray(spec.goto);
|
|
|
this.maxTerm = spec.maxTerm;
|
|
|
this.tokenizers = spec.tokenizers.map(value => typeof value == "number" ? new TokenGroup(tokenArray, value) : value);
|
|
|
this.topRules = spec.topRules;
|
|
|
this.dialects = spec.dialects || {};
|
|
|
this.dynamicPrecedences = spec.dynamicPrecedences || null;
|
|
|
this.tokenPrecTable = spec.tokenPrec;
|
|
|
this.termNames = spec.termNames || null;
|
|
|
this.maxNode = this.nodeSet.types.length - 1;
|
|
|
this.dialect = this.parseDialect();
|
|
|
this.top = this.topRules[Object.keys(this.topRules)[0]];
|
|
|
}
|
|
|
createParse(input, fragments, ranges) {
|
|
|
let parse = new Parse(this, input, fragments, ranges);
|
|
|
for (let w of this.wrappers)
|
|
|
parse = w(parse, input, fragments, ranges);
|
|
|
return parse;
|
|
|
}
|
|
|
/**
|
|
|
Get a goto table entry @internal
|
|
|
*/
|
|
|
getGoto(state, term, loose = false) {
|
|
|
let table = this.goto;
|
|
|
if (term >= table[0])
|
|
|
return -1;
|
|
|
for (let pos = table[term + 1];;) {
|
|
|
let groupTag = table[pos++], last = groupTag & 1;
|
|
|
let target = table[pos++];
|
|
|
if (last && loose)
|
|
|
return target;
|
|
|
for (let end = pos + (groupTag >> 1); pos < end; pos++)
|
|
|
if (table[pos] == state)
|
|
|
return target;
|
|
|
if (last)
|
|
|
return -1;
|
|
|
}
|
|
|
}
|
|
|
/**
|
|
|
Check if this state has an action for a given terminal @internal
|
|
|
*/
|
|
|
hasAction(state, terminal) {
|
|
|
let data = this.data;
|
|
|
for (let set = 0; set < 2; set++) {
|
|
|
for (let i = this.stateSlot(state, set ? 2 /* ParseState.Skip */ : 1 /* ParseState.Actions */), next;; i += 3) {
|
|
|
if ((next = data[i]) == 65535 /* Seq.End */) {
|
|
|
if (data[i + 1] == 1 /* Seq.Next */)
|
|
|
next = data[i = pair(data, i + 2)];
|
|
|
else if (data[i + 1] == 2 /* Seq.Other */)
|
|
|
return pair(data, i + 2);
|
|
|
else
|
|
|
break;
|
|
|
}
|
|
|
if (next == terminal || next == 0 /* Term.Err */)
|
|
|
return pair(data, i + 1);
|
|
|
}
|
|
|
}
|
|
|
return 0;
|
|
|
}
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
stateSlot(state, slot) {
|
|
|
return this.states[(state * 6 /* ParseState.Size */) + slot];
|
|
|
}
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
stateFlag(state, flag) {
|
|
|
return (this.stateSlot(state, 0 /* ParseState.Flags */) & flag) > 0;
|
|
|
}
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
validAction(state, action) {
|
|
|
return !!this.allActions(state, a => a == action ? true : null);
|
|
|
}
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
allActions(state, action) {
|
|
|
let deflt = this.stateSlot(state, 4 /* ParseState.DefaultReduce */);
|
|
|
let result = deflt ? action(deflt) : undefined;
|
|
|
for (let i = this.stateSlot(state, 1 /* ParseState.Actions */); result == null; i += 3) {
|
|
|
if (this.data[i] == 65535 /* Seq.End */) {
|
|
|
if (this.data[i + 1] == 1 /* Seq.Next */)
|
|
|
i = pair(this.data, i + 2);
|
|
|
else
|
|
|
break;
|
|
|
}
|
|
|
result = action(pair(this.data, i + 1));
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
/**
|
|
|
Get the states that can follow this one through shift actions or
|
|
|
goto jumps. @internal
|
|
|
*/
|
|
|
nextStates(state) {
|
|
|
let result = [];
|
|
|
for (let i = this.stateSlot(state, 1 /* ParseState.Actions */);; i += 3) {
|
|
|
if (this.data[i] == 65535 /* Seq.End */) {
|
|
|
if (this.data[i + 1] == 1 /* Seq.Next */)
|
|
|
i = pair(this.data, i + 2);
|
|
|
else
|
|
|
break;
|
|
|
}
|
|
|
if ((this.data[i + 2] & (65536 /* Action.ReduceFlag */ >> 16)) == 0) {
|
|
|
let value = this.data[i + 1];
|
|
|
if (!result.some((v, i) => (i & 1) && v == value))
|
|
|
result.push(this.data[i], value);
|
|
|
}
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
/**
|
|
|
Configure the parser. Returns a new parser instance that has the
|
|
|
given settings modified. Settings not provided in `config` are
|
|
|
kept from the original parser.
|
|
|
*/
|
|
|
configure(config) {
|
|
|
// Hideous reflection-based kludge to make it easy to create a
|
|
|
// slightly modified copy of a parser.
|
|
|
let copy = Object.assign(Object.create(LRParser.prototype), this);
|
|
|
if (config.props)
|
|
|
copy.nodeSet = this.nodeSet.extend(...config.props);
|
|
|
if (config.top) {
|
|
|
let info = this.topRules[config.top];
|
|
|
if (!info)
|
|
|
throw new RangeError(`Invalid top rule name ${config.top}`);
|
|
|
copy.top = info;
|
|
|
}
|
|
|
if (config.tokenizers)
|
|
|
copy.tokenizers = this.tokenizers.map(t => {
|
|
|
let found = config.tokenizers.find(r => r.from == t);
|
|
|
return found ? found.to : t;
|
|
|
});
|
|
|
if (config.specializers) {
|
|
|
copy.specializers = this.specializers.slice();
|
|
|
copy.specializerSpecs = this.specializerSpecs.map((s, i) => {
|
|
|
let found = config.specializers.find(r => r.from == s.external);
|
|
|
if (!found)
|
|
|
return s;
|
|
|
let spec = Object.assign(Object.assign({}, s), { external: found.to });
|
|
|
copy.specializers[i] = getSpecializer(spec);
|
|
|
return spec;
|
|
|
});
|
|
|
}
|
|
|
if (config.contextTracker)
|
|
|
copy.context = config.contextTracker;
|
|
|
if (config.dialect)
|
|
|
copy.dialect = this.parseDialect(config.dialect);
|
|
|
if (config.strict != null)
|
|
|
copy.strict = config.strict;
|
|
|
if (config.wrap)
|
|
|
copy.wrappers = copy.wrappers.concat(config.wrap);
|
|
|
if (config.bufferLength != null)
|
|
|
copy.bufferLength = config.bufferLength;
|
|
|
return copy;
|
|
|
}
|
|
|
/**
|
|
|
Tells you whether any [parse wrappers](#lr.ParserConfig.wrap)
|
|
|
are registered for this parser.
|
|
|
*/
|
|
|
hasWrappers() {
|
|
|
return this.wrappers.length > 0;
|
|
|
}
|
|
|
/**
|
|
|
Returns the name associated with a given term. This will only
|
|
|
work for all terms when the parser was generated with the
|
|
|
`--names` option. By default, only the names of tagged terms are
|
|
|
stored.
|
|
|
*/
|
|
|
getName(term) {
|
|
|
return this.termNames ? this.termNames[term] : String(term <= this.maxNode && this.nodeSet.types[term].name || term);
|
|
|
}
|
|
|
/**
|
|
|
The eof term id is always allocated directly after the node
|
|
|
types. @internal
|
|
|
*/
|
|
|
get eofTerm() { return this.maxNode + 1; }
|
|
|
/**
|
|
|
The type of top node produced by the parser.
|
|
|
*/
|
|
|
get topNode() { return this.nodeSet.types[this.top[1]]; }
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
dynamicPrecedence(term) {
|
|
|
let prec = this.dynamicPrecedences;
|
|
|
return prec == null ? 0 : prec[term] || 0;
|
|
|
}
|
|
|
/**
|
|
|
@internal
|
|
|
*/
|
|
|
parseDialect(dialect) {
|
|
|
let values = Object.keys(this.dialects), flags = values.map(() => false);
|
|
|
if (dialect)
|
|
|
for (let part of dialect.split(" ")) {
|
|
|
let id = values.indexOf(part);
|
|
|
if (id >= 0)
|
|
|
flags[id] = true;
|
|
|
}
|
|
|
let disabled = null;
|
|
|
for (let i = 0; i < values.length; i++)
|
|
|
if (!flags[i]) {
|
|
|
for (let j = this.dialects[values[i]], id; (id = this.data[j++]) != 65535 /* Seq.End */;)
|
|
|
(disabled || (disabled = new Uint8Array(this.maxTerm + 1)))[id] = 1;
|
|
|
}
|
|
|
return new Dialect(dialect, flags, disabled);
|
|
|
}
|
|
|
/**
|
|
|
Used by the output of the parser generator. Not available to
|
|
|
user code. @hide
|
|
|
*/
|
|
|
static deserialize(spec) {
|
|
|
return new LRParser(spec);
|
|
|
}
|
|
|
}
|
|
|
function pair(data, off) { return data[off] | (data[off + 1] << 16); }
|
|
|
function findFinished(stacks) {
|
|
|
let best = null;
|
|
|
for (let stack of stacks) {
|
|
|
let stopped = stack.p.stoppedAt;
|
|
|
if ((stack.pos == stack.p.stream.end || stopped != null && stack.pos > stopped) &&
|
|
|
stack.p.parser.stateFlag(stack.state, 2 /* StateFlag.Accepting */) &&
|
|
|
(!best || best.score < stack.score))
|
|
|
best = stack;
|
|
|
}
|
|
|
return best;
|
|
|
}
|
|
|
function getSpecializer(spec) {
|
|
|
if (spec.external) {
|
|
|
let mask = spec.extend ? 1 /* Specialize.Extend */ : 0 /* Specialize.Specialize */;
|
|
|
return (value, stack) => (spec.external(value, stack) << 1) | mask;
|
|
|
}
|
|
|
return spec.get;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/***/ }),
|
|
|
|
|
|
/***/ 27061:
|
|
|
/***/ ((module) => {
|
|
|
|
|
|
// shim for using process in browser
|
|
|
var process = module.exports = {};
|
|
|
|
|
|
// cached from whatever global is present so that test runners that stub it
|
|
|
// don't break things. But we need to wrap it in a try catch in case it is
|
|
|
// wrapped in strict mode code which doesn't define any globals. It's inside a
|
|
|
// function because try/catches deoptimize in certain engines.
|
|
|
|
|
|
var cachedSetTimeout;
|
|
|
var cachedClearTimeout;
|
|
|
|
|
|
function defaultSetTimout() {
|
|
|
throw new Error('setTimeout has not been defined');
|
|
|
}
|
|
|
function defaultClearTimeout () {
|
|
|
throw new Error('clearTimeout has not been defined');
|
|
|
}
|
|
|
(function () {
|
|
|
try {
|
|
|
if (typeof setTimeout === 'function') {
|
|
|
cachedSetTimeout = setTimeout;
|
|
|
} else {
|
|
|
cachedSetTimeout = defaultSetTimout;
|
|
|
}
|
|
|
} catch (e) {
|
|
|
cachedSetTimeout = defaultSetTimout;
|
|
|
}
|
|
|
try {
|
|
|
if (typeof clearTimeout === 'function') {
|
|
|
cachedClearTimeout = clearTimeout;
|
|
|
} else {
|
|
|
cachedClearTimeout = defaultClearTimeout;
|
|
|
}
|
|
|
} catch (e) {
|
|
|
cachedClearTimeout = defaultClearTimeout;
|
|
|
}
|
|
|
} ())
|
|
|
function runTimeout(fun) {
|
|
|
if (cachedSetTimeout === setTimeout) {
|
|
|
//normal enviroments in sane situations
|
|
|
return setTimeout(fun, 0);
|
|
|
}
|
|
|
// if setTimeout wasn't available but was latter defined
|
|
|
if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {
|
|
|
cachedSetTimeout = setTimeout;
|
|
|
return setTimeout(fun, 0);
|
|
|
}
|
|
|
try {
|
|
|
// when when somebody has screwed with setTimeout but no I.E. maddness
|
|
|
return cachedSetTimeout(fun, 0);
|
|
|
} catch(e){
|
|
|
try {
|
|
|
// When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
|
|
|
return cachedSetTimeout.call(null, fun, 0);
|
|
|
} catch(e){
|
|
|
// same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error
|
|
|
return cachedSetTimeout.call(this, fun, 0);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
}
|
|
|
function runClearTimeout(marker) {
|
|
|
if (cachedClearTimeout === clearTimeout) {
|
|
|
//normal enviroments in sane situations
|
|
|
return clearTimeout(marker);
|
|
|
}
|
|
|
// if clearTimeout wasn't available but was latter defined
|
|
|
if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {
|
|
|
cachedClearTimeout = clearTimeout;
|
|
|
return clearTimeout(marker);
|
|
|
}
|
|
|
try {
|
|
|
// when when somebody has screwed with setTimeout but no I.E. maddness
|
|
|
return cachedClearTimeout(marker);
|
|
|
} catch (e){
|
|
|
try {
|
|
|
// When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
|
|
|
return cachedClearTimeout.call(null, marker);
|
|
|
} catch (e){
|
|
|
// same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.
|
|
|
// Some versions of I.E. have different rules for clearTimeout vs setTimeout
|
|
|
return cachedClearTimeout.call(this, marker);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
var queue = [];
|
|
|
var draining = false;
|
|
|
var currentQueue;
|
|
|
var queueIndex = -1;
|
|
|
|
|
|
function cleanUpNextTick() {
|
|
|
if (!draining || !currentQueue) {
|
|
|
return;
|
|
|
}
|
|
|
draining = false;
|
|
|
if (currentQueue.length) {
|
|
|
queue = currentQueue.concat(queue);
|
|
|
} else {
|
|
|
queueIndex = -1;
|
|
|
}
|
|
|
if (queue.length) {
|
|
|
drainQueue();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function drainQueue() {
|
|
|
if (draining) {
|
|
|
return;
|
|
|
}
|
|
|
var timeout = runTimeout(cleanUpNextTick);
|
|
|
draining = true;
|
|
|
|
|
|
var len = queue.length;
|
|
|
while(len) {
|
|
|
currentQueue = queue;
|
|
|
queue = [];
|
|
|
while (++queueIndex < len) {
|
|
|
if (currentQueue) {
|
|
|
currentQueue[queueIndex].run();
|
|
|
}
|
|
|
}
|
|
|
queueIndex = -1;
|
|
|
len = queue.length;
|
|
|
}
|
|
|
currentQueue = null;
|
|
|
draining = false;
|
|
|
runClearTimeout(timeout);
|
|
|
}
|
|
|
|
|
|
process.nextTick = function (fun) {
|
|
|
var args = new Array(arguments.length - 1);
|
|
|
if (arguments.length > 1) {
|
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
|
args[i - 1] = arguments[i];
|
|
|
}
|
|
|
}
|
|
|
queue.push(new Item(fun, args));
|
|
|
if (queue.length === 1 && !draining) {
|
|
|
runTimeout(drainQueue);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// v8 likes predictible objects
|
|
|
function Item(fun, array) {
|
|
|
this.fun = fun;
|
|
|
this.array = array;
|
|
|
}
|
|
|
Item.prototype.run = function () {
|
|
|
this.fun.apply(null, this.array);
|
|
|
};
|
|
|
process.title = 'browser';
|
|
|
process.browser = true;
|
|
|
process.env = {};
|
|
|
process.argv = [];
|
|
|
process.version = ''; // empty string to avoid regexp issues
|
|
|
process.versions = {};
|
|
|
|
|
|
function noop() {}
|
|
|
|
|
|
process.on = noop;
|
|
|
process.addListener = noop;
|
|
|
process.once = noop;
|
|
|
process.off = noop;
|
|
|
process.removeListener = noop;
|
|
|
process.removeAllListeners = noop;
|
|
|
process.emit = noop;
|
|
|
process.prependListener = noop;
|
|
|
process.prependOnceListener = noop;
|
|
|
|
|
|
process.listeners = function (name) { return [] }
|
|
|
|
|
|
process.binding = function (name) {
|
|
|
throw new Error('process.binding is not supported');
|
|
|
};
|
|
|
|
|
|
process.cwd = function () { return '/' };
|
|
|
process.chdir = function (dir) {
|
|
|
throw new Error('process.chdir is not supported');
|
|
|
};
|
|
|
process.umask = function() { return 0; };
|
|
|
|
|
|
|
|
|
/***/ })
|
|
|
|
|
|
}]);
|
|
|
//# sourceMappingURL=9799.f8f37b03cc4afc27f8f0.js.map?v=f8f37b03cc4afc27f8f0
|