big-moving.ru/api/soft/ajaxorg/lib/ace/search.js

408 lines
15 KiB
JavaScript
Raw Normal View History

2022-06-24 15:29:23 +05:00
/* ***** BEGIN LICENSE BLOCK *****
* Distributed under the BSD license:
*
* Copyright (c) 2010, Ajax.org B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Ajax.org B.V. nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* ***** END LICENSE BLOCK ***** */
define(function(require, exports, module) {
"use strict";
var lang = require("./lib/lang");
var oop = require("./lib/oop");
var Range = require("./range").Range;
/**
* @class Search
*
* A class designed to handle all sorts of text searches within a [[Document `Document`]].
*
**/
/**
*
*
* Creates a new `Search` object. The following search options are available:
*
* - `needle`: The string or regular expression you're looking for
* - `backwards`: Whether to search backwards from where cursor currently is. Defaults to `false`.
* - `wrap`: Whether to wrap the search back to the beginning when it hits the end. Defaults to `false`.
* - `caseSensitive`: Whether the search ought to be case-sensitive. Defaults to `false`.
* - `wholeWord`: Whether the search matches only on whole words. Defaults to `false`.
* - `range`: The [[Range]] to search within. Set this to `null` for the whole document
* - `regExp`: Whether the search is a regular expression or not. Defaults to `false`.
* - `start`: The starting [[Range]] or cursor position to begin the search
* - `skipCurrent`: Whether or not to include the current line in the search. Default to `false`.
*
* @constructor
**/
var Search = function() {
this.$options = {};
};
(function() {
/**
* Sets the search options via the `options` parameter.
* @param {Object} options An object containing all the new search properties
*
*
* @returns {Search}
* @chainable
**/
this.set = function(options) {
oop.mixin(this.$options, options);
return this;
};
/**
* [Returns an object containing all the search options.]{: #Search.getOptions}
* @returns {Object}
**/
this.getOptions = function() {
return lang.copyObject(this.$options);
};
/**
* Sets the search options via the `options` parameter.
* @param {Object} An object containing all the search propertie
* @related Search.set
**/
this.setOptions = function(options) {
this.$options = options;
};
/**
* Searches for `options.needle`. If found, this method returns the [[Range `Range`]] where the text first occurs. If `options.backwards` is `true`, the search goes backwards in the session.
* @param {EditSession} session The session to search with
*
*
* @returns {Range}
**/
this.find = function(session) {
var options = this.$options;
var iterator = this.$matchIterator(session, options);
if (!iterator)
return false;
var firstRange = null;
iterator.forEach(function(sr, sc, er, ec) {
firstRange = new Range(sr, sc, er, ec);
if (sc == ec && options.start && options.start.start
&& options.skipCurrent != false && firstRange.isEqual(options.start)
) {
firstRange = null;
return false;
}
return true;
});
return firstRange;
};
/**
* Searches for all occurrances `options.needle`. If found, this method returns an array of [[Range `Range`s]] where the text first occurs. If `options.backwards` is `true`, the search goes backwards in the session.
* @param {EditSession} session The session to search with
*
*
* @returns {[Range]}
**/
this.findAll = function(session) {
var options = this.$options;
if (!options.needle)
return [];
this.$assembleRegExp(options);
var range = options.range;
var lines = range
? session.getLines(range.start.row, range.end.row)
: session.doc.getAllLines();
var ranges = [];
var re = options.re;
if (options.$isMultiLine) {
var len = re.length;
var maxRow = lines.length - len;
var prevRange;
outer: for (var row = re.offset || 0; row <= maxRow; row++) {
for (var j = 0; j < len; j++)
if (lines[row + j].search(re[j]) == -1)
continue outer;
var startLine = lines[row];
var line = lines[row + len - 1];
var startIndex = startLine.length - startLine.match(re[0])[0].length;
var endIndex = line.match(re[len - 1])[0].length;
if (prevRange && prevRange.end.row === row &&
prevRange.end.column > startIndex
) {
continue;
}
ranges.push(prevRange = new Range(
row, startIndex, row + len - 1, endIndex
));
if (len > 2)
row = row + len - 2;
}
} else {
for (var i = 0; i < lines.length; i++) {
var matches = lang.getMatchOffsets(lines[i], re);
for (var j = 0; j < matches.length; j++) {
var match = matches[j];
ranges.push(new Range(i, match.offset, i, match.offset + match.length));
}
}
}
if (range) {
var startColumn = range.start.column;
var endColumn = range.start.column;
var i = 0, j = ranges.length - 1;
while (i < j && ranges[i].start.column < startColumn && ranges[i].start.row == range.start.row)
i++;
while (i < j && ranges[j].end.column > endColumn && ranges[j].end.row == range.end.row)
j--;
ranges = ranges.slice(i, j + 1);
for (i = 0, j = ranges.length; i < j; i++) {
ranges[i].start.row += range.start.row;
ranges[i].end.row += range.start.row;
}
}
return ranges;
};
/**
* Searches for `options.needle` in `input`, and, if found, replaces it with `replacement`.
* @param {String} input The text to search in
* @param {String} replacement The replacing text
* + (String): If `options.regExp` is `true`, this function returns `input` with the replacement already made. Otherwise, this function just returns `replacement`.<br/>
* If `options.needle` was not found, this function returns `null`.
*
*
* @returns {String}
**/
this.replace = function(input, replacement) {
var options = this.$options;
var re = this.$assembleRegExp(options);
if (options.$isMultiLine)
return replacement;
if (!re)
return;
var match = re.exec(input);
if (!match || match[0].length != input.length)
return null;
replacement = input.replace(re, replacement);
if (options.preserveCase) {
replacement = replacement.split("");
for (var i = Math.min(input.length, input.length); i--; ) {
var ch = input[i];
if (ch && ch.toLowerCase() != ch)
replacement[i] = replacement[i].toUpperCase();
else
replacement[i] = replacement[i].toLowerCase();
}
replacement = replacement.join("");
}
return replacement;
};
this.$assembleRegExp = function(options, $disableFakeMultiline) {
if (options.needle instanceof RegExp)
return options.re = options.needle;
var needle = options.needle;
if (!options.needle)
return options.re = false;
if (!options.regExp)
needle = lang.escapeRegExp(needle);
if (options.wholeWord)
needle = addWordBoundary(needle, options);
var modifier = options.caseSensitive ? "gm" : "gmi";
options.$isMultiLine = !$disableFakeMultiline && /[\n\r]/.test(needle);
if (options.$isMultiLine)
return options.re = this.$assembleMultilineRegExp(needle, modifier);
try {
var re = new RegExp(needle, modifier);
} catch(e) {
re = false;
}
return options.re = re;
};
this.$assembleMultilineRegExp = function(needle, modifier) {
var parts = needle.replace(/\r\n|\r|\n/g, "$\n^").split("\n");
var re = [];
for (var i = 0; i < parts.length; i++) try {
re.push(new RegExp(parts[i], modifier));
} catch(e) {
return false;
}
return re;
};
this.$matchIterator = function(session, options) {
var re = this.$assembleRegExp(options);
if (!re)
return false;
var backwards = options.backwards == true;
var skipCurrent = options.skipCurrent != false;
var range = options.range;
var start = options.start;
if (!start)
start = range ? range[backwards ? "end" : "start"] : session.selection.getRange();
if (start.start)
start = start[skipCurrent != backwards ? "end" : "start"];
var firstRow = range ? range.start.row : 0;
var lastRow = range ? range.end.row : session.getLength() - 1;
if (backwards) {
var forEach = function(callback) {
var row = start.row;
if (forEachInLine(row, start.column, callback))
return;
for (row--; row >= firstRow; row--)
if (forEachInLine(row, Number.MAX_VALUE, callback))
return;
if (options.wrap == false)
return;
for (row = lastRow, firstRow = start.row; row >= firstRow; row--)
if (forEachInLine(row, Number.MAX_VALUE, callback))
return;
};
}
else {
var forEach = function(callback) {
var row = start.row;
if (forEachInLine(row, start.column, callback))
return;
for (row = row + 1; row <= lastRow; row++)
if (forEachInLine(row, 0, callback))
return;
if (options.wrap == false)
return;
for (row = firstRow, lastRow = start.row; row <= lastRow; row++)
if (forEachInLine(row, 0, callback))
return;
};
}
if (options.$isMultiLine) {
var len = re.length;
var forEachInLine = function(row, offset, callback) {
var startRow = backwards ? row - len + 1 : row;
if (startRow < 0) return;
var line = session.getLine(startRow);
var startIndex = line.search(re[0]);
if (!backwards && startIndex < offset || startIndex === -1) return;
for (var i = 1; i < len; i++) {
line = session.getLine(startRow + i);
if (line.search(re[i]) == -1)
return;
}
var endIndex = line.match(re[len - 1])[0].length;
if (backwards && endIndex > offset) return;
if (callback(startRow, startIndex, startRow + len - 1, endIndex))
return true;
};
}
else if (backwards) {
var forEachInLine = function(row, endIndex, callback) {
var line = session.getLine(row);
var matches = [];
var m, last = 0;
re.lastIndex = 0;
while((m = re.exec(line))) {
var length = m[0].length;
last = m.index;
if (!length) {
if (last >= line.length) break;
re.lastIndex = last += 1;
}
if (m.index + length > endIndex)
break;
matches.push(m.index, length);
}
for (var i = matches.length - 1; i >= 0; i -= 2) {
var column = matches[i - 1];
var length = matches[i];
if (callback(row, column, row, column + length))
return true;
}
};
}
else {
var forEachInLine = function(row, startIndex, callback) {
var line = session.getLine(row);
var last;
var m;
re.lastIndex = startIndex;
while((m = re.exec(line))) {
var length = m[0].length;
last = m.index;
if (callback(row, last, row,last + length))
return true;
if (!length) {
re.lastIndex = last += 1;
if (last >= line.length) return false;
}
}
};
}
return {forEach: forEach};
};
}).call(Search.prototype);
function addWordBoundary(needle, options) {
function wordBoundary(c) {
if (/\w/.test(c) || options.regExp) return "\\b";
return "";
}
return wordBoundary(needle[0]) + needle
+ wordBoundary(needle[needle.length - 1]);
}
exports.Search = Search;
});