basant307/AI_Governance_Project
048
1import { showDialog, EditorView, Decoration, ViewPlugin, showPanel, runScopeHandlers, getPanel } from '@codemirror/view';2import { codePointAt, fromCodePoint, codePointSize, EditorSelection, Facet, combineConfig, CharCategory, StateEffect, StateField, RangeSetBuilder, Prec, EditorState, findClusterBreak } from '@codemirror/state';3import elt from 'crelt';4 5const basicNormalize = typeof String.prototype.normalize == "function"6 ? x => x.normalize("NFKD") : x => x;7/**8A search cursor provides an iterator over text matches in a9document.10*/11class SearchCursor {12 /**13 Create a text cursor. The query is the search string, `from` to14 `to` provides the region to search.15 16 When `normalize` is given, it will be called, on both the query17 string and the content it is matched against, before comparing.18 You can, for example, create a case-insensitive search by19 passing `s => s.toLowerCase()`.20 21 Text is always normalized with22 [`.normalize("NFKD")`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize)23 (when supported).24 */25 constructor(text, query, from = 0, to = text.length, normalize, test) {26 this.test = test;27 /**28 The current match (only holds a meaningful value after29 [`next`](https://codemirror.net/6/docs/ref/#search.SearchCursor.next) has been called and when30 `done` is false).31 32 The `precise` flag will be set to false if the match starts or33 ends _inside_ a character that, when normalized, expands to34 multiple characters. It indicates that the `from`-`to` range35 covers content that isn't part of the actual match.36 */37 this.value = { from: 0, to: 0, precise: false };38 /**39 Whether the end of the iterated region has been reached.40 */41 this.done = false;42 this.matches = [];43 this.buffer = "";44 this.bufferPos = 0;45 this.iter = text.iterRange(from, to);46 this.bufferStart = from;47 this.normalize = normalize ? x => normalize(basicNormalize(x)) : basicNormalize;48 this.query = this.normalize(query);49 }50 peek() {51 if (this.bufferPos == this.buffer.length) {52 this.bufferStart += this.buffer.length;53 this.iter.next();54 if (this.iter.done)55 return -1;56 this.bufferPos = 0;57 this.buffer = this.iter.value;58 }59 return codePointAt(this.buffer, this.bufferPos);60 }61 /**62 Look for the next match. Updates the iterator's63 [`value`](https://codemirror.net/6/docs/ref/#search.SearchCursor.value) and64 [`done`](https://codemirror.net/6/docs/ref/#search.SearchCursor.done) properties. Should be called65 at least once before using the cursor.66 */67 next() {68 while (this.matches.length)69 this.matches.pop();70 return this.nextOverlapping();71 }72 /**73 The `next` method will ignore matches that partially overlap a74 previous match. This method behaves like `next`, but includes75 such matches.76 */77 nextOverlapping() {78 for (;;) {79 let next = this.peek();80 if (next < 0) {81 this.done = true;82 return this;83 }84 let str = fromCodePoint(next), start = this.bufferStart + this.bufferPos;85 this.bufferPos += codePointSize(next);86 let norm = this.normalize(str);87 if (norm.length)88 for (let i = 0, pos = start, posPrecise = true;; i++) {89 let code = norm.charCodeAt(i);90 let match = this.match(code, pos, posPrecise, this.bufferPos + this.bufferStart, i == norm.length - 1);91 if (match) {92 this.value = match;93 return this;94 }95 if (i == norm.length - 1)96 break;97 if (posPrecise && i < str.length && str.charCodeAt(i) == code)98 pos++;99 else100 posPrecise = false;101 }102 }103 }104 match(code, pos, posPrecise, end, endPrecise) {105 let match = null;106 for (let i = 0; i < this.matches.length;) {107 let partial = this.matches[i], keep = false;108 if (this.query.charCodeAt(partial.index) == code) {109 if (partial.index == this.query.length - 1) {110 match = { from: partial.from, to: end, precise: endPrecise && partial.precise };111 }112 else {113 partial.index++;114 keep = true;115 }116 }117 if (keep)118 i++;119 else120 this.matches.splice(i, 1);121 }122 if (this.query.charCodeAt(0) == code) {123 if (this.query.length == 1)124 match = { from: pos, to: end, precise: posPrecise && endPrecise };125 else126 this.matches.push({ from: pos, index: 1, precise: posPrecise });127 }128 if (match && this.test && !this.test(match.from, match.to, this.buffer, this.bufferStart))129 match = null;130 return match;131 }132}133if (typeof Symbol != "undefined")134 SearchCursor.prototype[Symbol.iterator] = function () { return this; };135 136const empty = { from: -1, to: -1, match: /*@__PURE__*//.*/.exec(""), precise: true };137const baseFlags = "gm" + (/x/.unicode == null ? "" : "u");138/**139This class is similar to [`SearchCursor`](https://codemirror.net/6/docs/ref/#search.SearchCursor)140but searches for a regular expression pattern instead of a plain141string.142*/143class RegExpCursor {144 /**145 Create a cursor that will search the given range in the given146 document. `query` should be the raw pattern (as you'd pass it to147 `new RegExp`).148 */149 constructor(text, query, options, from = 0, to = text.length) {150 this.text = text;151 this.to = to;152 this.curLine = "";153 /**154 Set to `true` when the cursor has reached the end of the search155 range.156 */157 this.done = false;158 /**159 Will contain an object with the extent of the match and the160 match object when [`next`](https://codemirror.net/6/docs/ref/#search.RegExpCursor.next)161 sucessfully finds a match. The `precise` flag is always true for162 this type of cursor, and only there to make sure this cursor is163 a subtype of `SearchCursor`.164 */165 this.value = empty;166 if (/\\[sWDnr]|\n|\r|\[\^/.test(query))167 return new MultilineRegExpCursor(text, query, options, from, to);168 this.re = new RegExp(query, baseFlags + ((options === null || options === void 0 ? void 0 : options.ignoreCase) ? "i" : ""));169 this.test = options === null || options === void 0 ? void 0 : options.test;170 this.iter = text.iter();171 let startLine = text.lineAt(from);172 this.curLineStart = startLine.from;173 this.matchPos = toCharEnd(text, from);174 this.getLine(this.curLineStart);175 }176 getLine(skip) {177 this.iter.next(skip);178 if (this.iter.lineBreak) {179 this.curLine = "";180 }181 else {182 this.curLine = this.iter.value;183 if (this.curLineStart + this.curLine.length > this.to)184 this.curLine = this.curLine.slice(0, this.to - this.curLineStart);185 this.iter.next();186 }187 }188 nextLine() {189 this.curLineStart = this.curLineStart + this.curLine.length + 1;190 if (this.curLineStart > this.to)191 this.curLine = "";192 else193 this.getLine(0);194 }195 /**196 Move to the next match, if there is one.197 */198 next() {199 for (let off = this.matchPos - this.curLineStart;;) {200 this.re.lastIndex = off;201 let match = this.matchPos <= this.to && this.re.exec(this.curLine);202 if (match) {203 let from = this.curLineStart + match.index, to = from + match[0].length;204 this.matchPos = toCharEnd(this.text, to + (from == to ? 1 : 0));205 if (from == this.curLineStart + this.curLine.length)206 this.nextLine();207 if ((from < to || from > this.value.to) && (!this.test || this.test(from, to, match))) {208 this.value = { from, to, precise: true, match };209 return this;210 }211 off = this.matchPos - this.curLineStart;212 }213 else if (this.curLineStart + this.curLine.length < this.to) {214 this.nextLine();215 off = 0;216 }217 else {218 this.done = true;219 return this;220 }221 }222 }223}224const flattened = /*@__PURE__*/new WeakMap();225// Reusable (partially) flattened document strings226class FlattenedDoc {227 constructor(from, text) {228 this.from = from;229 this.text = text;230 }231 get to() { return this.from + this.text.length; }232 static get(doc, from, to) {233 let cached = flattened.get(doc);234 if (!cached || cached.from >= to || cached.to <= from) {235 let flat = new FlattenedDoc(from, doc.sliceString(from, to));236 flattened.set(doc, flat);237 return flat;238 }239 if (cached.from == from && cached.to == to)240 return cached;241 let { text, from: cachedFrom } = cached;242 if (cachedFrom > from) {243 text = doc.sliceString(from, cachedFrom) + text;244 cachedFrom = from;245 }246 if (cached.to < to)247 text += doc.sliceString(cached.to, to);248 flattened.set(doc, new FlattenedDoc(cachedFrom, text));249 return new FlattenedDoc(from, text.slice(from - cachedFrom, to - cachedFrom));250 }251}252class MultilineRegExpCursor {253 constructor(text, query, options, from, to) {254 this.text = text;255 this.to = to;256 this.done = false;257 this.value = empty;258 this.matchPos = toCharEnd(text, from);259 this.re = new RegExp(query, baseFlags + ((options === null || options === void 0 ? void 0 : options.ignoreCase) ? "i" : ""));260 this.test = options === null || options === void 0 ? void 0 : options.test;261 this.flat = FlattenedDoc.get(text, from, this.chunkEnd(from + 5000 /* Chunk.Base */));262 }263 chunkEnd(pos) {264 return pos >= this.to ? this.to : this.text.lineAt(pos).to;265 }266 next() {267 for (;;) {268 let off = this.re.lastIndex = this.matchPos - this.flat.from;269 let match = this.re.exec(this.flat.text);270 // Skip empty matches directly after the last match271 if (match && !match[0] && match.index == off) {272 this.re.lastIndex = off + 1;273 match = this.re.exec(this.flat.text);274 }275 if (match) {276 let from = this.flat.from + match.index, to = from + match[0].length;277 // If a match goes almost to the end of a noncomplete chunk, try278 // again, since it'll likely be able to match more279 if ((this.flat.to >= this.to || match.index + match[0].length <= this.flat.text.length - 10) &&280 (!this.test || this.test(from, to, match))) {281 this.value = { from, to, precise: true, match };282 this.matchPos = toCharEnd(this.text, to + (from == to ? 1 : 0));283 return this;284 }285 }286 if (this.flat.to == this.to) {287 this.done = true;288 return this;289 }290 // Grow the flattened doc291 this.flat = FlattenedDoc.get(this.text, this.flat.from, this.chunkEnd(this.flat.from + this.flat.text.length * 2));292 }293 }294}295if (typeof Symbol != "undefined") {296 RegExpCursor.prototype[Symbol.iterator] = MultilineRegExpCursor.prototype[Symbol.iterator] =297 function () { return this; };298}299function validRegExp(source) {300 try {301 new RegExp(source, baseFlags);302 return true;303 }304 catch (_a) {305 return false;306 }307}308function toCharEnd(text, pos) {309 if (pos >= text.length)310 return pos;311 let line = text.lineAt(pos), next;312 while (pos < line.to && (next = line.text.charCodeAt(pos - line.from)) >= 0xDC00 && next < 0xE000)313 pos++;314 return pos;315}316 317/**318Command that shows a dialog asking the user for a line number, and319when a valid position is provided, moves the cursor to that line.320 321Supports line numbers, relative line offsets prefixed with `+` or322`-`, document percentages suffixed with `%`, and an optional323column position by adding `:` and a second number after the line324number.325*/326const gotoLine = view => {327 let { state } = view;328 let line = String(state.doc.lineAt(view.state.selection.main.head).number);329 let { close, result } = showDialog(view, {330 label: state.phrase("Go to line"),331 input: { type: "text", name: "line", value: line },332 focus: true,333 submitLabel: state.phrase("go"),334 });335 result.then(form => {336 let match = form && /^([+-])?(\d+)?(:\d+)?(%)?$/.exec(form.elements["line"].value);337 if (!match) {338 view.dispatch({ effects: close });339 return;340 }341 let startLine = state.doc.lineAt(state.selection.main.head);342 let [, sign, ln, cl, percent] = match;343 let col = cl ? +cl.slice(1) : 0;344 let line = ln ? +ln : startLine.number;345 if (ln && percent) {346 let pc = line / 100;347 if (sign)348 pc = pc * (sign == "-" ? -1 : 1) + (startLine.number / state.doc.lines);349 line = Math.round(state.doc.lines * pc);350 }351 else if (ln && sign) {352 line = line * (sign == "-" ? -1 : 1) + startLine.number;353 }354 let docLine = state.doc.line(Math.max(1, Math.min(state.doc.lines, line)));355 let selection = EditorSelection.cursor(docLine.from + Math.max(0, Math.min(col, docLine.length)));356 view.dispatch({357 effects: [close, EditorView.scrollIntoView(selection.from, { y: 'center' })],358 selection,359 });360 });361 return true;362};363 364const defaultHighlightOptions = {365 highlightWordAroundCursor: false,366 minSelectionLength: 1,367 maxMatches: 100,368 wholeWords: false369};370const highlightConfig = /*@__PURE__*/Facet.define({371 combine(options) {372 return combineConfig(options, defaultHighlightOptions, {373 highlightWordAroundCursor: (a, b) => a || b,374 minSelectionLength: Math.min,375 maxMatches: Math.min376 });377 }378});379/**380This extension highlights text that matches the selection. It uses381the `"cm-selectionMatch"` class for the highlighting. When382`highlightWordAroundCursor` is enabled, the word at the cursor383itself will be highlighted with `"cm-selectionMatch-main"`.384*/385function highlightSelectionMatches(options) {386 let ext = [defaultTheme, matchHighlighter];387 if (options)388 ext.push(highlightConfig.of(options));389 return ext;390}391const matchDeco = /*@__PURE__*/Decoration.mark({ class: "cm-selectionMatch" });392const mainMatchDeco = /*@__PURE__*/Decoration.mark({ class: "cm-selectionMatch cm-selectionMatch-main" });393// Whether the characters directly outside the given positions are non-word characters394function insideWordBoundaries(check, state, from, to) {395 return (from == 0 || check(state.sliceDoc(from - 1, from)) != CharCategory.Word) &&396 (to == state.doc.length || check(state.sliceDoc(to, to + 1)) != CharCategory.Word);397}398// Whether the characters directly at the given positions are word characters399function insideWord(check, state, from, to) {400 return check(state.sliceDoc(from, from + 1)) == CharCategory.Word401 && check(state.sliceDoc(to - 1, to)) == CharCategory.Word;402}403const matchHighlighter = /*@__PURE__*/ViewPlugin.fromClass(class {404 constructor(view) {405 this.decorations = this.getDeco(view);406 }407 update(update) {408 if (update.selectionSet || update.docChanged || update.viewportChanged)409 this.decorations = this.getDeco(update.view);410 }411 getDeco(view) {412 let conf = view.state.facet(highlightConfig);413 let { state } = view, sel = state.selection;414 if (sel.ranges.length > 1)415 return Decoration.none;416 let range = sel.main, query, check = null;417 if (range.empty) {418 if (!conf.highlightWordAroundCursor)419 return Decoration.none;420 let word = state.wordAt(range.head);421 if (!word)422 return Decoration.none;423 check = state.charCategorizer(range.head);424 query = state.sliceDoc(word.from, word.to);425 }426 else {427 let len = range.to - range.from;428 if (len < conf.minSelectionLength || len > 200)429 return Decoration.none;430 if (conf.wholeWords) {431 query = state.sliceDoc(range.from, range.to); // TODO: allow and include leading/trailing space?432 check = state.charCategorizer(range.head);433 if (!(insideWordBoundaries(check, state, range.from, range.to) &&434 insideWord(check, state, range.from, range.to)))435 return Decoration.none;436 }437 else {438 query = state.sliceDoc(range.from, range.to);439 if (!query)440 return Decoration.none;441 }442 }443 let deco = [];444 for (let part of view.visibleRanges) {445 let cursor = new SearchCursor(state.doc, query, part.from, part.to);446 while (!cursor.next().done) {447 let { from, to } = cursor.value;448 if (!check || insideWordBoundaries(check, state, from, to)) {449 if (range.empty && from <= range.from && to >= range.to)450 deco.push(mainMatchDeco.range(from, to));451 else if (from >= range.to || to <= range.from)452 deco.push(matchDeco.range(from, to));453 if (deco.length > conf.maxMatches)454 return Decoration.none;455 }456 }457 }458 return Decoration.set(deco);459 }460}, {461 decorations: v => v.decorations462});463const defaultTheme = /*@__PURE__*/EditorView.baseTheme({464 ".cm-selectionMatch": { backgroundColor: "#99ff7780" },465 ".cm-searchMatch .cm-selectionMatch": { backgroundColor: "transparent" }466});467// Select the words around the cursors.468const selectWord = ({ state, dispatch }) => {469 let { selection } = state;470 let newSel = EditorSelection.create(selection.ranges.map(range => state.wordAt(range.head) || EditorSelection.cursor(range.head)), selection.mainIndex);471 if (newSel.eq(selection))472 return false;473 dispatch(state.update({ selection: newSel }));474 return true;475};476// Find next occurrence of query relative to last cursor. Wrap around477// the document if there are no more matches.478function findNextOccurrence(state, query) {479 let { main, ranges } = state.selection;480 let word = state.wordAt(main.head), fullWord = word && word.from == main.from && word.to == main.to;481 for (let cycled = false, cursor = new SearchCursor(state.doc, query, ranges[ranges.length - 1].to);;) {482 cursor.next();483 if (cursor.done) {484 if (cycled)485 return null;486 cursor = new SearchCursor(state.doc, query, 0, Math.max(0, ranges[ranges.length - 1].from - 1));487 cycled = true;488 }489 else {490 if (cycled && ranges.some(r => r.from == cursor.value.from))491 continue;492 if (fullWord) {493 let word = state.wordAt(cursor.value.from);494 if (!word || word.from != cursor.value.from || word.to != cursor.value.to)495 continue;496 }497 return cursor.value;498 }499 }500}501/**502Select next occurrence of the current selection. Expand selection503to the surrounding word when the selection is empty.504*/505const selectNextOccurrence = ({ state, dispatch }) => {506 let { ranges } = state.selection;507 if (ranges.some(sel => sel.from === sel.to))508 return selectWord({ state, dispatch });509 let searchedText = state.sliceDoc(ranges[0].from, ranges[0].to);510 if (state.selection.ranges.some(r => state.sliceDoc(r.from, r.to) != searchedText))511 return false;512 let range = findNextOccurrence(state, searchedText);513 if (!range)514 return false;515 dispatch(state.update({516 selection: state.selection.addRange(EditorSelection.range(range.from, range.to), false),517 effects: EditorView.scrollIntoView(range.to)518 }));519 return true;520};521 522const searchConfigFacet = /*@__PURE__*/Facet.define({523 combine(configs) {524 return combineConfig(configs, {525 top: false,526 caseSensitive: false,527 literal: false,528 regexp: false,529 wholeWord: false,530 createPanel: view => new SearchPanel(view),531 scrollToMatch: range => EditorView.scrollIntoView(range)532 });533 }534});535/**536Add search state to the editor configuration, and optionally537configure the search extension.538([`openSearchPanel`](https://codemirror.net/6/docs/ref/#search.openSearchPanel) will automatically539enable this if it isn't already on).540*/541function search(config) {542 return config ? [searchConfigFacet.of(config), searchExtensions] : searchExtensions;543}544/**545A search query. Part of the editor's search state.546*/547class SearchQuery {548 /**549 Create a query object.550 */551 constructor(config) {552 this.search = config.search;553 this.caseSensitive = !!config.caseSensitive;554 this.literal = !!config.literal;555 this.regexp = !!config.regexp;556 this.replace = config.replace || "";557 this.valid = !!this.search && (!this.regexp || validRegExp(this.search));558 this.unquoted = this.unquote(this.search);559 this.wholeWord = !!config.wholeWord;560 this.test = config.test;561 }562 /**563 @internal564 */565 unquote(text) {566 return this.literal ? text :567 text.replace(/\\([nrt\\])/g, (_, ch) => ch == "n" ? "\n" : ch == "r" ? "\r" : ch == "t" ? "\t" : "\\");568 }569 /**570 Compare this query to another query.571 */572 eq(other) {573 return this.search == other.search && this.replace == other.replace &&574 this.caseSensitive == other.caseSensitive && this.regexp == other.regexp &&575 this.wholeWord == other.wholeWord && this.test == other.test;576 }577 /**578 @internal579 */580 create() {581 return this.regexp ? new RegExpQuery(this) : new StringQuery(this);582 }583 /**584 Get a search cursor for this query, searching through the given585 range in the given state.586 */587 getCursor(state, from = 0, to) {588 let st = state.doc ? state : EditorState.create({ doc: state });589 if (to == null)590 to = st.doc.length;591 return this.regexp ? regexpCursor(this, st, from, to) : stringCursor(this, st, from, to);592 }593}594class QueryType {595 constructor(spec) {596 this.spec = spec;597 }598}599function wrapStringTest(test, state, inner) {600 return (from, to, buffer, bufferPos) => {601 if (inner && !inner(from, to, buffer, bufferPos))602 return false;603 let match = from >= bufferPos && to <= bufferPos + buffer.length604 ? buffer.slice(from - bufferPos, to - bufferPos)605 : state.doc.sliceString(from, to);606 return test(match, state, from, to);607 };608}609function stringCursor(spec, state, from, to) {610 let test;611 if (spec.wholeWord)612 test = stringWordTest(state.doc, state.charCategorizer(state.selection.main.head));613 if (spec.test)614 test = wrapStringTest(spec.test, state, test);615 return new SearchCursor(state.doc, spec.unquoted, from, to, spec.caseSensitive ? undefined : x => x.toLowerCase(), test);616}617function stringWordTest(doc, categorizer) {618 return (from, to, buf, bufPos) => {619 if (bufPos > from || bufPos + buf.length < to) {620 bufPos = Math.max(0, from - 2);621 buf = doc.sliceString(bufPos, Math.min(doc.length, to + 2));622 }623 return (categorizer(charBefore(buf, from - bufPos)) != CharCategory.Word ||624 categorizer(charAfter(buf, from - bufPos)) != CharCategory.Word) &&625 (categorizer(charAfter(buf, to - bufPos)) != CharCategory.Word ||626 categorizer(charBefore(buf, to - bufPos)) != CharCategory.Word);627 };628}629class StringQuery extends QueryType {630 constructor(spec) {631 super(spec);632 }633 nextMatch(state, curFrom, curTo) {634 let cursor = stringCursor(this.spec, state, curTo, state.doc.length).nextOverlapping();635 if (cursor.done) {636 let end = Math.min(state.doc.length, curFrom + this.spec.unquoted.length);637 cursor = stringCursor(this.spec, state, 0, end).nextOverlapping();638 }639 return cursor.done || cursor.value.from == curFrom && cursor.value.to == curTo ? null : cursor.value;640 }641 // Searching in reverse is, rather than implementing an inverted search642 // cursor, done by scanning chunk after chunk forward.643 prevMatchInRange(state, from, to) {644 for (let pos = to;;) {645 let start = Math.max(from, pos - 10000 /* FindPrev.ChunkSize */ - this.spec.unquoted.length);646 let cursor = stringCursor(this.spec, state, start, pos), range = null;647 while (!cursor.nextOverlapping().done)648 range = cursor.value;649 if (range)650 return range;651 if (start == from)652 return null;653 pos -= 10000 /* FindPrev.ChunkSize */;654 }655 }656 prevMatch(state, curFrom, curTo) {657 let found = this.prevMatchInRange(state, 0, curFrom);658 if (!found)659 found = this.prevMatchInRange(state, Math.max(0, curTo - this.spec.unquoted.length), state.doc.length);660 return found && (found.from != curFrom || found.to != curTo) ? found : null;661 }662 getReplacement(_result) { return this.spec.unquote(this.spec.replace); }663 matchAll(state, limit) {664 let cursor = stringCursor(this.spec, state, 0, state.doc.length), ranges = [];665 while (!cursor.next().done) {666 if (ranges.length >= limit)667 return null;668 ranges.push(cursor.value);669 }670 return ranges;671 }672 highlight(state, from, to, add) {673 let cursor = stringCursor(this.spec, state, Math.max(0, from - this.spec.unquoted.length), Math.min(to + this.spec.unquoted.length, state.doc.length));674 while (!cursor.next().done)675 add(cursor.value.from, cursor.value.to);676 }677}678function wrapRegexpTest(test, state, inner) {679 return (from, to, match) => {680 return (!inner || inner(from, to, match)) && test(match[0], state, from, to);681 };682}683function regexpCursor(spec, state, from, to) {684 let test;685 if (spec.wholeWord)686 test = regexpWordTest(state.charCategorizer(state.selection.main.head));687 if (spec.test)688 test = wrapRegexpTest(spec.test, state, test);689 return new RegExpCursor(state.doc, spec.search, { ignoreCase: !spec.caseSensitive, test }, from, to);690}691function charBefore(str, index) {692 return str.slice(findClusterBreak(str, index, false), index);693}694function charAfter(str, index) {695 return str.slice(index, findClusterBreak(str, index));696}697function regexpWordTest(categorizer) {698 return (_from, _to, match) => !match[0].length ||699 (categorizer(charBefore(match.input, match.index)) != CharCategory.Word ||700 categorizer(charAfter(match.input, match.index)) != CharCategory.Word) &&701 (categorizer(charAfter(match.input, match.index + match[0].length)) != CharCategory.Word ||702 categorizer(charBefore(match.input, match.index + match[0].length)) != CharCategory.Word);703}704class RegExpQuery extends QueryType {705 nextMatch(state, curFrom, curTo) {706 let cursor = regexpCursor(this.spec, state, curTo, state.doc.length).next();707 if (cursor.done)708 cursor = regexpCursor(this.spec, state, 0, curFrom).next();709 return cursor.done ? null : cursor.value;710 }711 prevMatchInRange(state, from, to) {712 for (let size = 1;; size++) {713 let start = Math.max(from, to - size * 10000 /* FindPrev.ChunkSize */);714 let cursor = regexpCursor(this.spec, state, start, to), range = null;715 while (!cursor.next().done)716 range = cursor.value;717 if (range && (start == from || range.from > start + 10))718 return range;719 if (start == from)720 return null;721 }722 }723 prevMatch(state, curFrom, curTo) {724 return this.prevMatchInRange(state, 0, curFrom) ||725 this.prevMatchInRange(state, curTo, state.doc.length);726 }727 getReplacement(result) {728 return this.spec.unquote(this.spec.replace).replace(/\$([$&]|\d+)/g, (m, i) => {729 if (i == "&")730 return result.match[0];731 if (i == "$")732 return "$";733 for (let l = i.length; l > 0; l--) {734 let n = +i.slice(0, l);735 if (n > 0 && n < result.match.length)736 return result.match[n] + i.slice(l);737 }738 return m;739 });740 }741 matchAll(state, limit) {742 let cursor = regexpCursor(this.spec, state, 0, state.doc.length), ranges = [];743 while (!cursor.next().done) {744 if (ranges.length >= limit)745 return null;746 ranges.push(cursor.value);747 }748 return ranges;749 }750 highlight(state, from, to, add) {751 let cursor = regexpCursor(this.spec, state, Math.max(0, from - 250 /* RegExp.HighlightMargin */), Math.min(to + 250 /* RegExp.HighlightMargin */, state.doc.length));752 while (!cursor.next().done)753 add(cursor.value.from, cursor.value.to);754 }755}756/**757A state effect that updates the current search query. Note that758this only has an effect if the search state has been initialized759(by including [`search`](https://codemirror.net/6/docs/ref/#search.search) in your configuration or760by running [`openSearchPanel`](https://codemirror.net/6/docs/ref/#search.openSearchPanel) at least761once).762*/763const setSearchQuery = /*@__PURE__*/StateEffect.define();764const togglePanel = /*@__PURE__*/StateEffect.define();765const searchState = /*@__PURE__*/StateField.define({766 create(state) {767 return new SearchState(defaultQuery(state).create(), null);768 },769 update(value, tr) {770 for (let effect of tr.effects) {771 if (effect.is(setSearchQuery))772 value = new SearchState(effect.value.create(), value.panel);773 else if (effect.is(togglePanel))774 value = new SearchState(value.query, effect.value ? createSearchPanel : null);775 }776 return value;777 },778 provide: f => showPanel.from(f, val => val.panel)779});780/**781Get the current search query from an editor state.782*/783function getSearchQuery(state) {784 let curState = state.field(searchState, false);785 return curState ? curState.query.spec : defaultQuery(state);786}787/**788Query whether the search panel is open in the given editor state.789*/790function searchPanelOpen(state) {791 var _a;792 return ((_a = state.field(searchState, false)) === null || _a === void 0 ? void 0 : _a.panel) != null;793}794class SearchState {795 constructor(query, panel) {796 this.query = query;797 this.panel = panel;798 }799}800const matchMark = /*@__PURE__*/Decoration.mark({ class: "cm-searchMatch" }), selectedMatchMark = /*@__PURE__*/Decoration.mark({ class: "cm-searchMatch cm-searchMatch-selected" });801const searchHighlighter = /*@__PURE__*/ViewPlugin.fromClass(class {802 constructor(view) {803 this.view = view;804 this.decorations = this.highlight(view.state.field(searchState));805 }806 update(update) {807 let state = update.state.field(searchState);808 if (state != update.startState.field(searchState) || update.docChanged || update.selectionSet || update.viewportChanged)809 this.decorations = this.highlight(state);810 }811 highlight({ query, panel }) {812 if (!panel || !query.spec.valid)813 return Decoration.none;814 let { view } = this;815 let builder = new RangeSetBuilder();816 for (let i = 0, ranges = view.visibleRanges, l = ranges.length; i < l; i++) {817 let { from, to } = ranges[i];818 while (i < l - 1 && to > ranges[i + 1].from - 2 * 250 /* RegExp.HighlightMargin */)819 to = ranges[++i].to;820 query.highlight(view.state, from, to, (from, to) => {821 let selected = view.state.selection.ranges.some(r => r.from == from && r.to == to);822 builder.add(from, to, selected ? selectedMatchMark : matchMark);823 });824 }825 return builder.finish();826 }827}, {828 decorations: v => v.decorations829});830function searchCommand(f) {831 return view => {832 let state = view.state.field(searchState, false);833 return state && state.query.spec.valid ? f(view, state) : openSearchPanel(view);834 };835}836/**837Open the search panel if it isn't already open, and move the838selection to the first match after the current main selection.839Will wrap around to the start of the document when it reaches the840end.841*/842const findNext = /*@__PURE__*/searchCommand((view, { query }) => {843 let { to } = view.state.selection.main;844 let next = query.nextMatch(view.state, to, to);845 if (!next)846 return false;847 let selection = EditorSelection.single(next.from, next.to);848 let config = view.state.facet(searchConfigFacet);849 view.dispatch({850 selection,851 effects: [announceMatch(view, next), config.scrollToMatch(selection.main, view)],852 userEvent: "select.search"853 });854 selectSearchInput(view);855 return true;856});857/**858Move the selection to the previous instance of the search query,859before the current main selection. Will wrap past the start860of the document to start searching at the end again.861*/862const findPrevious = /*@__PURE__*/searchCommand((view, { query }) => {863 let { state } = view, { from } = state.selection.main;864 let prev = query.prevMatch(state, from, from);865 if (!prev)866 return false;867 let selection = EditorSelection.single(prev.from, prev.to);868 let config = view.state.facet(searchConfigFacet);869 view.dispatch({870 selection,871 effects: [announceMatch(view, prev), config.scrollToMatch(selection.main, view)],872 userEvent: "select.search"873 });874 selectSearchInput(view);875 return true;876});877/**878Select all instances of the search query.879*/880const selectMatches = /*@__PURE__*/searchCommand((view, { query }) => {881 let ranges = query.matchAll(view.state, 1000);882 if (!ranges || !ranges.length)883 return false;884 view.dispatch({885 selection: EditorSelection.create(ranges.map(r => EditorSelection.range(r.from, r.to))),886 userEvent: "select.search.matches"887 });888 return true;889});890/**891Select all instances of the currently selected text.892*/893const selectSelectionMatches = ({ state, dispatch }) => {894 let sel = state.selection;895 if (sel.ranges.length > 1 || sel.main.empty)896 return false;897 let { from, to } = sel.main;898 let ranges = [], main = 0;899 for (let cur = new SearchCursor(state.doc, state.sliceDoc(from, to)); !cur.next().done;) {900 if (ranges.length > 1000)901 return false;902 if (cur.value.from == from)903 main = ranges.length;904 ranges.push(EditorSelection.range(cur.value.from, cur.value.to));905 }906 dispatch(state.update({907 selection: EditorSelection.create(ranges, main),908 userEvent: "select.search.matches"909 }));910 return true;911};912/**913Replace the current match of the search query.914*/915const replaceNext = /*@__PURE__*/searchCommand((view, { query }) => {916 let { state } = view, { from, to } = state.selection.main;917 if (state.readOnly)918 return false;919 let match = query.nextMatch(state, from, from);920 if (!match)921 return false;922 let next = match;923 let changes = [], selection, replacement;924 let effects = [];925 if (!next.precise) {926 next = query.nextMatch(state, next.from, next.to);927 }928 else if (next.from == from && next.to == to) {929 replacement = state.toText(query.getReplacement(next));930 changes.push({ from: next.from, to: next.to, insert: replacement });931 effects.push(EditorView.announce.of(state.phrase("replaced match on line $", state.doc.lineAt(from).number) + "."));932 }933 let changeSet = view.state.changes(changes);934 if (next) {935 selection = EditorSelection.single(next.from, next.to).map(changeSet);936 effects.push(announceMatch(view, next));937 effects.push(state.facet(searchConfigFacet).scrollToMatch(selection.main, view));938 }939 view.dispatch({940 changes: changeSet,941 selection,942 effects,943 userEvent: "input.replace"944 });945 return true;946});947/**948Replace all instances of the search query with the given949replacement.950*/951const replaceAll = /*@__PURE__*/searchCommand((view, { query }) => {952 if (view.state.readOnly)953 return false;954 let changes = [];955 for (let match of query.matchAll(view.state, 1e9)) {956 let { from, to, precise } = match;957 if (precise)958 changes.push({ from, to, insert: query.getReplacement(match) });959 }960 if (!changes.length)961 return false;962 let announceText = view.state.phrase("replaced $ matches", changes.length) + ".";963 view.dispatch({964 changes,965 effects: EditorView.announce.of(announceText),966 userEvent: "input.replace.all"967 });968 return true;969});970function createSearchPanel(view) {971 return view.state.facet(searchConfigFacet).createPanel(view);972}973function defaultQuery(state, fallback) {974 var _a, _b, _c, _d, _e;975 let sel = state.selection.main;976 let selText = sel.empty || sel.to > sel.from + 100 ? "" : state.sliceDoc(sel.from, sel.to);977 if (fallback && !selText)978 return fallback;979 let config = state.facet(searchConfigFacet);980 return new SearchQuery({981 search: ((_a = fallback === null || fallback === void 0 ? void 0 : fallback.literal) !== null && _a !== void 0 ? _a : config.literal) ? selText : selText.replace(/\n/g, "\\n"),982 caseSensitive: (_b = fallback === null || fallback === void 0 ? void 0 : fallback.caseSensitive) !== null && _b !== void 0 ? _b : config.caseSensitive,983 literal: (_c = fallback === null || fallback === void 0 ? void 0 : fallback.literal) !== null && _c !== void 0 ? _c : config.literal,984 regexp: (_d = fallback === null || fallback === void 0 ? void 0 : fallback.regexp) !== null && _d !== void 0 ? _d : config.regexp,985 wholeWord: (_e = fallback === null || fallback === void 0 ? void 0 : fallback.wholeWord) !== null && _e !== void 0 ? _e : config.wholeWord986 });987}988function getSearchInput(view) {989 let panel = getPanel(view, createSearchPanel);990 return panel && panel.dom.querySelector("[main-field]");991}992function selectSearchInput(view) {993 let input = getSearchInput(view);994 if (input && input == view.root.activeElement)995 input.select();996}997/**998Make sure the search panel is open and focused.999*/1000const openSearchPanel = view => {1001 let state = view.state.field(searchState, false);1002 if (state && state.panel) {1003 let searchInput = getSearchInput(view);1004 if (searchInput && searchInput != view.root.activeElement) {1005 let query = defaultQuery(view.state, state.query.spec);1006 if (query.valid)1007 view.dispatch({ effects: setSearchQuery.of(query) });1008 searchInput.focus();1009 searchInput.select();1010 }1011 }1012 else {1013 view.dispatch({ effects: [1014 togglePanel.of(true),1015 state ? setSearchQuery.of(defaultQuery(view.state, state.query.spec)) : StateEffect.appendConfig.of(searchExtensions)1016 ] });1017 }1018 return true;1019};1020/**1021Close the search panel.1022*/1023const closeSearchPanel = view => {1024 let state = view.state.field(searchState, false);1025 if (!state || !state.panel)1026 return false;1027 let panel = getPanel(view, createSearchPanel);1028 if (panel && panel.dom.contains(view.root.activeElement))1029 view.focus();1030 view.dispatch({ effects: togglePanel.of(false) });1031 return true;1032};1033/**1034Default search-related key bindings.1035 1036 - Mod-f: [`openSearchPanel`](https://codemirror.net/6/docs/ref/#search.openSearchPanel)1037 - F3, Mod-g: [`findNext`](https://codemirror.net/6/docs/ref/#search.findNext)1038 - Shift-F3, Shift-Mod-g: [`findPrevious`](https://codemirror.net/6/docs/ref/#search.findPrevious)1039 - Mod-Alt-g: [`gotoLine`](https://codemirror.net/6/docs/ref/#search.gotoLine)1040 - Mod-d: [`selectNextOccurrence`](https://codemirror.net/6/docs/ref/#search.selectNextOccurrence)1041*/1042const searchKeymap = [1043 { key: "Mod-f", run: openSearchPanel, scope: "editor search-panel" },1044 { key: "F3", run: findNext, shift: findPrevious, scope: "editor search-panel", preventDefault: true },1045 { key: "Mod-g", run: findNext, shift: findPrevious, scope: "editor search-panel", preventDefault: true },1046 { key: "Escape", run: closeSearchPanel, scope: "editor search-panel" },1047 { key: "Mod-Shift-l", run: selectSelectionMatches },1048 { key: "Mod-Alt-g", run: gotoLine },1049 { key: "Mod-d", run: selectNextOccurrence, preventDefault: true },1050];1051class SearchPanel {1052 constructor(view) {1053 this.view = view;1054 let query = this.query = view.state.field(searchState).query.spec;1055 this.commit = this.commit.bind(this);1056 this.searchField = elt("input", {1057 value: query.search,1058 placeholder: phrase(view, "Find"),1059 "aria-label": phrase(view, "Find"),1060 class: "cm-textfield",1061 name: "search",1062 form: "",1063 "main-field": "true",1064 onchange: this.commit,1065 onkeyup: this.commit1066 });1067 this.replaceField = elt("input", {1068 value: query.replace,1069 placeholder: phrase(view, "Replace"),1070 "aria-label": phrase(view, "Replace"),1071 class: "cm-textfield",1072 name: "replace",1073 form: "",1074 onchange: this.commit,1075 onkeyup: this.commit1076 });1077 this.caseField = elt("input", {1078 type: "checkbox",1079 name: "case",1080 form: "",1081 checked: query.caseSensitive,1082 onchange: this.commit1083 });1084 this.reField = elt("input", {1085 type: "checkbox",1086 name: "re",1087 form: "",1088 checked: query.regexp,1089 onchange: this.commit1090 });1091 this.wordField = elt("input", {1092 type: "checkbox",1093 name: "word",1094 form: "",1095 checked: query.wholeWord,1096 onchange: this.commit1097 });1098 function button(name, onclick, content) {1099 return elt("button", { class: "cm-button", name, onclick, type: "button" }, content);1100 }1101 this.dom = elt("div", { onkeydown: (e) => this.keydown(e), class: "cm-search" }, [1102 this.searchField,1103 button("next", () => findNext(view), [phrase(view, "next")]),1104 button("prev", () => findPrevious(view), [phrase(view, "previous")]),1105 button("select", () => selectMatches(view), [phrase(view, "all")]),1106 elt("label", null, [this.caseField, phrase(view, "match case")]),1107 elt("label", null, [this.reField, phrase(view, "regexp")]),1108 elt("label", null, [this.wordField, phrase(view, "by word")]),1109 ...view.state.readOnly ? [] : [1110 elt("br"),1111 this.replaceField,1112 button("replace", () => replaceNext(view), [phrase(view, "replace")]),1113 button("replaceAll", () => replaceAll(view), [phrase(view, "replace all")])1114 ],1115 elt("button", {1116 name: "close",1117 onclick: () => closeSearchPanel(view),1118 "aria-label": phrase(view, "close"),1119 type: "button"1120 }, ["×"])1121 ]);1122 }1123 commit() {1124 let query = new SearchQuery({1125 search: this.searchField.value,1126 caseSensitive: this.caseField.checked,1127 regexp: this.reField.checked,1128 wholeWord: this.wordField.checked,1129 replace: this.replaceField.value,1130 });1131 if (!query.eq(this.query)) {1132 this.query = query;1133 this.view.dispatch({ effects: setSearchQuery.of(query) });1134 }1135 }1136 keydown(e) {1137 if (runScopeHandlers(this.view, e, "search-panel")) {1138 e.preventDefault();1139 }1140 else if (e.keyCode == 13 && e.target == this.searchField) {1141 e.preventDefault();1142 (e.shiftKey ? findPrevious : findNext)(this.view);1143 }1144 else if (e.keyCode == 13 && e.target == this.replaceField) {1145 e.preventDefault();1146 replaceNext(this.view);1147 }1148 }1149 update(update) {1150 for (let tr of update.transactions)1151 for (let effect of tr.effects) {1152 if (effect.is(setSearchQuery) && !effect.value.eq(this.query))1153 this.setQuery(effect.value);1154 }1155 }1156 setQuery(query) {1157 this.query = query;1158 this.searchField.value = query.search;1159 this.replaceField.value = query.replace;1160 this.caseField.checked = query.caseSensitive;1161 this.reField.checked = query.regexp;1162 this.wordField.checked = query.wholeWord;1163 }1164 mount() {1165 this.searchField.select();1166 }1167 get pos() { return 80; }1168 get top() { return this.view.state.facet(searchConfigFacet).top; }1169}1170function phrase(view, phrase) { return view.state.phrase(phrase); }1171const AnnounceMargin = 30;1172const Break = /[\s\.,:;?!]/;1173function announceMatch(view, { from, to }) {1174 let line = view.state.doc.lineAt(from), lineEnd = view.state.doc.lineAt(to).to;1175 let start = Math.max(line.from, from - AnnounceMargin), end = Math.min(lineEnd, to + AnnounceMargin);1176 let text = view.state.sliceDoc(start, end);1177 if (start != line.from) {1178 for (let i = 0; i < AnnounceMargin; i++)1179 if (!Break.test(text[i + 1]) && Break.test(text[i])) {1180 text = text.slice(i);1181 break;1182 }1183 }1184 if (end != lineEnd) {1185 for (let i = text.length - 1; i > text.length - AnnounceMargin; i--)1186 if (!Break.test(text[i - 1]) && Break.test(text[i])) {1187 text = text.slice(0, i);1188 break;1189 }1190 }1191 return EditorView.announce.of(`${view.state.phrase("current match")}. ${text} ${view.state.phrase("on line")} ${line.number}.`);1192}1193const baseTheme = /*@__PURE__*/EditorView.baseTheme({1194 ".cm-panel.cm-search": {1195 padding: "2px 6px 4px",1196 position: "relative",1197 "& [name=close]": {1198 position: "absolute",1199 top: "0",1200 right: "4px",