NRuby
228 строк · 6.4 Кб
1Searcher = function(data) {2this.data = data;3this.handlers = [];4}
5
6Searcher.prototype = new function() {7// search is performed in chunks of 1000 for non-blocking user input8var CHUNK_SIZE = 1000;9// do not try to find more than 100 results10var MAX_RESULTS = 100;11var huid = 1;12var suid = 1;13var runs = 0;14
15this.find = function(query) {16var queries = splitQuery(query);17var regexps = buildRegexps(queries);18var highlighters = buildHilighters(queries);19var state = { from: 0, pass: 0, limit: MAX_RESULTS, n: suid++};20var _this = this;21
22this.currentSuid = state.n;23
24if (!query) return;25
26var run = function() {27// stop current search thread if new search started28if (state.n != _this.currentSuid) return;29
30var results =31performSearch(_this.data, regexps, queries, highlighters, state);32var hasMore = (state.limit > 0 && state.pass < 4);33
34triggerResults.call(_this, results, !hasMore);35if (hasMore) {36setTimeout(run, 2);37}38runs++;39};40runs = 0;41
42// start search thread43run();44}45
46/* ----- Events ------ */47this.ready = function(fn) {48fn.huid = huid;49this.handlers.push(fn);50}51
52/* ----- Utilities ------ */53function splitQuery(query) {54return query.split(/(\s+|::?|\(\)?)/).filter(function(string) {55return string.match(/\S/);56});57}58
59function buildRegexps(queries) {60return queries.map(function(query) {61return new RegExp(query.replace(/(.)/g, '([$1])([^$1]*?)'), 'i');62});63}64
65function buildHilighters(queries) {66return queries.map(function(query) {67return query.split('').map(function(l, i) {68return '\u0001$' + (i*2+1) + '\u0002$' + (i*2+2);69}).join('');70});71}72
73// function longMatchRegexp(index, longIndex, regexps) {74// for (var i = regexps.length - 1; i >= 0; i--){75// if (!index.match(regexps[i]) && !longIndex.match(regexps[i])) return false;76// };77// return true;78// }79
80
81/* ----- Mathchers ------ */82
83/*84* This record matches if the index starts with queries[0] and the record
85* matches all of the regexps
86*/
87function matchPassBeginning(index, longIndex, queries, regexps) {88if (index.indexOf(queries[0]) != 0) return false;89for (var i=1, l = regexps.length; i < l; i++) {90if (!index.match(regexps[i]) && !longIndex.match(regexps[i]))91return false;92};93return true;94}95
96/*97* This record matches if the longIndex starts with queries[0] and the
98* longIndex matches all of the regexps
99*/
100function matchPassLongIndex(index, longIndex, queries, regexps) {101if (longIndex.indexOf(queries[0]) != 0) return false;102for (var i=1, l = regexps.length; i < l; i++) {103if (!longIndex.match(regexps[i]))104return false;105};106return true;107}108
109/*110* This record matches if the index contains queries[0] and the record
111* matches all of the regexps
112*/
113function matchPassContains(index, longIndex, queries, regexps) {114if (index.indexOf(queries[0]) == -1) return false;115for (var i=1, l = regexps.length; i < l; i++) {116if (!index.match(regexps[i]) && !longIndex.match(regexps[i]))117return false;118};119return true;120}121
122/*123* This record matches if regexps[0] matches the index and the record
124* matches all of the regexps
125*/
126function matchPassRegexp(index, longIndex, queries, regexps) {127if (!index.match(regexps[0])) return false;128for (var i=1, l = regexps.length; i < l; i++) {129if (!index.match(regexps[i]) && !longIndex.match(regexps[i]))130return false;131};132return true;133}134
135
136/* ----- Highlighters ------ */137function highlightRegexp(info, queries, regexps, highlighters) {138var result = createResult(info);139for (var i=0, l = regexps.length; i < l; i++) {140result.title = result.title.replace(regexps[i], highlighters[i]);141result.namespace = result.namespace.replace(regexps[i], highlighters[i]);142};143return result;144}145
146function hltSubstring(string, pos, length) {147return string.substring(0, pos) + '\u0001' + string.substring(pos, pos + length) + '\u0002' + string.substring(pos + length);148}149
150function highlightQuery(info, queries, regexps, highlighters) {151var result = createResult(info);152var pos = 0;153var lcTitle = result.title.toLowerCase();154
155pos = lcTitle.indexOf(queries[0]);156if (pos != -1) {157result.title = hltSubstring(result.title, pos, queries[0].length);158}159
160result.namespace = result.namespace.replace(regexps[0], highlighters[0]);161for (var i=1, l = regexps.length; i < l; i++) {162result.title = result.title.replace(regexps[i], highlighters[i]);163result.namespace = result.namespace.replace(regexps[i], highlighters[i]);164};165return result;166}167
168function createResult(info) {169var result = {};170result.title = info[0];171result.namespace = info[1];172result.path = info[2];173result.params = info[3];174result.snippet = info[4];175result.badge = info[6];176return result;177}178
179/* ----- Searching ------ */180function performSearch(data, regexps, queries, highlighters, state) {181var searchIndex = data.searchIndex;182var longSearchIndex = data.longSearchIndex;183var info = data.info;184var result = [];185var i = state.from;186var l = searchIndex.length;187var togo = CHUNK_SIZE;188var matchFunc, hltFunc;189
190while (state.pass < 4 && state.limit > 0 && togo > 0) {191if (state.pass == 0) {192matchFunc = matchPassBeginning;193hltFunc = highlightQuery;194} else if (state.pass == 1) {195matchFunc = matchPassLongIndex;196hltFunc = highlightQuery;197} else if (state.pass == 2) {198matchFunc = matchPassContains;199hltFunc = highlightQuery;200} else if (state.pass == 3) {201matchFunc = matchPassRegexp;202hltFunc = highlightRegexp;203}204
205for (; togo > 0 && i < l && state.limit > 0; i++, togo--) {206if (info[i].n == state.n) continue;207if (matchFunc(searchIndex[i], longSearchIndex[i], queries, regexps)) {208info[i].n = state.n;209result.push(hltFunc(info[i], queries, regexps, highlighters));210state.limit--;211}212};213if (searchIndex.length <= i) {214state.pass++;215i = state.from = 0;216} else {217state.from = i;218}219}220return result;221}222
223function triggerResults(results, isLast) {224this.handlers.forEach(function(fn) {225fn.call(this, results, isLast)226});227}228}
229
230