monitor/web/public/js/codeEditor/searchcursor.js
2025-04-16 22:30:27 +07:00

322 lines
14 KiB
JavaScript

// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
(function (mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function (CodeMirror) {
"use strict";
var Pos = CodeMirror.Pos;
function regexpFlags(regexp) {
var flags = regexp.flags;
return flags != null ? flags : (regexp.ignoreCase ? "i" : "")
+ (regexp.global ? "g" : "")
+ (regexp.multiline ? "m" : "");
}
function ensureFlags(regexp, flags) {
var current = regexpFlags(regexp), target = current;
for (var i = 0; i < flags.length; i++) if (target.indexOf(flags.charAt(i)) == -1)
target += flags.charAt(i);
return current == target ? regexp : new RegExp(regexp.source, target);
}
function maybeMultiline(regexp) {
return /\\s|\\n|\n|\\W|\\D|\[\^/.test(regexp.source);
}
function searchRegexpForward(doc, regexp, start) {
regexp = ensureFlags(regexp, "g");
for (var line = start.line, ch = start.ch, last = doc.lastLine(); line <= last; line++, ch = 0) {
regexp.lastIndex = ch;
var string = doc.getLine(line), match = regexp.exec(string);
if (match)
return {
from: Pos(line, match.index),
to: Pos(line, match.index + match[0].length),
match: match
};
}
}
function searchRegexpForwardMultiline(doc, regexp, start) {
if (!maybeMultiline(regexp)) return searchRegexpForward(doc, regexp, start);
regexp = ensureFlags(regexp, "gm");
var string, chunk = 1;
for (var line = start.line, last = doc.lastLine(); line <= last;) {
// This grows the search buffer in exponentially-sized chunks
// between matches, so that nearby matches are fast and don't
// require concatenating the whole document (in case we're
// searching for something that has tons of matches), but at the
// same time, the amount of retries is limited.
for (var i = 0; i < chunk; i++) {
if (line > last) break;
var curLine = doc.getLine(line++);
string = string == null ? curLine : string + "\n" + curLine;
}
chunk = chunk * 2;
regexp.lastIndex = start.ch;
var match = regexp.exec(string);
if (match) {
var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n");
var startLine = start.line + before.length - 1, startCh = before[before.length - 1].length;
return {
from: Pos(startLine, startCh),
to: Pos(startLine + inside.length - 1,
inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length),
match: match
};
}
}
}
function lastMatchIn(string, regexp, endMargin) {
var match, from = 0;
while (from <= string.length) {
regexp.lastIndex = from;
var newMatch = regexp.exec(string);
if (!newMatch) break;
var end = newMatch.index + newMatch[0].length;
if (end > string.length - endMargin) break;
if (!match || end > match.index + match[0].length)
match = newMatch;
from = newMatch.index + 1;
}
return match;
}
function searchRegexpBackward(doc, regexp, start) {
regexp = ensureFlags(regexp, "g");
for (var line = start.line, ch = start.ch, first = doc.firstLine(); line >= first; line--, ch = -1) {
var string = doc.getLine(line);
var match = lastMatchIn(string, regexp, ch < 0 ? 0 : string.length - ch);
if (match)
return {
from: Pos(line, match.index),
to: Pos(line, match.index + match[0].length),
match: match
};
}
}
function searchRegexpBackwardMultiline(doc, regexp, start) {
if (!maybeMultiline(regexp)) return searchRegexpBackward(doc, regexp, start);
regexp = ensureFlags(regexp, "gm");
var string, chunkSize = 1, endMargin = doc.getLine(start.line).length - start.ch;
for (var line = start.line, first = doc.firstLine(); line >= first;) {
for (var i = 0; i < chunkSize && line >= first; i++) {
var curLine = doc.getLine(line--);
string = string == null ? curLine : curLine + "\n" + string;
}
chunkSize *= 2;
var match = lastMatchIn(string, regexp, endMargin);
if (match) {
var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n");
var startLine = line + before.length, startCh = before[before.length - 1].length;
return {
from: Pos(startLine, startCh),
to: Pos(startLine + inside.length - 1,
inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length),
match: match
};
}
}
}
var doFold, noFold;
if (String.prototype.normalize) {
doFold = function (str) { return str.normalize("NFD").toLowerCase(); };
noFold = function (str) { return str.normalize("NFD"); };
} else {
doFold = function (str) { return str.toLowerCase(); };
noFold = function (str) { return str; };
}
// Maps a position in a case-folded line back to a position in the original line
// (compensating for codepoints increasing in number during folding)
function adjustPos(orig, folded, pos, foldFunc) {
if (orig.length == folded.length) return pos;
for (var min = 0, max = pos + Math.max(0, orig.length - folded.length); ;) {
if (min == max) return min;
var mid = (min + max) >> 1;
var len = foldFunc(orig.slice(0, mid)).length;
if (len == pos) return mid;
else if (len > pos) max = mid;
else min = mid + 1;
}
}
function searchStringForward(doc, query, start, caseFold) {
// Empty string would match anything and never progress, so we
// define it to match nothing instead.
if (!query.length) return null;
var fold = caseFold ? doFold : noFold;
var lines = fold(query).split(/\r|\n\r?/);
search: for (var line = start.line, ch = start.ch, last = doc.lastLine() + 1 - lines.length; line <= last; line++, ch = 0) {
var orig = doc.getLine(line).slice(ch), string = fold(orig);
if (lines.length == 1) {
var found = string.indexOf(lines[0]);
if (found == -1) continue search;
var start = adjustPos(orig, string, found, fold) + ch;
return {
from: Pos(line, adjustPos(orig, string, found, fold) + ch),
to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold) + ch)
};
} else {
var cutFrom = string.length - lines[0].length;
if (string.slice(cutFrom) != lines[0]) continue search;
for (var i = 1; i < lines.length - 1; i++)
if (fold(doc.getLine(line + i)) != lines[i]) continue search;
var end = doc.getLine(line + lines.length - 1), endString = fold(end), lastLine = lines[lines.length - 1];
if (endString.slice(0, lastLine.length) != lastLine) continue search;
return {
from: Pos(line, adjustPos(orig, string, cutFrom, fold) + ch),
to: Pos(line + lines.length - 1, adjustPos(end, endString, lastLine.length, fold))
};
}
}
}
function searchStringBackward(doc, query, start, caseFold) {
if (!query.length) return null;
var fold = caseFold ? doFold : noFold;
var lines = fold(query).split(/\r|\n\r?/);
search: for (var line = start.line, ch = start.ch, first = doc.firstLine() - 1 + lines.length; line >= first; line--, ch = -1) {
var orig = doc.getLine(line);
if (ch > -1) orig = orig.slice(0, ch);
var string = fold(orig);
if (lines.length == 1) {
var found = string.lastIndexOf(lines[0]);
if (found == -1) continue search;
return {
from: Pos(line, adjustPos(orig, string, found, fold)),
to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold))
};
} else {
var lastLine = lines[lines.length - 1];
if (string.slice(0, lastLine.length) != lastLine) continue search;
for (var i = 1, start = line - lines.length + 1; i < lines.length - 1; i++)
if (fold(doc.getLine(start + i)) != lines[i]) continue search;
var top = doc.getLine(line + 1 - lines.length), topString = fold(top);
if (topString.slice(topString.length - lines[0].length) != lines[0]) continue search;
return {
from: Pos(line + 1 - lines.length, adjustPos(top, topString, top.length - lines[0].length, fold)),
to: Pos(line, adjustPos(orig, string, lastLine.length, fold))
};
}
}
}
function SearchCursor(doc, query, pos, options) {
this.atOccurrence = false;
this.afterEmptyMatch = false;
this.doc = doc;
pos = pos ? doc.clipPos(pos) : Pos(0, 0);
this.pos = { from: pos, to: pos };
var caseFold;
if (typeof options == "object") {
caseFold = options.caseFold;
} else { // Backwards compat for when caseFold was the 4th argument
caseFold = options;
options = null;
}
if (typeof query == "string") {
if (caseFold == null) caseFold = false;
this.matches = function (reverse, pos) {
return (reverse ? searchStringBackward : searchStringForward)(doc, query, pos, caseFold);
};
} else {
query = ensureFlags(query, "gm");
if (!options || options.multiline !== false)
this.matches = function (reverse, pos) {
return (reverse ? searchRegexpBackwardMultiline : searchRegexpForwardMultiline)(doc, query, pos);
};
else
this.matches = function (reverse, pos) {
return (reverse ? searchRegexpBackward : searchRegexpForward)(doc, query, pos);
};
}
}
SearchCursor.prototype = {
findNext: function () { return this.find(false); },
findPrevious: function () { return this.find(true); },
find: function (reverse) {
var head = this.doc.clipPos(reverse ? this.pos.from : this.pos.to);
if (this.afterEmptyMatch && this.atOccurrence) {
// do not return the same 0 width match twice
head = Pos(head.line, head.ch);
if (reverse) {
head.ch--;
if (head.ch < 0) {
head.line--;
head.ch = (this.doc.getLine(head.line) || "").length;
}
} else {
head.ch++;
if (head.ch > (this.doc.getLine(head.line) || "").length) {
head.ch = 0;
head.line++;
}
}
if (CodeMirror.cmpPos(head, this.doc.clipPos(head)) != 0) {
return this.atOccurrence = false;
}
}
var result = this.matches(reverse, head);
this.afterEmptyMatch = result && CodeMirror.cmpPos(result.from, result.to) == 0;
if (result) {
this.pos = result;
this.atOccurrence = true;
return this.pos.match || true;
} else {
var end = Pos(reverse ? this.doc.firstLine() : this.doc.lastLine() + 1, 0);
this.pos = { from: end, to: end };
return this.atOccurrence = false;
}
},
from: function () { if (this.atOccurrence) return this.pos.from; },
to: function () { if (this.atOccurrence) return this.pos.to; },
replace: function (newText, origin) {
if (!this.atOccurrence) return;
var lines = CodeMirror.splitLines(newText);
this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin);
this.pos.to = Pos(this.pos.from.line + lines.length - 1,
lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0));
}
};
CodeMirror.defineExtension("getSearchCursor", function (query, pos, caseFold) {
return new SearchCursor(this.doc, query, pos, caseFold);
});
CodeMirror.defineDocExtension("getSearchCursor", function (query, pos, caseFold) {
return new SearchCursor(this, query, pos, caseFold);
});
CodeMirror.defineExtension("selectMatches", function (query, caseFold) {
var ranges = [];
var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold);
while (cur.findNext()) {
if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break;
ranges.push({ anchor: cur.from(), head: cur.to() });
}
if (ranges.length)
this.setSelections(ranges, 0);
});
});