2010 lines
66 KiB
JavaScript
2010 lines
66 KiB
JavaScript
|
|
import {
|
|||
|
|
defaultKeymap,
|
|||
|
|
history,
|
|||
|
|
historyKeymap
|
|||
|
|
} from "./chunk-JXETLIGR.js";
|
|||
|
|
import {
|
|||
|
|
autocompletion,
|
|||
|
|
closeBrackets,
|
|||
|
|
closeBracketsKeymap,
|
|||
|
|
completionKeymap
|
|||
|
|
} from "./chunk-FTVURZJQ.js";
|
|||
|
|
import {
|
|||
|
|
bracketMatching,
|
|||
|
|
defaultHighlightStyle,
|
|||
|
|
foldGutter,
|
|||
|
|
foldKeymap,
|
|||
|
|
indentOnInput,
|
|||
|
|
syntaxHighlighting
|
|||
|
|
} from "./chunk-UIRP74HC.js";
|
|||
|
|
import {
|
|||
|
|
Decoration,
|
|||
|
|
EditorView,
|
|||
|
|
GutterMarker,
|
|||
|
|
ViewPlugin,
|
|||
|
|
WidgetType,
|
|||
|
|
activateHover,
|
|||
|
|
crelt,
|
|||
|
|
crosshairCursor,
|
|||
|
|
drawSelection,
|
|||
|
|
dropCursor,
|
|||
|
|
getPanel,
|
|||
|
|
gutter,
|
|||
|
|
highlightActiveLine,
|
|||
|
|
highlightActiveLineGutter,
|
|||
|
|
highlightSpecialChars,
|
|||
|
|
hoverTooltip,
|
|||
|
|
keymap,
|
|||
|
|
lineNumbers,
|
|||
|
|
logException,
|
|||
|
|
rectangularSelection,
|
|||
|
|
runScopeHandlers,
|
|||
|
|
showDialog,
|
|||
|
|
showPanel,
|
|||
|
|
showTooltip
|
|||
|
|
} from "./chunk-M6T3QFJD.js";
|
|||
|
|
import {
|
|||
|
|
CharCategory,
|
|||
|
|
EditorSelection,
|
|||
|
|
EditorState,
|
|||
|
|
Facet,
|
|||
|
|
Prec,
|
|||
|
|
RangeSet,
|
|||
|
|
RangeSetBuilder,
|
|||
|
|
StateEffect,
|
|||
|
|
StateField,
|
|||
|
|
codePointAt,
|
|||
|
|
codePointSize,
|
|||
|
|
combineConfig,
|
|||
|
|
findClusterBreak,
|
|||
|
|
fromCodePoint
|
|||
|
|
} from "./chunk-4MUKC4ON.js";
|
|||
|
|
|
|||
|
|
// node_modules/@codemirror/search/dist/index.js
|
|||
|
|
var basicNormalize = typeof String.prototype.normalize == "function" ? (x) => x.normalize("NFKD") : (x) => x;
|
|||
|
|
var SearchCursor = class {
|
|||
|
|
/**
|
|||
|
|
Create a text cursor. The query is the search string, `from` to
|
|||
|
|
`to` provides the region to search.
|
|||
|
|
|
|||
|
|
When `normalize` is given, it will be called, on both the query
|
|||
|
|
string and the content it is matched against, before comparing.
|
|||
|
|
You can, for example, create a case-insensitive search by
|
|||
|
|
passing `s => s.toLowerCase()`.
|
|||
|
|
|
|||
|
|
Text is always normalized with
|
|||
|
|
[`.normalize("NFKD")`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize)
|
|||
|
|
(when supported).
|
|||
|
|
*/
|
|||
|
|
constructor(text, query, from = 0, to = text.length, normalize, test) {
|
|||
|
|
this.test = test;
|
|||
|
|
this.value = { from: 0, to: 0, precise: false };
|
|||
|
|
this.done = false;
|
|||
|
|
this.matches = [];
|
|||
|
|
this.buffer = "";
|
|||
|
|
this.bufferPos = 0;
|
|||
|
|
this.iter = text.iterRange(from, to);
|
|||
|
|
this.bufferStart = from;
|
|||
|
|
this.normalize = normalize ? (x) => normalize(basicNormalize(x)) : basicNormalize;
|
|||
|
|
this.query = this.normalize(query);
|
|||
|
|
}
|
|||
|
|
peek() {
|
|||
|
|
if (this.bufferPos == this.buffer.length) {
|
|||
|
|
this.bufferStart += this.buffer.length;
|
|||
|
|
this.iter.next();
|
|||
|
|
if (this.iter.done)
|
|||
|
|
return -1;
|
|||
|
|
this.bufferPos = 0;
|
|||
|
|
this.buffer = this.iter.value;
|
|||
|
|
}
|
|||
|
|
return codePointAt(this.buffer, this.bufferPos);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Look for the next match. Updates the iterator's
|
|||
|
|
[`value`](https://codemirror.net/6/docs/ref/#search.SearchCursor.value) and
|
|||
|
|
[`done`](https://codemirror.net/6/docs/ref/#search.SearchCursor.done) properties. Should be called
|
|||
|
|
at least once before using the cursor.
|
|||
|
|
*/
|
|||
|
|
next() {
|
|||
|
|
while (this.matches.length)
|
|||
|
|
this.matches.pop();
|
|||
|
|
return this.nextOverlapping();
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
The `next` method will ignore matches that partially overlap a
|
|||
|
|
previous match. This method behaves like `next`, but includes
|
|||
|
|
such matches.
|
|||
|
|
*/
|
|||
|
|
nextOverlapping() {
|
|||
|
|
for (; ; ) {
|
|||
|
|
let next = this.peek();
|
|||
|
|
if (next < 0) {
|
|||
|
|
this.done = true;
|
|||
|
|
return this;
|
|||
|
|
}
|
|||
|
|
let str = fromCodePoint(next), start = this.bufferStart + this.bufferPos;
|
|||
|
|
this.bufferPos += codePointSize(next);
|
|||
|
|
let norm = this.normalize(str);
|
|||
|
|
if (norm.length)
|
|||
|
|
for (let i = 0, pos = start, posPrecise = true; ; i++) {
|
|||
|
|
let code = norm.charCodeAt(i);
|
|||
|
|
let match = this.match(code, pos, posPrecise, this.bufferPos + this.bufferStart, i == norm.length - 1);
|
|||
|
|
if (match) {
|
|||
|
|
this.value = match;
|
|||
|
|
return this;
|
|||
|
|
}
|
|||
|
|
if (i == norm.length - 1)
|
|||
|
|
break;
|
|||
|
|
if (posPrecise && i < str.length && str.charCodeAt(i) == code)
|
|||
|
|
pos++;
|
|||
|
|
else
|
|||
|
|
posPrecise = false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
match(code, pos, posPrecise, end, endPrecise) {
|
|||
|
|
let match = null;
|
|||
|
|
for (let i = 0; i < this.matches.length; ) {
|
|||
|
|
let partial = this.matches[i], keep = false;
|
|||
|
|
if (this.query.charCodeAt(partial.index) == code) {
|
|||
|
|
if (partial.index == this.query.length - 1) {
|
|||
|
|
match = { from: partial.from, to: end, precise: endPrecise && partial.precise };
|
|||
|
|
} else {
|
|||
|
|
partial.index++;
|
|||
|
|
keep = true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (keep)
|
|||
|
|
i++;
|
|||
|
|
else
|
|||
|
|
this.matches.splice(i, 1);
|
|||
|
|
}
|
|||
|
|
if (this.query.charCodeAt(0) == code) {
|
|||
|
|
if (this.query.length == 1)
|
|||
|
|
match = { from: pos, to: end, precise: posPrecise && endPrecise };
|
|||
|
|
else
|
|||
|
|
this.matches.push({ from: pos, index: 1, precise: posPrecise });
|
|||
|
|
}
|
|||
|
|
if (match && this.test && !this.test(match.from, match.to, this.buffer, this.bufferStart))
|
|||
|
|
match = null;
|
|||
|
|
return match;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
if (typeof Symbol != "undefined")
|
|||
|
|
SearchCursor.prototype[Symbol.iterator] = function() {
|
|||
|
|
return this;
|
|||
|
|
};
|
|||
|
|
var empty = { from: -1, to: -1, match: /.*/.exec(""), precise: true };
|
|||
|
|
var baseFlags = "gm" + (/x/.unicode == null ? "" : "u");
|
|||
|
|
var RegExpCursor = class {
|
|||
|
|
/**
|
|||
|
|
Create a cursor that will search the given range in the given
|
|||
|
|
document. `query` should be the raw pattern (as you'd pass it to
|
|||
|
|
`new RegExp`).
|
|||
|
|
*/
|
|||
|
|
constructor(text, query, options, from = 0, to = text.length) {
|
|||
|
|
this.text = text;
|
|||
|
|
this.to = to;
|
|||
|
|
this.curLine = "";
|
|||
|
|
this.done = false;
|
|||
|
|
this.value = empty;
|
|||
|
|
if (/\\[sWDnr]|\n|\r|\[\^/.test(query))
|
|||
|
|
return new MultilineRegExpCursor(text, query, options, from, to);
|
|||
|
|
this.re = new RegExp(query, baseFlags + ((options === null || options === void 0 ? void 0 : options.ignoreCase) ? "i" : ""));
|
|||
|
|
this.test = options === null || options === void 0 ? void 0 : options.test;
|
|||
|
|
this.iter = text.iter();
|
|||
|
|
let startLine = text.lineAt(from);
|
|||
|
|
this.curLineStart = startLine.from;
|
|||
|
|
this.matchPos = toCharEnd(text, from);
|
|||
|
|
this.getLine(this.curLineStart);
|
|||
|
|
}
|
|||
|
|
getLine(skip) {
|
|||
|
|
this.iter.next(skip);
|
|||
|
|
if (this.iter.lineBreak) {
|
|||
|
|
this.curLine = "";
|
|||
|
|
} else {
|
|||
|
|
this.curLine = this.iter.value;
|
|||
|
|
if (this.curLineStart + this.curLine.length > this.to)
|
|||
|
|
this.curLine = this.curLine.slice(0, this.to - this.curLineStart);
|
|||
|
|
this.iter.next();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
nextLine() {
|
|||
|
|
this.curLineStart = this.curLineStart + this.curLine.length + 1;
|
|||
|
|
if (this.curLineStart > this.to)
|
|||
|
|
this.curLine = "";
|
|||
|
|
else
|
|||
|
|
this.getLine(0);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Move to the next match, if there is one.
|
|||
|
|
*/
|
|||
|
|
next() {
|
|||
|
|
for (let off = this.matchPos - this.curLineStart; ; ) {
|
|||
|
|
this.re.lastIndex = off;
|
|||
|
|
let match = this.matchPos <= this.to && this.re.exec(this.curLine);
|
|||
|
|
if (match) {
|
|||
|
|
let from = this.curLineStart + match.index, to = from + match[0].length;
|
|||
|
|
this.matchPos = toCharEnd(this.text, to + (from == to ? 1 : 0));
|
|||
|
|
if (from == this.curLineStart + this.curLine.length)
|
|||
|
|
this.nextLine();
|
|||
|
|
if ((from < to || from > this.value.to) && (!this.test || this.test(from, to, match))) {
|
|||
|
|
this.value = { from, to, precise: true, match };
|
|||
|
|
return this;
|
|||
|
|
}
|
|||
|
|
off = this.matchPos - this.curLineStart;
|
|||
|
|
} else if (this.curLineStart + this.curLine.length < this.to) {
|
|||
|
|
this.nextLine();
|
|||
|
|
off = 0;
|
|||
|
|
} else {
|
|||
|
|
this.done = true;
|
|||
|
|
return this;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var flattened = /* @__PURE__ */ new WeakMap();
|
|||
|
|
var FlattenedDoc = class _FlattenedDoc {
|
|||
|
|
constructor(from, text) {
|
|||
|
|
this.from = from;
|
|||
|
|
this.text = text;
|
|||
|
|
}
|
|||
|
|
get to() {
|
|||
|
|
return this.from + this.text.length;
|
|||
|
|
}
|
|||
|
|
static get(doc, from, to) {
|
|||
|
|
let cached = flattened.get(doc);
|
|||
|
|
if (!cached || cached.from >= to || cached.to <= from) {
|
|||
|
|
let flat = new _FlattenedDoc(from, doc.sliceString(from, to));
|
|||
|
|
flattened.set(doc, flat);
|
|||
|
|
return flat;
|
|||
|
|
}
|
|||
|
|
if (cached.from == from && cached.to == to)
|
|||
|
|
return cached;
|
|||
|
|
let { text, from: cachedFrom } = cached;
|
|||
|
|
if (cachedFrom > from) {
|
|||
|
|
text = doc.sliceString(from, cachedFrom) + text;
|
|||
|
|
cachedFrom = from;
|
|||
|
|
}
|
|||
|
|
if (cached.to < to)
|
|||
|
|
text += doc.sliceString(cached.to, to);
|
|||
|
|
flattened.set(doc, new _FlattenedDoc(cachedFrom, text));
|
|||
|
|
return new _FlattenedDoc(from, text.slice(from - cachedFrom, to - cachedFrom));
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var MultilineRegExpCursor = class {
|
|||
|
|
constructor(text, query, options, from, to) {
|
|||
|
|
this.text = text;
|
|||
|
|
this.to = to;
|
|||
|
|
this.done = false;
|
|||
|
|
this.value = empty;
|
|||
|
|
this.matchPos = toCharEnd(text, from);
|
|||
|
|
this.re = new RegExp(query, baseFlags + ((options === null || options === void 0 ? void 0 : options.ignoreCase) ? "i" : ""));
|
|||
|
|
this.test = options === null || options === void 0 ? void 0 : options.test;
|
|||
|
|
this.flat = FlattenedDoc.get(text, from, this.chunkEnd(
|
|||
|
|
from + 5e3
|
|||
|
|
/* Chunk.Base */
|
|||
|
|
));
|
|||
|
|
}
|
|||
|
|
chunkEnd(pos) {
|
|||
|
|
return pos >= this.to ? this.to : this.text.lineAt(pos).to;
|
|||
|
|
}
|
|||
|
|
next() {
|
|||
|
|
for (; ; ) {
|
|||
|
|
let off = this.re.lastIndex = this.matchPos - this.flat.from;
|
|||
|
|
let match = this.re.exec(this.flat.text);
|
|||
|
|
if (match && !match[0] && match.index == off) {
|
|||
|
|
this.re.lastIndex = off + 1;
|
|||
|
|
match = this.re.exec(this.flat.text);
|
|||
|
|
}
|
|||
|
|
if (match) {
|
|||
|
|
let from = this.flat.from + match.index, to = from + match[0].length;
|
|||
|
|
if ((this.flat.to >= this.to || match.index + match[0].length <= this.flat.text.length - 10) && (!this.test || this.test(from, to, match))) {
|
|||
|
|
this.value = { from, to, precise: true, match };
|
|||
|
|
this.matchPos = toCharEnd(this.text, to + (from == to ? 1 : 0));
|
|||
|
|
return this;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (this.flat.to == this.to) {
|
|||
|
|
this.done = true;
|
|||
|
|
return this;
|
|||
|
|
}
|
|||
|
|
this.flat = FlattenedDoc.get(this.text, this.flat.from, this.chunkEnd(this.flat.from + this.flat.text.length * 2));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
if (typeof Symbol != "undefined") {
|
|||
|
|
RegExpCursor.prototype[Symbol.iterator] = MultilineRegExpCursor.prototype[Symbol.iterator] = function() {
|
|||
|
|
return this;
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
function validRegExp(source) {
|
|||
|
|
try {
|
|||
|
|
new RegExp(source, baseFlags);
|
|||
|
|
return true;
|
|||
|
|
} catch (_a) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
function toCharEnd(text, pos) {
|
|||
|
|
if (pos >= text.length)
|
|||
|
|
return pos;
|
|||
|
|
let line = text.lineAt(pos), next;
|
|||
|
|
while (pos < line.to && (next = line.text.charCodeAt(pos - line.from)) >= 56320 && next < 57344)
|
|||
|
|
pos++;
|
|||
|
|
return pos;
|
|||
|
|
}
|
|||
|
|
var gotoLine = (view) => {
|
|||
|
|
let { state } = view;
|
|||
|
|
let line = String(state.doc.lineAt(view.state.selection.main.head).number);
|
|||
|
|
let { close, result } = showDialog(view, {
|
|||
|
|
label: state.phrase("Go to line"),
|
|||
|
|
input: { type: "text", name: "line", value: line },
|
|||
|
|
focus: true,
|
|||
|
|
submitLabel: state.phrase("go")
|
|||
|
|
});
|
|||
|
|
result.then((form) => {
|
|||
|
|
let match = form && /^([+-])?(\d+)?(:\d+)?(%)?$/.exec(form.elements["line"].value);
|
|||
|
|
if (!match) {
|
|||
|
|
view.dispatch({ effects: close });
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
let startLine = state.doc.lineAt(state.selection.main.head);
|
|||
|
|
let [, sign, ln, cl, percent] = match;
|
|||
|
|
let col = cl ? +cl.slice(1) : 0;
|
|||
|
|
let line2 = ln ? +ln : startLine.number;
|
|||
|
|
if (ln && percent) {
|
|||
|
|
let pc = line2 / 100;
|
|||
|
|
if (sign)
|
|||
|
|
pc = pc * (sign == "-" ? -1 : 1) + startLine.number / state.doc.lines;
|
|||
|
|
line2 = Math.round(state.doc.lines * pc);
|
|||
|
|
} else if (ln && sign) {
|
|||
|
|
line2 = line2 * (sign == "-" ? -1 : 1) + startLine.number;
|
|||
|
|
}
|
|||
|
|
let docLine = state.doc.line(Math.max(1, Math.min(state.doc.lines, line2)));
|
|||
|
|
let selection = EditorSelection.cursor(docLine.from + Math.max(0, Math.min(col, docLine.length)));
|
|||
|
|
view.dispatch({
|
|||
|
|
effects: [close, EditorView.scrollIntoView(selection.from, { y: "center" })],
|
|||
|
|
selection
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
return true;
|
|||
|
|
};
|
|||
|
|
var defaultHighlightOptions = {
|
|||
|
|
highlightWordAroundCursor: false,
|
|||
|
|
minSelectionLength: 1,
|
|||
|
|
maxMatches: 100,
|
|||
|
|
wholeWords: false
|
|||
|
|
};
|
|||
|
|
var highlightConfig = Facet.define({
|
|||
|
|
combine(options) {
|
|||
|
|
return combineConfig(options, defaultHighlightOptions, {
|
|||
|
|
highlightWordAroundCursor: (a, b) => a || b,
|
|||
|
|
minSelectionLength: Math.min,
|
|||
|
|
maxMatches: Math.min
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
function highlightSelectionMatches(options) {
|
|||
|
|
let ext = [defaultTheme, matchHighlighter];
|
|||
|
|
if (options)
|
|||
|
|
ext.push(highlightConfig.of(options));
|
|||
|
|
return ext;
|
|||
|
|
}
|
|||
|
|
var matchDeco = Decoration.mark({ class: "cm-selectionMatch" });
|
|||
|
|
var mainMatchDeco = Decoration.mark({ class: "cm-selectionMatch cm-selectionMatch-main" });
|
|||
|
|
function insideWordBoundaries(check, state, from, to) {
|
|||
|
|
return (from == 0 || check(state.sliceDoc(from - 1, from)) != CharCategory.Word) && (to == state.doc.length || check(state.sliceDoc(to, to + 1)) != CharCategory.Word);
|
|||
|
|
}
|
|||
|
|
function insideWord(check, state, from, to) {
|
|||
|
|
return check(state.sliceDoc(from, from + 1)) == CharCategory.Word && check(state.sliceDoc(to - 1, to)) == CharCategory.Word;
|
|||
|
|
}
|
|||
|
|
var matchHighlighter = ViewPlugin.fromClass(class {
|
|||
|
|
constructor(view) {
|
|||
|
|
this.decorations = this.getDeco(view);
|
|||
|
|
}
|
|||
|
|
update(update) {
|
|||
|
|
if (update.selectionSet || update.docChanged || update.viewportChanged)
|
|||
|
|
this.decorations = this.getDeco(update.view);
|
|||
|
|
}
|
|||
|
|
getDeco(view) {
|
|||
|
|
let conf = view.state.facet(highlightConfig);
|
|||
|
|
let { state } = view, sel = state.selection;
|
|||
|
|
if (sel.ranges.length > 1)
|
|||
|
|
return Decoration.none;
|
|||
|
|
let range = sel.main, query, check = null;
|
|||
|
|
if (range.empty) {
|
|||
|
|
if (!conf.highlightWordAroundCursor)
|
|||
|
|
return Decoration.none;
|
|||
|
|
let word = state.wordAt(range.head);
|
|||
|
|
if (!word)
|
|||
|
|
return Decoration.none;
|
|||
|
|
check = state.charCategorizer(range.head);
|
|||
|
|
query = state.sliceDoc(word.from, word.to);
|
|||
|
|
} else {
|
|||
|
|
let len = range.to - range.from;
|
|||
|
|
if (len < conf.minSelectionLength || len > 200)
|
|||
|
|
return Decoration.none;
|
|||
|
|
if (conf.wholeWords) {
|
|||
|
|
query = state.sliceDoc(range.from, range.to);
|
|||
|
|
check = state.charCategorizer(range.head);
|
|||
|
|
if (!(insideWordBoundaries(check, state, range.from, range.to) && insideWord(check, state, range.from, range.to)))
|
|||
|
|
return Decoration.none;
|
|||
|
|
} else {
|
|||
|
|
query = state.sliceDoc(range.from, range.to);
|
|||
|
|
if (!query)
|
|||
|
|
return Decoration.none;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
let deco = [];
|
|||
|
|
for (let part of view.visibleRanges) {
|
|||
|
|
let cursor = new SearchCursor(state.doc, query, part.from, part.to);
|
|||
|
|
while (!cursor.next().done) {
|
|||
|
|
let { from, to } = cursor.value;
|
|||
|
|
if (!check || insideWordBoundaries(check, state, from, to)) {
|
|||
|
|
if (range.empty && from <= range.from && to >= range.to)
|
|||
|
|
deco.push(mainMatchDeco.range(from, to));
|
|||
|
|
else if (from >= range.to || to <= range.from)
|
|||
|
|
deco.push(matchDeco.range(from, to));
|
|||
|
|
if (deco.length > conf.maxMatches)
|
|||
|
|
return Decoration.none;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return Decoration.set(deco);
|
|||
|
|
}
|
|||
|
|
}, {
|
|||
|
|
decorations: (v) => v.decorations
|
|||
|
|
});
|
|||
|
|
var defaultTheme = EditorView.baseTheme({
|
|||
|
|
".cm-selectionMatch": { backgroundColor: "#99ff7780" },
|
|||
|
|
".cm-searchMatch .cm-selectionMatch": { backgroundColor: "transparent" }
|
|||
|
|
});
|
|||
|
|
var selectWord = ({ state, dispatch }) => {
|
|||
|
|
let { selection } = state;
|
|||
|
|
let newSel = EditorSelection.create(selection.ranges.map((range) => state.wordAt(range.head) || EditorSelection.cursor(range.head)), selection.mainIndex);
|
|||
|
|
if (newSel.eq(selection))
|
|||
|
|
return false;
|
|||
|
|
dispatch(state.update({ selection: newSel }));
|
|||
|
|
return true;
|
|||
|
|
};
|
|||
|
|
function findNextOccurrence(state, query) {
|
|||
|
|
let { main, ranges } = state.selection;
|
|||
|
|
let word = state.wordAt(main.head), fullWord = word && word.from == main.from && word.to == main.to;
|
|||
|
|
for (let cycled = false, cursor = new SearchCursor(state.doc, query, ranges[ranges.length - 1].to); ; ) {
|
|||
|
|
cursor.next();
|
|||
|
|
if (cursor.done) {
|
|||
|
|
if (cycled)
|
|||
|
|
return null;
|
|||
|
|
cursor = new SearchCursor(state.doc, query, 0, Math.max(0, ranges[ranges.length - 1].from - 1));
|
|||
|
|
cycled = true;
|
|||
|
|
} else {
|
|||
|
|
if (cycled && ranges.some((r) => r.from == cursor.value.from))
|
|||
|
|
continue;
|
|||
|
|
if (fullWord) {
|
|||
|
|
let word2 = state.wordAt(cursor.value.from);
|
|||
|
|
if (!word2 || word2.from != cursor.value.from || word2.to != cursor.value.to)
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
return cursor.value;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
var selectNextOccurrence = ({ state, dispatch }) => {
|
|||
|
|
let { ranges } = state.selection;
|
|||
|
|
if (ranges.some((sel) => sel.from === sel.to))
|
|||
|
|
return selectWord({ state, dispatch });
|
|||
|
|
let searchedText = state.sliceDoc(ranges[0].from, ranges[0].to);
|
|||
|
|
if (state.selection.ranges.some((r) => state.sliceDoc(r.from, r.to) != searchedText))
|
|||
|
|
return false;
|
|||
|
|
let range = findNextOccurrence(state, searchedText);
|
|||
|
|
if (!range)
|
|||
|
|
return false;
|
|||
|
|
dispatch(state.update({
|
|||
|
|
selection: state.selection.addRange(EditorSelection.range(range.from, range.to), false),
|
|||
|
|
effects: EditorView.scrollIntoView(range.to)
|
|||
|
|
}));
|
|||
|
|
return true;
|
|||
|
|
};
|
|||
|
|
var searchConfigFacet = Facet.define({
|
|||
|
|
combine(configs) {
|
|||
|
|
return combineConfig(configs, {
|
|||
|
|
top: false,
|
|||
|
|
caseSensitive: false,
|
|||
|
|
literal: false,
|
|||
|
|
regexp: false,
|
|||
|
|
wholeWord: false,
|
|||
|
|
createPanel: (view) => new SearchPanel(view),
|
|||
|
|
scrollToMatch: (range) => EditorView.scrollIntoView(range)
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
var SearchQuery = class {
|
|||
|
|
/**
|
|||
|
|
Create a query object.
|
|||
|
|
*/
|
|||
|
|
constructor(config) {
|
|||
|
|
this.search = config.search;
|
|||
|
|
this.caseSensitive = !!config.caseSensitive;
|
|||
|
|
this.literal = !!config.literal;
|
|||
|
|
this.regexp = !!config.regexp;
|
|||
|
|
this.replace = config.replace || "";
|
|||
|
|
this.valid = !!this.search && (!this.regexp || validRegExp(this.search));
|
|||
|
|
this.unquoted = this.unquote(this.search);
|
|||
|
|
this.wholeWord = !!config.wholeWord;
|
|||
|
|
this.test = config.test;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
@internal
|
|||
|
|
*/
|
|||
|
|
unquote(text) {
|
|||
|
|
return this.literal ? text : text.replace(/\\([nrt\\])/g, (_, ch) => ch == "n" ? "\n" : ch == "r" ? "\r" : ch == "t" ? " " : "\\");
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Compare this query to another query.
|
|||
|
|
*/
|
|||
|
|
eq(other) {
|
|||
|
|
return this.search == other.search && this.replace == other.replace && this.caseSensitive == other.caseSensitive && this.regexp == other.regexp && this.wholeWord == other.wholeWord && this.test == other.test;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
@internal
|
|||
|
|
*/
|
|||
|
|
create() {
|
|||
|
|
return this.regexp ? new RegExpQuery(this) : new StringQuery(this);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Get a search cursor for this query, searching through the given
|
|||
|
|
range in the given state.
|
|||
|
|
*/
|
|||
|
|
getCursor(state, from = 0, to) {
|
|||
|
|
let st = state.doc ? state : EditorState.create({ doc: state });
|
|||
|
|
if (to == null)
|
|||
|
|
to = st.doc.length;
|
|||
|
|
return this.regexp ? regexpCursor(this, st, from, to) : stringCursor(this, st, from, to);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var QueryType = class {
|
|||
|
|
constructor(spec) {
|
|||
|
|
this.spec = spec;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function wrapStringTest(test, state, inner) {
|
|||
|
|
return (from, to, buffer, bufferPos) => {
|
|||
|
|
if (inner && !inner(from, to, buffer, bufferPos))
|
|||
|
|
return false;
|
|||
|
|
let match = from >= bufferPos && to <= bufferPos + buffer.length ? buffer.slice(from - bufferPos, to - bufferPos) : state.doc.sliceString(from, to);
|
|||
|
|
return test(match, state, from, to);
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
function stringCursor(spec, state, from, to) {
|
|||
|
|
let test;
|
|||
|
|
if (spec.wholeWord)
|
|||
|
|
test = stringWordTest(state.doc, state.charCategorizer(state.selection.main.head));
|
|||
|
|
if (spec.test)
|
|||
|
|
test = wrapStringTest(spec.test, state, test);
|
|||
|
|
return new SearchCursor(state.doc, spec.unquoted, from, to, spec.caseSensitive ? void 0 : (x) => x.toLowerCase(), test);
|
|||
|
|
}
|
|||
|
|
function stringWordTest(doc, categorizer) {
|
|||
|
|
return (from, to, buf, bufPos) => {
|
|||
|
|
if (bufPos > from || bufPos + buf.length < to) {
|
|||
|
|
bufPos = Math.max(0, from - 2);
|
|||
|
|
buf = doc.sliceString(bufPos, Math.min(doc.length, to + 2));
|
|||
|
|
}
|
|||
|
|
return (categorizer(charBefore(buf, from - bufPos)) != CharCategory.Word || categorizer(charAfter(buf, from - bufPos)) != CharCategory.Word) && (categorizer(charAfter(buf, to - bufPos)) != CharCategory.Word || categorizer(charBefore(buf, to - bufPos)) != CharCategory.Word);
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
var StringQuery = class extends QueryType {
|
|||
|
|
constructor(spec) {
|
|||
|
|
super(spec);
|
|||
|
|
}
|
|||
|
|
nextMatch(state, curFrom, curTo) {
|
|||
|
|
let cursor = stringCursor(this.spec, state, curTo, state.doc.length).nextOverlapping();
|
|||
|
|
if (cursor.done) {
|
|||
|
|
let end = Math.min(state.doc.length, curFrom + this.spec.unquoted.length);
|
|||
|
|
cursor = stringCursor(this.spec, state, 0, end).nextOverlapping();
|
|||
|
|
}
|
|||
|
|
return cursor.done || cursor.value.from == curFrom && cursor.value.to == curTo ? null : cursor.value;
|
|||
|
|
}
|
|||
|
|
// Searching in reverse is, rather than implementing an inverted search
|
|||
|
|
// cursor, done by scanning chunk after chunk forward.
|
|||
|
|
prevMatchInRange(state, from, to) {
|
|||
|
|
for (let pos = to; ; ) {
|
|||
|
|
let start = Math.max(from, pos - 1e4 - this.spec.unquoted.length);
|
|||
|
|
let cursor = stringCursor(this.spec, state, start, pos), range = null;
|
|||
|
|
while (!cursor.nextOverlapping().done)
|
|||
|
|
range = cursor.value;
|
|||
|
|
if (range)
|
|||
|
|
return range;
|
|||
|
|
if (start == from)
|
|||
|
|
return null;
|
|||
|
|
pos -= 1e4;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
prevMatch(state, curFrom, curTo) {
|
|||
|
|
let found = this.prevMatchInRange(state, 0, curFrom);
|
|||
|
|
if (!found)
|
|||
|
|
found = this.prevMatchInRange(state, Math.max(0, curTo - this.spec.unquoted.length), state.doc.length);
|
|||
|
|
return found && (found.from != curFrom || found.to != curTo) ? found : null;
|
|||
|
|
}
|
|||
|
|
getReplacement(_result) {
|
|||
|
|
return this.spec.unquote(this.spec.replace);
|
|||
|
|
}
|
|||
|
|
matchAll(state, limit) {
|
|||
|
|
let cursor = stringCursor(this.spec, state, 0, state.doc.length), ranges = [];
|
|||
|
|
while (!cursor.next().done) {
|
|||
|
|
if (ranges.length >= limit)
|
|||
|
|
return null;
|
|||
|
|
ranges.push(cursor.value);
|
|||
|
|
}
|
|||
|
|
return ranges;
|
|||
|
|
}
|
|||
|
|
highlight(state, from, to, add) {
|
|||
|
|
let cursor = stringCursor(this.spec, state, Math.max(0, from - this.spec.unquoted.length), Math.min(to + this.spec.unquoted.length, state.doc.length));
|
|||
|
|
while (!cursor.next().done)
|
|||
|
|
add(cursor.value.from, cursor.value.to);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function wrapRegexpTest(test, state, inner) {
|
|||
|
|
return (from, to, match) => {
|
|||
|
|
return (!inner || inner(from, to, match)) && test(match[0], state, from, to);
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
function regexpCursor(spec, state, from, to) {
|
|||
|
|
let test;
|
|||
|
|
if (spec.wholeWord)
|
|||
|
|
test = regexpWordTest(state.charCategorizer(state.selection.main.head));
|
|||
|
|
if (spec.test)
|
|||
|
|
test = wrapRegexpTest(spec.test, state, test);
|
|||
|
|
return new RegExpCursor(state.doc, spec.search, { ignoreCase: !spec.caseSensitive, test }, from, to);
|
|||
|
|
}
|
|||
|
|
function charBefore(str, index) {
|
|||
|
|
return str.slice(findClusterBreak(str, index, false), index);
|
|||
|
|
}
|
|||
|
|
function charAfter(str, index) {
|
|||
|
|
return str.slice(index, findClusterBreak(str, index));
|
|||
|
|
}
|
|||
|
|
function regexpWordTest(categorizer) {
|
|||
|
|
return (_from, _to, match) => !match[0].length || (categorizer(charBefore(match.input, match.index)) != CharCategory.Word || categorizer(charAfter(match.input, match.index)) != CharCategory.Word) && (categorizer(charAfter(match.input, match.index + match[0].length)) != CharCategory.Word || categorizer(charBefore(match.input, match.index + match[0].length)) != CharCategory.Word);
|
|||
|
|
}
|
|||
|
|
var RegExpQuery = class extends QueryType {
|
|||
|
|
nextMatch(state, curFrom, curTo) {
|
|||
|
|
let cursor = regexpCursor(this.spec, state, curTo, state.doc.length).next();
|
|||
|
|
if (cursor.done)
|
|||
|
|
cursor = regexpCursor(this.spec, state, 0, curFrom).next();
|
|||
|
|
return cursor.done ? null : cursor.value;
|
|||
|
|
}
|
|||
|
|
prevMatchInRange(state, from, to) {
|
|||
|
|
for (let size = 1; ; size++) {
|
|||
|
|
let start = Math.max(
|
|||
|
|
from,
|
|||
|
|
to - size * 1e4
|
|||
|
|
/* FindPrev.ChunkSize */
|
|||
|
|
);
|
|||
|
|
let cursor = regexpCursor(this.spec, state, start, to), range = null;
|
|||
|
|
while (!cursor.next().done)
|
|||
|
|
range = cursor.value;
|
|||
|
|
if (range && (start == from || range.from > start + 10))
|
|||
|
|
return range;
|
|||
|
|
if (start == from)
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
prevMatch(state, curFrom, curTo) {
|
|||
|
|
return this.prevMatchInRange(state, 0, curFrom) || this.prevMatchInRange(state, curTo, state.doc.length);
|
|||
|
|
}
|
|||
|
|
getReplacement(result) {
|
|||
|
|
return this.spec.unquote(this.spec.replace).replace(/\$([$&]|\d+)/g, (m, i) => {
|
|||
|
|
if (i == "&")
|
|||
|
|
return result.match[0];
|
|||
|
|
if (i == "$")
|
|||
|
|
return "$";
|
|||
|
|
for (let l = i.length; l > 0; l--) {
|
|||
|
|
let n = +i.slice(0, l);
|
|||
|
|
if (n > 0 && n < result.match.length)
|
|||
|
|
return result.match[n] + i.slice(l);
|
|||
|
|
}
|
|||
|
|
return m;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
matchAll(state, limit) {
|
|||
|
|
let cursor = regexpCursor(this.spec, state, 0, state.doc.length), ranges = [];
|
|||
|
|
while (!cursor.next().done) {
|
|||
|
|
if (ranges.length >= limit)
|
|||
|
|
return null;
|
|||
|
|
ranges.push(cursor.value);
|
|||
|
|
}
|
|||
|
|
return ranges;
|
|||
|
|
}
|
|||
|
|
highlight(state, from, to, add) {
|
|||
|
|
let cursor = regexpCursor(this.spec, state, Math.max(
|
|||
|
|
0,
|
|||
|
|
from - 250
|
|||
|
|
/* RegExp.HighlightMargin */
|
|||
|
|
), Math.min(to + 250, state.doc.length));
|
|||
|
|
while (!cursor.next().done)
|
|||
|
|
add(cursor.value.from, cursor.value.to);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var setSearchQuery = StateEffect.define();
|
|||
|
|
var togglePanel = StateEffect.define();
|
|||
|
|
var searchState = StateField.define({
|
|||
|
|
create(state) {
|
|||
|
|
return new SearchState(defaultQuery(state).create(), null);
|
|||
|
|
},
|
|||
|
|
update(value, tr) {
|
|||
|
|
for (let effect of tr.effects) {
|
|||
|
|
if (effect.is(setSearchQuery))
|
|||
|
|
value = new SearchState(effect.value.create(), value.panel);
|
|||
|
|
else if (effect.is(togglePanel))
|
|||
|
|
value = new SearchState(value.query, effect.value ? createSearchPanel : null);
|
|||
|
|
}
|
|||
|
|
return value;
|
|||
|
|
},
|
|||
|
|
provide: (f) => showPanel.from(f, (val) => val.panel)
|
|||
|
|
});
|
|||
|
|
var SearchState = class {
|
|||
|
|
constructor(query, panel) {
|
|||
|
|
this.query = query;
|
|||
|
|
this.panel = panel;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var matchMark = Decoration.mark({ class: "cm-searchMatch" });
|
|||
|
|
var selectedMatchMark = Decoration.mark({ class: "cm-searchMatch cm-searchMatch-selected" });
|
|||
|
|
var searchHighlighter = ViewPlugin.fromClass(class {
|
|||
|
|
constructor(view) {
|
|||
|
|
this.view = view;
|
|||
|
|
this.decorations = this.highlight(view.state.field(searchState));
|
|||
|
|
}
|
|||
|
|
update(update) {
|
|||
|
|
let state = update.state.field(searchState);
|
|||
|
|
if (state != update.startState.field(searchState) || update.docChanged || update.selectionSet || update.viewportChanged)
|
|||
|
|
this.decorations = this.highlight(state);
|
|||
|
|
}
|
|||
|
|
highlight({ query, panel }) {
|
|||
|
|
if (!panel || !query.spec.valid)
|
|||
|
|
return Decoration.none;
|
|||
|
|
let { view } = this;
|
|||
|
|
let builder = new RangeSetBuilder();
|
|||
|
|
for (let i = 0, ranges = view.visibleRanges, l = ranges.length; i < l; i++) {
|
|||
|
|
let { from, to } = ranges[i];
|
|||
|
|
while (i < l - 1 && to > ranges[i + 1].from - 2 * 250)
|
|||
|
|
to = ranges[++i].to;
|
|||
|
|
query.highlight(view.state, from, to, (from2, to2) => {
|
|||
|
|
let selected = view.state.selection.ranges.some((r) => r.from == from2 && r.to == to2);
|
|||
|
|
builder.add(from2, to2, selected ? selectedMatchMark : matchMark);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
return builder.finish();
|
|||
|
|
}
|
|||
|
|
}, {
|
|||
|
|
decorations: (v) => v.decorations
|
|||
|
|
});
|
|||
|
|
function searchCommand(f) {
|
|||
|
|
return (view) => {
|
|||
|
|
let state = view.state.field(searchState, false);
|
|||
|
|
return state && state.query.spec.valid ? f(view, state) : openSearchPanel(view);
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
var findNext = searchCommand((view, { query }) => {
|
|||
|
|
let { to } = view.state.selection.main;
|
|||
|
|
let next = query.nextMatch(view.state, to, to);
|
|||
|
|
if (!next)
|
|||
|
|
return false;
|
|||
|
|
let selection = EditorSelection.single(next.from, next.to);
|
|||
|
|
let config = view.state.facet(searchConfigFacet);
|
|||
|
|
view.dispatch({
|
|||
|
|
selection,
|
|||
|
|
effects: [announceMatch(view, next), config.scrollToMatch(selection.main, view)],
|
|||
|
|
userEvent: "select.search"
|
|||
|
|
});
|
|||
|
|
selectSearchInput(view);
|
|||
|
|
return true;
|
|||
|
|
});
|
|||
|
|
var findPrevious = searchCommand((view, { query }) => {
|
|||
|
|
let { state } = view, { from } = state.selection.main;
|
|||
|
|
let prev = query.prevMatch(state, from, from);
|
|||
|
|
if (!prev)
|
|||
|
|
return false;
|
|||
|
|
let selection = EditorSelection.single(prev.from, prev.to);
|
|||
|
|
let config = view.state.facet(searchConfigFacet);
|
|||
|
|
view.dispatch({
|
|||
|
|
selection,
|
|||
|
|
effects: [announceMatch(view, prev), config.scrollToMatch(selection.main, view)],
|
|||
|
|
userEvent: "select.search"
|
|||
|
|
});
|
|||
|
|
selectSearchInput(view);
|
|||
|
|
return true;
|
|||
|
|
});
|
|||
|
|
var selectMatches = searchCommand((view, { query }) => {
|
|||
|
|
let ranges = query.matchAll(view.state, 1e3);
|
|||
|
|
if (!ranges || !ranges.length)
|
|||
|
|
return false;
|
|||
|
|
view.dispatch({
|
|||
|
|
selection: EditorSelection.create(ranges.map((r) => EditorSelection.range(r.from, r.to))),
|
|||
|
|
userEvent: "select.search.matches"
|
|||
|
|
});
|
|||
|
|
return true;
|
|||
|
|
});
|
|||
|
|
var selectSelectionMatches = ({ state, dispatch }) => {
|
|||
|
|
let sel = state.selection;
|
|||
|
|
if (sel.ranges.length > 1 || sel.main.empty)
|
|||
|
|
return false;
|
|||
|
|
let { from, to } = sel.main;
|
|||
|
|
let ranges = [], main = 0;
|
|||
|
|
for (let cur = new SearchCursor(state.doc, state.sliceDoc(from, to)); !cur.next().done; ) {
|
|||
|
|
if (ranges.length > 1e3)
|
|||
|
|
return false;
|
|||
|
|
if (cur.value.from == from)
|
|||
|
|
main = ranges.length;
|
|||
|
|
ranges.push(EditorSelection.range(cur.value.from, cur.value.to));
|
|||
|
|
}
|
|||
|
|
dispatch(state.update({
|
|||
|
|
selection: EditorSelection.create(ranges, main),
|
|||
|
|
userEvent: "select.search.matches"
|
|||
|
|
}));
|
|||
|
|
return true;
|
|||
|
|
};
|
|||
|
|
var replaceNext = searchCommand((view, { query }) => {
|
|||
|
|
let { state } = view, { from, to } = state.selection.main;
|
|||
|
|
if (state.readOnly)
|
|||
|
|
return false;
|
|||
|
|
let match = query.nextMatch(state, from, from);
|
|||
|
|
if (!match)
|
|||
|
|
return false;
|
|||
|
|
let next = match;
|
|||
|
|
let changes = [], selection, replacement;
|
|||
|
|
let effects = [];
|
|||
|
|
if (!next.precise) {
|
|||
|
|
next = query.nextMatch(state, next.from, next.to);
|
|||
|
|
} else if (next.from == from && next.to == to) {
|
|||
|
|
replacement = state.toText(query.getReplacement(next));
|
|||
|
|
changes.push({ from: next.from, to: next.to, insert: replacement });
|
|||
|
|
effects.push(EditorView.announce.of(state.phrase("replaced match on line $", state.doc.lineAt(from).number) + "."));
|
|||
|
|
}
|
|||
|
|
let changeSet = view.state.changes(changes);
|
|||
|
|
if (next) {
|
|||
|
|
selection = EditorSelection.single(next.from, next.to).map(changeSet);
|
|||
|
|
effects.push(announceMatch(view, next));
|
|||
|
|
effects.push(state.facet(searchConfigFacet).scrollToMatch(selection.main, view));
|
|||
|
|
}
|
|||
|
|
view.dispatch({
|
|||
|
|
changes: changeSet,
|
|||
|
|
selection,
|
|||
|
|
effects,
|
|||
|
|
userEvent: "input.replace"
|
|||
|
|
});
|
|||
|
|
return true;
|
|||
|
|
});
|
|||
|
|
var replaceAll = searchCommand((view, { query }) => {
|
|||
|
|
if (view.state.readOnly)
|
|||
|
|
return false;
|
|||
|
|
let changes = [];
|
|||
|
|
for (let match of query.matchAll(view.state, 1e9)) {
|
|||
|
|
let { from, to, precise } = match;
|
|||
|
|
if (precise)
|
|||
|
|
changes.push({ from, to, insert: query.getReplacement(match) });
|
|||
|
|
}
|
|||
|
|
if (!changes.length)
|
|||
|
|
return false;
|
|||
|
|
let announceText = view.state.phrase("replaced $ matches", changes.length) + ".";
|
|||
|
|
view.dispatch({
|
|||
|
|
changes,
|
|||
|
|
effects: EditorView.announce.of(announceText),
|
|||
|
|
userEvent: "input.replace.all"
|
|||
|
|
});
|
|||
|
|
return true;
|
|||
|
|
});
|
|||
|
|
function createSearchPanel(view) {
|
|||
|
|
return view.state.facet(searchConfigFacet).createPanel(view);
|
|||
|
|
}
|
|||
|
|
function defaultQuery(state, fallback) {
|
|||
|
|
var _a, _b, _c, _d, _e;
|
|||
|
|
let sel = state.selection.main;
|
|||
|
|
let selText = sel.empty || sel.to > sel.from + 100 ? "" : state.sliceDoc(sel.from, sel.to);
|
|||
|
|
if (fallback && !selText)
|
|||
|
|
return fallback;
|
|||
|
|
let config = state.facet(searchConfigFacet);
|
|||
|
|
return new SearchQuery({
|
|||
|
|
search: ((_a = fallback === null || fallback === void 0 ? void 0 : fallback.literal) !== null && _a !== void 0 ? _a : config.literal) ? selText : selText.replace(/\n/g, "\\n"),
|
|||
|
|
caseSensitive: (_b = fallback === null || fallback === void 0 ? void 0 : fallback.caseSensitive) !== null && _b !== void 0 ? _b : config.caseSensitive,
|
|||
|
|
literal: (_c = fallback === null || fallback === void 0 ? void 0 : fallback.literal) !== null && _c !== void 0 ? _c : config.literal,
|
|||
|
|
regexp: (_d = fallback === null || fallback === void 0 ? void 0 : fallback.regexp) !== null && _d !== void 0 ? _d : config.regexp,
|
|||
|
|
wholeWord: (_e = fallback === null || fallback === void 0 ? void 0 : fallback.wholeWord) !== null && _e !== void 0 ? _e : config.wholeWord
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
function getSearchInput(view) {
|
|||
|
|
let panel = getPanel(view, createSearchPanel);
|
|||
|
|
return panel && panel.dom.querySelector("[main-field]");
|
|||
|
|
}
|
|||
|
|
function selectSearchInput(view) {
|
|||
|
|
let input = getSearchInput(view);
|
|||
|
|
if (input && input == view.root.activeElement)
|
|||
|
|
input.select();
|
|||
|
|
}
|
|||
|
|
var openSearchPanel = (view) => {
|
|||
|
|
let state = view.state.field(searchState, false);
|
|||
|
|
if (state && state.panel) {
|
|||
|
|
let searchInput = getSearchInput(view);
|
|||
|
|
if (searchInput && searchInput != view.root.activeElement) {
|
|||
|
|
let query = defaultQuery(view.state, state.query.spec);
|
|||
|
|
if (query.valid)
|
|||
|
|
view.dispatch({ effects: setSearchQuery.of(query) });
|
|||
|
|
searchInput.focus();
|
|||
|
|
searchInput.select();
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
view.dispatch({ effects: [
|
|||
|
|
togglePanel.of(true),
|
|||
|
|
state ? setSearchQuery.of(defaultQuery(view.state, state.query.spec)) : StateEffect.appendConfig.of(searchExtensions)
|
|||
|
|
] });
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
};
|
|||
|
|
var closeSearchPanel = (view) => {
|
|||
|
|
let state = view.state.field(searchState, false);
|
|||
|
|
if (!state || !state.panel)
|
|||
|
|
return false;
|
|||
|
|
let panel = getPanel(view, createSearchPanel);
|
|||
|
|
if (panel && panel.dom.contains(view.root.activeElement))
|
|||
|
|
view.focus();
|
|||
|
|
view.dispatch({ effects: togglePanel.of(false) });
|
|||
|
|
return true;
|
|||
|
|
};
|
|||
|
|
var searchKeymap = [
|
|||
|
|
{ key: "Mod-f", run: openSearchPanel, scope: "editor search-panel" },
|
|||
|
|
{ key: "F3", run: findNext, shift: findPrevious, scope: "editor search-panel", preventDefault: true },
|
|||
|
|
{ key: "Mod-g", run: findNext, shift: findPrevious, scope: "editor search-panel", preventDefault: true },
|
|||
|
|
{ key: "Escape", run: closeSearchPanel, scope: "editor search-panel" },
|
|||
|
|
{ key: "Mod-Shift-l", run: selectSelectionMatches },
|
|||
|
|
{ key: "Mod-Alt-g", run: gotoLine },
|
|||
|
|
{ key: "Mod-d", run: selectNextOccurrence, preventDefault: true }
|
|||
|
|
];
|
|||
|
|
var SearchPanel = class {
|
|||
|
|
constructor(view) {
|
|||
|
|
this.view = view;
|
|||
|
|
let query = this.query = view.state.field(searchState).query.spec;
|
|||
|
|
this.commit = this.commit.bind(this);
|
|||
|
|
this.searchField = crelt("input", {
|
|||
|
|
value: query.search,
|
|||
|
|
placeholder: phrase(view, "Find"),
|
|||
|
|
"aria-label": phrase(view, "Find"),
|
|||
|
|
class: "cm-textfield",
|
|||
|
|
name: "search",
|
|||
|
|
form: "",
|
|||
|
|
"main-field": "true",
|
|||
|
|
onchange: this.commit,
|
|||
|
|
onkeyup: this.commit
|
|||
|
|
});
|
|||
|
|
this.replaceField = crelt("input", {
|
|||
|
|
value: query.replace,
|
|||
|
|
placeholder: phrase(view, "Replace"),
|
|||
|
|
"aria-label": phrase(view, "Replace"),
|
|||
|
|
class: "cm-textfield",
|
|||
|
|
name: "replace",
|
|||
|
|
form: "",
|
|||
|
|
onchange: this.commit,
|
|||
|
|
onkeyup: this.commit
|
|||
|
|
});
|
|||
|
|
this.caseField = crelt("input", {
|
|||
|
|
type: "checkbox",
|
|||
|
|
name: "case",
|
|||
|
|
form: "",
|
|||
|
|
checked: query.caseSensitive,
|
|||
|
|
onchange: this.commit
|
|||
|
|
});
|
|||
|
|
this.reField = crelt("input", {
|
|||
|
|
type: "checkbox",
|
|||
|
|
name: "re",
|
|||
|
|
form: "",
|
|||
|
|
checked: query.regexp,
|
|||
|
|
onchange: this.commit
|
|||
|
|
});
|
|||
|
|
this.wordField = crelt("input", {
|
|||
|
|
type: "checkbox",
|
|||
|
|
name: "word",
|
|||
|
|
form: "",
|
|||
|
|
checked: query.wholeWord,
|
|||
|
|
onchange: this.commit
|
|||
|
|
});
|
|||
|
|
function button(name, onclick, content) {
|
|||
|
|
return crelt("button", { class: "cm-button", name, onclick, type: "button" }, content);
|
|||
|
|
}
|
|||
|
|
this.dom = crelt("div", { onkeydown: (e) => this.keydown(e), class: "cm-search" }, [
|
|||
|
|
this.searchField,
|
|||
|
|
button("next", () => findNext(view), [phrase(view, "next")]),
|
|||
|
|
button("prev", () => findPrevious(view), [phrase(view, "previous")]),
|
|||
|
|
button("select", () => selectMatches(view), [phrase(view, "all")]),
|
|||
|
|
crelt("label", null, [this.caseField, phrase(view, "match case")]),
|
|||
|
|
crelt("label", null, [this.reField, phrase(view, "regexp")]),
|
|||
|
|
crelt("label", null, [this.wordField, phrase(view, "by word")]),
|
|||
|
|
...view.state.readOnly ? [] : [
|
|||
|
|
crelt("br"),
|
|||
|
|
this.replaceField,
|
|||
|
|
button("replace", () => replaceNext(view), [phrase(view, "replace")]),
|
|||
|
|
button("replaceAll", () => replaceAll(view), [phrase(view, "replace all")])
|
|||
|
|
],
|
|||
|
|
crelt("button", {
|
|||
|
|
name: "close",
|
|||
|
|
onclick: () => closeSearchPanel(view),
|
|||
|
|
"aria-label": phrase(view, "close"),
|
|||
|
|
type: "button"
|
|||
|
|
}, ["×"])
|
|||
|
|
]);
|
|||
|
|
}
|
|||
|
|
commit() {
|
|||
|
|
let query = new SearchQuery({
|
|||
|
|
search: this.searchField.value,
|
|||
|
|
caseSensitive: this.caseField.checked,
|
|||
|
|
regexp: this.reField.checked,
|
|||
|
|
wholeWord: this.wordField.checked,
|
|||
|
|
replace: this.replaceField.value
|
|||
|
|
});
|
|||
|
|
if (!query.eq(this.query)) {
|
|||
|
|
this.query = query;
|
|||
|
|
this.view.dispatch({ effects: setSearchQuery.of(query) });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
keydown(e) {
|
|||
|
|
if (runScopeHandlers(this.view, e, "search-panel")) {
|
|||
|
|
e.preventDefault();
|
|||
|
|
} else if (e.keyCode == 13 && e.target == this.searchField) {
|
|||
|
|
e.preventDefault();
|
|||
|
|
(e.shiftKey ? findPrevious : findNext)(this.view);
|
|||
|
|
} else if (e.keyCode == 13 && e.target == this.replaceField) {
|
|||
|
|
e.preventDefault();
|
|||
|
|
replaceNext(this.view);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
update(update) {
|
|||
|
|
for (let tr of update.transactions)
|
|||
|
|
for (let effect of tr.effects) {
|
|||
|
|
if (effect.is(setSearchQuery) && !effect.value.eq(this.query))
|
|||
|
|
this.setQuery(effect.value);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
setQuery(query) {
|
|||
|
|
this.query = query;
|
|||
|
|
this.searchField.value = query.search;
|
|||
|
|
this.replaceField.value = query.replace;
|
|||
|
|
this.caseField.checked = query.caseSensitive;
|
|||
|
|
this.reField.checked = query.regexp;
|
|||
|
|
this.wordField.checked = query.wholeWord;
|
|||
|
|
}
|
|||
|
|
mount() {
|
|||
|
|
this.searchField.select();
|
|||
|
|
}
|
|||
|
|
get pos() {
|
|||
|
|
return 80;
|
|||
|
|
}
|
|||
|
|
get top() {
|
|||
|
|
return this.view.state.facet(searchConfigFacet).top;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function phrase(view, phrase2) {
|
|||
|
|
return view.state.phrase(phrase2);
|
|||
|
|
}
|
|||
|
|
var AnnounceMargin = 30;
|
|||
|
|
var Break = /[\s\.,:;?!]/;
|
|||
|
|
function announceMatch(view, { from, to }) {
|
|||
|
|
let line = view.state.doc.lineAt(from), lineEnd = view.state.doc.lineAt(to).to;
|
|||
|
|
let start = Math.max(line.from, from - AnnounceMargin), end = Math.min(lineEnd, to + AnnounceMargin);
|
|||
|
|
let text = view.state.sliceDoc(start, end);
|
|||
|
|
if (start != line.from) {
|
|||
|
|
for (let i = 0; i < AnnounceMargin; i++)
|
|||
|
|
if (!Break.test(text[i + 1]) && Break.test(text[i])) {
|
|||
|
|
text = text.slice(i);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (end != lineEnd) {
|
|||
|
|
for (let i = text.length - 1; i > text.length - AnnounceMargin; i--)
|
|||
|
|
if (!Break.test(text[i - 1]) && Break.test(text[i])) {
|
|||
|
|
text = text.slice(0, i);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return EditorView.announce.of(`${view.state.phrase("current match")}. ${text} ${view.state.phrase("on line")} ${line.number}.`);
|
|||
|
|
}
|
|||
|
|
var baseTheme = EditorView.baseTheme({
|
|||
|
|
".cm-panel.cm-search": {
|
|||
|
|
padding: "2px 6px 4px",
|
|||
|
|
position: "relative",
|
|||
|
|
"& [name=close]": {
|
|||
|
|
position: "absolute",
|
|||
|
|
top: "0",
|
|||
|
|
right: "4px",
|
|||
|
|
backgroundColor: "inherit",
|
|||
|
|
border: "none",
|
|||
|
|
font: "inherit",
|
|||
|
|
padding: 0,
|
|||
|
|
margin: 0
|
|||
|
|
},
|
|||
|
|
"& input, & button, & label": {
|
|||
|
|
margin: ".2em .6em .2em 0"
|
|||
|
|
},
|
|||
|
|
"& input[type=checkbox]": {
|
|||
|
|
marginRight: ".2em"
|
|||
|
|
},
|
|||
|
|
"& label": {
|
|||
|
|
fontSize: "80%",
|
|||
|
|
whiteSpace: "pre"
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
"&light .cm-searchMatch": { backgroundColor: "#ffff0054" },
|
|||
|
|
"&dark .cm-searchMatch": { backgroundColor: "#00ffff8a" },
|
|||
|
|
"&light .cm-searchMatch-selected": { backgroundColor: "#ff6a0054" },
|
|||
|
|
"&dark .cm-searchMatch-selected": { backgroundColor: "#ff00ff8a" }
|
|||
|
|
});
|
|||
|
|
var searchExtensions = [
|
|||
|
|
searchState,
|
|||
|
|
Prec.low(searchHighlighter),
|
|||
|
|
baseTheme
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
// node_modules/@codemirror/lint/dist/index.js
|
|||
|
|
var SelectedDiagnostic = class {
|
|||
|
|
constructor(from, to, diagnostic) {
|
|||
|
|
this.from = from;
|
|||
|
|
this.to = to;
|
|||
|
|
this.diagnostic = diagnostic;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var LintState = class _LintState {
|
|||
|
|
constructor(diagnostics, panel, selected) {
|
|||
|
|
this.diagnostics = diagnostics;
|
|||
|
|
this.panel = panel;
|
|||
|
|
this.selected = selected;
|
|||
|
|
}
|
|||
|
|
static init(diagnostics, panel, state) {
|
|||
|
|
let diagnosticFilter = state.facet(lintConfig).markerFilter;
|
|||
|
|
if (diagnosticFilter)
|
|||
|
|
diagnostics = diagnosticFilter(diagnostics, state);
|
|||
|
|
let sorted = diagnostics.slice().sort((a, b) => a.from - b.from || a.to - b.to);
|
|||
|
|
let deco = new RangeSetBuilder(), active = [], pos = 0;
|
|||
|
|
let scan = state.doc.iter(), scanPos = 0, docLen = state.doc.length;
|
|||
|
|
for (let i = 0; ; ) {
|
|||
|
|
let next = i == sorted.length ? null : sorted[i];
|
|||
|
|
if (!next && !active.length)
|
|||
|
|
break;
|
|||
|
|
let from, to;
|
|||
|
|
if (active.length) {
|
|||
|
|
from = pos;
|
|||
|
|
to = active.reduce((p, d) => Math.min(p, d.to), next && next.from > from ? next.from : 1e8);
|
|||
|
|
} else {
|
|||
|
|
from = next.from;
|
|||
|
|
if (from > docLen)
|
|||
|
|
break;
|
|||
|
|
to = next.to;
|
|||
|
|
active.push(next);
|
|||
|
|
i++;
|
|||
|
|
}
|
|||
|
|
while (i < sorted.length) {
|
|||
|
|
let next2 = sorted[i];
|
|||
|
|
if (next2.from == from && (next2.to > next2.from || next2.to == from)) {
|
|||
|
|
active.push(next2);
|
|||
|
|
i++;
|
|||
|
|
to = Math.min(next2.to, to);
|
|||
|
|
} else {
|
|||
|
|
to = Math.min(next2.from, to);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
to = Math.min(to, docLen);
|
|||
|
|
let widget = false;
|
|||
|
|
if (active.some((d) => d.from == from && (d.to == to || to == docLen))) {
|
|||
|
|
widget = from == to;
|
|||
|
|
if (!widget && to - from < 10) {
|
|||
|
|
let behind = from - (scanPos + scan.value.length);
|
|||
|
|
if (behind > 0) {
|
|||
|
|
scan.next(behind);
|
|||
|
|
scanPos = from;
|
|||
|
|
}
|
|||
|
|
for (let check = from; ; ) {
|
|||
|
|
if (check >= to) {
|
|||
|
|
widget = true;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
if (!scan.lineBreak && scanPos + scan.value.length > check)
|
|||
|
|
break;
|
|||
|
|
check = scanPos + scan.value.length;
|
|||
|
|
scanPos += scan.value.length;
|
|||
|
|
scan.next();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
let sev = maxSeverity(active);
|
|||
|
|
if (widget) {
|
|||
|
|
deco.add(from, from, Decoration.widget({
|
|||
|
|
widget: new DiagnosticWidget(sev),
|
|||
|
|
diagnostics: active.slice()
|
|||
|
|
}));
|
|||
|
|
} else {
|
|||
|
|
let markClass = active.reduce((c, d) => d.markClass ? c + " " + d.markClass : c, "");
|
|||
|
|
deco.add(from, to, Decoration.mark({
|
|||
|
|
class: "cm-lintRange cm-lintRange-" + sev + markClass,
|
|||
|
|
diagnostics: active.slice(),
|
|||
|
|
inclusiveEnd: active.some((a) => a.to > to)
|
|||
|
|
}));
|
|||
|
|
}
|
|||
|
|
pos = to;
|
|||
|
|
if (pos == docLen)
|
|||
|
|
break;
|
|||
|
|
for (let i2 = 0; i2 < active.length; i2++)
|
|||
|
|
if (active[i2].to <= pos)
|
|||
|
|
active.splice(i2--, 1);
|
|||
|
|
}
|
|||
|
|
let set = deco.finish();
|
|||
|
|
return new _LintState(set, panel, findDiagnostic(set));
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function findDiagnostic(diagnostics, diagnostic = null, after = 0) {
|
|||
|
|
let found = null;
|
|||
|
|
diagnostics.between(after, 1e9, (from, to, { spec }) => {
|
|||
|
|
if (diagnostic && spec.diagnostics.indexOf(diagnostic) < 0)
|
|||
|
|
return;
|
|||
|
|
if (!found)
|
|||
|
|
found = new SelectedDiagnostic(from, to, diagnostic || spec.diagnostics[0]);
|
|||
|
|
else if (spec.diagnostics.indexOf(found.diagnostic) < 0)
|
|||
|
|
return false;
|
|||
|
|
else
|
|||
|
|
found = new SelectedDiagnostic(found.from, to, found.diagnostic);
|
|||
|
|
});
|
|||
|
|
return found;
|
|||
|
|
}
|
|||
|
|
function hideTooltip(tr, tooltip) {
|
|||
|
|
let from = tooltip.pos, to = tooltip.end || from;
|
|||
|
|
let result = tr.state.facet(lintConfig).hideOn(tr, from, to);
|
|||
|
|
if (result != null)
|
|||
|
|
return result;
|
|||
|
|
let line = tr.startState.doc.lineAt(tooltip.pos);
|
|||
|
|
return !!(tr.effects.some((e) => e.is(setDiagnosticsEffect)) || tr.changes.touchesRange(line.from, Math.max(line.to, to)));
|
|||
|
|
}
|
|||
|
|
function maybeEnableLint(state, effects) {
|
|||
|
|
return state.field(lintState, false) ? effects : effects.concat(StateEffect.appendConfig.of(lintExtensions));
|
|||
|
|
}
|
|||
|
|
function setDiagnostics(state, diagnostics) {
|
|||
|
|
return {
|
|||
|
|
effects: maybeEnableLint(state, [setDiagnosticsEffect.of(diagnostics)])
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
var setDiagnosticsEffect = StateEffect.define();
|
|||
|
|
var togglePanel2 = StateEffect.define();
|
|||
|
|
var movePanelSelection = StateEffect.define();
|
|||
|
|
var lintState = StateField.define({
|
|||
|
|
create() {
|
|||
|
|
return new LintState(Decoration.none, null, null);
|
|||
|
|
},
|
|||
|
|
update(value, tr) {
|
|||
|
|
if (tr.docChanged && value.diagnostics.size) {
|
|||
|
|
let mapped = value.diagnostics.map(tr.changes), selected = null, panel = value.panel;
|
|||
|
|
if (value.selected) {
|
|||
|
|
let selPos = tr.changes.mapPos(value.selected.from, 1);
|
|||
|
|
selected = findDiagnostic(mapped, value.selected.diagnostic, selPos) || findDiagnostic(mapped, null, selPos);
|
|||
|
|
}
|
|||
|
|
if (!mapped.size && panel && tr.state.facet(lintConfig).autoPanel)
|
|||
|
|
panel = null;
|
|||
|
|
value = new LintState(mapped, panel, selected);
|
|||
|
|
}
|
|||
|
|
for (let effect of tr.effects) {
|
|||
|
|
if (effect.is(setDiagnosticsEffect)) {
|
|||
|
|
let panel = !tr.state.facet(lintConfig).autoPanel ? value.panel : effect.value.length ? LintPanel.open : null;
|
|||
|
|
value = LintState.init(effect.value, panel, tr.state);
|
|||
|
|
} else if (effect.is(togglePanel2)) {
|
|||
|
|
value = new LintState(value.diagnostics, effect.value ? LintPanel.open : null, value.selected);
|
|||
|
|
} else if (effect.is(movePanelSelection)) {
|
|||
|
|
value = new LintState(value.diagnostics, value.panel, effect.value);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return value;
|
|||
|
|
},
|
|||
|
|
provide: (f) => [
|
|||
|
|
showPanel.from(f, (val) => val.panel),
|
|||
|
|
EditorView.decorations.from(f, (s) => s.diagnostics)
|
|||
|
|
]
|
|||
|
|
});
|
|||
|
|
var activeMark = Decoration.mark({ class: "cm-lintRange cm-lintRange-active" });
|
|||
|
|
function lintTooltip(view, pos, side) {
|
|||
|
|
let { diagnostics } = view.state.field(lintState);
|
|||
|
|
let found, start = -1, end = -1;
|
|||
|
|
diagnostics.between(pos - (side < 0 ? 1 : 0), pos + (side > 0 ? 1 : 0), (from, to, { spec }) => {
|
|||
|
|
if (pos >= from && pos <= to && (from == to || (pos > from || side > 0) && (pos < to || side < 0))) {
|
|||
|
|
found = spec.diagnostics;
|
|||
|
|
start = from;
|
|||
|
|
end = to;
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
let diagnosticFilter = view.state.facet(lintConfig).tooltipFilter;
|
|||
|
|
if (found && diagnosticFilter)
|
|||
|
|
found = diagnosticFilter(found, view.state);
|
|||
|
|
if (!found)
|
|||
|
|
return null;
|
|||
|
|
return {
|
|||
|
|
pos: start,
|
|||
|
|
end,
|
|||
|
|
above: view.state.doc.lineAt(start).to < end,
|
|||
|
|
create() {
|
|||
|
|
return { dom: diagnosticsTooltip(view, found) };
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
function diagnosticsTooltip(view, diagnostics) {
|
|||
|
|
return crelt("ul", { class: "cm-tooltip-lint" }, diagnostics.map((d) => renderDiagnostic(view, d, false)));
|
|||
|
|
}
|
|||
|
|
var openLintPanel = (view) => {
|
|||
|
|
let field = view.state.field(lintState, false);
|
|||
|
|
if (!field || !field.panel)
|
|||
|
|
view.dispatch({ effects: maybeEnableLint(view.state, [togglePanel2.of(true)]) });
|
|||
|
|
let panel = getPanel(view, LintPanel.open);
|
|||
|
|
if (panel)
|
|||
|
|
panel.dom.querySelector(".cm-panel-lint ul").focus();
|
|||
|
|
return true;
|
|||
|
|
};
|
|||
|
|
var closeLintPanel = (view) => {
|
|||
|
|
let field = view.state.field(lintState, false);
|
|||
|
|
if (!field || !field.panel)
|
|||
|
|
return false;
|
|||
|
|
view.dispatch({ effects: togglePanel2.of(false) });
|
|||
|
|
return true;
|
|||
|
|
};
|
|||
|
|
var nextDiagnostic = (view) => {
|
|||
|
|
let field = view.state.field(lintState, false);
|
|||
|
|
if (!field)
|
|||
|
|
return false;
|
|||
|
|
let sel = view.state.selection.main, next = findDiagnostic(field.diagnostics, null, sel.to + 1);
|
|||
|
|
if (!next) {
|
|||
|
|
next = findDiagnostic(field.diagnostics, null, 0);
|
|||
|
|
if (!next || next.from == sel.from && next.to == sel.to)
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
view.dispatch({ selection: { anchor: next.from, head: next.to }, scrollIntoView: true });
|
|||
|
|
activateHover(view, next.from, 1, {
|
|||
|
|
tooltip: lintHover,
|
|||
|
|
until: (tr) => tr.docChanged || tr.newSelection.main.head < next.from || tr.newSelection.main.head > next.to
|
|||
|
|
});
|
|||
|
|
return true;
|
|||
|
|
};
|
|||
|
|
var lintKeymap = [
|
|||
|
|
{ key: "Mod-Shift-m", run: openLintPanel, preventDefault: true },
|
|||
|
|
{ key: "F8", run: nextDiagnostic }
|
|||
|
|
];
|
|||
|
|
var lintPlugin = ViewPlugin.fromClass(class {
|
|||
|
|
constructor(view) {
|
|||
|
|
this.view = view;
|
|||
|
|
this.timeout = -1;
|
|||
|
|
this.set = true;
|
|||
|
|
let { delay } = view.state.facet(lintConfig);
|
|||
|
|
this.lintTime = Date.now() + delay;
|
|||
|
|
this.run = this.run.bind(this);
|
|||
|
|
this.timeout = setTimeout(this.run, delay);
|
|||
|
|
}
|
|||
|
|
run() {
|
|||
|
|
clearTimeout(this.timeout);
|
|||
|
|
let now = Date.now();
|
|||
|
|
if (now < this.lintTime - 10) {
|
|||
|
|
this.timeout = setTimeout(this.run, this.lintTime - now);
|
|||
|
|
} else {
|
|||
|
|
this.set = false;
|
|||
|
|
let { state } = this.view, { sources } = state.facet(lintConfig);
|
|||
|
|
if (sources.length)
|
|||
|
|
batchResults(sources.map((s) => Promise.resolve(s(this.view))), (annotations) => {
|
|||
|
|
if (this.view.state.doc == state.doc)
|
|||
|
|
this.view.dispatch(setDiagnostics(this.view.state, annotations.reduce((a, b) => a.concat(b))));
|
|||
|
|
}, (error) => {
|
|||
|
|
logException(this.view.state, error);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
update(update) {
|
|||
|
|
let config = update.state.facet(lintConfig);
|
|||
|
|
if (update.docChanged || config != update.startState.facet(lintConfig) || config.needsRefresh && config.needsRefresh(update)) {
|
|||
|
|
this.lintTime = Date.now() + config.delay;
|
|||
|
|
if (!this.set) {
|
|||
|
|
this.set = true;
|
|||
|
|
this.timeout = setTimeout(this.run, config.delay);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
force() {
|
|||
|
|
if (this.set) {
|
|||
|
|
this.lintTime = Date.now();
|
|||
|
|
this.run();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
destroy() {
|
|||
|
|
clearTimeout(this.timeout);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
function batchResults(promises, sink, error) {
|
|||
|
|
let collected = [], timeout = -1;
|
|||
|
|
for (let p of promises)
|
|||
|
|
p.then((value) => {
|
|||
|
|
collected.push(value);
|
|||
|
|
clearTimeout(timeout);
|
|||
|
|
if (collected.length == promises.length)
|
|||
|
|
sink(collected);
|
|||
|
|
else
|
|||
|
|
timeout = setTimeout(() => sink(collected), 200);
|
|||
|
|
}, error);
|
|||
|
|
}
|
|||
|
|
var lintConfig = Facet.define({
|
|||
|
|
combine(input) {
|
|||
|
|
return {
|
|||
|
|
sources: input.map((i) => i.source).filter((x) => x != null),
|
|||
|
|
...combineConfig(input.map((i) => i.config), {
|
|||
|
|
delay: 750,
|
|||
|
|
markerFilter: null,
|
|||
|
|
tooltipFilter: null,
|
|||
|
|
needsRefresh: null,
|
|||
|
|
hideOn: () => null
|
|||
|
|
}, {
|
|||
|
|
delay: Math.max,
|
|||
|
|
markerFilter: combineFilter,
|
|||
|
|
tooltipFilter: combineFilter,
|
|||
|
|
needsRefresh: (a, b) => !a ? b : !b ? a : (u) => a(u) || b(u),
|
|||
|
|
hideOn: (a, b) => !a ? b : !b ? a : (t, x, y) => a(t, x, y) || b(t, x, y),
|
|||
|
|
autoPanel: (a, b) => a || b
|
|||
|
|
})
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
function combineFilter(a, b) {
|
|||
|
|
return !a ? b : !b ? a : (d, s) => b(a(d, s), s);
|
|||
|
|
}
|
|||
|
|
function assignKeys(actions) {
|
|||
|
|
let assigned = [];
|
|||
|
|
if (actions)
|
|||
|
|
actions: for (let { name } of actions) {
|
|||
|
|
for (let i = 0; i < name.length; i++) {
|
|||
|
|
let ch = name[i];
|
|||
|
|
if (/[a-zA-Z]/.test(ch) && !assigned.some((c) => c.toLowerCase() == ch.toLowerCase())) {
|
|||
|
|
assigned.push(ch);
|
|||
|
|
continue actions;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
assigned.push("");
|
|||
|
|
}
|
|||
|
|
return assigned;
|
|||
|
|
}
|
|||
|
|
function renderDiagnostic(view, diagnostic, inPanel) {
|
|||
|
|
var _a;
|
|||
|
|
let keys = inPanel ? assignKeys(diagnostic.actions) : [];
|
|||
|
|
return crelt("li", { class: "cm-diagnostic cm-diagnostic-" + diagnostic.severity }, crelt("span", { class: "cm-diagnosticText" }, diagnostic.renderMessage ? diagnostic.renderMessage(view) : diagnostic.message), (_a = diagnostic.actions) === null || _a === void 0 ? void 0 : _a.map((action, i) => {
|
|||
|
|
let fired = false, click = (e) => {
|
|||
|
|
e.preventDefault();
|
|||
|
|
if (fired)
|
|||
|
|
return;
|
|||
|
|
fired = true;
|
|||
|
|
let found = findDiagnostic(view.state.field(lintState).diagnostics, diagnostic);
|
|||
|
|
if (found)
|
|||
|
|
action.apply(view, found.from, found.to);
|
|||
|
|
};
|
|||
|
|
let { name } = action, keyIndex = keys[i] ? name.indexOf(keys[i]) : -1;
|
|||
|
|
let nameElt = keyIndex < 0 ? name : [
|
|||
|
|
name.slice(0, keyIndex),
|
|||
|
|
crelt("u", name.slice(keyIndex, keyIndex + 1)),
|
|||
|
|
name.slice(keyIndex + 1)
|
|||
|
|
];
|
|||
|
|
let markClass = action.markClass ? " " + action.markClass : "";
|
|||
|
|
return crelt("button", {
|
|||
|
|
type: "button",
|
|||
|
|
class: "cm-diagnosticAction" + markClass,
|
|||
|
|
onclick: click,
|
|||
|
|
onmousedown: click,
|
|||
|
|
"aria-label": ` Action: ${name}${keyIndex < 0 ? "" : ` (access key "${keys[i]})"`}.`
|
|||
|
|
}, nameElt);
|
|||
|
|
}), diagnostic.source && crelt("div", { class: "cm-diagnosticSource" }, diagnostic.source));
|
|||
|
|
}
|
|||
|
|
var DiagnosticWidget = class extends WidgetType {
|
|||
|
|
constructor(sev) {
|
|||
|
|
super();
|
|||
|
|
this.sev = sev;
|
|||
|
|
}
|
|||
|
|
eq(other) {
|
|||
|
|
return other.sev == this.sev;
|
|||
|
|
}
|
|||
|
|
toDOM() {
|
|||
|
|
return crelt("span", { class: "cm-lintPoint cm-lintPoint-" + this.sev });
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var PanelItem = class {
|
|||
|
|
constructor(view, diagnostic) {
|
|||
|
|
this.diagnostic = diagnostic;
|
|||
|
|
this.id = "item_" + Math.floor(Math.random() * 4294967295).toString(16);
|
|||
|
|
this.dom = renderDiagnostic(view, diagnostic, true);
|
|||
|
|
this.dom.id = this.id;
|
|||
|
|
this.dom.setAttribute("role", "option");
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var LintPanel = class _LintPanel {
|
|||
|
|
constructor(view) {
|
|||
|
|
this.view = view;
|
|||
|
|
this.items = [];
|
|||
|
|
let onkeydown = (event) => {
|
|||
|
|
if (event.ctrlKey || event.altKey || event.metaKey)
|
|||
|
|
return;
|
|||
|
|
if (event.keyCode == 27) {
|
|||
|
|
closeLintPanel(this.view);
|
|||
|
|
this.view.focus();
|
|||
|
|
} else if (event.keyCode == 38 || event.keyCode == 33) {
|
|||
|
|
this.moveSelection((this.selectedIndex - 1 + this.items.length) % this.items.length);
|
|||
|
|
} else if (event.keyCode == 40 || event.keyCode == 34) {
|
|||
|
|
this.moveSelection((this.selectedIndex + 1) % this.items.length);
|
|||
|
|
} else if (event.keyCode == 36) {
|
|||
|
|
this.moveSelection(0);
|
|||
|
|
} else if (event.keyCode == 35) {
|
|||
|
|
this.moveSelection(this.items.length - 1);
|
|||
|
|
} else if (event.keyCode == 13) {
|
|||
|
|
this.view.focus();
|
|||
|
|
} else if (event.keyCode >= 65 && event.keyCode <= 90 && this.selectedIndex >= 0) {
|
|||
|
|
let { diagnostic } = this.items[this.selectedIndex], keys = assignKeys(diagnostic.actions);
|
|||
|
|
for (let i = 0; i < keys.length; i++)
|
|||
|
|
if (keys[i].toUpperCase().charCodeAt(0) == event.keyCode) {
|
|||
|
|
let found = findDiagnostic(this.view.state.field(lintState).diagnostics, diagnostic);
|
|||
|
|
if (found)
|
|||
|
|
diagnostic.actions[i].apply(view, found.from, found.to);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
event.preventDefault();
|
|||
|
|
};
|
|||
|
|
let onclick = (event) => {
|
|||
|
|
for (let i = 0; i < this.items.length; i++) {
|
|||
|
|
if (this.items[i].dom.contains(event.target))
|
|||
|
|
this.moveSelection(i);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
this.list = crelt("ul", {
|
|||
|
|
tabIndex: 0,
|
|||
|
|
role: "listbox",
|
|||
|
|
"aria-label": this.view.state.phrase("Diagnostics"),
|
|||
|
|
onkeydown,
|
|||
|
|
onclick
|
|||
|
|
});
|
|||
|
|
this.dom = crelt("div", { class: "cm-panel-lint" }, this.list, crelt("button", {
|
|||
|
|
type: "button",
|
|||
|
|
name: "close",
|
|||
|
|
"aria-label": this.view.state.phrase("close"),
|
|||
|
|
onclick: () => closeLintPanel(this.view)
|
|||
|
|
}, "×"));
|
|||
|
|
this.update();
|
|||
|
|
}
|
|||
|
|
get selectedIndex() {
|
|||
|
|
let selected = this.view.state.field(lintState).selected;
|
|||
|
|
if (!selected)
|
|||
|
|
return -1;
|
|||
|
|
for (let i = 0; i < this.items.length; i++)
|
|||
|
|
if (this.items[i].diagnostic == selected.diagnostic)
|
|||
|
|
return i;
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
update() {
|
|||
|
|
let { diagnostics, selected } = this.view.state.field(lintState);
|
|||
|
|
let i = 0, needsSync = false, newSelectedItem = null;
|
|||
|
|
let seen = /* @__PURE__ */ new Set();
|
|||
|
|
diagnostics.between(0, this.view.state.doc.length, (_start, _end, { spec }) => {
|
|||
|
|
for (let diagnostic of spec.diagnostics) {
|
|||
|
|
if (seen.has(diagnostic))
|
|||
|
|
continue;
|
|||
|
|
seen.add(diagnostic);
|
|||
|
|
let found = -1, item;
|
|||
|
|
for (let j = i; j < this.items.length; j++)
|
|||
|
|
if (this.items[j].diagnostic == diagnostic) {
|
|||
|
|
found = j;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
if (found < 0) {
|
|||
|
|
item = new PanelItem(this.view, diagnostic);
|
|||
|
|
this.items.splice(i, 0, item);
|
|||
|
|
needsSync = true;
|
|||
|
|
} else {
|
|||
|
|
item = this.items[found];
|
|||
|
|
if (found > i) {
|
|||
|
|
this.items.splice(i, found - i);
|
|||
|
|
needsSync = true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (selected && item.diagnostic == selected.diagnostic) {
|
|||
|
|
if (!item.dom.hasAttribute("aria-selected")) {
|
|||
|
|
item.dom.setAttribute("aria-selected", "true");
|
|||
|
|
newSelectedItem = item;
|
|||
|
|
}
|
|||
|
|
} else if (item.dom.hasAttribute("aria-selected")) {
|
|||
|
|
item.dom.removeAttribute("aria-selected");
|
|||
|
|
}
|
|||
|
|
i++;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
while (i < this.items.length && !(this.items.length == 1 && this.items[0].diagnostic.from < 0)) {
|
|||
|
|
needsSync = true;
|
|||
|
|
this.items.pop();
|
|||
|
|
}
|
|||
|
|
if (this.items.length == 0) {
|
|||
|
|
this.items.push(new PanelItem(this.view, {
|
|||
|
|
from: -1,
|
|||
|
|
to: -1,
|
|||
|
|
severity: "info",
|
|||
|
|
message: this.view.state.phrase("No diagnostics")
|
|||
|
|
}));
|
|||
|
|
needsSync = true;
|
|||
|
|
}
|
|||
|
|
if (newSelectedItem) {
|
|||
|
|
this.list.setAttribute("aria-activedescendant", newSelectedItem.id);
|
|||
|
|
this.view.requestMeasure({
|
|||
|
|
key: this,
|
|||
|
|
read: () => ({ sel: newSelectedItem.dom.getBoundingClientRect(), panel: this.list.getBoundingClientRect() }),
|
|||
|
|
write: ({ sel, panel }) => {
|
|||
|
|
let scaleY = panel.height / this.list.offsetHeight;
|
|||
|
|
if (sel.top < panel.top)
|
|||
|
|
this.list.scrollTop -= (panel.top - sel.top) / scaleY;
|
|||
|
|
else if (sel.bottom > panel.bottom)
|
|||
|
|
this.list.scrollTop += (sel.bottom - panel.bottom) / scaleY;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
} else if (this.selectedIndex < 0) {
|
|||
|
|
this.list.removeAttribute("aria-activedescendant");
|
|||
|
|
}
|
|||
|
|
if (needsSync)
|
|||
|
|
this.sync();
|
|||
|
|
}
|
|||
|
|
sync() {
|
|||
|
|
let domPos = this.list.firstChild;
|
|||
|
|
function rm() {
|
|||
|
|
let prev = domPos;
|
|||
|
|
domPos = prev.nextSibling;
|
|||
|
|
prev.remove();
|
|||
|
|
}
|
|||
|
|
for (let item of this.items) {
|
|||
|
|
if (item.dom.parentNode == this.list) {
|
|||
|
|
while (domPos != item.dom)
|
|||
|
|
rm();
|
|||
|
|
domPos = item.dom.nextSibling;
|
|||
|
|
} else {
|
|||
|
|
this.list.insertBefore(item.dom, domPos);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
while (domPos)
|
|||
|
|
rm();
|
|||
|
|
}
|
|||
|
|
moveSelection(selectedIndex) {
|
|||
|
|
if (this.selectedIndex < 0)
|
|||
|
|
return;
|
|||
|
|
let field = this.view.state.field(lintState);
|
|||
|
|
let selection = findDiagnostic(field.diagnostics, this.items[selectedIndex].diagnostic);
|
|||
|
|
if (!selection)
|
|||
|
|
return;
|
|||
|
|
this.view.dispatch({
|
|||
|
|
selection: { anchor: selection.from, head: selection.to },
|
|||
|
|
scrollIntoView: true,
|
|||
|
|
effects: movePanelSelection.of(selection)
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
static open(view) {
|
|||
|
|
return new _LintPanel(view);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function svg(content, attrs = `viewBox="0 0 40 40"`) {
|
|||
|
|
return `url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" ${attrs}>${encodeURIComponent(content)}</svg>')`;
|
|||
|
|
}
|
|||
|
|
function underline(color) {
|
|||
|
|
return svg(`<path d="m0 2.5 l2 -1.5 l1 0 l2 1.5 l1 0" stroke="${color}" fill="none" stroke-width=".7"/>`, `width="6" height="3"`);
|
|||
|
|
}
|
|||
|
|
var baseTheme2 = EditorView.baseTheme({
|
|||
|
|
".cm-diagnostic": {
|
|||
|
|
padding: "3px 6px 3px 8px",
|
|||
|
|
marginLeft: "-1px",
|
|||
|
|
display: "block",
|
|||
|
|
whiteSpace: "pre-wrap"
|
|||
|
|
},
|
|||
|
|
".cm-diagnostic-error": { borderLeft: "5px solid #d11" },
|
|||
|
|
".cm-diagnostic-warning": { borderLeft: "5px solid orange" },
|
|||
|
|
".cm-diagnostic-info": { borderLeft: "5px solid #999" },
|
|||
|
|
".cm-diagnostic-hint": { borderLeft: "5px solid #66d" },
|
|||
|
|
".cm-diagnosticAction": {
|
|||
|
|
font: "inherit",
|
|||
|
|
border: "none",
|
|||
|
|
padding: "2px 4px",
|
|||
|
|
backgroundColor: "#444",
|
|||
|
|
color: "white",
|
|||
|
|
borderRadius: "3px",
|
|||
|
|
marginLeft: "8px",
|
|||
|
|
cursor: "pointer"
|
|||
|
|
},
|
|||
|
|
".cm-diagnosticSource": {
|
|||
|
|
fontSize: "70%",
|
|||
|
|
opacity: 0.7
|
|||
|
|
},
|
|||
|
|
".cm-lintRange": {
|
|||
|
|
backgroundPosition: "left bottom",
|
|||
|
|
backgroundRepeat: "repeat-x",
|
|||
|
|
paddingBottom: "0.7px"
|
|||
|
|
},
|
|||
|
|
".cm-lintRange-error": { backgroundImage: underline("#f11") },
|
|||
|
|
".cm-lintRange-warning": { backgroundImage: underline("orange") },
|
|||
|
|
".cm-lintRange-info": { backgroundImage: underline("#999") },
|
|||
|
|
".cm-lintRange-hint": { backgroundImage: underline("#66d") },
|
|||
|
|
".cm-lintRange-active": { backgroundColor: "#ffdd9980" },
|
|||
|
|
".cm-tooltip-lint": {
|
|||
|
|
padding: 0,
|
|||
|
|
margin: 0
|
|||
|
|
},
|
|||
|
|
".cm-lintPoint": {
|
|||
|
|
position: "relative",
|
|||
|
|
"&:after": {
|
|||
|
|
content: '""',
|
|||
|
|
position: "absolute",
|
|||
|
|
bottom: 0,
|
|||
|
|
left: "-2px",
|
|||
|
|
borderLeft: "3px solid transparent",
|
|||
|
|
borderRight: "3px solid transparent",
|
|||
|
|
borderBottom: "4px solid #d11"
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
".cm-lintPoint-warning": {
|
|||
|
|
"&:after": { borderBottomColor: "orange" }
|
|||
|
|
},
|
|||
|
|
".cm-lintPoint-info": {
|
|||
|
|
"&:after": { borderBottomColor: "#999" }
|
|||
|
|
},
|
|||
|
|
".cm-lintPoint-hint": {
|
|||
|
|
"&:after": { borderBottomColor: "#66d" }
|
|||
|
|
},
|
|||
|
|
".cm-panel.cm-panel-lint": {
|
|||
|
|
position: "relative",
|
|||
|
|
"& ul": {
|
|||
|
|
maxHeight: "100px",
|
|||
|
|
overflowY: "auto",
|
|||
|
|
"& [aria-selected]": {
|
|||
|
|
backgroundColor: "#ddd",
|
|||
|
|
"& u": { textDecoration: "underline" }
|
|||
|
|
},
|
|||
|
|
"&:focus [aria-selected]": {
|
|||
|
|
background_fallback: "#bdf",
|
|||
|
|
backgroundColor: "Highlight",
|
|||
|
|
color_fallback: "white",
|
|||
|
|
color: "HighlightText"
|
|||
|
|
},
|
|||
|
|
"& u": { textDecoration: "none" },
|
|||
|
|
padding: 0,
|
|||
|
|
margin: 0
|
|||
|
|
},
|
|||
|
|
"& [name=close]": {
|
|||
|
|
position: "absolute",
|
|||
|
|
top: "0",
|
|||
|
|
right: "2px",
|
|||
|
|
background: "inherit",
|
|||
|
|
border: "none",
|
|||
|
|
font: "inherit",
|
|||
|
|
padding: 0,
|
|||
|
|
margin: 0
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
"&dark .cm-lintRange-active": { backgroundColor: "#86714a80" },
|
|||
|
|
"&dark .cm-panel.cm-panel-lint ul": {
|
|||
|
|
"& [aria-selected]": {
|
|||
|
|
backgroundColor: "#2e343e"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
function severityWeight(sev) {
|
|||
|
|
return sev == "error" ? 4 : sev == "warning" ? 3 : sev == "info" ? 2 : 1;
|
|||
|
|
}
|
|||
|
|
function maxSeverity(diagnostics) {
|
|||
|
|
let sev = "hint", weight = 1;
|
|||
|
|
for (let d of diagnostics) {
|
|||
|
|
let w = severityWeight(d.severity);
|
|||
|
|
if (w > weight) {
|
|||
|
|
weight = w;
|
|||
|
|
sev = d.severity;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return sev;
|
|||
|
|
}
|
|||
|
|
var LintGutterMarker = class extends GutterMarker {
|
|||
|
|
constructor(diagnostics) {
|
|||
|
|
super();
|
|||
|
|
this.diagnostics = diagnostics;
|
|||
|
|
this.severity = maxSeverity(diagnostics);
|
|||
|
|
}
|
|||
|
|
toDOM(view) {
|
|||
|
|
let elt = document.createElement("div");
|
|||
|
|
elt.className = "cm-lint-marker cm-lint-marker-" + this.severity;
|
|||
|
|
let diagnostics = this.diagnostics;
|
|||
|
|
let diagnosticsFilter = view.state.facet(lintGutterConfig).tooltipFilter;
|
|||
|
|
if (diagnosticsFilter)
|
|||
|
|
diagnostics = diagnosticsFilter(diagnostics, view.state);
|
|||
|
|
if (diagnostics.length)
|
|||
|
|
elt.onmouseover = () => gutterMarkerMouseOver(view, elt, diagnostics);
|
|||
|
|
return elt;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function trackHoverOn(view, marker) {
|
|||
|
|
let mousemove = (event) => {
|
|||
|
|
let rect = marker.getBoundingClientRect();
|
|||
|
|
if (event.clientX > rect.left - 10 && event.clientX < rect.right + 10 && event.clientY > rect.top - 10 && event.clientY < rect.bottom + 10)
|
|||
|
|
return;
|
|||
|
|
for (let target = event.target; target; target = target.parentNode) {
|
|||
|
|
if (target.nodeType == 1 && target.classList.contains("cm-tooltip-lint"))
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
window.removeEventListener("mousemove", mousemove);
|
|||
|
|
if (view.state.field(lintGutterTooltip))
|
|||
|
|
view.dispatch({ effects: setLintGutterTooltip.of(null) });
|
|||
|
|
};
|
|||
|
|
window.addEventListener("mousemove", mousemove);
|
|||
|
|
}
|
|||
|
|
function gutterMarkerMouseOver(view, marker, diagnostics) {
|
|||
|
|
function hovered() {
|
|||
|
|
let line = view.elementAtHeight(marker.getBoundingClientRect().top + 5 - view.documentTop);
|
|||
|
|
const linePos = view.coordsAtPos(line.from);
|
|||
|
|
if (linePos) {
|
|||
|
|
view.dispatch({ effects: setLintGutterTooltip.of({
|
|||
|
|
pos: line.from,
|
|||
|
|
above: false,
|
|||
|
|
clip: false,
|
|||
|
|
create() {
|
|||
|
|
return {
|
|||
|
|
dom: diagnosticsTooltip(view, diagnostics),
|
|||
|
|
getCoords: () => marker.getBoundingClientRect()
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}) });
|
|||
|
|
}
|
|||
|
|
marker.onmouseout = marker.onmousemove = null;
|
|||
|
|
trackHoverOn(view, marker);
|
|||
|
|
}
|
|||
|
|
let { hoverTime } = view.state.facet(lintGutterConfig);
|
|||
|
|
let hoverTimeout = setTimeout(hovered, hoverTime);
|
|||
|
|
marker.onmouseout = () => {
|
|||
|
|
clearTimeout(hoverTimeout);
|
|||
|
|
marker.onmouseout = marker.onmousemove = null;
|
|||
|
|
};
|
|||
|
|
marker.onmousemove = () => {
|
|||
|
|
clearTimeout(hoverTimeout);
|
|||
|
|
hoverTimeout = setTimeout(hovered, hoverTime);
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
function markersForDiagnostics(doc, diagnostics) {
|
|||
|
|
let byLine = /* @__PURE__ */ Object.create(null);
|
|||
|
|
for (let diagnostic of diagnostics) {
|
|||
|
|
let line = doc.lineAt(diagnostic.from);
|
|||
|
|
(byLine[line.from] || (byLine[line.from] = [])).push(diagnostic);
|
|||
|
|
}
|
|||
|
|
let markers = [];
|
|||
|
|
for (let line in byLine) {
|
|||
|
|
markers.push(new LintGutterMarker(byLine[line]).range(+line));
|
|||
|
|
}
|
|||
|
|
return RangeSet.of(markers, true);
|
|||
|
|
}
|
|||
|
|
var lintGutterExtension = gutter({
|
|||
|
|
class: "cm-gutter-lint",
|
|||
|
|
markers: (view) => view.state.field(lintGutterMarkers),
|
|||
|
|
widgetMarker: (view, widget, block) => {
|
|||
|
|
let diagnostics = [];
|
|||
|
|
view.state.field(lintGutterMarkers).between(block.from, block.to, (from, to, value) => {
|
|||
|
|
if (from > block.from && from < block.to)
|
|||
|
|
diagnostics.push(...value.diagnostics);
|
|||
|
|
});
|
|||
|
|
return diagnostics.length ? new LintGutterMarker(diagnostics) : null;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
var lintGutterMarkers = StateField.define({
|
|||
|
|
create() {
|
|||
|
|
return RangeSet.empty;
|
|||
|
|
},
|
|||
|
|
update(markers, tr) {
|
|||
|
|
markers = markers.map(tr.changes);
|
|||
|
|
let diagnosticFilter = tr.state.facet(lintGutterConfig).markerFilter;
|
|||
|
|
for (let effect of tr.effects) {
|
|||
|
|
if (effect.is(setDiagnosticsEffect)) {
|
|||
|
|
let diagnostics = effect.value;
|
|||
|
|
if (diagnosticFilter)
|
|||
|
|
diagnostics = diagnosticFilter(diagnostics || [], tr.state);
|
|||
|
|
markers = markersForDiagnostics(tr.state.doc, diagnostics.slice(0));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return markers;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
var setLintGutterTooltip = StateEffect.define();
|
|||
|
|
var lintGutterTooltip = StateField.define({
|
|||
|
|
create() {
|
|||
|
|
return null;
|
|||
|
|
},
|
|||
|
|
update(tooltip, tr) {
|
|||
|
|
if (tooltip && tr.docChanged)
|
|||
|
|
tooltip = hideTooltip(tr, tooltip) ? null : { ...tooltip, pos: tr.changes.mapPos(tooltip.pos) };
|
|||
|
|
return tr.effects.reduce((t, e) => e.is(setLintGutterTooltip) ? e.value : t, tooltip);
|
|||
|
|
},
|
|||
|
|
provide: (field) => showTooltip.from(field)
|
|||
|
|
});
|
|||
|
|
var lintGutterTheme = EditorView.baseTheme({
|
|||
|
|
".cm-gutter-lint": {
|
|||
|
|
width: "1.4em",
|
|||
|
|
"& .cm-gutterElement": {
|
|||
|
|
padding: ".2em"
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
".cm-lint-marker": {
|
|||
|
|
width: "1em",
|
|||
|
|
height: "1em"
|
|||
|
|
},
|
|||
|
|
".cm-lint-marker-info": {
|
|||
|
|
content: svg(`<path fill="#aaf" stroke="#77e" stroke-width="6" stroke-linejoin="round" d="M5 5L35 5L35 35L5 35Z"/>`)
|
|||
|
|
},
|
|||
|
|
".cm-lint-marker-warning": {
|
|||
|
|
content: svg(`<path fill="#fe8" stroke="#fd7" stroke-width="6" stroke-linejoin="round" d="M20 6L37 35L3 35Z"/>`)
|
|||
|
|
},
|
|||
|
|
".cm-lint-marker-error": {
|
|||
|
|
content: svg(`<circle cx="20" cy="20" r="15" fill="#f87" stroke="#f43" stroke-width="6"/>`)
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
var lintHover = hoverTooltip(lintTooltip, { hideOn: hideTooltip });
|
|||
|
|
var lintExtensions = [
|
|||
|
|
lintState,
|
|||
|
|
EditorView.decorations.compute([lintState], (state) => {
|
|||
|
|
let { selected, panel } = state.field(lintState);
|
|||
|
|
return !selected || !panel || selected.from == selected.to ? Decoration.none : Decoration.set([
|
|||
|
|
activeMark.range(selected.from, selected.to)
|
|||
|
|
]);
|
|||
|
|
}),
|
|||
|
|
lintHover,
|
|||
|
|
baseTheme2
|
|||
|
|
];
|
|||
|
|
var lintGutterConfig = Facet.define({
|
|||
|
|
combine(configs) {
|
|||
|
|
return combineConfig(configs, {
|
|||
|
|
hoverTime: 300,
|
|||
|
|
markerFilter: null,
|
|||
|
|
tooltipFilter: null
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// node_modules/codemirror/dist/index.js
|
|||
|
|
var basicSetup = (() => [
|
|||
|
|
lineNumbers(),
|
|||
|
|
highlightActiveLineGutter(),
|
|||
|
|
highlightSpecialChars(),
|
|||
|
|
history(),
|
|||
|
|
foldGutter(),
|
|||
|
|
drawSelection(),
|
|||
|
|
dropCursor(),
|
|||
|
|
EditorState.allowMultipleSelections.of(true),
|
|||
|
|
indentOnInput(),
|
|||
|
|
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
|||
|
|
bracketMatching(),
|
|||
|
|
closeBrackets(),
|
|||
|
|
autocompletion(),
|
|||
|
|
rectangularSelection(),
|
|||
|
|
crosshairCursor(),
|
|||
|
|
highlightActiveLine(),
|
|||
|
|
highlightSelectionMatches(),
|
|||
|
|
keymap.of([
|
|||
|
|
...closeBracketsKeymap,
|
|||
|
|
...defaultKeymap,
|
|||
|
|
...searchKeymap,
|
|||
|
|
...historyKeymap,
|
|||
|
|
...foldKeymap,
|
|||
|
|
...completionKeymap,
|
|||
|
|
...lintKeymap
|
|||
|
|
])
|
|||
|
|
])();
|
|||
|
|
var minimalSetup = (() => [
|
|||
|
|
highlightSpecialChars(),
|
|||
|
|
history(),
|
|||
|
|
drawSelection(),
|
|||
|
|
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
|||
|
|
keymap.of([
|
|||
|
|
...defaultKeymap,
|
|||
|
|
...historyKeymap
|
|||
|
|
])
|
|||
|
|
])();
|
|||
|
|
export {
|
|||
|
|
EditorView,
|
|||
|
|
basicSetup,
|
|||
|
|
minimalSetup
|
|||
|
|
};
|
|||
|
|
//# sourceMappingURL=codemirror.js.map
|