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