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