// 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); }); });