c9-core/plugins/c9.ide.find.replace/async_search.js

276 wiersze
8.7 KiB
JavaScript

/**
* Searchreplace Module for the Cloud9
*
* @copyright 2013, Ajax.org B.V.
*/
define(function(require, exports, module) {
var $worker = null;
var workerSrc = "(" + function(_self) {
var str = "";
function setValue($str) {
str = $str;
}
function findAll(str, regex, cb) {
var matches = [];
var last = regex.lastIndex = 0;
var m;
while (m = regex.exec(str)) {
matches.push(last = m.index);
if (!m[0].length) {
regex.lastIndex = last += 1;
if (last >= str.length)
matches.pop();
}
}
cb({ matches: matches });
}
_self.onmessage = function(e) {
var msg = e.data;
if (msg.command == "setValue") {
setValue(msg.data);
} else if (msg.command == "findAll") {
try {
var regex = RegExp(msg.source, msg.flags || "g");
var searchStr = str;
if (msg.range)
searchStr = searchStr.slice(msg.range[0], msg.range[1]);
findAll(searchStr, regex, function(r) {
r.callbackId = msg.callbackId;
postMessage(r);
});
} catch (e) {}
} else if (msg.eval) {
try {
var r = eval(msg.eval);
} catch (e) {
r = e.message;
}
postMessage({ type: "event", data: r });
}
};
} + ")(this)";
function getWorker() {
if ($worker)
return $worker;
var blob = new Blob([ workerSrc ], { type: 'application/javascript' });
var blobUrl = (window.URL || window.webkitURL).createObjectURL(blob);
$worker = new Worker(blobUrl);
setTimeout(function() { // IE EDGE needs a timeout here
(window.URL || window.webkitURL).revokeObjectURL(blobUrl);
});
$worker.onmessage = onMessage;
$worker.onerror = function(e) {
throw e;
};
return $worker;
}
function onMessage(e) {
var msg = e.data;
var id = msg.callbackId;
$worker.responseTime = Date.now();
// console.log(id, callbacks)
if (id && callbacks[id]) {
if (id == callbackId)
callbacks[id](msg);
callbacks[id] = null;
}
}
var callbacks = {};
var callbackId = 1;
function terminateWorker() {
$worker && $worker.terminate();
$worker = null;
callbacks = {};
}
function execFind(session, options, cb) {
if (!session.searchTracker) {
session.searchTracker = new SearchTracker(session);
session.once("change", function() {session.searchTracker = null;});
}
var st = session.searchTracker;
session.searchTracker.get(options, function(all) {
if (!all)
return cb("waiting");
// find in range
var offset = options.indexRange ? options.indexRange[0] : 0;
if (options.findAll)
return cb({ value: st.value, matches: all, offset: offset });
// preprocess options
var backwards = options.backwards === true;
var skipCurrent = options.skipCurrent !== false;
var wrap = options.wrap;
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"];
if (!options.regex)
options.regex = RegExp(options.source, options.flags);
var value = st.value;
if (options.indexRange)
value = value.slice(offset, options.indexRange[1]);
// find index
var index = st.session.doc.positionToIndex(start) - offset;
var i = binIndexOf(all, index);
var next = i;
var match;
var wrapped = false;
var updateWrap = function() {
if (next > all.length - 1) {
next = wrap ? 0 : all.length - 1;
wrapped = wrap;
} else if (next < 0) {
next = wrap ? all.length - 1 : 0;
wrapped = wrap;
}
};
var getMatch = function() {
if (all[next] !== undefined) {
options.regex.lastIndex = all[next];
return options.regex.exec(value);
}
};
if (backwards) {
match = getMatch();
if (!match || all[next] + match[0].length > index) {
next -= 1;
updateWrap();
match = getMatch();
}
} else {
if (all[i] != index)
next += 1;
updateWrap();
match = getMatch();
}
if (!match)
return cb(null);
start = st.session.doc.indexToPosition(match.index + offset);
var end = st.session.doc.indexToPosition(start.column + match[0].length, start.row);
cb({
start: start,
end: end,
total: all.length,
current: next,
wrapped: wrapped,
value: st.value,
startIndex: match.index + offset
});
});
}
function binIndexOf(array, val) {
var low = 0;
var hi = array.length - 1;
while (low <= hi) {
var mid = (low + hi) >> 1;
var c = array[mid];
if (val > c)
low = mid + 1;
else if (val < c)
hi = mid - 1;
else
return mid;
}
return low - 1;
}
function SearchTracker(session) {
this.value = session.getValue();
this.results = Object.create(null);
this.session = session;
this.initWorker();
}
(function() {
this.rangeToIndex = function(r) {
var start = this.session.doc.positionToIndex(r.start);
var end = start - r.start.column +
this.session.doc.positionToIndex(r.end, r.start.row);
return [start, end];
};
this.get = function(re, cb) {
if (!re.id) {
re.id = re.source + "|" + re.flags + (re.range || "");
}
if (re.range && !re.indexRange) {
re.indexRange = this.rangeToIndex(re.range);
}
clearTimeout(this.crashTimer);
var all = this.results[re.id];
if (!all) {
this.getMatchOffsets(re, function(data) {
clearTimeout(this.crashTimer);
this.results[re.id] = data.matches;
cb(data.matches);
}.bind(this));
// invalid regex can crash the worker
this.crashTimer = setTimeout(function() {
cb();
}, 500);
} else
cb(all);
};
this.getMatchOffsets = function(re, cb) {
var worker = this.initWorker();
var now = Date.now();
if (!worker.responseTime && worker.requestTime && now - worker.requestTime > 1000) {
terminateWorker();
worker = this.initWorker();
}
callbackId += 1;
var id = callbackId;
worker.responseTime = null;
worker.requestTime = now;
worker.postMessage({
source: re.source,
flags: re.flags,
range: re.indexRange,
callbackId: callbackId,
command: "findAll"
});
callbacks[id] = cb;
};
this.initWorker = function() {
var worker = getWorker();
if (worker.value != this.value) {
worker.postMessage({ command: "setValue", data: this.value });
worker.value = this.value;
}
return worker;
};
}).call(SearchTracker.prototype);
exports.terminateWorker = terminateWorker;
exports.execFind = execFind;
});